1use ark_ff::AdditiveGroup;
10use num_bigint::BigUint;
11use serde::Serialize;
12
13use crate::cipher::encrypt_amount_token;
14use crate::eddsa::{ScalarSignatureError, ScalarSigningKey, Signature, sign_hex};
15use crate::field::{Bn254Fr, Fr, fr_to_biguint, fr_to_dec};
16use crate::hash_utils::sha256_bigint;
17use crate::imt::Imt;
18use crate::note as commitments;
19use crate::poseidon::poseidon;
20
21#[derive(Clone)]
25pub struct Note {
26 pub amount: Fr,
27 pub token: Fr,
28 pub owner_pub: (Fr, Fr),
29 pub shared_secret: Fr,
30 pub ephemeral_key: (Fr, Fr),
31 pub view_tag: Fr,
32}
33
34#[derive(Clone, Copy, Debug, PartialEq, Eq)]
38pub struct KnownOwner {
39 pub owner: crate::babyjubjub::BabyJubPoint,
40 pub shared_secret: Bn254Fr,
41}
42
43impl KnownOwner {
44 pub fn new(owner: crate::babyjubjub::BabyJubPoint, shared_secret: Bn254Fr) -> Self {
45 Self {
46 owner,
47 shared_secret,
48 }
49 }
50
51 pub fn note(self, amount: Fr, token: Fr, ephemeral_key: (Fr, Fr), view_tag: Fr) -> Note {
52 Note {
53 amount,
54 token,
55 owner_pub: self.owner.as_tuple(),
56 shared_secret: self.shared_secret.into_inner(),
57 ephemeral_key,
58 view_tag,
59 }
60 }
61}
62
63impl Note {
64 pub fn owner_hash(&self) -> Fr {
65 commitments::owner_hash(self.owner_pub, self.shared_secret)
66 }
67 pub fn id(&self) -> Fr {
68 commitments::note_id(self.owner_hash(), self.amount, self.token)
69 }
70 pub fn nullifier(&self) -> Fr {
71 commitments::nullifier(self.shared_secret, self.owner_pub)
72 }
73 fn flat(&self) -> Vec<String> {
75 vec![
76 fr_to_dec(&self.owner_pub.0),
77 fr_to_dec(&self.owner_pub.1),
78 fr_to_dec(&self.shared_secret),
79 fr_to_dec(&self.amount),
80 fr_to_dec(&self.token),
81 ]
82 }
83 fn encrypted(&self) -> (Fr, Fr) {
85 let out = encrypt_amount_token(
86 self.amount,
87 self.token,
88 &fr_to_biguint(&self.shared_secret),
89 (
90 &fr_to_biguint(&self.ephemeral_key.0),
91 &fr_to_biguint(&self.ephemeral_key.1),
92 ),
93 );
94 (out.encrypted_amount, out.encrypted_token)
95 }
96 fn flat_encrypted(&self) -> Vec<String> {
98 let (ea, et) = self.encrypted();
99 vec![
100 fr_to_dec(&ea),
101 fr_to_dec(&et),
102 fr_to_dec(&self.ephemeral_key.0),
103 fr_to_dec(&self.ephemeral_key.1),
104 fr_to_dec(&self.view_tag),
105 ]
106 }
107}
108
109pub struct Proof {
111 pub leaf_index: u64,
112 pub siblings: Vec<Fr>,
113}
114
115impl Proof {
116 fn flat(&self) -> Vec<String> {
118 let mut out = vec![self.leaf_index.to_string()];
119 out.extend(self.siblings.iter().map(fr_to_dec));
120 out
121 }
122}
123
124fn flat_signature(r8: (Fr, Fr), s: &BigUint) -> [String; 3] {
125 [s.to_string(), fr_to_dec(&r8.0), fr_to_dec(&r8.1)]
126}
127
128pub trait NoteSigner {
132 fn public_key(&self) -> (Fr, Fr);
133 fn sign(&self, message: Fr) -> Result<Signature, ScalarSignatureError>;
134}
135
136pub struct SeedNoteSigner<'a> {
138 private_key_hex: &'a str,
139 public_key: (Fr, Fr),
140}
141
142impl<'a> SeedNoteSigner<'a> {
143 pub fn new(private_key_hex: &'a str) -> Self {
145 Self {
146 private_key_hex,
147 public_key: crate::eddsa::pub_from_private_key_hex(private_key_hex),
148 }
149 }
150
151 fn from_parts(private_key_hex: &'a str, public_key: (Fr, Fr)) -> Self {
154 Self {
155 private_key_hex,
156 public_key,
157 }
158 }
159}
160
161impl NoteSigner for SeedNoteSigner<'_> {
162 fn public_key(&self) -> (Fr, Fr) {
163 self.public_key
164 }
165
166 fn sign(&self, message: Fr) -> Result<Signature, ScalarSignatureError> {
167 Ok(sign_hex(&fr_to_biguint(&message), self.private_key_hex))
168 }
169}
170
171impl NoteSigner for ScalarSigningKey {
172 fn public_key(&self) -> (Fr, Fr) {
173 self.verifying_key().as_tuple()
174 }
175
176 fn sign(&self, message: Fr) -> Result<Signature, ScalarSignatureError> {
177 Ok(self
178 .sign_curvy_v1(Bn254Fr::from_fr(message))?
179 .to_signature())
180 }
181}
182
183#[derive(Serialize, PartialEq, Eq, Debug)]
186pub struct WithdrawalWitness {
187 #[serde(rename = "inputNotes")]
188 pub input_notes: Vec<Vec<String>>,
189 #[serde(rename = "publicKey")]
190 pub public_key: [String; 2],
191 #[serde(rename = "inputNoteInclusionProofs")]
192 pub input_note_inclusion_proofs: Vec<Vec<String>>,
193 pub signature: [String; 3],
194 #[serde(rename = "notesRoot")]
195 pub notes_root: String,
196 #[serde(rename = "destinationAddress")]
197 pub destination_address: String,
198 #[serde(rename = "tokenId")]
199 pub token_id: String,
200}
201
202pub fn build_withdrawal(
205 notes: &[Note],
206 owner_key_hex: &str,
207 public_key: (Fr, Fr),
208 proofs: &[Proof],
209 notes_root: Fr,
210 destination_address: Fr,
211 token_id: Fr,
212) -> WithdrawalWitness {
213 let signer = SeedNoteSigner::from_parts(owner_key_hex, public_key);
214 build_withdrawal_with_signer(
215 notes,
216 &signer,
217 proofs,
218 notes_root,
219 destination_address,
220 token_id,
221 )
222 .expect("seed-backed signing is infallible")
223}
224
225pub fn build_withdrawal_with_signer(
228 notes: &[Note],
229 signer: &impl NoteSigner,
230 proofs: &[Proof],
231 notes_root: Fr,
232 destination_address: Fr,
233 token_id: Fr,
234) -> Result<WithdrawalWitness, ScalarSignatureError> {
235 let total: Fr = notes.iter().fold(Fr::ZERO, |a, n| a + n.amount);
236 let mut msg: Vec<Fr> = notes.iter().map(|n| n.nullifier()).collect();
237 msg.push(destination_address);
238 msg.push(total);
239 msg.push(token_id);
240 let sig = signer.sign(poseidon(&msg))?;
241 let public_key = signer.public_key();
242
243 Ok(WithdrawalWitness {
244 input_notes: notes.iter().map(|n| n.flat()).collect(),
245 public_key: [fr_to_dec(&public_key.0), fr_to_dec(&public_key.1)],
246 input_note_inclusion_proofs: proofs.iter().map(|p| p.flat()).collect(),
247 signature: flat_signature(sig.r8, &sig.s),
248 notes_root: fr_to_dec(¬es_root),
249 destination_address: fr_to_dec(&destination_address),
250 token_id: fr_to_dec(&token_id),
251 })
252}
253
254#[derive(Serialize, PartialEq, Eq, Debug)]
257pub struct AggregationWitness {
258 #[serde(rename = "inputNotes")]
259 pub input_notes: Vec<Vec<String>>,
260 #[serde(rename = "inputNoteInclusionProofs")]
261 pub input_note_inclusion_proofs: Vec<Vec<String>>,
262 #[serde(rename = "outputNotes")]
263 pub output_notes: Vec<Vec<String>>,
264 #[serde(rename = "publicKey")]
265 pub public_key: [String; 2],
266 pub signature: [String; 3],
267 #[serde(rename = "feeNote")]
268 pub fee_note: Vec<String>,
269 #[serde(rename = "encryptedNoteData")]
270 pub encrypted_note_data: Vec<Vec<String>>,
271 #[serde(rename = "notesRoot")]
272 pub notes_root: String,
273 #[serde(rename = "protocolFeePerThousand")]
274 pub protocol_fee_per_thousand: String,
275 #[serde(rename = "gasFee")]
276 pub gas_fee: String,
277 #[serde(rename = "feeNotePublicKey")]
278 pub fee_note_public_key: [String; 2],
279}
280
281#[allow(clippy::too_many_arguments)]
285pub fn build_aggregation(
286 input_notes: &[Note],
287 input_proofs: &[Proof],
288 output_notes: &[Note],
289 fee_note: &Note,
290 owner_key_hex: &str,
291 public_key: (Fr, Fr),
292 notes_root: Fr,
293 protocol_fee_per_thousand: Fr,
294 gas_fee: Fr,
295 fee_note_public_key: (Fr, Fr),
296) -> AggregationWitness {
297 let signer = SeedNoteSigner::from_parts(owner_key_hex, public_key);
298 build_aggregation_with_signer(
299 input_notes,
300 input_proofs,
301 output_notes,
302 fee_note,
303 &signer,
304 notes_root,
305 protocol_fee_per_thousand,
306 gas_fee,
307 fee_note_public_key,
308 )
309 .expect("seed-backed signing is infallible")
310}
311
312#[allow(clippy::too_many_arguments)]
315pub fn build_aggregation_with_signer(
316 input_notes: &[Note],
317 input_proofs: &[Proof],
318 output_notes: &[Note],
319 fee_note: &Note,
320 signer: &impl NoteSigner,
321 notes_root: Fr,
322 protocol_fee_per_thousand: Fr,
323 gas_fee: Fr,
324 fee_note_public_key: (Fr, Fr),
325) -> Result<AggregationWitness, ScalarSignatureError> {
326 let enc_notes: Vec<Note> = output_notes
327 .iter()
328 .chain(std::iter::once(fee_note))
329 .cloned()
330 .collect();
331 let encrypted: Vec<(Fr, Fr)> = enc_notes.iter().map(|n| n.encrypted()).collect();
332
333 let output_note_hash = poseidon(&output_notes.iter().map(|n| n.id()).collect::<Vec<_>>());
334 let mut enc_flat: Vec<Fr> = Vec::with_capacity(encrypted.len() * 2);
335 for (ea, et) in &encrypted {
336 enc_flat.push(*ea);
337 enc_flat.push(*et);
338 }
339 let encrypted_note_data_hash = poseidon(&enc_flat);
340 let signing_hash = poseidon(&[output_note_hash, encrypted_note_data_hash]);
341 let sig = signer.sign(signing_hash)?;
342 let public_key = signer.public_key();
343
344 Ok(AggregationWitness {
345 input_notes: input_notes.iter().map(|n| n.flat()).collect(),
346 input_note_inclusion_proofs: input_proofs.iter().map(|p| p.flat()).collect(),
347 output_notes: output_notes.iter().map(|n| n.flat()).collect(),
348 public_key: [fr_to_dec(&public_key.0), fr_to_dec(&public_key.1)],
349 signature: flat_signature(sig.r8, &sig.s),
350 fee_note: fee_note.flat(),
351 encrypted_note_data: enc_notes.iter().map(|n| n.flat_encrypted()).collect(),
352 notes_root: fr_to_dec(¬es_root),
353 protocol_fee_per_thousand: fr_to_dec(&protocol_fee_per_thousand),
354 gas_fee: fr_to_dec(&gas_fee),
355 fee_note_public_key: [
356 fr_to_dec(&fee_note_public_key.0),
357 fr_to_dec(&fee_note_public_key.1),
358 ],
359 })
360}
361
362#[derive(Serialize, PartialEq, Eq, Debug)]
365pub struct PendingCommitmentWitness {
366 #[serde(rename = "currentNoteIndex")]
367 pub current_note_index: String,
368 #[serde(rename = "inputHash")]
369 pub input_hash: String,
370 #[serde(rename = "currentNotesRoot")]
371 pub current_notes_root: String,
372 #[serde(rename = "pendingNoteIds")]
373 pub pending_note_ids: Vec<String>,
374 pub siblings: Vec<Vec<String>>,
375 #[serde(rename = "newNotesRoot")]
376 pub new_notes_root: String,
377}
378
379pub fn build_pending_commitment(
383 tree: &Imt,
384 tree_depth: usize,
385 batch_size: usize,
386 pending_note_ids: &[Fr],
387) -> PendingCommitmentWitness {
388 assert!(
389 pending_note_ids.len() <= batch_size,
390 "pending ids exceed batch size"
391 );
392 let current_notes_root = tree.root();
393 let current_note_index = tree.leaf_count() as u64;
394
395 let mut padded = pending_note_ids.to_vec();
396 padded.resize(batch_size, Fr::ZERO);
397
398 let mut work = tree.clone();
399 let mut siblings: Vec<Vec<Fr>> = Vec::with_capacity(batch_size);
400 for &id in &padded {
401 if id == Fr::ZERO {
402 siblings.push(vec![Fr::ZERO; tree_depth]);
403 continue;
404 }
405 work.insert(id);
406 let idx = work.leaf_count() - 1;
407 siblings.push(work.create_proof(idx).siblings);
408 }
409
410 let new_notes_root = work.root();
411 let new_note_index = work.leaf_count() as u64;
412
413 let mut hash_inputs: Vec<BigUint> = padded.iter().map(fr_to_biguint).collect();
414 hash_inputs.push(fr_to_biguint(¤t_notes_root));
415 hash_inputs.push(fr_to_biguint(&new_notes_root));
416 hash_inputs.push(BigUint::from(current_note_index));
417 hash_inputs.push(BigUint::from(new_note_index));
418 let input_hash = sha256_bigint(&hash_inputs);
419
420 PendingCommitmentWitness {
421 current_note_index: current_note_index.to_string(),
422 input_hash: input_hash.to_string(),
423 current_notes_root: fr_to_dec(¤t_notes_root),
424 pending_note_ids: padded.iter().map(fr_to_dec).collect(),
425 siblings: siblings
426 .iter()
427 .map(|row| row.iter().map(fr_to_dec).collect())
428 .collect(),
429 new_notes_root: fr_to_dec(&new_notes_root),
430 }
431}