diff --git a/Cargo.toml b/Cargo.toml index 3354f53..0aa2610 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,4 +10,10 @@ default-members = [ "libzkbob-rs", "libzkbob-rs-node", "libs/kvdb-web", -] \ No newline at end of file +] + +[patch."https://github.com/zkbob/libzeropool-zkbob"] +libzeropool = { git = "https://github.com/zkbob//libzeropool-zkbob", branch = "devel", package = "libzeropool-zkbob" } + +[patch.crates-io] +libzeropool = { git = "https://github.com/zkbob//libzeropool-zkbob", branch = "devel", package = "libzeropool-zkbob" } \ No newline at end of file diff --git a/libzkbob-rs-node/Cargo.toml b/libzkbob-rs-node/Cargo.toml index 7a3396b..2fafa23 100644 --- a/libzkbob-rs-node/Cargo.toml +++ b/libzkbob-rs-node/Cargo.toml @@ -11,8 +11,8 @@ exclude = ["index.node"] crate-type = ["cdylib"] [dependencies] -libzkbob-rs = { version = "1.4.0", features = ["native"] } -#libzkbob-rs = { path = "../libzkbob-rs", features = ["native"] } +#libzkbob-rs = { version = "1.4.0", features = ["native"] } +libzkbob-rs = { path = "../libzkbob-rs", features = ["native"] } neon = { version = "0.10.0", default-features = false, features = ["channel-api", "napi-6", "promise-api", "task-api"] } # FIXME: Using a random fork for now neon-serde = { package = "neon-serde3", version = "0.10" } diff --git a/libzkbob-rs-wasm/src/client/mod.rs b/libzkbob-rs-wasm/src/client/mod.rs index 8c36350..a9341fe 100644 --- a/libzkbob-rs-wasm/src/client/mod.rs +++ b/libzkbob-rs-wasm/src/client/mod.rs @@ -479,6 +479,7 @@ impl UserAccount { .into_iter() .map(|bulk| -> Vec { let eta = &self.inner.borrow().keys.eta; + let kappa = &self.inner.borrow().keys.kappa; let params = &self.inner.borrow().params; let range = from_index.unwrap_or(0)..to_index.unwrap_or(u64::MAX); let bulk_results: Vec = vec_into_iter(bulk.txs) @@ -490,6 +491,7 @@ impl UserAccount { &tx.memo, Some(&tx.tx_hash), eta, + kappa, params ).ok() }) diff --git a/libzkbob-rs-wasm/src/client/tx_parser.rs b/libzkbob-rs-wasm/src/client/tx_parser.rs index fa112cb..65e5ab1 100644 --- a/libzkbob-rs-wasm/src/client/tx_parser.rs +++ b/libzkbob-rs-wasm/src/client/tx_parser.rs @@ -1,4 +1,3 @@ -use byteorder::{LittleEndian, ReadBytesExt}; use libzkbob_rs::libzeropool::{ native::{ account::Account as NativeAccount, @@ -7,7 +6,7 @@ use libzkbob_rs::libzeropool::{ self, symcipher_decryption_keys, decrypt_account_no_validate, - decrypt_note_no_validate + decrypt_note_no_validate, Version }, key::{ self,derive_key_p_d @@ -21,7 +20,6 @@ use libzkbob_rs::{ keys::Keys, utils::zero_account, delegated_deposit::{ - DELEGATED_DEPOSIT_FLAG, MEMO_DELEGATED_DEPOSIT_SIZE, MemoDelegatedDeposit } @@ -127,7 +125,9 @@ impl TxParser { let sk = Num::::from_uint(NumRepr(Uint::from_little_endian(sk))) .ok_or_else(|| js_err!("Invalid spending key"))?; let params = &self.params; - let eta = Keys::derive(sk, params).eta; + let keys = Keys::derive(sk, params); + let eta = keys.eta; + let kappa = &keys.kappa; let txs: Vec = serde_wasm_bindgen::from_value(txs.to_owned()).map_err(|err| js_err!(&err.to_string()))?; @@ -137,7 +137,7 @@ impl TxParser { let memo = hex::decode(memo).unwrap(); let commitment = hex::decode(commitment).unwrap(); - parse_tx(index, &commitment, &memo, None, &eta, params) + parse_tx(index, &commitment, &memo, None, &eta, kappa, params) }) .partition(Result::is_ok); @@ -184,9 +184,11 @@ impl TxParser { ) -> Result, JsValue> { let sk = Num::::from_uint(NumRepr(Uint::from_little_endian(sk))) .ok_or_else(|| js_err!("Invalid spending key"))?; - let eta = Keys::derive(sk, &self.params).eta; + let keys = Keys::derive(sk, &self.params); + let eta = keys.eta; + let kappa = keys.kappa; //(index, chunk, key) - let result = symcipher_decryption_keys(eta, memo, &self.params).unwrap_or(vec![]); + let result = symcipher_decryption_keys(eta, &kappa, memo, &self.params).unwrap_or(vec![]); let chunks = result .iter() @@ -232,181 +234,202 @@ pub fn parse_tx( memo: &Vec, tx_hash: Option<&Vec>, eta: &Num, - params: &PoolParams + kappa: &[u8; 32], + params: &PoolParams, ) -> Result { if memo.len() < 4 { - return Err(ParseError::NoPrefix(index)) + return Err(ParseError::NoPrefix(index)); } - let (is_delegated_deposit, num_items) = parse_prefix(&memo); - // Special case: transaction contains delegated deposits - if is_delegated_deposit { - let num_deposits = num_items as usize; - - let delegated_deposits = memo[4..] - .chunks(MEMO_DELEGATED_DEPOSIT_SIZE) - .take(num_deposits) - .map(|data| MemoDelegatedDeposit::read(data)) - .collect::>>() - .unwrap(); - - let in_notes_indexed = delegated_deposits - .iter() - .enumerate() - .filter_map(|(i, d)| { - let p_d = derive_key_p_d(d.receiver_d.to_num(), eta.clone(), params).x; - if d.receiver_p == p_d { - Some(IndexedNote { - index: index + 1 + (i as u64), - note: d.to_delegated_deposit().to_note(), - }) + let (num_items, version) = + cipher::parse_memo_header(&mut memo.as_slice()).ok_or(ParseError::NoPrefix(0))?; + + if num_items > constants::OUT + 1 { + return Err(ParseError::IncorrectPrefix( + index, + num_items as u32, + (constants::OUT + 1) as u32, + )); + } + + match version { + + Version::DelegatedDeposit => {// Special case: transaction contains delegated deposits + let num_deposits = num_items as usize; + + let delegated_deposits = memo[4..] + .chunks(MEMO_DELEGATED_DEPOSIT_SIZE) + .take(num_deposits) + .map(|data| MemoDelegatedDeposit::read(data)) + .collect::>>() + .unwrap(); + + let in_notes_indexed = delegated_deposits + .iter() + .enumerate() + .filter_map(|(i, d)| { + let p_d = derive_key_p_d(d.receiver_d.to_num(), eta.clone(), params).x; + if d.receiver_p == p_d { + Some(IndexedNote { + index: index + 1 + (i as u64), + note: d.to_delegated_deposit().to_note(), + }) + } else { + None + } + }) + .collect::>(); + + let in_notes: Vec<_> = in_notes_indexed.iter().map(|n| (n.index, n.note)).collect(); + + let hashes = [zero_account().hash(params)] + .iter() + .copied() + .chain( + delegated_deposits + .iter() + .map(|d| d.to_delegated_deposit().to_note().hash(params)), + ) + .collect(); + + let parse_result = { + if !in_notes.is_empty() { + ParseResult { + decrypted_memos: vec![DecMemo { + index, + in_notes: in_notes_indexed, + tx_hash: match tx_hash { + Some(bytes) => Some(format!("0x{}", hex::encode(bytes))), + _ => None, + }, + ..Default::default() + }], + state_update: StateUpdate { + new_leafs: vec![(index, hashes)], + new_notes: vec![in_notes], + ..Default::default() + }, + } } else { - None - } - }) - .collect::>(); - - let in_notes: Vec<_> = in_notes_indexed.iter().map(|n| (n.index, n.note)).collect(); - - let hashes = [zero_account().hash(params)] - .iter() - .copied() - .chain( - delegated_deposits - .iter() - .map(|d| d.to_delegated_deposit().to_note().hash(params)), - ) - .collect(); - - let parse_result = { - if !in_notes.is_empty() { - ParseResult { - decrypted_memos: vec![DecMemo { - index, - in_notes: in_notes_indexed, - tx_hash: match tx_hash { - Some(bytes) => Some(format!("0x{}", hex::encode(bytes))), - _ => None, + ParseResult { + state_update: StateUpdate { + new_commitments: vec![( + index, + Num::from_uint_reduced(NumRepr(Uint::from_big_endian(&commitment))), + )], + ..Default::default() }, ..Default::default() - }], - state_update: StateUpdate { - new_leafs: vec![(index, hashes)], - new_notes: vec![in_notes], - ..Default::default() - }, - } - } else { - ParseResult { - state_update: StateUpdate { - new_commitments: vec![(index, Num::from_uint_reduced(NumRepr(Uint::from_big_endian(&commitment))))], - ..Default::default() - }, - ..Default::default() + } } - } - }; - - return Ok(parse_result); - } + }; - // regular case: simple transaction memo - let num_hashes = num_items; - if num_hashes <= (constants::OUT + 1) as u32 { - let hashes = (&memo[4..]) - .chunks(32) - .take(num_hashes as usize) - .map(|bytes| Num::from_uint_reduced(NumRepr(Uint::from_little_endian(bytes)))); - - let pair = cipher::decrypt_out(*eta, &memo, params); - - match pair { - Some((account, notes)) => { - let mut in_notes = Vec::new(); - let mut out_notes = Vec::new(); - notes.into_iter() - .enumerate() - .for_each(|(i, note)| { + return Ok(parse_result); + } + Version::SymmetricEncryption | Version::Original => {// regular case: simple transaction memo + let num_hashes = num_items; + let hashes = (&memo[4..]) + .chunks(32) + .take(num_hashes as usize) + .map(|bytes| Num::from_uint_reduced(NumRepr(Uint::from_little_endian(bytes)))); + + let pair = cipher::decrypt_out(*eta, kappa, &memo, params); + + match pair { + Some((account, notes)) => { + let mut in_notes = Vec::new(); + let mut out_notes = Vec::new(); + notes.into_iter().enumerate().for_each(|(i, note)| { out_notes.push((index + 1 + (i as u64), note)); if note.p_d == key::derive_key_p_d(note.d.to_num(), *eta, params).x { - in_notes.push((index + 1 + (i as u64), note)); + in_notes.push((index + 1 + (i as u64), note)); } }); - Ok(ParseResult { - decrypted_memos: vec![ DecMemo { - index, - acc: Some(account), - in_notes: in_notes.iter().map(|(index, note)| IndexedNote{index: *index, note: *note}).collect(), - out_notes: out_notes.into_iter().map(|(index, note)| IndexedNote{index, note}).collect(), - tx_hash: match tx_hash { - Some(bytes) => Some(format!("0x{}", hex::encode(bytes))), - _ => None, - }, - ..Default::default() - }], - state_update: StateUpdate { - new_leafs: vec![(index, hashes.collect())], - new_accounts: vec![(index, account)], - new_notes: vec![in_notes], - ..Default::default() - } - }) - }, - None => { - let in_notes: Vec<(_, _)> = cipher::decrypt_in(*eta, &memo, params) - .into_iter() - .enumerate() - .filter_map(|(i, note)| { - match note { - Some(note) if note.p_d == key::derive_key_p_d(note.d.to_num(), *eta, params).x => { - Some((index + 1 + (i as u64), note)) - } - _ => None, - } - }) - .collect(); - - - if !in_notes.is_empty() { Ok(ParseResult { - decrypted_memos: vec![ DecMemo{ - index, - in_notes: in_notes.iter().map(|(index, note)| IndexedNote{index: *index, note: *note}).collect(), + decrypted_memos: vec![DecMemo { + index, + acc: Some(account), + in_notes: in_notes + .iter() + .map(|(index, note)| IndexedNote { + index: *index, + note: *note, + }) + .collect(), + out_notes: out_notes + .into_iter() + .map(|(index, note)| IndexedNote { index, note }) + .collect(), tx_hash: match tx_hash { Some(bytes) => Some(format!("0x{}", hex::encode(bytes))), - None => None, + _ => None, }, ..Default::default() }], state_update: StateUpdate { new_leafs: vec![(index, hashes.collect())], + new_accounts: vec![(index, account)], new_notes: vec![in_notes], ..Default::default() - } - }) - } else { - Ok(ParseResult { - state_update: StateUpdate { - new_commitments: vec![(index, Num::from_uint_reduced(NumRepr(Uint::from_big_endian(&commitment))))], - ..Default::default() }, - ..Default::default() }) } + None => { + let in_notes: Vec<(_, _)> = cipher::decrypt_in(*eta, &memo, params) + .into_iter() + .enumerate() + .filter_map(|(i, note)| match note { + Some(note) + if note.p_d + == key::derive_key_p_d(note.d.to_num(), *eta, params).x => + { + Some((index + 1 + (i as u64), note)) + } + _ => None, + }) + .collect(); + + if !in_notes.is_empty() { + Ok(ParseResult { + decrypted_memos: vec![DecMemo { + index, + in_notes: in_notes + .iter() + .map(|(index, note)| IndexedNote { + index: *index, + note: *note, + }) + .collect(), + tx_hash: match tx_hash { + Some(bytes) => Some(format!("0x{}", hex::encode(bytes))), + None => None, + }, + ..Default::default() + }], + state_update: StateUpdate { + new_leafs: vec![(index, hashes.collect())], + new_notes: vec![in_notes], + ..Default::default() + }, + }) + } else { + Ok(ParseResult { + state_update: StateUpdate { + new_commitments: vec![( + index, + Num::from_uint_reduced(NumRepr(Uint::from_big_endian( + &commitment, + ))), + )], + ..Default::default() + }, + ..Default::default() + }) + } + } } } - } else { - Err(ParseError::IncorrectPrefix(index, num_hashes, (constants::OUT + 1) as u32)) - } -} - -fn parse_prefix(memo: &[u8]) -> (bool, u32) { - let prefix = (&memo[0..4]).read_u32::().unwrap(); - let is_delegated_deposit = prefix & DELEGATED_DEPOSIT_FLAG > 0; - match is_delegated_deposit { - true => (true, (prefix ^ DELEGATED_DEPOSIT_FLAG)), - false => (false, prefix) } } \ No newline at end of file diff --git a/libzkbob-rs/src/client/mod.rs b/libzkbob-rs/src/client/mod.rs index 9dac544..d16497c 100644 --- a/libzkbob-rs/src/client/mod.rs +++ b/libzkbob-rs/src/client/mod.rs @@ -277,7 +277,7 @@ where /// Attempts to decrypt account and notes. pub fn decrypt_pair(&self, data: Vec) -> Option<(Account, Vec>)> { - cipher::decrypt_out(self.keys.eta, &data, &self.params) + cipher::decrypt_out(self.keys.eta, &self.keys.kappa, &data, &self.params) } pub fn initial_account(&self) -> Account { @@ -526,7 +526,7 @@ where // No need to include all the zero notes in the encrypted transaction let out_notes = &out_notes[0..num_real_out_notes]; - cipher::encrypt(&entropy, keys.eta, out_account, out_notes, &self.params) + cipher::encrypt(&entropy, &keys.kappa, out_account, out_notes, &self.params) }; // Hash input account + notes filling remaining space with non-hashed zeroes diff --git a/libzkbob-rs/src/keys.rs b/libzkbob-rs/src/keys.rs index efc481d..e64cd3c 100644 --- a/libzkbob-rs/src/keys.rs +++ b/libzkbob-rs/src/keys.rs @@ -1,7 +1,7 @@ use libzeropool::{ fawkes_crypto::ff_uint::PrimeField, fawkes_crypto::ff_uint::{Num, NumRepr, Uint}, - native::key::{derive_key_a, derive_key_eta}, + native::key::{derive_key_a, derive_key_eta, derive_key_kappa}, native::params::PoolParams, }; use serde::{Deserialize, Serialize}; @@ -15,13 +15,15 @@ pub struct Keys { pub sk: Num, pub a: Num, pub eta: Num, + pub kappa: [u8; 32], } impl Keys

{ pub fn derive(sk: Num, params: &P) -> Self { let a = derive_key_a(sk, params).x; let eta = derive_key_eta(a, params); + let kappa = derive_key_kappa(eta); - Keys { sk, a, eta } + Keys { sk, a, eta, kappa } } }