spl_token_confidential_transfer_proof_generation/
transfer.rs1#[cfg(target_arch = "wasm32")]
43use solana_zk_sdk::encryption::grouped_elgamal::GroupedElGamalCiphertext3Handles;
44use {
45 crate::{
46 encryption::TransferAmountCiphertext, errors::TokenProofGenerationError,
47 try_combine_lo_hi_ciphertexts, try_split_u64, CiphertextValidityProofWithAuditorCiphertext,
48 REMAINING_BALANCE_BIT_LENGTH, TRANSFER_AMOUNT_HI_BITS, TRANSFER_AMOUNT_LO_BITS,
49 },
50 solana_zk_sdk::{
51 encryption::{
52 auth_encryption::{AeCiphertext, AeKey},
53 elgamal::{ElGamalCiphertext, ElGamalKeypair, ElGamalPubkey},
54 pedersen::Pedersen,
55 },
56 zk_elgamal_proof_program::proof_data::{
57 BatchedGroupedCiphertext3HandlesValidityProofData, BatchedRangeProofU128Data,
58 CiphertextCommitmentEqualityProofData, ZkProofData,
59 },
60 },
61};
62
63const RANGE_PROOF_PADDING_BIT_LENGTH: usize = 16;
66
67pub struct TransferProofData {
70 pub equality_proof_data: CiphertextCommitmentEqualityProofData,
71 pub ciphertext_validity_proof_data_with_ciphertext:
72 CiphertextValidityProofWithAuditorCiphertext,
73 pub range_proof_data: BatchedRangeProofU128Data,
74}
75
76pub fn transfer_split_proof_data(
77 current_available_balance: &ElGamalCiphertext,
78 current_decryptable_available_balance: &AeCiphertext,
79 transfer_amount: u64,
80 source_elgamal_keypair: &ElGamalKeypair,
81 aes_key: &AeKey,
82 destination_elgamal_pubkey: &ElGamalPubkey,
83 auditor_elgamal_pubkey: Option<&ElGamalPubkey>,
84) -> Result<TransferProofData, TokenProofGenerationError> {
85 let default_auditor_pubkey = ElGamalPubkey::default();
86 let auditor_elgamal_pubkey = auditor_elgamal_pubkey.unwrap_or(&default_auditor_pubkey);
87
88 let (transfer_amount_lo, transfer_amount_hi) =
90 try_split_u64(transfer_amount, TRANSFER_AMOUNT_LO_BITS)
91 .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
92
93 let (transfer_amount_grouped_ciphertext_lo, transfer_amount_opening_lo) =
95 TransferAmountCiphertext::new(
96 transfer_amount_lo,
97 source_elgamal_keypair.pubkey(),
98 destination_elgamal_pubkey,
99 auditor_elgamal_pubkey,
100 );
101 #[cfg(not(target_arch = "wasm32"))]
102 let grouped_ciphertext_lo = transfer_amount_grouped_ciphertext_lo.0;
103 #[cfg(target_arch = "wasm32")]
104 let grouped_ciphertext_lo = GroupedElGamalCiphertext3Handles::encrypt_with_u64(
105 source_elgamal_keypair.pubkey(),
106 destination_elgamal_pubkey,
107 auditor_elgamal_pubkey,
108 transfer_amount_lo,
109 &transfer_amount_opening_lo,
110 );
111
112 let (transfer_amount_grouped_ciphertext_hi, transfer_amount_opening_hi) =
113 TransferAmountCiphertext::new(
114 transfer_amount_hi,
115 source_elgamal_keypair.pubkey(),
116 destination_elgamal_pubkey,
117 auditor_elgamal_pubkey,
118 );
119 #[cfg(not(target_arch = "wasm32"))]
120 let grouped_ciphertext_hi = transfer_amount_grouped_ciphertext_hi.0;
121 #[cfg(target_arch = "wasm32")]
122 let grouped_ciphertext_hi = GroupedElGamalCiphertext3Handles::encrypt_with_u64(
123 source_elgamal_keypair.pubkey(),
124 destination_elgamal_pubkey,
125 auditor_elgamal_pubkey,
126 transfer_amount_hi,
127 &transfer_amount_opening_hi,
128 );
129
130 let current_decrypted_available_balance = current_decryptable_available_balance
132 .decrypt(aes_key)
133 .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
134
135 let new_decrypted_available_balance = current_decrypted_available_balance
137 .checked_sub(transfer_amount)
138 .ok_or(TokenProofGenerationError::NotEnoughFunds)?;
139
140 let (new_available_balance_commitment, new_source_opening) =
142 Pedersen::new(new_decrypted_available_balance);
143
144 let transfer_amount_source_ciphertext_lo = transfer_amount_grouped_ciphertext_lo
146 .0
147 .to_elgamal_ciphertext(0)
148 .unwrap();
149 let transfer_amount_source_ciphertext_hi = transfer_amount_grouped_ciphertext_hi
150 .0
151 .to_elgamal_ciphertext(0)
152 .unwrap();
153
154 #[allow(clippy::arithmetic_side_effects)]
155 let new_available_balance_ciphertext = current_available_balance
156 - try_combine_lo_hi_ciphertexts(
157 &transfer_amount_source_ciphertext_lo,
158 &transfer_amount_source_ciphertext_hi,
159 TRANSFER_AMOUNT_LO_BITS,
160 )
161 .ok_or(TokenProofGenerationError::IllegalAmountBitLength)?;
162
163 let equality_proof_data = CiphertextCommitmentEqualityProofData::new(
165 source_elgamal_keypair,
166 &new_available_balance_ciphertext,
167 &new_available_balance_commitment,
168 &new_source_opening,
169 new_decrypted_available_balance,
170 )
171 .map_err(TokenProofGenerationError::from)?;
172
173 let ciphertext_validity_proof_data = BatchedGroupedCiphertext3HandlesValidityProofData::new(
175 source_elgamal_keypair.pubkey(),
176 destination_elgamal_pubkey,
177 auditor_elgamal_pubkey,
178 &grouped_ciphertext_lo,
179 &grouped_ciphertext_hi,
180 transfer_amount_lo,
181 transfer_amount_hi,
182 &transfer_amount_opening_lo,
183 &transfer_amount_opening_hi,
184 )
185 .map_err(TokenProofGenerationError::from)?;
186
187 let transfer_amount_auditor_ciphertext_lo = ciphertext_validity_proof_data
188 .context_data()
189 .grouped_ciphertext_lo
190 .try_extract_ciphertext(2)
191 .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?;
192
193 let transfer_amount_auditor_ciphertext_hi = ciphertext_validity_proof_data
194 .context_data()
195 .grouped_ciphertext_hi
196 .try_extract_ciphertext(2)
197 .map_err(|_| TokenProofGenerationError::CiphertextExtraction)?;
198
199 let ciphertext_validity_proof_data_with_ciphertext =
200 CiphertextValidityProofWithAuditorCiphertext {
201 proof_data: ciphertext_validity_proof_data,
202 ciphertext_lo: transfer_amount_auditor_ciphertext_lo,
203 ciphertext_hi: transfer_amount_auditor_ciphertext_hi,
204 };
205
206 let (padding_commitment, padding_opening) = Pedersen::new(0_u64);
212 let range_proof_data = BatchedRangeProofU128Data::new(
213 vec![
214 &new_available_balance_commitment,
215 transfer_amount_grouped_ciphertext_lo.get_commitment(),
216 transfer_amount_grouped_ciphertext_hi.get_commitment(),
217 &padding_commitment,
218 ],
219 vec![
220 new_decrypted_available_balance,
221 transfer_amount_lo,
222 transfer_amount_hi,
223 0,
224 ],
225 vec![
226 REMAINING_BALANCE_BIT_LENGTH,
227 TRANSFER_AMOUNT_LO_BITS,
228 TRANSFER_AMOUNT_HI_BITS,
229 RANGE_PROOF_PADDING_BIT_LENGTH,
230 ],
231 vec![
232 &new_source_opening,
233 &transfer_amount_opening_lo,
234 &transfer_amount_opening_hi,
235 &padding_opening,
236 ],
237 )
238 .map_err(TokenProofGenerationError::from)?;
239
240 Ok(TransferProofData {
241 equality_proof_data,
242 ciphertext_validity_proof_data_with_ciphertext,
243 range_proof_data,
244 })
245}