Confidential assets:
A cryptographic deep dive
March 24th, 2026
First: a moment of silence for "veiled coins"...
2
Outline
3
Public transactions (the status quo)
4
🌍
💰 250
🌍
💰 100
🔏 "Send 💰 20 to Bob"
🌍
Everybody sees balances and TXN amounts.
Everybody sees sender & recipient.
💰 -20
💰 +20
Public transactions (the status quo)
5
🌍
💰 230
🌍
💰 120
Everybody sees balances and TXN amounts.
Everybody sees sender & recipient.
6
🌍
💰 230
🌍
💰 120
💰 500
💰 700
Nobody sees balances nor TXN amounts.
🔏 "Send 💰 50 to Bob"
🌍
Confidential transactions as an option
💰 -50
💰 +50
7
🌍
💰 230
🌍
💰 120
Nobody sees balances nor TXN amounts.
Everybody still sees sender & recipient.
💰 550
💰 650
🔏 "Send 💰 50 to Bob"
🌍
Confidential transactions as an option
Except auditor(s).
Outline
8
Why?
9
Regulatory compliance:
Confidentiality = user safety
New use cases:
Human dignity ⊇ ability to choose what information you reveal about yourself. -- Hester Pierce, SEC Commissioner (video)
Outline
10
Demo: Confidential payments (confidential.aptoslabs.com)
11
Keyless (sign in with your Google):
Venmo-like:
Outline
12
Before we dig in… What are the next steps?
We can pause for some early Q&A, if any… 🙋
13
Outline
14
Comparison to previous account model protocols
15
Comparison to previous account model protocols
16
Comparison to previous account model protocols
17
Comparison to previous account model protocols
18
Comparison to previous account model protocols
19
Comparison to previous account model protocols
20
Comparison to previous account model protocols
21
Deposits: enroll for confidentiality
22
Prereq 1: Chunked'n'twisted ElGamal encryption 🔐
KGen(1λ) → (dk, ek)
dk ∈R ℤp
ek ← dk-1 ᐧ H
Eℓ(ek, v1, …, vℓ; r1, ..., rℓ) → c
Ri← ri ᐧ ek, ∀i ∈ [ℓ]
Pi ← vi ᐧ G + ri ᐧ H, ∀i ∈ [ℓ]
c ← (Pi, Ri)i ∈ [ℓ]
23
i.e., Pedersen commitment to vi using randomness ri and commitment key (G, H)
Bolded, underlined variables denote vectors (e.g., c).
Convention: We will use c.Ped := (Pi)i ∈ [ℓ] to denote the Pedersen components of the ciphertext.
Randomness; important!
Prereq 2: Chunking
For Aptos, we will work with:
i.e., ToChunks returns u’s base-2b representation:
24
Convention: u = ToChunks(u, b, m) splits u into m chunks of b bits each & returns them as u.
u = Unchunk(u, b, m) does the opposite such that Unchunk( ToChunks(u, b, m), b, m ) = u.
25
pubS: $10
Sherry (ekS)
A'S← Eℓ(ekS, v; 0)
AS: empty / unset
i.e.: ToChunks(v, b, ℓ)
2
set new
ava. bal.
TXN
1. public amount v
// to deposit for Sherry
1
Note: Deposited amount is public ⇒ No privacy just yet. (Must send or receive a transfer for that.)
🎉 Sherry can deposit confidential coins into her account!
Withdrawal: dispose of confidentiality
26
Prereq 3: Zero-knowledge proofs (ZKP) for NP relations
𝓡(x; w) = 1
27
A ZKP for x ∈ 𝓡
convinces verifier that prover knows
a secret witness w such that 𝓡(x; w) = 1
Public statement: Both prover & verifier have it.
Private witness: Only prover has it.
Goal: Hide it from verifier.
Any publicly-known algorithm, henceforth called a relation
Prereq 3’: Zero-knowledge proofs (ZKP) (e.g.: Sudoku)
Sudoku(puzzle; sol) = 1
28
A ZKP for puzzle ∈ Sudoku
convinces verifier that prover knows
a secret sol such that Sudoku(puzzle; sol) = 1
A publicly-known algorithm for checking if sol is a valid solution to puzzle.
ZK range proof relation for Pedersen-committed values
𝓡b((Pi)i ∈ [ℓ]; (vi, ri)i ∈ [ℓ]) = 1
29
∀i ∈ [ℓ]:
Pi = vi ᐧ G + ri ᐧ H ⴷ
vi ∈ [0, 2b),
⇔
Prereq 4: Chunked'n'twisted ElGamal decryption 🔐
Decℓ(c; dk) → (v1ᐧ G, …, vℓᐧ G)
(Pi, Ri)i ∈ [ℓ] ← c
vi ᐧ G ← Pi - dk ᐧ Ri, ∀i ∈ [ℓ]
Dec*ℓ(c; dk) → v ᐧ G
(v1ᐧ G, …, vℓᐧ G) ← Decℓ(dk, c)
v ᐧ G ←Σi∈ [ℓ](216)i ᐧ (vi ᐧ G)
30
= (Pi) - dk ᐧ (Ri)
= (vi ᐧ G + ri ᐧ H) - dk ᐧ (ri ᐧ ek)
= (vi ᐧ G + ri ᐧ H) - dk ᐧ (ri ᐧ dk-1 ᐧ H)
= (vi ᐧ G + ri ᐧ H) - ri ᐧ H
= vi ᐧ G
Discrete logarithm problem (DLP):
given viᐧ G, compute vi.
Fast for 32-bit vi’s (e.g., ~10ms in Rust)
Note: Fix b and ℓ. Then, Dec*ℓ( Eℓ(ek, ToChunks(u, b, ℓ); r); dk ) = u ᐧ G, for all u, r, ek = dk-1 ᐧ H
31
TXN
1
pubS: $10
Sherry (ekS)
AS: Eℓ(ekS, aS; ᐧ )
i.e.: ToChunks(aS, b, ℓ)
2. A'S // Sherry’s new balance
3. πwithdraw for (ekS, v, AS, A'S) ∈ 𝓡withdraw
1. public amount v
// to withdraw from Sherry
A'S← Eℓ(ekS, as’; r)
2
set new
ava. bal.
i.e.: ToChunks(aS - v, b, ℓ)
🎉 Sherry can withdraw confidential coins back into her public!
𝓡withdraw(ekS, v, AS, A'S; dkS, r, as’)
a. ekS = dkS-1 ᐧ H // dk is 👌
b. A'S = Eℓ(ekS, aS’; r)
c. Unchunk( as’, b, ℓ) + v ᐧ G = D*ℓ(dkS, AS)
Infinite money glitch: aS = 0 and v = 1 ⇒ aS’ = aS - v = -1 (mod p) = p - 1 (mod p)
Why? 𝓡withdraw does not check that v ≤ aS.
4. π≤ for (A'S.Ped) ∈ 𝓡b
That’s why we need a range proof!
Transfers: leverage confidentiality
32
Prereq 5: Chunked'n'twisted ElGamal batch encryption 🔐
Eℓ(ek1, …, ekm, v1, …, vℓ; r1, ..., rℓ) → c
Ri, j← ri ᐧ ekj, ∀i ∈ [ℓ], ∀j ∈ [m]
Pi ← vi ᐧ G + ri ᐧ H, ∀i ∈ [ℓ]
c ← (Pi, Ri, 1, …, Ri, m)i ∈ [ℓ]
33
Convention: As before, c.Ped := (Pi)i ∈ [ℓ] denotes the Pedersen components of the batch ciphertext.
(m+1)ℓ-sized vs. 2mℓ, if done naively via Eℓ(ekj, v1, …, vℓ; r1,j, ..., rℓ, j), ∀j ∈ [m]
Prereq 6: Chunked'n'twisted ElGamal homomorphism 🔐
Rai← ri ᐧ ek
Pai ← ai ᐧ G + ri ᐧ H
34
Eℓ(ek, a; r) + Eℓ(ek, b; r’) = Eℓ(ek, (a + b) mod p; r + r’)
Eℓ(ek, a; r) + En(ek, v; r’) = Eℓ(ek, (a + v) mod p; r + r’)
Convention: When adding a size-n ciphertext with a larger size-ℓone, we implicitly pad with 0s.
Note: All ciphertexts are under the same ek
+
Rbi← r’i ᐧ ek
Pbi ← bi ᐧ G + r’i ᐧ H
=
Ri←Rai + Rbi = (ri+r’i) ᐧ ek
Pi ← Pai + Pbi= (ai+bi) ᐧ G + (ri+r’i) ᐧ H
Food for thought: If a and b were base-2b, when combined like this will the result remain base-2b?
35
1
TXN
pubR: $60
Rob (ekR)
AR: Eℓ(ekR, aR; ᐧ )
2. A'S // Sherry’s new balance
3. πtxfer for (ekS, ekR, c, AS, A'S) ∈ 𝓡txfer
4. π≤ for (c.Ped), (A'S.Ped) ∈ 𝓡b
1. c = En(ekS, ekR, v; s)
// to withdraw from Sherry and deposit for Rob
i.e.: ToChunks(v, b, n)
3
hom. upd.
A'S← Eℓ(ekS, as’; r)
2
set new
ava. bal.
This update can invalidate Rob’s ZKP w.r.t. AR
pubS: $10
Sherry (ekS)
AS: Eℓ(ekS, aS; ᐧ )
𝓡txfer(ekS, ekR, c, AS, A'S; dkS, v, r, as’)
a. 1 = 𝓡withdraw(ekS, v, AS, A'S; dkS, r, as’)
b. c = En(ekS, ekR, ToChunks(v, b, n); s)
Why? If v = p - 1 ⇒ aS’ = aS - v = aS + 1 ⇒ (A'S.Ped) ∈ 𝓡b will pass
A'R← AR+ c =
= Eℓ(ekR, aR+ v; ᐧ )
🎉 Sherry can transfer confidential coins to anyone!
36
1
TXN
pubR: $60
Rob (ekR)
AR: Eℓ(ekR, aR; ᐧ )
2. A'S // Sherry’s new balance
3. πtxfer for (ekS, ekR, c, AS, A'S) ∈ 𝓡txfer
4. π≤ for (c.Ped), (A'S.Ped) ∈ 𝓡b
1. c = En(ekS, ekR, v; s)
// to withdraw from Sherry and deposit for Rob
i.e.: ToChunks(v, b, n)
3
hom. upd.
A'S← Eℓ(ekS, as’; r)
2
set new
ava. bal.
This update can invalidate Rob’s ZKP w.r.t. AR
pubS: $10
Sherry (ekS)
AS: Eℓ(ekS, aS; ᐧ )
𝓡txfer(ekS, ekR, c, AS, A'S; dkS, v, r, as’)
a. ekS = dkS-1 ᐧ H // dk is 👌
b. A'S = Eℓ(ekS, aS’; r)
c. Unchunk( as’, b, ℓ) + v ᐧ G = D*ℓ(dkS, AS)
b. c = En(ekS, ekR, ToChunks(v, b, n); s)
Why? If v = p - 1 ⇒ aS’ = aS - v = aS + 1 ⇒ (A'S.Ped) ∈ 𝓡b will pass
A'R← AR+ c =
= Eℓ(ekR, aR+ v; ᐧ )
37
A'S← Eℓ(ekS, as’; r)
TXN
2. A'S // Sherry’s new balance
3. πtxfer for (ekS, ekR, c, AS, A'S) ∈ 𝓡txfer
4. π≤ for (c.Ped), (A'S.Ped) ∈ 𝓡b
1. c = En(ekS, ekR, v; s)
// to withdraw from Sherry and deposit for Rob
pubS: $10
Sherry (ekS)
pubR: $60
Rob (ekR)
AR: Eℓ(ekR, aR; ᐧ )
1
PR: En(ekR, pR; ᐧ )
# recv’d: 35
# recv’dR: 21
AS: Eℓ(ekS, aS; ᐧ )
PS: En(ekS, ps; ᐧ )
P'R← PR+ c =
= Eℓ(ekR, pR+ v; ᐧ )
3
hom. upd.
# recv’dR← 21 + 1
2
set new
ava. bal.
Total bal.: aS + pS
i.e.: ToChunks(v, b, n)
38
pubS: $10
Sherry (ekS)
pubR: $60
Rob (ekR)
AR: Eℓ(ekR, ekA, aR; ᐧ )
1
TXN
PR: En(ekR, pR; ᐧ )
2. A'S // Sherry’s new (normalized) balance
3. π≥ for (c.Ped), (A'S.Ped) ∈ 𝓡b
4. πtxfer for (ekS, ekR, ekA, c, AS, A'S) ∈ 𝓡txfer
1. c = En(ekS, ekR, ekA, v; ᐧ )
// for debiting Sherry, crediting Rob, and 👀 auditor
# recv’d: 35
# recv’dR: 21
AS: Eℓ(ekS, ekA, aS; ᐧ )
PS: En(ekS, ps; ᐧ )
A'S← Eℓ(ekS, ekA, as’; r)
Added auditor ekA!
TXN
2. A'S // Sherry’s new balance
3. πtxfer for (ekS, ekR, ekA, c, AS, A'S) ∈ 𝓡txfer
4. π≥ for (c.Ped), (A'S.Ped) ∈ 𝓡b
1. c = En(ekS, ekR, ekA, v; s)
// to withdraw from Sherry and deposit for Rob + 👀 auditor
P'R← PR+ c =
= Eℓ(ekR, pR+ v; ᐧ )
3
hom. upd.
2
set new
ava. bal.
# recv’dR← 21 + 1
i.e.: ToChunks(v, b, n)
Too many tx’fers ⇒ chunks get large ⇒ expensive DLs.
Solution: Roll over into available balance after 2b in-transfers.
39
# recv’dR← 0
2
hom. upd.
pubR: $60
Rob (ekR)
AR: Eℓ(ekR, ekA, aR; r)
PR: En(ekR, pR; t)
# recv’dR: 22
A'R← AR+ PR =
= Eℓ(ekR, aR+ pR; r + t)
1
TXN
“Roll over!”
After 2b in-transfers, pending chunks will become < 22b
⇒ after rollover, available balance chunks may be ≤ 2b bit-wide.
Solution: “Normalize” to b-bit by withdrawing 0 from your account!
A lot (like a lot) more details can be found at…
alinush.org/confidential-assets:
40
That's it folks! 👋👋
Resources:
Time for Q&A (part II) 🙋
41
Appendix
42
Part 4a: Move APIs
At a high-level, the public entry Move functions are:
Compatible with:
43
Actually, a bit more complicated: see next slide
Part 4b: Move APIs (additional complexity)
At a lower-level, there are some extra public entry Move functions:
44
Part 4c: SDK APIs (TODO: update extra/normalization/withdraw auditors)
At a high-level, the TypeScript SDK APIs exposed are:
45
Base-2b vs. radix-2b representation
Definition 2: v = [v1, …, vℓ] is a size-ℓ base-2b representation of v iff.:
Definition 1: v = [v1, …, vℓ] is a size-ℓ radix-2b representation of v iff. v = ∑i ∈ [ℓ] (2b)i-1 ᐧ vi
46
Does NOT restrict the sizes of the vi's at all, nor the size of v!
e.g., v = [1, 1, -1], and v' = [0, 0, 5] are size-3 radix-2 representations of v = 5
e,g., v = [1, 0, 1] is the unique size-3 base-2 representation of v = 5
Size-ℓ base-2b representations
47
ToChunks(v, b, ℓ) → v = [ v1, v2, …, vm , …, vℓ]
Returns the size-ℓ base-2b representation v of v: i.e., v = ∑i ∈ [ℓ] (2b)i-1 ᐧ vi
log2(v) / b chunks
ℓ could be larger than this
v1 is the least-significant chunk (LSC)
vℓ is the most-significant chunk (MSC)
128-bit ± 64-bit integer arithmetic mod (~256-bit) prime q
Recall that when we "subtract" two encryptions of bal ∈ [0, 2128) and v ∈ [0, 264), we get an encryption of (bal - v) mod q.
Q: How can the validators check that the encrypted values satisfy bal ≥ v?
A: Check that v ∈ [0, 264) and bal - v ∈ [0, 2128) or, otherwise see what happens 👇
48
Attack 1 (works when you only check v ∈ [0, 264)):
(bal - v) mod q =
(bal - (bal + 1)) mod q =
(-1) mod q =
(q - 1) mod q ∉ [0, 2128)
⇒ new balance > the old balance! 😱
Attack 2 (works when you only check bal - v ∈ [0, 2128)):
⇒ transferred amount likely > the balance! 😱
(bal - v) mod q =
(bal - (q - c)) mod q =
(bal + c) mod q
⇒ new balance > the old balance! 😱
In general: k-bit ± n-bit integer arithmetic mod prime q
Recall that when we "subtract" two encryptions of bal ∈ [0, 2n) and v ∈ [0, 2k), we get an encryption of (bal - v) mod q.
Q: How can the validators check that the encrypted values satisfy bal ≥ v?
A: Check that v ∈ [0, 2k) and bal - v ∈ [0, 2n), assuming that 2n + 2k ≤ q,
49
Attack 1 (works when you only check v ∈ [0, 2k)):
(bal - v) mod q =
(bal - (bal + 1)) mod q =
(-1) mod q =
(q - 1) mod q ∉ [0, 2n)
⇒ new balance > the old balance! 😱
Attack 2 (works when you only check bal - v ∈ [0, 2n)):
⇒ transferred amount likely > the balance! 😱
(bal - v) mod q =
(bal - (q - c)) mod q =
(bal + c) mod q
⇒ new balance > the old balance! 😱
i.e., as long as bal' + v does not "overflow" in the field ℤq = {0, 1, …, q-1}; o/w, receiver's balance will wrap around.
Part 1: k-bit ± n-bit integer arithmetic modulo a prime q
Goal: TXN seeks to prove that the encrypted integers v, balS, balR ∈ [0, q) satisfy:
Assumption 1: ElGamal ciphertext encrypts numbers in {0, 1, …, q-1} = ℤq
Assumption 2: We maintain an encrypted balance invariant (EBI) that balS, balR ∈ [0, 2n)
Assumption 3: TXN includes ZKPs for:
50
mod q, because that's the only thing range proofs guarantee.
Part 2: k-bit ± n-bit integer arithmetic modulo a prime q
Observation 1: If balR ∈ [0, 2n) (from EBI) and v ∈ [0, 2k) (from TXN ZKP) hold, then balR + v < 2n + 2k.
So, if we assume 2n + 2k ≤ q, then balR + v < q also holds!
Theorem 1: balR ∈ [0, 2n) & v ∈ [0, 2k) & 2n + 2k ≤ q ⇒ balR + v < q
51
Proof is sketched above ☝
Part 3: k-bit ± n-bit integer arithmetic modulo a prime q
Theorem 2: If k, n, q ∈ ℕ* and integers balS, balR, v satisfy:
Then, v ≤ balS (as integers, no mod q anymore!) and, of course, balR + v < q (restating Theorem 1)
Proof: Suppose v > balS. Let δ = balS - v. Then δ < 0. We know that balS - v mod q ∈ [0, 2n) ⇔ 0 ≤ δ mod q < 2n ⇔ ∃q s.t. 0 ≤ δ + q · q < 2n. Note that if q = 0 ⇒ δ ≥ 0 ⇒ contradiction. So, q ≠ 0. Next, suppose q ≥ 2.
Lemma 1: Since δ = balS - v, and (1) + (2) hold ⇒ δ ∈ (-2k, 0) b.c. min balS = 0 and max v = 2k - 1 ⇒ δ ≥ -(2k-1).
�Applying Lemma 1 and (4), we can lower bound: δ + q · q ≥ -(2k-1) + 2(2n + 2k) = 2n+1 + 2k + 1. But we know from before that δ + q · q < 2n. And 2n+1 + 2k + 1 exceeds that. Contradiction. So, q can only be 1.�
Finally, since q = 1, we know δ + q · q < 2n ⇔ δ + q < 2n. But q ≥ 2n + 2k and δ ≥ -(2k-1) ⇒ δ + q ≥ 2n + 2k - (2k-1) = 2n + 1. This is a contradiction. Original, v > balS assumption must be false ⇒ v ≤ balS. QED.
52