Confidential Transfer

The ConfidentialTransfer contract manages encrypted balances using ElGamal encryption on the STARK curve. Every balance is stored as a ciphertext — the contract never sees plaintext amounts. Six zero-knowledge proofs verify each transfer on-chain.

6
Proofs/Transfer
ElGamal
Encryption
32-bit
Range Proof
Auditor Key
Compliance

How It Works

1
Register your public key
Register your ElGamal public key on-chain. This is the key others will use to encrypt amounts when sending you tokens.
2
Fund your encrypted balance
Deposit plaintext tokens into the contract. The contract encrypts the amount under your public key and stores the ciphertext.
3
Transfer encrypted amounts
Send tokens to another registered user. The sender provides 6 ZK proofs: ownership, blinding, encryption (Chaum-Pedersen), range, and 2 same-encryption proofs.
4
Withdraw to plaintext
Decrypt your balance using your private key and withdraw tokens back to your wallet.

Six Proofs Per Transfer

Every confidential transfer requires six zero-knowledge proofs, all verified on-chain:

#ProofWhat It Proves
1Ownership (Schnorr)Sender knows their private key
2Blinding ProofEncryption randomness is correctly formed
3Encryption (Chaum-Pedersen)Ciphertext correctly encrypts the amount under recipient's key
4Range ProofAmount is in valid range (0 to 2^32), preventing overflow
5Same-Encryption (sender/receiver)Both ciphertexts encrypt the same amount
6Same-Encryption (receiver/auditor)Auditor ciphertext matches (for compliance)
Homomorphic Balance Updates

Because ElGamal is additively homomorphic, the contract updates balances by adding ciphertexts: new_balance = old_balance ⊕ Enc(delta). The contract never decrypts — it just manipulates ciphertexts.

Balance Model

Each user's balance = ElGamal ciphertext (L, R)

  L = amount · G + randomness · pk
  R = randomness · G

To add delta:
  L' = L + delta·G + r'·pk
  R' = R + r'·G

Net effect: encrypted balance increases by delta

Pending Transfers

As an anti-spam mechanism, incoming transfers are placed in a pending state. The recipient must call rollover() to accept pending transfers into their active balance.

// Accept pending transfers
await obelysk.confidentialTransfer.rollover('sage');

AE Hints for Fast Decryption

Decrypting ElGamal normally requires solving discrete log (expensive). Instead, the sender includes an AE hint — the plaintext amount encrypted with a shared ECDH secret:

shared_secret = ECDH(sender_sk, recipient_pk)
ae_hint = AES_Encrypt(shared_secret, amount)

The recipient decrypts the hint in O(1) instead of brute-forcing discrete log.

Auditor Keys

The contract supports an optional auditor escrow key. When set, every transfer also encrypts the amount under the auditor's public key (proven by the 6th proof). This enables compliance monitoring without breaking privacy for regular observers.

Optional Compliance

The auditor key is optional and configurable by the contract owner. When disabled, only sender and recipient can see the amount. When enabled, a designated auditor can also decrypt for compliance purposes.

SDK Usage

// Register your ElGamal public key
await obelysk.confidentialTransfer.register(myPublicKey);

// Fund encrypted balance (public → private)
await obelysk.confidentialTransfer.fund({
  token: 'sage',
  amount: '5000',
});

// Transfer (private → private, 6 proofs generated automatically)
await obelysk.confidentialTransfer.transfer({
  token: 'sage',
  amount: '1000',
  recipient: '0xRECIPIENT',
});

// Withdraw (private → public)
await obelysk.confidentialTransfer.withdraw({
  token: 'sage',
  amount: '2000',
});

// Check encrypted balance
const balance = await obelysk.confidentialTransfer.getEncryptedBalance(
  '0xADDRESS', 'sage'
);

Contract Details

  • Address: 0x0673685bdb01fbf57c390ec2c0d893e7c77316cdea315b0fbfbc85b9a9a979d2
  • Encryption: ElGamal over STARK curve
  • Proofs: 6 Schnorr/Chaum-Pedersen per transfer
  • Upgrade timelock: 5 minutes (fast upgrade for security patches)

Next Steps