1use std::{convert::TryInto, io::Write};
2
3use kvdb::KeyValueDB;
4use libzeropool::{
5 constants,
6 fawkes_crypto::{
7 core::sizedvec::SizedVec,
8 ff_uint::{Num, NumRepr, PrimeField, Uint},
9 rand::Rng,
10 },
11 native::{
12 account::Account,
13 boundednum::BoundedNum,
14 cipher,
15 key::derive_key_p_d,
16 note::Note,
17 params::PoolParams,
18 tx::{
19 make_delta, nullifier, out_commitment_hash, tx_hash, tx_sign, TransferPub, TransferSec,
20 Tx,
21 },
22 },
23};
24use serde::{Deserialize, Serialize};
25use thiserror::Error;
26
27use self::state::{State, Transaction};
28use crate::{
29 address::{format_address, parse_address, AddressParseError},
30 keys::{reduce_sk, Keys},
31 merkle::Hash,
32 random::CustomRng,
33 utils::{keccak256, zero_note, zero_proof},
34};
35
36pub mod state;
37
38#[derive(Debug, Error)]
39pub enum CreateTxError {
40 #[error("Too many outputs: expected {max} max got {got}")]
41 TooManyOutputs { max: usize, got: usize },
42 #[error("Could not get merkle proof for leaf {0}")]
43 ProofNotFound(u64),
44 #[error("Failed to parse address: {0}")]
45 AddressParseError(#[from] AddressParseError),
46 #[error("Insufficient balance: sum of outputs is greater than sum of inputs: {0} > {1}")]
47 InsufficientBalance(String, String),
48 #[error("Insufficient energy: available {0}, received {1}")]
49 InsufficientEnergy(String, String),
50}
51
52#[derive(Serialize, Deserialize, Default, Clone)]
53pub struct StateFragment<Fr: PrimeField> {
54 pub new_leafs: Vec<(u64, Vec<Hash<Fr>>)>,
55 pub new_commitments: Vec<(u64, Hash<Fr>)>,
56 pub new_accounts: Vec<(u64, Account<Fr>)>,
57 pub new_notes: Vec<(u64, Note<Fr>)>,
58}
59
60#[derive(Clone, Serialize, Deserialize)]
61pub struct TransactionData<Fr: PrimeField> {
62 pub public: TransferPub<Fr>,
63 pub secret: TransferSec<Fr>,
64 pub ciphertext: Vec<u8>,
65 pub memo: Vec<u8>,
66 pub commitment_root: Num<Fr>,
67 pub out_hashes: SizedVec<Num<Fr>, { constants::OUT + 1 }>,
68}
69
70pub type TokenAmount<Fr> = BoundedNum<Fr, { constants::BALANCE_SIZE_BITS }>;
71
72#[derive(Debug, Serialize, Deserialize, Clone)]
73pub struct TxOutput<Fr: PrimeField> {
74 pub to: String,
75 pub amount: TokenAmount<Fr>,
76}
77
78#[derive(Debug, Serialize, Deserialize, Clone)]
79pub enum TxType<Fr: PrimeField> {
80 Transfer {
81 fee: TokenAmount<Fr>,
82 outputs: Vec<TxOutput<Fr>>,
83 },
84 Deposit {
85 fee: TokenAmount<Fr>,
86 deposit_amount: TokenAmount<Fr>,
87 outputs: Vec<TxOutput<Fr>>,
88 },
89 DepositPermittable {
90 fee: TokenAmount<Fr>,
91 deposit_amount: TokenAmount<Fr>,
92 deadline: u64,
93 holder: Vec<u8>,
94 outputs: Vec<TxOutput<Fr>>,
95 },
96 Withdraw {
97 fee: TokenAmount<Fr>,
98 withdraw_amount: TokenAmount<Fr>,
99 to: Vec<u8>,
100 native_amount: TokenAmount<Fr>,
101 energy_amount: TokenAmount<Fr>,
102 },
103}
104
105pub struct UserAccount<D: KeyValueDB, P: PoolParams> {
106 pub pool_id: BoundedNum<P::Fr, { constants::DIVERSIFIER_SIZE_BITS }>,
107 pub keys: Keys<P>,
108 pub params: P,
109 pub state: State<D, P>,
110 pub sign_callback: Option<Box<dyn Fn(&[u8]) -> Vec<u8>>>, }
112
113impl<'p, D, P> UserAccount<D, P>
114where
115 D: KeyValueDB,
116 P: PoolParams,
117 P::Fr: 'static,
118{
119 pub fn new(sk: Num<P::Fs>, state: State<D, P>, params: P) -> Self {
121 let keys = Keys::derive(sk, ¶ms);
122
123 UserAccount {
124 pool_id: BoundedNum::new(Num::ZERO),
126 keys,
127 state,
128 params,
129 sign_callback: None,
130 }
131 }
132
133 pub fn from_seed(seed: &[u8], state: State<D, P>, params: P) -> Self {
135 let sk = reduce_sk(seed);
136 Self::new(sk, state, params)
137 }
138
139 fn generate_address_components(
140 &self,
141 ) -> (
142 BoundedNum<P::Fr, { constants::DIVERSIFIER_SIZE_BITS }>,
143 Num<P::Fr>,
144 ) {
145 let mut rng = CustomRng;
146
147 let d: BoundedNum<_, { constants::DIVERSIFIER_SIZE_BITS }> = rng.gen();
148 let pk_d = derive_key_p_d(d.to_num(), self.keys.eta, &self.params);
149 (d, pk_d.x)
150 }
151
152 pub fn generate_address(&self) -> String {
154 let (d, p_d) = self.generate_address_components();
155
156 format_address::<P>(d, p_d)
157 }
158
159 pub fn decrypt_notes(&self, data: Vec<u8>) -> Vec<Option<Note<P::Fr>>> {
161 cipher::decrypt_in(self.keys.eta, &data, &self.params)
162 }
163
164 pub fn decrypt_pair(&self, data: Vec<u8>) -> Option<(Account<P::Fr>, Vec<Note<P::Fr>>)> {
166 cipher::decrypt_out(self.keys.eta, &data, &self.params)
167 }
168
169 pub fn is_own_address(&self, address: &str) -> bool {
170 let mut result = false;
171 if let Ok((d, p_d)) = parse_address::<P>(address) {
172 let own_p_d = derive_key_p_d(d.to_num(), self.keys.eta, &self.params).x;
173 result = own_p_d == p_d;
174 }
175
176 result
177 }
178
179 pub fn create_tx(
181 &self,
182 tx: TxType<P::Fr>,
183 delta_index: Option<u64>,
184 extra_state: Option<StateFragment<P::Fr>>,
185 ) -> Result<TransactionData<P::Fr>, CreateTxError> {
186 let mut rng = CustomRng;
187 let keys = self.keys.clone();
188 let state = &self.state;
189
190 let extra_state = extra_state.unwrap_or(StateFragment {
191 new_leafs: [].to_vec(),
192 new_commitments: [].to_vec(),
193 new_accounts: [].to_vec(),
194 new_notes: [].to_vec(),
195 });
196
197 let (in_account_optimistic_index, in_account_optimistic) = {
199 let last_acc = extra_state.new_accounts.last();
200 match last_acc {
201 Some(last_acc) => (Some(last_acc.0), Some(last_acc.1)),
202 _ => (None, None),
203 }
204 };
205
206 let in_account = in_account_optimistic.unwrap_or_else(|| {
208 state.latest_account.unwrap_or_else(|| {
209 let d = self.pool_id;
211 let p_d = derive_key_p_d(d.to_num(), self.keys.eta, &self.params).x;
212 Account {
213 d: self.pool_id,
214 p_d,
215 i: BoundedNum::new(Num::ZERO),
216 b: BoundedNum::new(Num::ZERO),
217 e: BoundedNum::new(Num::ZERO),
218 }
219 })
220 });
221
222 let tree = &self.state.tree;
223
224 let in_account_index = in_account_optimistic_index.or(state.latest_account_index);
225
226 let next_usable_index = state
228 .earliest_usable_index_optimistic(&extra_state.new_accounts, &extra_state.new_notes);
229
230 let latest_note_index_optimistic = extra_state
231 .new_notes
232 .last()
233 .map(|indexed_note| indexed_note.0)
234 .unwrap_or(state.latest_note_index);
235
236 let delta_index = Num::from(delta_index.unwrap_or_else(|| {
239 let next_by_optimistic_leaf = extra_state.new_leafs.last().map(|leafs| {
240 (((leafs.0 + (leafs.1.len() as u64)) >> constants::OUTPLUSONELOG) + 1)
241 << constants::OUTPLUSONELOG
242 });
243 let next_by_optimistic_commitment =
244 extra_state.new_commitments.last().map(|commitment| {
245 ((commitment.0 >> constants::OUTPLUSONELOG) + 1) << constants::OUTPLUSONELOG
246 });
247 next_by_optimistic_leaf
248 .into_iter()
249 .chain(next_by_optimistic_commitment)
250 .max()
251 .unwrap_or(self.state.tree.next_index())
252 }));
253
254 let (fee, tx_data) = {
255 let mut tx_data: Vec<u8> = vec![];
256 match &tx {
257 TxType::Deposit { fee, .. } => {
258 let raw_fee: u64 = fee.to_num().try_into().unwrap();
259 tx_data.write_all(&raw_fee.to_be_bytes()).unwrap();
260 (fee, tx_data)
261 }
262 TxType::DepositPermittable {
263 fee,
264 deadline,
265 holder,
266 ..
267 } => {
268 let raw_fee: u64 = fee.to_num().try_into().unwrap();
269
270 tx_data.write_all(&raw_fee.to_be_bytes()).unwrap();
271 tx_data.write_all(&deadline.to_be_bytes()).unwrap();
272 tx_data.append(&mut holder.clone());
273
274 (fee, tx_data)
275 }
276 TxType::Transfer { fee, .. } => {
277 let raw_fee: u64 = fee.to_num().try_into().unwrap();
278 tx_data.write_all(&raw_fee.to_be_bytes()).unwrap();
279 (fee, tx_data)
280 }
281 TxType::Withdraw {
282 fee,
283 to,
284 native_amount,
285 ..
286 } => {
287 let raw_fee: u64 = fee.to_num().try_into().unwrap();
288 let raw_native_amount: u64 = native_amount.to_num().try_into().unwrap();
289
290 tx_data.write_all(&raw_fee.to_be_bytes()).unwrap();
291 tx_data.write_all(&raw_native_amount.to_be_bytes()).unwrap();
292 tx_data.append(&mut to.clone());
293
294 (fee, tx_data)
295 }
296 }
297 };
298
299 let optimistic_available_notes = extra_state
301 .new_notes
302 .into_iter()
303 .filter(|indexed_note| indexed_note.0 >= next_usable_index);
304
305 let in_notes_original: Vec<(u64, Note<P::Fr>)> = state
307 .txs
308 .iter_slice(next_usable_index..=state.latest_note_index)
309 .filter_map(|(index, tx)| match tx {
310 Transaction::Note(note) => Some((index, note)),
311 _ => None,
312 })
313 .chain(optimistic_available_notes)
314 .take(constants::IN)
315 .collect();
316
317 let spend_interval_index = in_notes_original
318 .last()
319 .map(|(index, _)| *index + 1)
320 .unwrap_or(if latest_note_index_optimistic > 0 {
321 latest_note_index_optimistic + 1
322 } else {
323 0
324 });
325
326 let mut input_value = in_account.b.to_num();
328 for (_index, note) in &in_notes_original {
329 input_value += note.b.to_num();
330 }
331
332 let mut output_value = Num::ZERO;
333
334 let (num_real_out_notes, out_notes) = match &tx {
335 TxType::Transfer { outputs, .. }
336 | TxType::Deposit { outputs, .. }
337 | TxType::DepositPermittable { outputs, .. } => {
338 if outputs.len() >= constants::OUT {
339 return Err(CreateTxError::TooManyOutputs {
340 max: constants::OUT,
341 got: outputs.len(),
342 });
343 }
344
345 let out_notes = outputs
346 .iter()
347 .map(|dest| {
348 let (to_d, to_p_d) = parse_address::<P>(&dest.to)?;
349
350 output_value += dest.amount.to_num();
351
352 Ok(Note {
353 d: to_d,
354 p_d: to_p_d,
355 b: dest.amount,
356 t: rng.gen(),
357 })
358 })
359 .chain((0..).map(|_| Ok(zero_note())))
361 .take(constants::OUT)
362 .collect::<Result<SizedVec<_, { constants::OUT }>, AddressParseError>>()?;
363
364 (outputs.len(), out_notes)
365 }
366 _ => (0, (0..).map(|_| zero_note()).take(constants::OUT).collect()),
367 };
368
369 let mut delta_value = -fee.as_num();
370 let mut delta_energy = Num::ZERO;
372
373 let in_account_pos = in_account_index.unwrap_or(0);
374
375 let mut input_energy = in_account.e.to_num();
376 input_energy += in_account.b.to_num() * (delta_index - Num::from(in_account_pos));
377
378 for (note_index, note) in &in_notes_original {
379 input_energy += note.b.to_num() * (delta_index - Num::from(*note_index));
380 }
381 let new_balance = match &tx {
382 TxType::Transfer { .. } => {
383 if input_value.to_uint() >= (output_value + fee.as_num()).to_uint() {
384 input_value - output_value - fee.as_num()
385 } else {
386 return Err(CreateTxError::InsufficientBalance(
387 (output_value + fee.as_num()).to_string(),
388 input_value.to_string(),
389 ));
390 }
391 }
392 TxType::Withdraw {
393 withdraw_amount,
394 energy_amount,
395 ..
396 } => {
397 let amount = withdraw_amount.to_num();
398 let energy = energy_amount.to_num();
399
400 if energy.to_uint() > input_energy.to_uint() {
401 return Err(CreateTxError::InsufficientEnergy(
402 input_energy.to_string(),
403 energy.to_string(),
404 ));
405 }
406
407 delta_energy -= energy;
408 delta_value -= amount;
409
410 if input_value.to_uint() >= amount.to_uint() {
411 input_value + delta_value
412 } else {
413 return Err(CreateTxError::InsufficientBalance(
414 delta_value.to_string(),
415 input_value.to_string(),
416 ));
417 }
418 }
419 TxType::Deposit { deposit_amount, .. }
420 | TxType::DepositPermittable { deposit_amount, .. } => {
421 delta_value += deposit_amount.to_num();
422 let new_total_balance = input_value + delta_value;
423 if new_total_balance.to_uint() >= output_value.to_uint() {
424 new_total_balance - output_value
425 } else {
426 return Err(CreateTxError::InsufficientBalance(
427 output_value.to_string(),
428 new_total_balance.to_string(),
429 ));
430 }
431 }
432 };
433
434 let (d, p_d) = self.generate_address_components();
435 let out_account = Account {
436 d,
437 p_d,
438 i: BoundedNum::new(Num::from(spend_interval_index)),
439 b: BoundedNum::new(new_balance),
440 e: BoundedNum::new(delta_energy + input_energy),
441 };
442
443 let in_account_hash = in_account.hash(&self.params);
444 let nullifier = nullifier(
445 in_account_hash,
446 keys.eta,
447 in_account_pos.into(),
448 &self.params,
449 );
450
451 let ciphertext = {
452 let entropy: [u8; 32] = rng.gen();
453
454 let out_notes = &out_notes[0..num_real_out_notes];
456
457 cipher::encrypt(&entropy, keys.eta, out_account, out_notes, &self.params)
458 };
459
460 let owned_zero_notes = (0..).map(|_| {
462 let d: BoundedNum<_, { constants::DIVERSIFIER_SIZE_BITS }> = rng.gen();
463 let p_d = derive_key_p_d::<P, P::Fr>(d.to_num(), keys.eta, &self.params).x;
464 Note {
465 d,
466 p_d,
467 b: BoundedNum::new(Num::ZERO),
468 t: rng.gen(),
469 }
470 });
471 let in_notes: SizedVec<Note<P::Fr>, { constants::IN }> = in_notes_original
472 .iter()
473 .map(|(_, note)| note)
474 .cloned()
475 .chain(owned_zero_notes)
476 .take(constants::IN)
477 .collect();
478 let in_note_hashes = in_notes.iter().map(|note| note.hash(&self.params));
479 let input_hashes: SizedVec<_, { constants::IN + 1 }> = [in_account_hash]
480 .iter()
481 .copied()
482 .chain(in_note_hashes)
483 .collect();
484
485 let out_account_hash = out_account.hash(&self.params);
487 let out_note_hashes = out_notes.iter().map(|n| n.hash(&self.params));
488 let out_hashes: SizedVec<Num<P::Fr>, { constants::OUT + 1 }> = [out_account_hash]
489 .iter()
490 .copied()
491 .chain(out_note_hashes)
492 .collect();
493
494 let out_commit = out_commitment_hash(out_hashes.as_slice(), &self.params);
495 let tx_hash = tx_hash(input_hashes.as_slice(), out_commit, &self.params);
496
497 let delta = make_delta::<P::Fr>(
498 delta_value,
499 delta_energy,
500 delta_index,
501 *self.pool_id.clone().as_num(),
502 );
503
504 let new_leafs = extra_state.new_leafs.iter().cloned();
506 let new_commitments = extra_state.new_commitments.iter().cloned();
507 let (mut virtual_nodes, update_boundaries) =
508 tree.get_virtual_subtree(new_leafs, new_commitments);
509
510 let root: Num<P::Fr> = tree.get_root_optimistic(&mut virtual_nodes, &update_boundaries);
511
512 let mut memo_data = {
514 let tx_data_size = tx_data.len();
515 let ciphertext_size = ciphertext.len();
516 Vec::with_capacity(tx_data_size + ciphertext_size)
517 };
518
519 memo_data.extend(&tx_data);
520 memo_data.extend(&ciphertext);
521
522 let memo_hash = keccak256(&memo_data);
523 let memo = Num::from_uint_reduced(NumRepr(Uint::from_big_endian(&memo_hash)));
524
525 let public = TransferPub::<P::Fr> {
526 root,
527 nullifier,
528 out_commit,
529 delta,
530 memo,
531 };
532
533 let tx = Tx {
534 input: (in_account, in_notes),
535 output: (out_account, out_notes),
536 };
537
538 let (eddsa_s, eddsa_r) = tx_sign(keys.sk, tx_hash, &self.params);
539
540 let account_proof = in_account_index.map_or_else(
541 || Ok(zero_proof()),
542 |i| {
543 tree.get_proof_optimistic_index(i, &mut virtual_nodes, &update_boundaries)
544 .ok_or(CreateTxError::ProofNotFound(i))
545 },
546 )?;
547 let note_proofs = in_notes_original
548 .iter()
549 .copied()
550 .map(|(index, _note)| {
551 tree.get_proof_optimistic_index(index, &mut virtual_nodes, &update_boundaries)
552 .ok_or(CreateTxError::ProofNotFound(index))
553 })
554 .chain((0..).map(|_| Ok(zero_proof())))
555 .take(constants::IN)
556 .collect::<Result<_, _>>()?;
557
558 let secret = TransferSec::<P::Fr> {
559 tx,
560 in_proof: (account_proof, note_proofs),
561 eddsa_s: eddsa_s.to_other().unwrap(),
562 eddsa_r,
563 eddsa_a: keys.a,
564 };
565
566 Ok(TransactionData {
567 public,
568 secret,
569 ciphertext,
570 memo: memo_data,
571 commitment_root: out_commit,
572 out_hashes,
573 })
574 }
575}
576
577#[cfg(test)]
578mod tests {
579 use libzeropool::POOL_PARAMS;
580
581 use super::*;
582
583 #[test]
584 fn test_create_tx_deposit_zero() {
585 let state = State::init_test(POOL_PARAMS.clone());
586 let acc = UserAccount::new(Num::ZERO, state, POOL_PARAMS.clone());
587
588 acc.create_tx(
589 TxType::Deposit {
590 fee: BoundedNum::new(Num::ZERO),
591 deposit_amount: BoundedNum::new(Num::ZERO),
592 outputs: vec![],
593 },
594 None,
595 None,
596 )
597 .unwrap();
598 }
599
600 #[test]
601 fn test_create_tx_deposit_one() {
602 let state = State::init_test(POOL_PARAMS.clone());
603 let acc = UserAccount::new(Num::ZERO, state, POOL_PARAMS.clone());
604
605 acc.create_tx(
606 TxType::Deposit {
607 fee: BoundedNum::new(Num::ZERO),
608 deposit_amount: BoundedNum::new(Num::ONE),
609 outputs: vec![],
610 },
611 None,
612 None,
613 )
614 .unwrap();
615 }
616
617 #[test]
619 fn test_create_tx_transfer_zero() {
620 let state = State::init_test(POOL_PARAMS.clone());
621 let acc = UserAccount::new(Num::ZERO, state, POOL_PARAMS.clone());
622
623 let addr = acc.generate_address();
624
625 let out = TxOutput {
626 to: addr,
627 amount: BoundedNum::new(Num::ZERO),
628 };
629
630 acc.create_tx(
631 TxType::Transfer {
632 fee: BoundedNum::new(Num::ZERO),
633 outputs: vec![out],
634 },
635 None,
636 None,
637 )
638 .unwrap();
639 }
640
641 #[test]
642 #[should_panic]
643 fn test_create_tx_transfer_one_no_balance() {
644 let state = State::init_test(POOL_PARAMS.clone());
645 let acc = UserAccount::new(Num::ZERO, state, POOL_PARAMS.clone());
646
647 let addr = acc.generate_address();
648
649 let out = TxOutput {
650 to: addr,
651 amount: BoundedNum::new(Num::ONE),
652 };
653
654 acc.create_tx(
655 TxType::Transfer {
656 fee: BoundedNum::new(Num::ZERO),
657 outputs: vec![out],
658 },
659 None,
660 None,
661 )
662 .unwrap();
663 }
664
665 #[test]
666 fn test_user_account_is_own_address() {
667 let acc_1 = UserAccount::new(
668 Num::ZERO,
669 State::init_test(POOL_PARAMS.clone()),
670 POOL_PARAMS.clone(),
671 );
672 let acc_2 = UserAccount::new(
673 Num::ONE,
674 State::init_test(POOL_PARAMS.clone()),
675 POOL_PARAMS.clone(),
676 );
677
678 let address_1 = acc_1.generate_address();
679 let address_2 = acc_2.generate_address();
680
681 assert!(acc_1.is_own_address(&address_1));
682 assert!(acc_2.is_own_address(&address_2));
683
684 assert!(!acc_1.is_own_address(&address_2));
685 assert!(!acc_2.is_own_address(&address_1));
686 }
687}