From 449ef3a9483f9bc971b08af467fad4919b6f0550 Mon Sep 17 00:00:00 2001 From: EvgenKor Date: Wed, 15 Feb 2023 18:37:28 +0300 Subject: [PATCH] Renaming package to libzeropool-zkbob (multicore-wasm + direct_deposits) (#12) * use the multicore version of fawkes-crypto * Reduce number of runtime allocations (#5) * Replace try_to_vec with serialize * Try to decrypt not in place * Implement decrypt account, note and ss in place * Deserialize only necessary note hashes * Fix Cargo.toml * Refactor symcipher_decode * Bump version Co-authored-by: Evgen * Set fawkes-crypto version: 4.3.4 (#6) * Uses https instead of ssh for fawkes-crypto (#7) * Feature/delegated deposits v2 (#11) * Add snark for verifying delegated deposits * delegated deposits fix & tests covered * change number of delegated deposits to 16 * add 0xffffffff prefix for message * join all snarks into one for delegated deposits * remove tree update from delegated deposit snark * Fix dependencies * remove out_commitment from dd secret inputs --------- Co-authored-by: Igor Gulamov Co-authored-by: Vladimir Popov Co-authored-by: Alexander Filippov * Renaming package (libzeropool-zkbob) * Updating gitignore * Changing fawkes-crypto branch --------- Co-authored-by: vms Co-authored-by: Alexander Filippov Co-authored-by: Igor Gulamov Co-authored-by: Vladimir Popov --- .cargo/config.toml | 2 + .gitignore | 12 +- Cargo.toml | 34 +++-- README.md | 9 ++ src/.gitignore | 1 + src/circuit/delegated_deposit.rs | 132 +++++++++++++++++ src/circuit/mod.rs | 3 +- src/constants/mod.rs | 16 ++- src/helpers/sample_data.rs | 197 +++++++++++++++++++++++++- src/native/borsh/delegated_deposit.rs | 23 +++ src/native/borsh/mod.rs | 3 +- src/native/boundednum.rs | 3 + src/native/cipher.rs | 87 +++++++++--- src/native/delegated_deposit.rs | 37 +++++ src/native/mod.rs | 1 + src/setup/main.rs | 51 +++++-- tests/circuit.rs | 29 ---- tests/delegated_deposit.rs | 85 +++++++++++ tests/encryption.rs | 8 +- tests/transaction.rs | 23 ++- tests/tree.rs | 7 +- 21 files changed, 664 insertions(+), 99 deletions(-) create mode 100644 .cargo/config.toml create mode 100644 src/.gitignore create mode 100644 src/circuit/delegated_deposit.rs create mode 100644 src/native/borsh/delegated_deposit.rs create mode 100644 src/native/delegated_deposit.rs delete mode 100644 tests/circuit.rs create mode 100644 tests/delegated_deposit.rs diff --git a/.cargo/config.toml b/.cargo/config.toml new file mode 100644 index 0000000..c91c3f3 --- /dev/null +++ b/.cargo/config.toml @@ -0,0 +1,2 @@ +[net] +git-fetch-with-cli = true diff --git a/.gitignore b/.gitignore index af994a2..23c2ef6 100644 --- a/.gitignore +++ b/.gitignore @@ -14,4 +14,14 @@ Cargo.lock /target -.vscode \ No newline at end of file +.vscode +.idea/* + +#Generated data +**_inputs.json +**_object.json +**_params.bin +**_proof.json +**_key.json +**_verifier.sol +*.DS_Store diff --git a/Cargo.toml b/Cargo.toml index 254a1e5..c4c363a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,10 +1,12 @@ [package] -name = "libzeropool" -version = "0.5.6" +name = "libzeropool-zkbob" +version = "0.6.0" authors = ["Igor Gulamov "] +homepage = "https://github.com/zkbob/libzeropool" +repository = "https://github.com/zkbob/libzeropool" edition = "2018" license = "MIT OR Apache-2.0" -description = "zk-SNARK circuit and cryptography for ZeroPool" +description = "zk-SNARK circuit and cryptography for zkBob" [lib] crate-type = ["rlib"] @@ -17,17 +19,27 @@ name = "libzeropool-setup" required-features = ["cli_libzeropool_setup"] [dependencies] -fawkes-crypto = { version = "4.3.3", features = ["rand_support"] } - - sha3 = "0.9.1" serde = { version = "1.0", features = ["derive"] } serde_json = { version = "1.0"} lazy_static = "1.4.0" -chacha20poly1305 = "0.8.0" +chacha20poly1305 = { version = "0.8.0", features = ["heapless"] } clap={ package = "clap-v3", version = "3.0.0-beta.1", optional=true} convert_case = "0.4.0" +[dependencies.fawkes-crypto] +git = "https://github.com/zkBob/fawkes-crypto" +branch = "master" +package = "fawkes-crypto-zkbob" +version = "4.5.0" +features = ["rand_support"] + +[dependencies.fawkes-crypto-keccak256] +git = "https://github.com/zkbob/keccak" +branch = "master" +package = "fawkes-crypto-zkbob-keccak256" +version = "0.1.1" + [features] in1out127=[] in3out127=[] @@ -36,5 +48,9 @@ in15out127=[] cli_libzeropool_setup = ["clap", "fawkes-crypto/rand_support", "fawkes-crypto/backend_bellman_groth16"] default=["cli_libzeropool_setup", "in3out127"] -[dev-dependencies] -fawkes-crypto = { version = "4.3.3", features = ["rand_support", "backend_bellman_groth16"] } +[dev-dependencies.fawkes-crypto] +git = "https://github.com/zkBob/fawkes-crypto" +branch = "master" +package = "fawkes-crypto-zkbob" +version = "4.5.0" +features = ["rand_support", "backend_bellman_groth16"] diff --git a/README.md b/README.md index ca4aa2b..e6ae571 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,12 @@ +# libzeropool-zkbob + +This is libzeropool core library adapted for (zkBob)[https://zkbob.com/] solution +It was forked from (original ZeroPool repository)[https://github.com/zeropoolnetwork/libzeropool] +This library published in crates.io with the title libzeropool-zkbob + +The undderlying dependency of this library: fawkes-crypto-zkbob +This library used by: libzkbob-rs (core zkbob library) + # libzeropool This is library with circuits and cryptography of ZeroPool. diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 0000000..5509140 --- /dev/null +++ b/src/.gitignore @@ -0,0 +1 @@ +*.DS_Store diff --git a/src/circuit/delegated_deposit.rs b/src/circuit/delegated_deposit.rs new file mode 100644 index 0000000..1c037ff --- /dev/null +++ b/src/circuit/delegated_deposit.rs @@ -0,0 +1,132 @@ +use crate::fawkes_crypto::circuit::{ + bool::CBool, + num::CNum, + bitify::{c_into_bits_le_strict, c_into_bits_le, c_from_bits_le}, + cs::{RCS, CS} +}; +use crate::fawkes_crypto::ff_uint::{PrimeFieldParams, Num}; +use crate::fawkes_crypto::core::{ + signal::Signal, + sizedvec::SizedVec +}; +use crate::circuit::{ + boundednum::CBoundedNum, + note::CNote, + tx::c_out_commitment_hash, +}; +use crate::native::{ + params::PoolParams, + note::Note, + boundednum::BoundedNum, + account::Account, + delegated_deposit::{DelegatedDeposit, DelegatedDepositBatchPub, DelegatedDepositBatchSec} +}; +use crate::constants::{DIVERSIFIER_SIZE_BITS, BALANCE_SIZE_BITS, DELEGATED_DEPOSITS_NUM, OUT}; +use fawkes_crypto_keccak256::circuit::hash::c_keccak256; + +#[derive(Clone, Signal)] +#[Value = "DelegatedDeposit"] +pub struct CDelegatedDeposit { + pub d: CBoundedNum, + pub p_d: CNum, + pub b: CBoundedNum, +} + + + +pub fn num_to_iter_bits_be(n:&CNum) -> impl Iterator> { + assert!(C::Fr::MODULUS_BITS <= 256); + let bits = c_into_bits_le_strict(n); + let zero = n.derive_const(&false); + let bits_le = bits.into_iter().chain(std::iter::repeat(zero)).take(256).collect::>(); + let bits_be = bits_le.chunks(8).rev().flatten().cloned().collect::>(); + bits_be.into_iter() +} + + +pub fn boundednum_to_iter_bits_be(n:&CBoundedNum) -> impl Iterator> { + assert!(L < C::Fr::MODULUS_BITS as usize); + assert!(L%8 == 0); + let bits_le = c_into_bits_le(n.as_num(), L); + let bits_be = bits_le.chunks(8).rev().flatten().cloned().collect::>(); + bits_be.into_iter() +} + +impl CDelegatedDeposit { + pub fn to_note(&self) -> CNote { + let cs = self.d.get_cs(); + CNote { + d: self.d.clone(), + p_d: self.p_d.clone(), + b: self.b.clone(), + t: CBoundedNum::new(&CNum::from_const(cs, &Num::ZERO)) + } + } + + // convert to iter over bits be + pub fn to_iter_bits_be(&self) -> impl Iterator> { + boundednum_to_iter_bits_be(&self.d) + .chain(num_to_iter_bits_be(&self.p_d)) + .chain(boundednum_to_iter_bits_be(&self.b)) + } + +} + +#[derive(Clone, Signal)] +#[Value = "DelegatedDepositBatchPub"] +pub struct CDelegatedDepositBatchPub { + pub keccak_sum: CNum +} + +#[derive(Clone, Signal)] +#[Value = "DelegatedDepositBatchSec"] +pub struct CDelegatedDepositBatchSec { + pub deposits: SizedVec, DELEGATED_DEPOSITS_NUM> +} + +fn c_keccak256_be_reduced(cs:&RCS, bits:&[CBool]) -> CNum { + let keccak_bits_be = c_keccak256(cs, &bits); + let keccak_bits_le = keccak_bits_be.as_slice().chunks(8).rev().flatten().cloned().collect::>(); + c_from_bits_le(&keccak_bits_le) +} + +pub fn check_delegated_deposit_batch>( + p: &CDelegatedDepositBatchPub, + s: &CDelegatedDepositBatchSec, + params: &P +) { + assert!(DELEGATED_DEPOSITS_NUM <= OUT); + let cs = p.get_cs(); + + let c_zero_account_hash = CNum::from_const(cs, &Account { + d:BoundedNum::ZERO, + p_d:Num::ZERO, + i:BoundedNum::ZERO, + b:BoundedNum::ZERO, + e:BoundedNum::ZERO, + }.hash(params)); + + let c_zero_note_hash = CNum::from_const(cs, &Note { + d:BoundedNum::ZERO, + p_d:Num::ZERO, + b:BoundedNum::ZERO, + t:BoundedNum::ZERO + }.hash(params)); + + + let out_hash = std::iter::once(c_zero_account_hash) + .chain(s.deposits.iter().map(|d| d.to_note().hash(params))) + .chain(std::iter::repeat(c_zero_note_hash)).take(OUT+1).collect::>(); + + let out_commitment_hash = c_out_commitment_hash(&out_hash, params); + + let bits:Vec<_> = num_to_iter_bits_be(&out_commitment_hash) + .chain( + s.deposits.iter().flat_map( + |d| d.to_iter_bits_be() + )).collect(); + + c_keccak256_be_reduced(cs, &bits).assert_eq(&p.keccak_sum); + +} + diff --git a/src/circuit/mod.rs b/src/circuit/mod.rs index 9830ec3..4a477ec 100644 --- a/src/circuit/mod.rs +++ b/src/circuit/mod.rs @@ -3,4 +3,5 @@ pub mod boundednum; pub mod account; pub mod note; pub mod key; -pub mod tree; \ No newline at end of file +pub mod tree; +pub mod delegated_deposit; \ No newline at end of file diff --git a/src/constants/mod.rs b/src/constants/mod.rs index 25270c7..1850d62 100644 --- a/src/constants/mod.rs +++ b/src/constants/mod.rs @@ -15,8 +15,10 @@ pub const ENERGY_SIZE_BITS: usize = BALANCE_SIZE_BITS+HEIGHT; pub const SALT_SIZE_BITS: usize = 80; pub const POOLID_SIZE_BITS: usize = 24; +pub const DELEGATED_DEPOSITS_NUM:usize = 16; + pub const POLY_1305_TAG_SIZE: usize = 16; -pub const U256_SIZE:usize = 32; +pub const U256_SIZE: usize = 32; pub fn num_size_bits() -> usize { Fp::Inner::NUM_WORDS*Fp::Inner::WORD_BITS @@ -32,4 +34,14 @@ pub fn account_size_bits() -> usize { //fist 12 bytes from keccak256("ZeroPool") -pub const ENCRYPTION_NONCE: [u8;12] = [0x5b, 0xbd, 0xff, 0xc6, 0xfe, 0x73, 0xc4, 0x60, 0xf1, 0xb2, 0xb8, 0x5d]; \ No newline at end of file +pub const ENCRYPTION_NONCE: [u8;12] = [0x5b, 0xbd, 0xff, 0xc6, 0xfe, 0x73, 0xc4, 0x60, 0xf1, 0xb2, 0xb8, 0x5d]; + +/// Size of prealloced buffer for shared secrets decryption. +/// It's enough for shared secrets with 10 or less keys. +pub const SHARED_SECRETS_HEAPLESS_SIZE: usize = 32 * 10 + 16; +/// Size of prealloced buffer for account decryption. +/// 86 bytes is an account size for bls12-381, buffer needs 16-bytes overhead for auth tag. +pub const ACCOUNT_HEAPLESS_SIZE: usize = 86 + 16; +/// Size of prealloced buffer for note decryption. +/// 76 bytes is a note size for bls12-381, buffer needs 16-bytes overhead for auth tag. +pub const NOTE_HEAPLESS_SIZE: usize = 76 + 16; \ No newline at end of file diff --git a/src/helpers/sample_data.rs b/src/helpers/sample_data.rs index d745b30..74cdb71 100644 --- a/src/helpers/sample_data.rs +++ b/src/helpers/sample_data.rs @@ -1,9 +1,12 @@ +use fawkes_crypto::{BorshSerialize, ff_uint::PrimeField}; + use crate::{constants, fawkes_crypto::{ ff_uint::Num, native::poseidon::{poseidon, MerkleProof}, rand::{self, Rng}, + core::sizedvec::SizedVec }, native::{ account::Account, @@ -11,10 +14,14 @@ use crate::{constants, note::Note, params::{PoolParams}, tx::{make_delta, Tx, TransferPub, TransferSec, nullifier, tx_hash, tx_sign, out_commitment_hash}, - key::{derive_key_a, derive_key_eta, derive_key_p_d} + key::{derive_key_a, derive_key_eta, derive_key_p_d}, + tree::{TreePub, TreeSec}, + delegated_deposit::{DelegatedDepositBatchPub, DelegatedDepositBatchSec, DelegatedDeposit} } }; +use fawkes_crypto_keccak256::native::hash::keccak256; + pub const N_ITEMS:usize = 1000; @@ -97,7 +104,7 @@ impl State

{ } items[account_id].0.p_d = derive_key_p_d(items[account_id].0.d.to_num(), eta, params).x; - items[account_id].0.i = BoundedNum::new(Num::ZERO); + items[account_id].0.i = BoundedNum::ZERO; let mut default_hashes = vec![Num::ZERO;constants::HEIGHT+1]; let mut hashes = vec![]; @@ -141,15 +148,14 @@ impl State

{ } } - pub fn random_sample_transfer(&self, rng:&mut R, params:&P) -> (TransferPub, TransferSec) { let zero_note = Note { - d: BoundedNum::new(Num::ZERO), + d: BoundedNum::ZERO, p_d: Num::ZERO, - b: BoundedNum::new(Num::ZERO), - t: BoundedNum::new(Num::ZERO), + b: BoundedNum::ZERO, + t: BoundedNum::ZERO, }; let root = self.root(); @@ -181,7 +187,7 @@ impl State

{ let mut out_note: Note = Note::sample(rng, params); - out_note.b = BoundedNum::new(Num::ZERO); + out_note.b = BoundedNum::ZERO; let mut input_hashes = vec![self.items[self.account_id].0.hash(params)]; for &i in self.note_id.iter() { @@ -249,3 +255,180 @@ impl State

{ } } + + +pub fn random_sample_tree_update(rng:&mut R, params:&P) -> (TreePub, TreeSec) { + use std::collections::HashMap; + + let index_filled:usize = rng.gen_range(0, N_ITEMS); + let index_free = index_filled + 1; + + const PATH_LENGTH:usize = constants::HEIGHT-constants::OUTPLUSONELOG; + + let mut cell = HashMap::new(); + + let zero_leaf_value = { + let mut c = Num::ZERO; + for _ in 0..constants::OUTPLUSONELOG { + c = poseidon(&[c, c], params.compress()); + } + c + }; + + let cell_defaults = { + let mut c = zero_leaf_value; + let mut res = vec![c;PATH_LENGTH+1]; + for i in 1..PATH_LENGTH { + c = poseidon(&[c,c], params.compress()); + res[i] = c; + } + res + }; + + macro_rules! cell_get { + ($h:expr, $i:expr) => { cell.get(&(($h),($i))).unwrap_or_else(||&cell_defaults[($h)]).clone() } + } + + macro_rules! cell_set { + ($h:expr, $i:expr, $v:expr) => { cell.insert((($h),($i)), ($v)); } + } + + + + let prev_leaf:Num = rng.gen(); + cell_set!(0, index_filled, prev_leaf); + for h in 0..PATH_LENGTH { + let index_level = index_filled>>h; + if index_level & 1 == 1 { + cell_set!(h, index_level^1, rng.gen()); + } + } + + for h in 1..PATH_LENGTH+1 { + let index = index_filled>>h; + let left = cell_get!(h-1, index*2); + let right = cell_get!(h-1, index*2+1); + let hash = poseidon(&[left,right], params.compress()); + cell_set!(h, index, hash); + } + + + + + let path_filled = (0..PATH_LENGTH).map(|i| (index_filled>>i)&1==1).collect(); + let sibling_filled:SizedVec, PATH_LENGTH> = (0..PATH_LENGTH).map(|h| cell_get!(h, (index_filled>>h)^1 )).collect(); + + + let proof_filled = MerkleProof { + sibling: sibling_filled, + path: path_filled + }; + + let root_before = cell_get!(PATH_LENGTH, 0); + + let path_free = (0..PATH_LENGTH).map(|i| (index_free>>i)&1==1).collect(); + let sibling_free:SizedVec, PATH_LENGTH> = (0..PATH_LENGTH).map(|h| cell_get!(h, (index_free>>h)^1 )).collect(); + + let leaf = rng.gen(); + cell_set!(0, index_free, leaf); + + for h in 1..PATH_LENGTH+1 { + let index = index_free>>h; + let left = cell_get!(h-1, index*2); + let right = cell_get!(h-1, index*2+1); + let hash = poseidon(&[left,right], params.compress()); + cell_set!(h, index, hash); + } + + let root_after = cell_get!(PATH_LENGTH, 0); + + let proof_free = MerkleProof { + sibling: sibling_free, + path: path_free + }; + + let p = TreePub { + root_before, + root_after, + leaf + }; + + let s = TreeSec { + proof_filled, + proof_free, + prev_leaf + }; + + (p,s) + +} + +pub fn serialize_scalars_and_delegated_deposits_be(scalars:&[Num], deposits:&[DelegatedDeposit]) -> Vec { + deposits.iter().rev().flat_map(|d| { + let mut res = d.b.try_to_vec().unwrap(); + res.extend(d.p_d.try_to_vec().unwrap()); + res.extend(d.d.try_to_vec().unwrap()); + res + + }) + .chain(scalars.iter().rev().flat_map(|s| s.try_to_vec().unwrap())) + .rev().collect::>() +} + + +pub fn random_sample_delegated_deposit(rng:&mut R, params:&P) -> (DelegatedDepositBatchPub, DelegatedDepositBatchSec) { + + let deposits:SizedVec<_,{constants::DELEGATED_DEPOSITS_NUM}> = (0..constants::DELEGATED_DEPOSITS_NUM).map(|_| { + let n = Note::sample(rng, params); + DelegatedDeposit { + d:n.d, + p_d:n.p_d, + b:n.b, + } + }).collect(); + + let zero_note_hash = Note { + d:BoundedNum::ZERO, + p_d:Num::ZERO, + b:BoundedNum::ZERO, + t:BoundedNum::ZERO + }.hash(params); + + let zero_account_hash = Account { + d: BoundedNum::ZERO, + p_d: Num::ZERO, + i: BoundedNum::ZERO, + b: BoundedNum::ZERO, + e: BoundedNum::ZERO, + }.hash(params); + + let out_hash = std::iter::once(zero_account_hash) + .chain(deposits.iter().map(|d| d.to_note().hash(params))) + .chain(std::iter::repeat(zero_note_hash)).take(constants::OUT+1).collect::>(); + + let _out_commitment_hash = out_commitment_hash(&out_hash, params); + + + + let data = serialize_scalars_and_delegated_deposits_be( + &[_out_commitment_hash], deposits.as_slice()); + + + + let keccak_sum = { + let t = keccak256(&data); + let mut res = Num::ZERO; + for limb in t.iter() { + res = res * Num::from(256) + Num::from(*limb); + } + res + }; + + let p = DelegatedDepositBatchPub {keccak_sum}; + + let s = DelegatedDepositBatchSec { + deposits + }; + (p,s) + +} \ No newline at end of file diff --git a/src/native/borsh/delegated_deposit.rs b/src/native/borsh/delegated_deposit.rs new file mode 100644 index 0000000..4b901ff --- /dev/null +++ b/src/native/borsh/delegated_deposit.rs @@ -0,0 +1,23 @@ +use crate::native::delegated_deposit::*; +use fawkes_crypto::ff_uint::PrimeField; +use crate::fawkes_crypto::borsh::{BorshSerialize, BorshDeserialize}; +use std::io::{self, Write}; + + +impl BorshSerialize for DelegatedDeposit { + fn serialize(&self, writer: &mut W) -> io::Result<()> { + self.d.serialize(writer)?; + self.p_d.serialize(writer)?; + self.b.serialize(writer) + } +} + +impl BorshDeserialize for DelegatedDeposit { + fn deserialize(buf: &mut &[u8]) -> io::Result { + Ok(Self{ + d: BorshDeserialize::deserialize(buf)?, + p_d: BorshDeserialize::deserialize(buf)?, + b: BorshDeserialize::deserialize(buf)? + }) + } +} \ No newline at end of file diff --git a/src/native/borsh/mod.rs b/src/native/borsh/mod.rs index ac07c42..378270f 100644 --- a/src/native/borsh/mod.rs +++ b/src/native/borsh/mod.rs @@ -1,3 +1,4 @@ pub mod account; pub mod note; -pub mod boundednum; \ No newline at end of file +pub mod boundednum; +pub mod delegated_deposit; \ No newline at end of file diff --git a/src/native/boundednum.rs b/src/native/boundednum.rs index 41a24f5..308e949 100644 --- a/src/native/boundednum.rs +++ b/src/native/boundednum.rs @@ -18,6 +18,9 @@ impl PartialEq for BoundedNum { impl BoundedNum { + pub const ONE: Self = BoundedNum(Num::::ONE); + pub const ZERO: Self = BoundedNum(Num::::ZERO); + pub fn new(n:Num) -> Self { assert!(L < Fr::MODULUS_BITS as usize && n.to_uint() < (NumRepr::::ONE << L as u32)); Self::new_unchecked(n) diff --git a/src/native/cipher.rs b/src/native/cipher.rs index 691f984..09d73e4 100644 --- a/src/native/cipher.rs +++ b/src/native/cipher.rs @@ -11,13 +11,29 @@ use crate::{ params::PoolParams, key::{derive_key_a, derive_key_p_d} }, - constants + constants::{self, SHARED_SECRETS_HEAPLESS_SIZE, ACCOUNT_HEAPLESS_SIZE, NOTE_HEAPLESS_SIZE} }; use sha3::{Digest, Keccak256}; -use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce}; +use chacha20poly1305::{ChaCha20Poly1305, Key, Nonce, aead::AeadMutInPlace}; use chacha20poly1305::aead::{Aead, NewAead}; +use chacha20poly1305::aead::heapless::Vec as HeaplessVec; + +/// Wrapper for HeaplessVec (if buffer size is less or equals to N) or Vec otherwise +enum Buffer { + HeapBuffer(Vec), + HeaplessBuffer(HeaplessVec) +} + +impl Buffer { + fn as_slice(&self) -> &[T] { + match self { + Self::HeapBuffer(vec) => vec.as_slice(), + Self::HeaplessBuffer(heapless_vec) => heapless_vec.as_slice() + } + } +} fn keccak256(data:&[u8])->[u8;constants::U256_SIZE] { let mut hasher = Keccak256::new(); @@ -35,17 +51,23 @@ fn symcipher_encode(key:&[u8], data:&[u8])->Vec { cipher.encrypt(nonce, data.as_ref()).unwrap() } -//key stricly assumed to be unique for all messages. Using this function with multiple messages and one key is insecure! -fn symcipher_decode(key:&[u8], data:&[u8])->Option> { +/// Decrypts message in place if `ciphertext.len()` is less or equals to N, otherwise allocates memory in heap. +/// Key stricly assumed to be unique for all messages. Using this function with multiple messages and one key is insecure! +fn symcipher_decode(key: &[u8], ciphertext: &[u8]) -> Option> { assert!(key.len()==constants::U256_SIZE); let nonce = Nonce::from_slice(&constants::ENCRYPTION_NONCE); - let cipher = ChaCha20Poly1305::new(Key::from_slice(key)); - cipher.decrypt(nonce, data).ok() + let mut cipher = ChaCha20Poly1305::new(Key::from_slice(key)); + if ciphertext.len() <= N { + let mut buffer = HeaplessVec::::from_slice(ciphertext).ok()?; + cipher.decrypt_in_place(nonce, b"", &mut buffer).ok()?; + Some(Buffer::HeaplessBuffer(buffer)) + } else { + let plain = cipher.decrypt(nonce, ciphertext).ok()?; + Some(Buffer::HeapBuffer(plain)) + } } - - pub fn encrypt( entropy: &[u8], eta:Num, @@ -134,23 +156,27 @@ pub fn decrypt_out(eta:Num, mut memo:&[u8], params:&P)->Op let shared_secret_ciphertext_size = nozero_items_num * constants::U256_SIZE + constants::POLY_1305_TAG_SIZE; let account_hash = Num::deserialize(&mut memo).ok()?; - let note_hash = (0..nozero_notes_num).map(|_| Num::deserialize(&mut memo)).collect::, _>>().ok()?; + let note_hashes = buf_take(&mut memo, nozero_notes_num * num_size)?; let shared_secret_text = { let a_p = EdwardsPoint::subgroup_decompress(Num::deserialize(&mut memo).ok()?, params.jubjub())?; let ecdh = a_p.mul(eta.to_other_reduced(), params.jubjub()); - let key = keccak256(&ecdh.x.try_to_vec().unwrap()); + let key = { + let mut x: [u8; 32] = [0; 32]; + ecdh.x.serialize(&mut &mut x[..]).unwrap(); + keccak256(&x) + }; let ciphertext = buf_take(&mut memo, shared_secret_ciphertext_size)?; - symcipher_decode(&key, ciphertext)? + symcipher_decode::(&key, ciphertext)? }; - let mut shared_secret_text_ptr =&shared_secret_text[..]; + let mut shared_secret_text_ptr = shared_secret_text.as_slice(); let account_key= <[u8;constants::U256_SIZE]>::deserialize(&mut shared_secret_text_ptr).ok()?; let note_key = (0..nozero_notes_num).map(|_| <[u8;constants::U256_SIZE]>::deserialize(&mut shared_secret_text_ptr)).collect::,_>>().ok()?; let account_ciphertext = buf_take(&mut memo, account_size+constants::POLY_1305_TAG_SIZE)?; - let account_text = symcipher_decode(&account_key, account_ciphertext)?; - let account = Account::try_from_slice(&account_text).ok()?; + let account_plain = symcipher_decode::(&account_key, account_ciphertext)?; + let account = Account::try_from_slice(account_plain.as_slice()).ok()?; if account.hash(params)!= account_hash { return None; @@ -159,9 +185,15 @@ pub fn decrypt_out(eta:Num, mut memo:&[u8], params:&P)->Op let note = (0..nozero_notes_num).map(|i| { buf_take(&mut memo, num_size)?; let ciphertext = buf_take(&mut memo, note_size+constants::POLY_1305_TAG_SIZE)?; - let text = symcipher_decode(¬e_key[i], ciphertext)?; - let note = Note::try_from_slice(&text).ok()?; - if note.hash(params) != note_hash[i] { + let plain = symcipher_decode::(¬e_key[i], ciphertext)?; + let note = Note::try_from_slice(plain.as_slice()).ok()?; + + let note_hash = { + let note_hash = &mut ¬e_hashes[i * num_size..(i + 1) * num_size]; + Num::deserialize(note_hash).ok()? + }; + + if note.hash(params) != note_hash { None } else { Some(note) @@ -186,7 +218,7 @@ fn _decrypt_in(eta:Num, mut memo:&[u8], params:&P)->Option let shared_secret_ciphertext_size = nozero_items_num * constants::U256_SIZE + constants::POLY_1305_TAG_SIZE; buf_take(&mut memo, num_size)?; - let note_hash = (0..nozero_notes_num).map(|_| Num::deserialize(&mut memo)).collect::, _>>().ok()?; + let note_hashes = buf_take(&mut memo, nozero_notes_num * num_size)?; buf_take(&mut memo, num_size)?; buf_take(&mut memo, shared_secret_ciphertext_size)?; @@ -196,12 +228,23 @@ fn _decrypt_in(eta:Num, mut memo:&[u8], params:&P)->Option let note = (0..nozero_notes_num).map(|i| { let a_pub = EdwardsPoint::subgroup_decompress(Num::deserialize(&mut memo).ok()?, params.jubjub())?; let ecdh = a_pub.mul(eta.to_other_reduced(), params.jubjub()); - let key = keccak256(&ecdh.x.try_to_vec().unwrap()); + + let key = { + let mut x: [u8; 32] = [0; 32]; + ecdh.x.serialize(&mut &mut x[..]).unwrap(); + keccak256(&x) + }; let ciphertext = buf_take(&mut memo, note_size+constants::POLY_1305_TAG_SIZE)?; - let text = symcipher_decode(&key, ciphertext)?; - let note = Note::try_from_slice(&text).ok()?; - if note.hash(params) != note_hash[i] { + let plain = symcipher_decode::(&key, ciphertext)?; + let note = Note::try_from_slice(plain.as_slice()).ok()?; + + let note_hash = { + let note_hash = &mut ¬e_hashes[i * num_size..(i + 1) * num_size]; + Num::deserialize(note_hash).ok()? + }; + + if note.hash(params) != note_hash { None } else { Some(note) diff --git a/src/native/delegated_deposit.rs b/src/native/delegated_deposit.rs new file mode 100644 index 0000000..9ac8645 --- /dev/null +++ b/src/native/delegated_deposit.rs @@ -0,0 +1,37 @@ +use fawkes_crypto::ff_uint::{Num, PrimeField}; +use crate::native::{boundednum::BoundedNum, note::Note}; +use std::fmt::Debug; +use crate::fawkes_crypto::core::sizedvec::SizedVec; +use crate::constants::{DIVERSIFIER_SIZE_BITS, BALANCE_SIZE_BITS, DELEGATED_DEPOSITS_NUM}; + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct DelegatedDeposit { + pub d: BoundedNum, + pub p_d: Num, + pub b: BoundedNum +} + +impl DelegatedDeposit { + //convert to a note with zero salt + pub fn to_note(&self) -> Note { + Note { + d: self.d, + p_d: self.p_d, + b: self.b, + t: BoundedNum::ZERO + } + } +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct DelegatedDepositBatchPub { + pub keccak_sum: Num +} + +#[derive(Clone, Debug, Serialize, Deserialize)] +#[serde(bound(serialize = "", deserialize = ""))] +pub struct DelegatedDepositBatchSec { + pub deposits: SizedVec, DELEGATED_DEPOSITS_NUM> +} diff --git a/src/native/mod.rs b/src/native/mod.rs index bf28d76..eedb029 100644 --- a/src/native/mod.rs +++ b/src/native/mod.rs @@ -6,6 +6,7 @@ pub mod account; pub mod cipher; pub mod key; pub mod tree; +pub mod delegated_deposit; mod borsh; mod sample; diff --git a/src/setup/main.rs b/src/setup/main.rs index b0e194d..009514a 100644 --- a/src/setup/main.rs +++ b/src/setup/main.rs @@ -1,9 +1,10 @@ mod evm_verifier; -use libzeropool::{ +use libzeropool_zkbob::{ POOL_PARAMS, circuit::tree::{tree_update, CTreePub, CTreeSec}, circuit::tx::{c_transfer, CTransferPub, CTransferSec}, + circuit::delegated_deposit::{check_delegated_deposit_batch, CDelegatedDepositBatchPub, CDelegatedDepositBatchSec}, clap::Clap, }; use core::panic; @@ -16,7 +17,7 @@ use fawkes_crypto::backend::bellman_groth16::{verifier::{VK, verify}, prover::{P use evm_verifier::generate_sol_data; use fawkes_crypto::circuit::cs::CS; use fawkes_crypto::rand::rngs::OsRng; -use libzeropool::helpers::sample_data::State; +use libzeropool_zkbob::helpers::sample_data::{State, random_sample_tree_update, random_sample_delegated_deposit}; use convert_case::{Case, Casing}; #[derive(Clap)] @@ -125,6 +126,10 @@ fn tx_circuit>(public: CTransferPub, secret: CTransferSec) { c_transfer(&public, &secret, &*POOL_PARAMS); } +fn delegated_deposit_circuit>(public: CDelegatedDepositBatchPub, secret: CDelegatedDepositBatchSec) { + check_delegated_deposit_batch(&public, &secret, &*POOL_PARAMS); +} + fn cli_setup(o:SetupOpts) { let params_path = o.params.unwrap_or(format!("{}_params.bin", o.circuit)); let vk_path = o.vk.unwrap_or(format!("{}_verification_key.json", o.circuit)); @@ -133,6 +138,7 @@ fn cli_setup(o:SetupOpts) { let params = match o.circuit.as_str() { "tree_update" => setup::(tree_circuit), "transfer" => setup::(tx_circuit), + "delegated_deposit" => setup::(delegated_deposit_circuit), _ => panic!("Wrong cicruit parameter") }; @@ -177,18 +183,25 @@ fn cli_verify(o:VerifyOpts) { fn cli_generate_test_data(o:GenerateTestDataOpts) { let object_path = o.object.unwrap_or(format!("{}_object.json", o.circuit)); - - match o.circuit.as_str() { + let mut rng = OsRng::default(); + let data_str = match o.circuit.as_str() { "transfer" => { - let mut rng = OsRng::default(); let state = State::random_sample_state(&mut rng, &*POOL_PARAMS); let data = state.random_sample_transfer(&mut rng, &*POOL_PARAMS); - let data_str = serde_json::to_string_pretty(&data).unwrap(); - std::fs::write(object_path, &data_str.into_bytes()).unwrap(); + serde_json::to_string_pretty(&data).unwrap() + + }, + "tree_update" => { + let data = random_sample_tree_update(&mut rng, &*POOL_PARAMS); + serde_json::to_string_pretty(&data).unwrap() + }, + "delegated_deposit" => { + let data = random_sample_delegated_deposit(&mut rng, &*POOL_PARAMS); + serde_json::to_string_pretty(&data).unwrap() }, - "tree_update" => std::unimplemented!(), _ => panic!("Wrong cicruit parameter") - } + }; + std::fs::write(object_path, &data_str.into_bytes()).unwrap(); println!("Test data generated") @@ -206,12 +219,20 @@ fn cli_prove(o:ProveOpts) { let params = Parameters::::read(&mut params_data_cur, false, false).unwrap(); let object_str = std::fs::read_to_string(object_path).unwrap(); - let (inputs, snark_proof) = if o.circuit.eq("tree_update") { - let (public, secret) = serde_json::from_str(&object_str).unwrap(); - prove(¶ms, &public, &secret, tree_circuit) - } else { - let (public, secret) = serde_json::from_str(&object_str).unwrap(); - prove(¶ms, &public, &secret, tx_circuit) + let (inputs, snark_proof) = match o.circuit.as_str() { + "transfer" => { + let (public, secret) = serde_json::from_str(&object_str).unwrap(); + prove(¶ms, &public, &secret, tx_circuit) + }, + "tree_update" => { + let (public, secret) = serde_json::from_str(&object_str).unwrap(); + prove(¶ms, &public, &secret, tree_circuit) + }, + "delegated_deposit" => { + let (public, secret) = serde_json::from_str(&object_str).unwrap(); + prove(¶ms, &public, &secret, delegated_deposit_circuit) + }, + _ => panic!("Wrong cicruit parameter") }; diff --git a/tests/circuit.rs b/tests/circuit.rs deleted file mode 100644 index fa87e84..0000000 --- a/tests/circuit.rs +++ /dev/null @@ -1,29 +0,0 @@ -use fawkes_crypto::circuit::cs::CS; -use libzeropool::{ - fawkes_crypto::{ - circuit::cs::DebugCS, - core::signal::Signal, - } -}; - -use libzeropool::POOL_PARAMS; -use libzeropool::circuit::tx::{CTransferPub, CTransferSec, c_transfer}; -use std::time::Instant; - -#[test] -fn test_circuit_tx() { - let ref cs = DebugCS::rc_new(); - let ref p = CTransferPub::alloc(cs, None); - let ref s = CTransferSec::alloc(cs, None); - - - let mut n_gates = cs.borrow().num_gates(); - let start = Instant::now(); - c_transfer(p, s, &*POOL_PARAMS); - let duration = start.elapsed(); - n_gates=cs.borrow().num_gates()-n_gates; - - println!("tx constraints = {}", n_gates); - println!("Time elapsed in c_transfer() is: {:?}", duration); - -} diff --git a/tests/delegated_deposit.rs b/tests/delegated_deposit.rs new file mode 100644 index 0000000..3207c3e --- /dev/null +++ b/tests/delegated_deposit.rs @@ -0,0 +1,85 @@ +use libzeropool_zkbob::{POOL_PARAMS, + circuit::delegated_deposit::{CDelegatedDepositBatchPub, CDelegatedDepositBatchSec, check_delegated_deposit_batch, CDelegatedDeposit, num_to_iter_bits_be}, + native::note::Note, + native::delegated_deposit::{DelegatedDeposit}, + helpers::sample_data::serialize_scalars_and_delegated_deposits_be, + fawkes_crypto::{ + circuit::{ + cs::{CS, DebugCS}, + num::CNum, + }, + core::{signal::Signal, sizedvec::SizedVec}, + rand::{thread_rng, Rng}, + ff_uint::Num + }, +}; + +use std::time::Instant; +use libzeropool_zkbob::fawkes_crypto::engines::bn256::Fr; + + + +#[test] +fn test_check_delegated_deposit_batch() { + let ref cs = DebugCS::rc_new(); + let ref p = CDelegatedDepositBatchPub::alloc(cs, None); + let ref s = CDelegatedDepositBatchSec::alloc(cs, None); + + + let mut n_gates = cs.borrow().num_gates(); + let start = Instant::now(); + check_delegated_deposit_batch(p, s, &*POOL_PARAMS); + let duration = start.elapsed(); + n_gates=cs.borrow().num_gates()-n_gates; + + println!("tx constraints = {}", n_gates); + println!("Time elapsed in check_delegated_deposit_batch() is: {:?}", duration); + +} + +#[test] +fn test_bitify_delegated_deposits_be() { + const N_ITEMS:usize = 10; + let mut rng = thread_rng(); + + let deposits:SizedVec<_,{N_ITEMS}> = (0..N_ITEMS).map(|_| { + let n = Note::sample(&mut rng, &*POOL_PARAMS); + DelegatedDeposit { + d:n.d, + p_d:n.p_d, + b:n.b, + } + }).collect(); + + let roots:SizedVec,1> = (0..1).map(|_|rng.gen()).collect(); + + + let data = serialize_scalars_and_delegated_deposits_be(roots.as_slice(), deposits.as_slice()); + + let bitlen = data.len()*8; + + let bits = (0..bitlen).map(|i| { + let byte = data[i/8]; + let bit = byte & (1 << (i%8)); + bit != 0 + }).collect::>(); + + let ref cs = DebugCS::rc_new(); + + let c_deposits:SizedVec>,{N_ITEMS}> = Signal::alloc(cs, Some(deposits).as_ref()); + + let c_roots:SizedVec>,1> = Signal::alloc(cs, Some(roots).as_ref()); + + let c_bits = c_roots.iter().flat_map(num_to_iter_bits_be) + .chain(c_deposits.iter().flat_map( + |d| d.to_iter_bits_be() + )).collect::>(); + + assert_eq!(bits.len(), c_bits.len()); + + for (i, (b, c_b)) in bits.iter().zip(c_bits.iter()).enumerate() { + assert_eq!(*b, c_b.get_value().unwrap(), "bit {} is not equal", i); + } + +} + \ No newline at end of file diff --git a/tests/encryption.rs b/tests/encryption.rs index 726cfde..88bd75b 100644 --- a/tests/encryption.rs +++ b/tests/encryption.rs @@ -1,14 +1,14 @@ -use libzeropool::POOL_PARAMS; +use libzeropool_zkbob::POOL_PARAMS; -use libzeropool::fawkes_crypto::rand::{thread_rng, Rng}; -use libzeropool::native::{ +use libzeropool_zkbob::fawkes_crypto::rand::{thread_rng, Rng}; +use libzeropool_zkbob::native::{ note::Note, account::Account, key::{derive_key_p_d}, cipher }; -use libzeropool::fawkes_crypto::engines::bn256::Fr; +use libzeropool_zkbob::fawkes_crypto::engines::bn256::Fr; #[test] diff --git a/tests/transaction.rs b/tests/transaction.rs index 8a695d5..a5e0f28 100644 --- a/tests/transaction.rs +++ b/tests/transaction.rs @@ -1,4 +1,4 @@ -use libzeropool::{POOL_PARAMS, circuit::tx::{CTransferPub, CTransferSec, c_transfer}, +use libzeropool_zkbob::{POOL_PARAMS, circuit::tx::{CTransferPub, CTransferSec, c_transfer}, fawkes_crypto::{ circuit::{ cs::{CS, DebugCS} @@ -14,12 +14,29 @@ use libzeropool::{POOL_PARAMS, circuit::tx::{CTransferPub, CTransferSec, c_trans }, }; -use libzeropool::fawkes_crypto::engines::bn256::Fr; +use libzeropool_zkbob::fawkes_crypto::engines::bn256::Fr; use std::time::Instant; -use libzeropool::helpers::sample_data::State; +use libzeropool_zkbob::helpers::sample_data::State; +#[test] +fn test_circuit_tx() { + let ref cs = DebugCS::rc_new(); + let ref p = CTransferPub::alloc(cs, None); + let ref s = CTransferSec::alloc(cs, None); + + + let mut n_gates = cs.borrow().num_gates(); + let start = Instant::now(); + c_transfer(p, s, &*POOL_PARAMS); + let duration = start.elapsed(); + n_gates=cs.borrow().num_gates()-n_gates; + + println!("tx constraints = {}", n_gates); + println!("Time elapsed in c_transfer() is: {:?}", duration); + +} #[test] fn test_circuit_tx_fullfill() { diff --git a/tests/tree.rs b/tests/tree.rs index 68fabec..1eb01e2 100644 --- a/tests/tree.rs +++ b/tests/tree.rs @@ -1,5 +1,5 @@ use fawkes_crypto::rand::Rng; -use libzeropool::{POOL_PARAMS, circuit::tree::{CTreePub, CTreeSec, tree_update}, +use libzeropool_zkbob::{POOL_PARAMS, circuit::tree::{CTreePub, CTreeSec, tree_update}, native::tree::{TreePub, TreeSec}, fawkes_crypto::{ ff_uint::Num, @@ -11,11 +11,8 @@ use libzeropool::{POOL_PARAMS, circuit::tree::{CTreePub, CTreeSec, tree_update}, }, }; - use std::time::Instant; - - -use libzeropool::helpers::sample_data::HashTreeState; +use libzeropool_zkbob::helpers::sample_data::HashTreeState; #[test]