Skip to content
This repository was archived by the owner on Mar 11, 2025. It is now read-only.

Commit 271d235

Browse files
Confidential transfer encryption doc (#3399)
* doc: add discussion on twisted elgamal * doc: add twisted elgamal latex pdf * doc: add available and pending balance discussion * doc: additional minor wordings
1 parent 674b0ac commit 271d235

File tree

3 files changed

+289
-4
lines changed

3 files changed

+289
-4
lines changed
Lines changed: 281 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,284 @@
11
---
22
title: Encryption
33
---
4+
5+
The confidential extension program makes use of a public key encryption scheme
6+
and an authenticated symmetric encryption scheme. For public key encryption, the
7+
program uses the [twisted ElGamal encryption](https://eprint.iacr.org/2019/319)
8+
scheme. For symmetric encryption, it uses
9+
[AES-GCM-SIV](https://datatracker.ietf.org/doc/html/rfc8452).
10+
11+
## Twisted ElGamal Encryption
12+
13+
The twisted ElGamal encryption scheme is a simple variant of the standard
14+
ElGamal encryption scheme where a ciphertext is divided into two components:
15+
16+
- A Pedersen commitment of the encrypted message. This component is independent
17+
of the public key.
18+
- A "decryption handle" that binds the encryption randomness with respect to a
19+
specific ElGamal public key. This component is independent of the actually
20+
encrypted message.
21+
22+
The structure of the twisted ElGamal ciphertexts simplifies their design of some
23+
zero-knowledge proof systems. Furthermore, since the encrypted messages are
24+
encoded as Pedersen commitments, many of the existing zero-knowledge proof
25+
systems that are designed to work specifically for Pedersen commitments can be
26+
directly used on the twisted ElGamal ciphertexts.
27+
28+
We provide the formal description of the twisted ElGamal encryption in the
29+
[notes](./twisted_elgamal.pdf).
30+
31+
### Ciphertext Decryption
32+
33+
One aspect that makes the use of the ElGamal encryption cumbersome in protocols
34+
is the inefficiency of decryption. The decryption of an ElGamal ciphertext grows
35+
exponentially with the size of the encrypted number. With modern hardware, the
36+
decryption of 32-bit messages can be in the order of seconds, but it quickly
37+
becomes infeasible as the message size grows. A standard Token account stores
38+
general `u64` balances, but an ElGamal ciphertext that encrypts large 64-bit
39+
values are not decryptable. Therefore, extra care is put into the way the
40+
balances and transfer amounts are encrypted and handled in the account state and
41+
transfer data.
42+
43+
## Account State
44+
45+
If the decryption of the twisted ElGamal encryption scheme were fast, then a
46+
confidential transfer account and a confidential instruction data could be
47+
modeled as follows:
48+
49+
```rust
50+
struct ConfidentialTransferAccount {
51+
/// `true` if this account has been approved for use. All confidential
52+
/// transfer operations for
53+
/// the account will fail until approval is granted.
54+
approved: PodBool,
55+
56+
/// The public key associated with ElGamal encryption
57+
encryption_pubkey: ElGamalPubkey,
58+
59+
/// The pending balance (encrypted by `encryption_pubkey`)
60+
pending_balance: ElGamalCiphertext,
61+
62+
/// The available balance (encrypted by `encryption_pubkey`)
63+
available_balance: ElGamalCiphertext,
64+
}
65+
```
66+
67+
```rust
68+
// Actual cryptographic components are organized in `VerifyTransfer`
69+
// instruction data
70+
struct ConfidentialTransferInstructionData {
71+
/// The transfer amount encrypted under the sender ElGamal public key
72+
encrypted_amount_sender: ElGamalCiphertext,
73+
/// The transfer amount encrypted under the receiver ElGamal public key
74+
encrypted_amount_receiver: ElGamalCiphertext,
75+
}
76+
```
77+
78+
Upon receiving a transfer instruction, the Token program aggregates
79+
`encrypted_amount_receiver` into the account `pending_balance`.
80+
81+
The actual structures of these two components are more involved. Since the
82+
`TransferInstructionData` requires zero-knowledge proof components, we defer the
83+
discussion of its precise structure to the next subsection and focus on
84+
`ConfidentialTransferAccount` here. We start from the ideal
85+
`ConfidentialTransferAccount` structure above and incrementally modify it to
86+
produce the final structure.
87+
88+
### Available Balance
89+
90+
If the available balance is encrypted solely as general `u64` values, then it
91+
becomes infeasible for clients to decrypt and recover the exact balance in an
92+
account. Therefore, in the Token program, the available balance is additionally
93+
encrypted using an authenticated symmetric encryption scheme. The resulting
94+
ciphertext is stored as the `decryptable_balance` of an account and the
95+
corresponding symmetric key should either be stored on the client side as an
96+
independent key or be derived on-the-fly from the owner signing key.
97+
98+
```rust
99+
struct ConfidentialTransferAccount {
100+
/// `true` if this account has been approved for use. All confidential
101+
/// transfer operations for
102+
/// the account will fail until approval is granted.
103+
approved: PodBool,
104+
105+
/// The public key associated with ElGamal encryption
106+
encryption_pubkey: ElGamalPubkey,
107+
108+
/// The pending balance (encrypted by `encryption_pubkey`)
109+
pending_balance: ElGamalCiphertext,
110+
111+
/// The available balance (encrypted by `encryption_pubkey`)
112+
available_balance: ElGamalCiphertext,
113+
114+
/// The decryptable available balance
115+
decryptable_available_balance: AeCiphertext,
116+
}
117+
```
118+
119+
Since `decryptable_available_balance` is easily decryptable, clients should
120+
generally use it to decrypt the available balance in an account. The
121+
`available_balance` ElGamal ciphertext should generally only be used to generate
122+
zero-knowledge proofs when creating a transfer instruction.
123+
124+
The `available_balance` and `decryptable_available_balance` should encrypt the
125+
same available balance that is associated with the account. The available
126+
balance of an account can change only after an `ApplyPendingBalance` instruction
127+
and an outgoing `Transfer` instruction. Both of these instructions require a
128+
`new_decryptable_available_balance` to be included as part of their instruction
129+
data.
130+
131+
### Pending Balance
132+
133+
Like in the case of the available balance, one can consider adding a
134+
`decryptable_pending_balance` to the pending balance. However, whereas the
135+
available balance is always controlled by the owner of an account (via the
136+
`ApplyPendingBalance` and `Transfer` instructions), the pending balance of an
137+
account could constantly change with incoming transfers. Since the corresponding
138+
decryption key of a decryptable balance ciphertext is only known to the owner of
139+
an account, the sender of a `Transfer` instruction cannot update the decryptable
140+
balance of the receiver's account.
141+
142+
Therefore, for the case of the pending balance, the Token program stores two
143+
independent ElGamal ciphertexts, one encrypting the low bits of the 64-bit
144+
pending balance and one encrypting the high bits.
145+
146+
```rust
147+
struct ConfidentialTransferAccount {
148+
/// `true` if this account has been approved for use. All confidential
149+
/// transfer operations for
150+
/// the account will fail until approval is granted.
151+
approved: PodBool,
152+
153+
/// The public key associated with ElGamal encryption
154+
encryption_pubkey: ElGamalPubkey,
155+
156+
/// The low-bits of the pending balance (encrypted by `encryption_pubkey`)
157+
pending_balance_lo: ElGamalCiphertext,
158+
159+
/// The high-bits of the pending balance (encrypted by `encryption_pubkey`)
160+
pending_balance_hi: ElGamalCiphertext,
161+
162+
/// The available balance (encrypted by `encryption_pubkey`)
163+
available_balance: ElGamalCiphertext,
164+
165+
/// The decryptable available balance
166+
decryptable_available_balance: AeCiphertext,
167+
}
168+
```
169+
170+
We correspondingly divide the ciphertext that encrypts the transfer amount in
171+
the transfer instruction data as low and high bit encryptions.
172+
173+
```rust
174+
// Actual cryptographic components are organized in `VerifyTransfer`
175+
// instruction data
176+
struct ConfidentialTransferInstructionData {
177+
/// The transfer amount encrypted under the sender ElGamal public key
178+
encrypted_amount_sender: ElGamalCiphertext,
179+
/// The low-bits of the transfer amount encrypted under the receiver
180+
/// ElGamal public key
181+
encrypted_amount_lo_receiver: ElGamalCiphertext,
182+
/// The high-bits of the transfer amount encrypted under the receiver
183+
/// ElGamal public key
184+
encrypted_amount_hi_receiver: ElGamalCiphertext,
185+
}
186+
```
187+
188+
Upon receiving a transfer instruction, the Token program aggregates
189+
`encrypted_amount_lo_receiver` in the instruction data to `pending_balance_lo`
190+
in the account and `encrypted_amount_hi_receiver` to `pending_balance_hi`.
191+
192+
One natural way to divide the 64-bit pending balance and transfer amount in the
193+
structures above is to evenly split the number as low and high 32-bit numbers.
194+
Then since the amounts that are encrypted in each ciphertexts are 32-bit
195+
numbers, each of their decryption can be done efficiently.
196+
197+
The problem with this approach is that the 32-bit number that is encrypted as
198+
`pending_balance_lo` could easily overflow and grow larger than a 32-bit number.
199+
For example, two transfers of the amount `2^32-1` to an account force the
200+
`pending_balance_lo` ciphertext in the account to `2^32`, a 33-bit number.
201+
As the encrypted amount overflows, it becomes increasingly more difficult to
202+
decrypt the ciphertext.
203+
204+
To cope with overflows, we add the following two components to the account
205+
state.
206+
207+
- The account state keeps track of the number of incoming transfers that it
208+
received since the last `ApplyPendingBalance` instruction.
209+
- The account state stores a `maximum_pending_balance_credit_counter` which
210+
limits the number of incoming transfers that it can receive before an
211+
`ApplyPendingBalance` instruction is applied to the account. This upper bound
212+
can be configured with the `ConfigureAccount` and should typically be set to
213+
`2^16`.
214+
215+
```rust
216+
struct ConfidentialTransferAccount {
217+
... // `approved`, `encryption_pubkey`, available balance fields omitted
218+
219+
/// The low bits of the pending balance (encrypted by `encryption_pubkey`)
220+
pending_balance_lo: ElGamalCiphertext,
221+
222+
/// The high bits of the pending balance (encrypted by `encryption_pubkey`)
223+
pending_balance_hi: ElGamalCiphertext,
224+
225+
/// The maximum number of `Deposit` and `Transfer` instructions that can credit
226+
/// `pending_balance` before the `ApplyPendingBalance` instruction is executed
227+
pub maximum_pending_balance_credit_counter: u64,
228+
229+
/// The number of incoming transfers since the `ApplyPendingBalance` instruction
230+
/// was executed
231+
pub pending_balance_credit_counter: u64,
232+
}
233+
```
234+
235+
For the case of the transfer instruction data, we make the following
236+
modifications:
237+
238+
- The transfer amount is restricted to be a 48-bit number.
239+
- The transfer amount is divided into 16 and 32-bit numbers and is encrypted as
240+
two ciphertexts `encrypted_amount_lo_receiver` and
241+
`encrypted_amount_hi_receiver`.
242+
243+
```rust
244+
// Actual cryptographic components are organized in `VerifyTransfer`
245+
// instruction data
246+
struct ConfidentialTransferInstructionData {
247+
/// The transfer amount encrypted under the sender ElGamal public key
248+
encrypted_amount_sender: ElGamalCiphertext,
249+
/// The low *16-bits* of the transfer amount encrypted under the receiver
250+
/// ElGamal public key
251+
encrypted_amount_lo_receiver: ElGamalCiphertext,
252+
/// The high *32-bits* of the transfer amount encrypted under the receiver
253+
/// ElGamal public key
254+
encrypted_amount_hi_receiver: ElGamalCiphertext,
255+
}
256+
```
257+
258+
The fields `pending_balance_credit_counter` and
259+
`maximum_pending_balance_credit_counter` are used to limit amounts that are
260+
encrypted in the pending balance ciphertexts `pending_balance_lo` and
261+
`pending_balance_hi`. The choice of the limit on the transfer amount is
262+
done to balance the efficiency of ElGamal decryption with the usability of a
263+
confidential transfer.
264+
265+
Consider the case where `maximum_pending_balance_credit_counter` is set to
266+
`2^16`.
267+
268+
- The `encrypted_amount_lo_receiver` encrypts a number that is at most a 16-bit
269+
number. Therefore, even after `2^16` incoming transfers, the ciphertext
270+
`pending_balance_lo` in an account encrypts a balance that is at most a 32-bit
271+
number. This component of the pending balance can be decrypted efficiently.
272+
273+
- The `encrypted_amount_hi_receiver` encrypts a number that is at most a 32-bit
274+
number. Therefore, after `2^16` incoming transfers, the ciphertext
275+
`pending_balance_hi` encrypts a balance that is at most a 48-bit number.
276+
277+
The decryption of a large 48-bit number is slow. However, for most
278+
applications, transfers of very high transaction amounts are relatively more
279+
rare. For an account to hold a pending balance of large 48-bit numbers, it
280+
must receive a large number of high transactions amounts. Clients that
281+
maintain accounts with high token balances can frequently submit the
282+
`ApplyPendingBalance` instruction to flush out the pending balance into the
283+
available balance to prevent `pending_balance_hi` from encrypting a number
284+
that is too large.

docs/src/confidential-token/deep-dive/overview.md

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,7 @@ zero-knowledge proofs.
250250
the proof should certify that `ct_transfer` encrypts a value `x` such that
251251
`0 <= x < u64::MAX`.
252252

253-
- _Equality proof_: Reall that a transfer instruction contains two ciphertexts
253+
- _Equality proof_: Recall that a transfer instruction contains two ciphertexts
254254
of the transfer value `x`: a ciphertext under the sender public key
255255
`ct_sender = PKE::encrypt(pk_sender, x)` and one under the receiver public key
256256
`ct_receiver = PKE::encrypt(pk_receiver, x)`. A malicious user can encrypt two
@@ -438,10 +438,14 @@ the originally encrypted value, one must solve a computational problem called
438438
the _discrete logarithm_, which requires an expoential time to solve. In the
439439
confidential extension program, we address this issue in the following two ways:
440440

441-
- We restrict a transfer amount to 48-bit numbers.
442-
- We encrypt the transfer amount as two independent ciphertexts.
441+
- Transfer amounts are restricted to 48-bit numbers.
442+
- Transfer amounts and account pending balances are encrypted as two
443+
independent ciphertexts.
444+
- Account available balances are additionally encrypted using a symmetric
445+
encryption scheme.
443446

444-
We refer to the documentation in the source code for more details.
447+
We refer to the subsequent sections and the documentation in the source code for
448+
additional details.
445449

446450
### Twisted ElGamal encryption
447451

Binary file not shown.

0 commit comments

Comments
 (0)