1use {
2 crate::{
3 encryption::TransferAmountCiphertext, errors::TokenProofGenerationError,
4 try_combine_lo_hi_ciphertexts, try_split_u64, CiphertextValidityProofWithAuditorCiphertext,
5 REMAINING_BALANCE_BIT_LENGTH, TRANSFER_AMOUNT_HI_BITS, TRANSFER_AMOUNT_LO_BITS,
6 },
7 solana_zk_sdk::{
8 encryption::{
9 auth_encryption::{AeCiphertext, AeKey},
10 elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
11 pedersen::Pedersen,
12 },
13 zk_elgamal_proof_program::proof_data::{
14 BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data,
15 CiphertextCommitmentEqualityProofData, ZkProofData,
16 },
17 },
18};
19
20const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16;
23
24pub struct TransferProofData {
27 pub equality_proof_data: CiphertextCommitmentEqualityProofData,
28 pub ciphertext_validity_proof_data_with_ciphertext:
29 CiphertextValidityProofWithAuditorCiphertext,
30 pub range_proof_data: BatchedRangeProofU128Data,
31}
32
33pub fn transfer_split_proof_data(
34 current_available_balance: &ElGamalCiphertext,
35 current_decryptable_available_balance: &AeCiphertext,
36 transfer_amount: u64,
37 source_elgamal_keypair: &ElGamalKeypair,
38 aes_key: &AeKey,
39 destination_elgamal_pubkey: &ElGamalPubkey,
40 auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
41) -> Result<TransferProofData, TokenProofGenerationError> {
42 let default_auditor_pubkey = ElGamalPubkey::default();
43 let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or(&default_auditor_pubkey);
44
45 let (transfer_amount_lo, transfer_amount_hi) =
47 try_split_u64(transfer_amount, TRANSFER_AMOUNT_LO_BITS)
48 .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
49
50 let (transfer_amount_grouped_ciphertext_lo, transfer_amount_opening_lo) =
52 TransferAmountCiphertext::new(
53 transfer_amount_lo,
54 source_elgamal_keypair.pubkey(),
55 destination_elgamal_pubkey,
56 auditor_elgamal_pubkey,
57 );
58
59 let (transfer_amount_grouped_ciphertext_hi, transfer_amount_opening_hi) =
60 TransferAmountCiphertext::new(
61 transfer_amount_hi,
62 source_elgamal_keypair.pubkey(),
63 destination_elgamal_pubkey,
64 auditor_elgamal_pubkey,
65 );
66
67 let current_decrypted_available_balance = current_decryptable_available_balance
69 .decrypt(aes_key)
70 .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
71
72 let new_decrypted_available_balance = current_decrypted_available_balance
74 .checked_sub(transfer_amount)
75 .ok_or(TokenProofGenerationError::NotEnoughFunds)?;
76
77 let (new_available_balance_commitment, new_source_opening) =
79 Pedersen::new(new_decrypted_available_balance);
80
81 let transfer_amount_source_ciphertext_lo = transfer_amount_grouped_ciphertext_lo
83 .0
84 .to_elgamal_ciphertext(0)
85 .unwrap();
86 let transfer_amount_source_ciphertext_hi = transfer_amount_grouped_ciphertext_hi
87 .0
88 .to_elgamal_ciphertext(0)
89 .unwrap();
90
91 #[allow(clippy::arithmetic_side_effects)]
92 let new_available_balance_ciphertext = current_available_balance
93 - try_combine_lo_hi_ciphertexts(
94 &transfer_amount_source_ciphertext_lo,
95 &transfer_amount_source_ciphertext_hi,
96 TRANSFER_AMOUNT_LO_BITS,
97 )
98 .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
99
100 let equality_proof_data = CiphertextCommitmentEqualityProofData::new(
102 source_elgamal_keypair,
103 &new_available_balance_ciphertext,
104 &new_available_balance_commitment,
105 &new_source_opening,
106 new_decrypted_available_balance,
107 )
108 .map_err(TokenProofGenerationError::from)?;
109
110 let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new(
112 source_elgamal_keypair.pubkey(),
113 destination_elgamal_pubkey,
114 auditor_elgamal_pubkey,
115 &transfer_amount_grouped_ciphertext_lo.0,
116 &transfer_amount_grouped_ciphertext_hi.0,
117 transfer_amount_lo,
118 transfer_amount_hi,
119 &transfer_amount_opening_lo,
120 &transfer_amount_opening_hi,
121 )
122 .map_err(TokenProofGenerationError::from)?;
123
124 let transfer_amount_auditor_ciphertext_lo = ciphertext_validity_proof_data
125 .context_data()
126 .grouped_ciphertext_lo
127 .try_extract_ciphertext(2)
128 .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?;
129
130 let transfer_amount_auditor_ciphertext_hi = ciphertext_validity_proof_data
131 .context_data()
132 .grouped_ciphertext_hi
133 .try_extract_ciphertext(2)
134 .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?;
135
136 let ciphertext_validity_proof_data_with_ciphertext =
137 CiphertextValidityProofWithAuditorCiphertext {
138 proof_data: ciphertext_validity_proof_data,
139 ciphertext_lo: transfer_amount_auditor_ciphertext_lo,
140 ciphertext_hi: transfer_amount_auditor_ciphertext_hi,
141 };
142
143 let (padding_commitment, padding_opening) = Pedersen::new(0_u64);
145 let range_proof_data = BatchedRangeProofU128Data::new(
146 vec![
147 &new_available_balance_commitment,
148 transfer_amount_grouped_ciphertext_lo.get_commitment(),
149 transfer_amount_grouped_ciphertext_hi.get_commitment(),
150 &padding_commitment,
151 ],
152 vec![
153 new_decrypted_available_balance,
154 transfer_amount_lo,
155 transfer_amount_hi,
156 0,
157 ],
158 vec![
159 REMAINING_BALANCE_BIT_LENGTH,
160 TRANSFER_AMOUNT_LO_BITS,
161 TRANSFER_AMOUNT_HI_BITS,
162 RANGE_PROOF_PADDING_BIT_LENGTH,
163 ],
164 vec![
165 &new_source_opening,
166 &transfer_amount_opening_lo,
167 &transfer_amount_opening_hi,
168 &padding_opening,
169 ],
170 )
171 .map_err(TokenProofGenerationError::from)?;
172
173 Ok(TransferProofData {
174 equality_proof_data,
175 ciphertext_validity_proof_data_with_ciphertext,
176 range_proof_data,
177 })
178}