1
+ //! Generates the zero-knowledge proofs required for a confidential transfer with a fee.
2
+ //!
3
+ //! A confidential transfer with a fee is more complex than a simple transfer. It requires five
4
+ //! distinct zero-knowledge proofs to ensure the validity of the transfer, the solvency of the
5
+ //! sender, and the correctness of the fee amount according to the on-chain mint configuration.
6
+ //!
7
+ //! ## Protocol Flow and Proof Components
8
+ //!
9
+ //! 1. **Fee Calculation**: The client first calculates the required fee based on the transfer
10
+ //! amount and the on-chain fee parameters (rate and maximum cap).
11
+ //!
12
+ //! 2. **Encrypt Amounts**: The gross transfer amount and the fee amount are each split into low
13
+ //! and high bit components. These components are then encrypted into separate grouped (twisted)
14
+ //! ElGamal ciphertexts with the appropriate decryption handles for the involved parties (source,
15
+ //! destination, auditor, and withdraw authority).
16
+ //!
17
+ //! 3. **Generate Proofs**: The sender generates five proofs that work in concert:
18
+ //!
19
+ //! - **Transfer Amount Ciphertext Validity Proof
20
+ //! (`BatchedGroupedCiphertext3HandlesValidityProofData`)**: Certifies that the grouped
21
+ //! ElGamal ciphertext for the gross transfer amount is well-formed.
22
+ //!
23
+ //! - **Fee Ciphertext Validity Proof
24
+ //! (`BatchedGroupedCiphertext2HandlesValidityProofData`)**: Certifies that the grouped
25
+ //! ElGamal ciphertext for the transfer fee is well-formed.
26
+ //!
27
+ //! - **Fee Calculation Proof (`PercentageWithCapProofData`)**:
28
+ //! It's a "one-of-two" proof that certifies **either**:
29
+ //! 1. The `fee_amount` is exactly equal to the on-chain `maximum_fee`.
30
+ //! 2. The `fee_amount` was correctly calculated as a percentage of the
31
+ //! `transfer_amount`, according to the on-chain `fee_rate_basis_points`.
32
+ //!
33
+ //! **Note**: The proof certifies that the transfer fee is a valid percentage of the
34
+ //! transfer amount or that the fee is exactly the maximum fee. While the sender is
35
+ //! expected to choose the lower of these two options, the proof does not enforce this
36
+ //! choice.
37
+ //!
38
+ //! - **Range Proof (`BatchedRangeProofU256Data`)**:
39
+ //! This expanded range proof ensures the solvency of the entire transaction by certifying
40
+ //! that all critical monetary values are non-negative. This includes the sender's remaining
41
+ //! balance, the gross transfer amount, the fee amount, and the net transfer amount that the
42
+ //! destination receives.
43
+ //!
44
+ //! - **Ciphertext-Commitment Equality Proof (`CiphertextCommitmentEqualityProofData`)**:
45
+ //! Identical in purpose to the simple transfer, this proof links the sender's remaining
46
+ //! balance (as a homomorphically computed ElGamal ciphertext) to a new Pedersen commitment.
47
+ //! This commitment is then used in the Range Proof to prove the sender's solvency.
48
+
1
49
#[ cfg( not( target_arch = "wasm32" ) ) ]
2
50
use solana_zk_sdk:: encryption:: grouped_elgamal:: GroupedElGamal ;
3
51
#[ cfg( target_arch = "wasm32" ) ]
@@ -40,11 +88,6 @@ const NET_TRANSFER_AMOUNT_BIT_LENGTH: usize = 64;
40
88
41
89
/// The proof data required for a confidential transfer instruction when the
42
90
/// mint is extended for fees
43
- ///
44
- /// NOTE: The proofs certify that the transfer fee is a valid percentage of the
45
- /// transfer amount, as determined by the fee basis points, or that the fee is
46
- /// exactly the maximum fee. While the sender is expected to choose the lower of
47
- /// these two options, the proof does not enforce this choice.
48
91
pub struct TransferWithFeeProofData {
49
92
pub equality_proof_data : CiphertextCommitmentEqualityProofData ,
50
93
pub transfer_amount_ciphertext_validity_proof_data_with_ciphertext :
@@ -193,12 +236,16 @@ pub fn transfer_with_fee_split_proof_data(
193
236
// calculate fee
194
237
let transfer_fee_basis_points = fee_rate_basis_points;
195
238
let transfer_fee_maximum_fee = maximum_fee;
196
- let ( raw_fee_amount, delta_fee ) = calculate_fee ( transfer_amount, transfer_fee_basis_points)
239
+ let ( raw_fee_amount, raw_delta_fee ) = calculate_fee ( transfer_amount, transfer_fee_basis_points)
197
240
. ok_or ( TokenProofGenerationError :: FeeCalculation ) ?;
198
241
199
242
// if raw fee is greater than the maximum fee, then use the maximum fee for the
200
- // fee amount
201
- let fee_amount = std:: cmp:: min ( transfer_fee_maximum_fee, raw_fee_amount) ;
243
+ // fee amount and set the claimed delta fee to be 0 for simplicity
244
+ let ( fee_amount, claimed_delta_fee) = if transfer_fee_maximum_fee < raw_fee_amount {
245
+ ( transfer_fee_maximum_fee, 0 )
246
+ } else {
247
+ ( raw_fee_amount, raw_delta_fee)
248
+ } ;
202
249
let net_transfer_amount = transfer_amount
203
250
. checked_sub ( fee_amount)
204
251
. ok_or ( TokenProofGenerationError :: FeeCalculation ) ?;
@@ -249,7 +296,7 @@ pub fn transfer_with_fee_split_proof_data(
249
296
let net_transfer_amount_opening = & combined_transfer_amount_opening - & combined_fee_opening;
250
297
251
298
// compute claimed and real delta commitment
252
- let ( claimed_commitment, claimed_opening) = Pedersen :: new ( delta_fee ) ;
299
+ let ( claimed_commitment, claimed_opening) = Pedersen :: new ( claimed_delta_fee ) ;
253
300
let ( delta_commitment, delta_opening) = compute_delta_commitment_and_opening (
254
301
(
255
302
& combined_transfer_amount_commitment,
@@ -266,7 +313,7 @@ pub fn transfer_with_fee_split_proof_data(
266
313
fee_amount,
267
314
& delta_commitment,
268
315
& delta_opening,
269
- delta_fee ,
316
+ claimed_delta_fee ,
270
317
& claimed_commitment,
271
318
& claimed_opening,
272
319
transfer_fee_maximum_fee,
@@ -327,7 +374,7 @@ pub fn transfer_with_fee_split_proof_data(
327
374
328
375
// generate range proof data
329
376
let delta_fee_complement = MAX_FEE_BASIS_POINTS_SUB_ONE
330
- . checked_sub ( delta_fee )
377
+ . checked_sub ( claimed_delta_fee )
331
378
. ok_or ( TokenProofGenerationError :: FeeCalculation ) ?;
332
379
333
380
let max_fee_basis_points_sub_one_commitment =
@@ -353,7 +400,7 @@ pub fn transfer_with_fee_split_proof_data(
353
400
new_decrypted_available_balance,
354
401
transfer_amount_lo,
355
402
transfer_amount_hi,
356
- delta_fee ,
403
+ claimed_delta_fee ,
357
404
delta_fee_complement,
358
405
fee_amount_lo,
359
406
fee_amount_hi,
0 commit comments