diff --git a/libzkbob-rs-node/Cargo.toml b/libzkbob-rs-node/Cargo.toml index 2fafa23..c92b68e 100644 --- a/libzkbob-rs-node/Cargo.toml +++ b/libzkbob-rs-node/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "libzkbob-rs-node" -version = "1.1.0" +version = "1.2.0" authors = ["Dmitry Vdovin "] repository = "https://github.com/zkBob/libzkbob-rs/" license = "MIT OR Apache-2.0" @@ -23,5 +23,5 @@ hex = "0.4.3" git = "https://github.com/zkbob/fawkes-crypto" branch = "master" package = "fawkes-crypto-zkbob" -version = "4.6.0" +version = "4.7.0" features = ["multicore"] \ No newline at end of file diff --git a/libzkbob-rs-node/package.json b/libzkbob-rs-node/package.json index 258ef71..de993e0 100644 --- a/libzkbob-rs-node/package.json +++ b/libzkbob-rs-node/package.json @@ -1,6 +1,6 @@ { "name": "libzkbob-rs-node", - "version": "1.1.0", + "version": "1.2.0", "description": "Neon version of libzkbob-rs", "main": "index.js", "types": "index.d.ts", diff --git a/libzkbob-rs-wasm/Cargo.toml b/libzkbob-rs-wasm/Cargo.toml index 061cfb3..2d5210c 100644 --- a/libzkbob-rs-wasm/Cargo.toml +++ b/libzkbob-rs-wasm/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libzkbob-rs-wasm" description = "A higher level zkBob API for Wasm" -version = "1.6.0" +version = "1.7.0" authors = ["Dmitry Vdovin "] repository = "https://github.com/zkBob/libzkbob-rs/" license = "MIT OR Apache-2.0" @@ -18,7 +18,7 @@ nodejs = ["libzkbob-rs/node"] multicore = ["fawkes-crypto/multicore", "wasm-bindgen-rayon"] [dependencies] -wasm-bindgen = { version = "0.2.74" } +wasm-bindgen = { version = "0.2.90" } # The `console_error_panic_hook` crate provides better debugging of panics by # logging them with `console.error`. This is great for development, but requires @@ -40,16 +40,17 @@ kvdb-web = { version = "0.9.0", path = "../libs/kvdb-web"} kvdb = "0.9.0" kvdb-memorydb = "0.9.0" byteorder = "1.4.3" -wasm-bindgen-futures = "0.4.24" -serde = "1.0.126" +wasm-bindgen-futures = "0.4.40" +serde = "1.0.195" +serde_bytes = "0.11.14" sha3 = "0.9.1" thiserror = "1.0.26" -serde-wasm-bindgen = "0.3.0" +serde-wasm-bindgen = "0.6.3" lazy_static = "1.4.0" hex = { version = "0.4.3", features = ["serde"] } js-sys = "0.3.55" -wasm-bindgen-rayon = { version = "1.0", optional = true } -rayon = "1.5.1" +wasm-bindgen-rayon = { version = "1.2.1", optional = true } +rayon = "1.8.1" bincode = "1.3.3" web-sys = "0.3.61" @@ -57,16 +58,16 @@ web-sys = "0.3.61" git = "https://github.com/zkBob/fawkes-crypto" branch = "master" package = "fawkes-crypto-zkbob" -version = "4.6.0" +version = "4.7.0" features = ["wasm", "serde_support"] [dev-dependencies] -wasm-bindgen-test = "0.3.24" +wasm-bindgen-test = "0.3.40" test-case = "1.2.0" rand = "0.8.4" -serde_json = "1.0.64" -wasm-bindgen = { version = "0.2.74", features = ["serde-serialize"] } +serde_json = "1.0.111" +wasm-bindgen = { version = "0.2.90", features = ["serde-serialize"] } [profile.release] # Tell `rustc` to optimize for small code size. diff --git a/libzkbob-rs-wasm/scripts/publish-beta b/libzkbob-rs-wasm/scripts/publish-beta new file mode 100755 index 0000000..76cae1d --- /dev/null +++ b/libzkbob-rs-wasm/scripts/publish-beta @@ -0,0 +1,6 @@ +#!/usr/bin/env bash + +PARENT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )/.." &> /dev/null && pwd )" + +npm publish $PARENT_DIR/web --tag beta +npm publish $PARENT_DIR/web-mt --tag beta \ No newline at end of file diff --git a/libzkbob-rs-wasm/src/client/mod.rs b/libzkbob-rs-wasm/src/client/mod.rs index a9341fe..9ccbdb5 100644 --- a/libzkbob-rs-wasm/src/client/mod.rs +++ b/libzkbob-rs-wasm/src/client/mod.rs @@ -2,7 +2,6 @@ use std::collections::HashMap; use std::rc::Rc; use std::{cell::RefCell, convert::TryInto}; use std::str::FromStr; - #[cfg(feature = "multicore")] use rayon::prelude::*; @@ -63,13 +62,12 @@ pub struct UserAccount { impl UserAccount { #[wasm_bindgen(constructor)] /// Initializes UserAccount with a spending key that has to be an element of the prime field Fs (p = 6554484396890773809930967563523245729705921265872317281365359162392183254199). - pub fn new(sk: &[u8], pool_id: u32, state: UserState) -> Result { + pub fn new(sk: &[u8], pool_id: u32, is_obsolete_pool: bool, state: UserState) -> Result { crate::utils::set_panic_hook(); - let sk = Num::::from_uint(NumRepr(Uint::from_little_endian(sk))) .ok_or_else(|| js_err!("Invalid spending key"))?; - let account = NativeUserAccount::new(sk, pool_id, state.inner, POOL_PARAMS.clone()); + let account = NativeUserAccount::new(sk, pool_id, is_obsolete_pool, state.inner, POOL_PARAMS.clone()); Ok(UserAccount { inner: Rc::new(RefCell::new(account)), diff --git a/libzkbob-rs-wasm/src/client/tx_parser.rs b/libzkbob-rs-wasm/src/client/tx_parser.rs index 65e5ab1..84bab45 100644 --- a/libzkbob-rs-wasm/src/client/tx_parser.rs +++ b/libzkbob-rs-wasm/src/client/tx_parser.rs @@ -6,7 +6,7 @@ use libzkbob_rs::libzeropool::{ self, symcipher_decryption_keys, decrypt_account_no_validate, - decrypt_note_no_validate, Version + decrypt_note_no_validate, MessageEncryptionType }, key::{ self,derive_key_p_d @@ -48,7 +48,7 @@ pub enum ParseError { } impl ParseError { - pub fn index(&self) -> u64 { + pub fn _index(&self) -> u64 { match *self { ParseError::NoPrefix(idx) => idx, ParseError::IncorrectPrefix(idx, _, _) => idx, @@ -131,48 +131,52 @@ impl TxParser { let txs: Vec = serde_wasm_bindgen::from_value(txs.to_owned()).map_err(|err| js_err!(&err.to_string()))?; - let (parse_results, parse_errors): (Vec<_>, Vec<_>) = vec_into_iter(txs) - .map(|tx| -> Result { + let parse_results: Vec<_> = vec_into_iter(txs) + .map(|tx| -> ParseResult { let IndexedTx{index, memo, commitment} = tx; let memo = hex::decode(memo).unwrap(); let commitment = hex::decode(commitment).unwrap(); - parse_tx(index, &commitment, &memo, None, &eta, kappa, params) - }) - .partition(Result::is_ok); - - if parse_errors.is_empty() { - let parse_result = parse_results - .into_iter() - .map(Result::unwrap) - .fold(Default::default(), |acc: ParseResult, parse_result| { - ParseResult { - decrypted_memos: vec![acc.decrypted_memos, parse_result.decrypted_memos].concat(), - state_update: StateUpdate { - new_leafs: vec![acc.state_update.new_leafs, parse_result.state_update.new_leafs].concat(), - new_commitments: vec![acc.state_update.new_commitments, parse_result.state_update.new_commitments].concat(), - new_accounts: vec![acc.state_update.new_accounts, parse_result.state_update.new_accounts].concat(), - new_notes: vec![acc.state_update.new_notes, parse_result.state_update.new_notes].concat() + match parse_tx(index, &commitment, &memo, None, &eta, kappa, params) { + Ok(res) => res, + Err(err) => { + console::log_1(&format!("[WASM TxParser] ERROR: {}", err.to_string()).into()); + // Skip transaction in case of parsing errors (assume it doesn't belongs to the our account) + ParseResult { + state_update: StateUpdate { + new_commitments: vec![( + index, + Num::from_uint_reduced(NumRepr(Uint::from_big_endian( + &commitment, + ))), + )], + ..Default::default() + }, + ..Default::default() } } - }); + } + }) + .collect(); + + let parse_result = parse_results + .into_iter() + .fold(Default::default(), |acc: ParseResult, parse_result| { + ParseResult { + decrypted_memos: vec![acc.decrypted_memos, parse_result.decrypted_memos].concat(), + state_update: StateUpdate { + new_leafs: vec![acc.state_update.new_leafs, parse_result.state_update.new_leafs].concat(), + new_commitments: vec![acc.state_update.new_commitments, parse_result.state_update.new_commitments].concat(), + new_accounts: vec![acc.state_update.new_accounts, parse_result.state_update.new_accounts].concat(), + new_notes: vec![acc.state_update.new_notes, parse_result.state_update.new_notes].concat() + } + } + }); - let parse_result = serde_wasm_bindgen::to_value(&parse_result) - .unwrap() - .unchecked_into::(); - Ok(parse_result) - } else { - let errors: Vec<_> = parse_errors - .into_iter() - .map(|err| -> ParseError { - let err = err.unwrap_err(); - console::log_1(&format!("[WASM TxParser] ERROR: {}", err.to_string()).into()); - err - }) - .collect(); - let all_errs: Vec = errors.into_iter().map(|err| err.index()).collect(); - Err(js_err!("The following txs cannot be processed: {:?}", all_errs)) - } + let parse_result = serde_wasm_bindgen::to_value(&parse_result) + .unwrap() + .unchecked_into::(); + Ok(parse_result) } #[wasm_bindgen(js_name = "extractDecryptKeys")] @@ -241,8 +245,8 @@ pub fn parse_tx( return Err(ParseError::NoPrefix(index)); } - let (num_items, version) = - cipher::parse_memo_header(&mut memo.as_slice()).ok_or(ParseError::NoPrefix(0))?; + let (num_items, enc_type) = + cipher::parse_memo_header(&mut memo.as_slice()).ok_or(ParseError::NoPrefix(index))?; if num_items > constants::OUT + 1 { return Err(ParseError::IncorrectPrefix( @@ -252,9 +256,9 @@ pub fn parse_tx( )); } - match version { + match enc_type { - Version::DelegatedDeposit => {// Special case: transaction contains delegated deposits + MessageEncryptionType::Plain => {// Special case: transaction contains delegated deposits let num_deposits = num_items as usize; let delegated_deposits = memo[4..] @@ -326,7 +330,7 @@ pub fn parse_tx( return Ok(parse_result); } - Version::SymmetricEncryption | Version::Original => {// regular case: simple transaction memo + MessageEncryptionType::Symmetric | MessageEncryptionType::ECDH => {// regular case: simple transaction memo let num_hashes = num_items; let hashes = (&memo[4..]) .chunks(32) diff --git a/libzkbob-rs-wasm/src/client/tx_types.rs b/libzkbob-rs-wasm/src/client/tx_types.rs index 2d59e3f..326042d 100644 --- a/libzkbob-rs-wasm/src/client/tx_types.rs +++ b/libzkbob-rs-wasm/src/client/tx_types.rs @@ -1,5 +1,5 @@ -use crate::{Fr, IDepositData, IDepositPermittableData, ITransferData, IWithdrawData, IMultiTransferData, IMultiWithdrawData}; -use libzkbob_rs::client::{TokenAmount, TxOutput, TxType as NativeTxType}; +use crate::{Fr, IDepositData, IDepositPermittableData, ITransferData, IWithdrawData }; +use libzkbob_rs::client::{TokenAmount, TxOutput, TxType as NativeTxType, ExtraItem, TxOperator}; use serde::Deserialize; use wasm_bindgen::prelude::*; @@ -16,15 +16,26 @@ pub trait JsTxType { fn to_native(&self) -> Result, JsValue>; } -pub trait JsMultiTxType { - fn to_native_array(&self) -> Result>, JsValue>; +#[wasm_bindgen] +#[derive(Deserialize)] +pub struct TxExtraData { + leaf_index: u8, + pad_length: u16, + need_encrypt: bool, + #[serde(with = "serde_bytes")] + data: Vec, } #[wasm_bindgen] #[derive(Deserialize)] pub struct TxBaseFields { - fee: TokenAmount, - data: Option>, + #[serde(with = "serde_bytes")] + proxy: Vec, + #[serde(with = "serde_bytes")] + prover: Vec, + proxy_fee: TokenAmount, + prover_fee: TokenAmount, + data: Vec, } #[wasm_bindgen] @@ -42,9 +53,26 @@ impl JsTxType for IDepositData { amount, } = serde_wasm_bindgen::from_value(self.into())?; + let operator = TxOperator { + proxy_address: base_fields.proxy, + prover_address: base_fields.prover, + proxy_fee: base_fields.proxy_fee, + prover_fee: base_fields.prover_fee, + }; + + let extra = base_fields.data + .into_iter() + .map(|item| ExtraItem { + leaf_index: item.leaf_index, + pad_length: item.pad_length, + need_encrypt: item.need_encrypt, + data: item.data, + }) + .collect::>(); + Ok(NativeTxType::Deposit( - base_fields.fee, - base_fields.data.unwrap_or_default(), + operator, + extra, amount, )) } @@ -57,6 +85,7 @@ pub struct DepositPermittableData { base_fields: TxBaseFields, amount: TokenAmount, deadline: String, + #[serde(with = "serde_bytes")] holder: Vec, } @@ -69,9 +98,26 @@ impl JsTxType for IDepositPermittableData { holder, } = serde_wasm_bindgen::from_value(self.into())?; + let operator = TxOperator { + proxy_address: base_fields.proxy, + prover_address: base_fields.prover, + proxy_fee: base_fields.proxy_fee, + prover_fee: base_fields.prover_fee, + }; + + let extra = base_fields.data + .into_iter() + .map(|item| ExtraItem { + leaf_index: item.leaf_index, + pad_length: item.pad_length, + need_encrypt: item.need_encrypt, + data: item.data, + }) + .collect::>(); + Ok(NativeTxType::DepositPermittable( - base_fields.fee, - base_fields.data.unwrap_or_default(), + operator, + extra, amount, deadline.parse::().unwrap_or(0), holder @@ -107,36 +153,29 @@ impl JsTxType for ITransferData { amount: out.amount, }) .collect::>(); - - Ok(NativeTxType::Transfer( - base_fields.fee, - base_fields.data.unwrap_or_default(), - outputs, - )) - } -} - -impl JsMultiTxType for IMultiTransferData { - fn to_native_array(&self) -> Result>, JsValue> { - let array: Vec = serde_wasm_bindgen::from_value(self.into())?; - - let tx_array = array.into_iter().map(|tx| { - let outputs = tx.outputs + + let operator = TxOperator { + proxy_address: base_fields.proxy, + prover_address: base_fields.prover, + proxy_fee: base_fields.proxy_fee, + prover_fee: base_fields.prover_fee, + }; + + let extra = base_fields.data .into_iter() - .map(|out| TxOutput { - to: out.to, - amount: out.amount, + .map(|item| ExtraItem { + leaf_index: item.leaf_index, + pad_length: item.pad_length, + need_encrypt: item.need_encrypt, + data: item.data, }) .collect::>(); - NativeTxType::Transfer( - tx.base_fields.fee, - tx.base_fields.data.unwrap_or_default(), - outputs, - ) - }).collect(); - - Ok(tx_array) + Ok(NativeTxType::Transfer( + operator, + extra, + outputs, + )) } } @@ -146,6 +185,7 @@ pub struct WithdrawData { #[serde(flatten)] base_fields: TxBaseFields, amount: TokenAmount, + #[serde(with = "serde_bytes")] to: Vec, native_amount: TokenAmount, energy_amount: TokenAmount, @@ -161,9 +201,26 @@ impl JsTxType for IWithdrawData { energy_amount, } = serde_wasm_bindgen::from_value(self.into())?; + let operator = TxOperator { + proxy_address: base_fields.proxy, + prover_address: base_fields.prover, + proxy_fee: base_fields.proxy_fee, + prover_fee: base_fields.prover_fee, + }; + + let extra = base_fields.data + .into_iter() + .map(|item| ExtraItem { + leaf_index: item.leaf_index, + pad_length: item.pad_length, + need_encrypt: item.need_encrypt, + data: item.data, + }) + .collect::>(); + Ok(NativeTxType::Withdraw( - base_fields.fee, - base_fields.data.unwrap_or_default(), + operator, + extra, amount, to, native_amount, @@ -171,22 +228,3 @@ impl JsTxType for IWithdrawData { )) } } - -impl JsMultiTxType for IMultiWithdrawData { - fn to_native_array(&self) -> Result>, JsValue> { - let array: Vec = serde_wasm_bindgen::from_value(self.into())?; - - let tx_array = array.into_iter().map(|tx| { - NativeTxType::Withdraw( - tx.base_fields.fee, - tx.base_fields.data.unwrap_or_default(), - tx.amount, - tx.to, - tx.native_amount, - tx.energy_amount, - ) - }).collect(); - - Ok(tx_array) - } -} diff --git a/libzkbob-rs-wasm/src/lib.rs b/libzkbob-rs-wasm/src/lib.rs index 5d12f55..ca9c50b 100644 --- a/libzkbob-rs-wasm/src/lib.rs +++ b/libzkbob-rs-wasm/src/lib.rs @@ -1,9 +1,7 @@ use libzkbob_rs::libzeropool::{ constants, - fawkes_crypto::{backend::bellman_groth16::engines::Bn256}, - native::{ - params::{PoolBN256, PoolParams as PoolParamsTrait}, - }, + fawkes_crypto::backend::bellman_groth16::engines::Bn256, + native::params::{PoolBN256, PoolParams as PoolParamsTrait}, POOL_PARAMS, }; use serde::Serialize; @@ -11,7 +9,6 @@ use wasm_bindgen::{prelude::*, JsCast}; pub use crate::{ client::*, - proof::*, state::{Transaction, UserState}, ts_types::*, }; diff --git a/libzkbob-rs-wasm/src/ts_types.rs b/libzkbob-rs-wasm/src/ts_types.rs index 175f4c3..b7c629e 100644 --- a/libzkbob-rs-wasm/src/ts_types.rs +++ b/libzkbob-rs-wasm/src/ts_types.rs @@ -119,9 +119,19 @@ export interface IAddressComponents { is_pool_valid: boolean; } +export interface IExtraItem { + leaf_index: number; + pad_length: number; + need_encrypt: boolean; + data: Uint8Array; +} + export interface ITxBaseFields { - fee: string; - data?: Uint8Array; + proxy: Uint8Array; + prover: Uint8Array; + proxy_fee: string; + prover_fee: string; + data: IExtraItem[]; } export interface IDepositData extends ITxBaseFields { @@ -274,15 +284,9 @@ extern "C" { #[wasm_bindgen(typescript_type = "ITransferData")] pub type ITransferData; - #[wasm_bindgen(typescript_type = "ITransferData[]")] - pub type IMultiTransferData; - #[wasm_bindgen(typescript_type = "IWithdrawData")] pub type IWithdrawData; - #[wasm_bindgen(typescript_type = "IWithdrawData[]")] - pub type IMultiWithdrawData; - #[wasm_bindgen(typescript_type = "DecryptedMemo")] pub type DecryptedMemo; diff --git a/libzkbob-rs/Cargo.toml b/libzkbob-rs/Cargo.toml index d611443..28e0f71 100644 --- a/libzkbob-rs/Cargo.toml +++ b/libzkbob-rs/Cargo.toml @@ -26,7 +26,7 @@ hex = { version = "0.4.3", features = ["serde"] } git = "https://github.com/zkbob/libzeropool-zkbob" branch = "master" package = "libzeropool-zkbob" -version = "1.3.0" +version = "1.4.0" default-features = false features = ["in3out127"] diff --git a/libzkbob-rs/src/address.rs b/libzkbob-rs/src/address.rs index e66279d..ea0a1e7 100644 --- a/libzkbob-rs/src/address.rs +++ b/libzkbob-rs/src/address.rs @@ -8,7 +8,7 @@ use libzeropool::{ constants, fawkes_crypto::{ borsh::{BorshDeserialize, BorshSerialize}, - ff_uint::{Num, Uint, NumRepr}, + ff_uint::Num, native::ecc::EdwardsPoint, }, native::boundednum::BoundedNum, diff --git a/libzkbob-rs/src/client/mod.rs b/libzkbob-rs/src/client/mod.rs index 511b322..65ee4a7 100644 --- a/libzkbob-rs/src/client/mod.rs +++ b/libzkbob-rs/src/client/mod.rs @@ -92,24 +92,62 @@ pub struct TxOutput { pub amount: TokenAmount, } +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct TxOperator { + pub proxy_address: Vec, + pub prover_address: Vec, + pub proxy_fee: TokenAmount, + pub prover_fee: TokenAmount, +} + +#[derive(Debug, Serialize, Deserialize, Clone)] +pub struct ExtraItem { + pub leaf_index: u8, // encrypted items will use associated account\note key + // if leaf doesn't exist the item cannot be encrypted + pub pad_length: u16, // how many random bytes should be add before the data + pub need_encrypt: bool, + pub data: Vec, // the rust library doesn't know about the item + // content and interpret it just as a byte array +} + +impl TxOperator { + pub fn serialize(&self, dst: &mut Vec, is_obsolete_format: bool) { + if is_obsolete_format { + let raw_fee: u64 = self.total_fee().try_into().unwrap(); + dst.write_all(&raw_fee.to_be_bytes()).unwrap(); + } else { + dst.append(&mut self.proxy_address.clone()); + dst.append(&mut self.prover_address.clone()); + let raw_proxy_fee: u64 = self.proxy_fee.to_num().try_into().unwrap(); + let raw_prover_fee: u64 = self.prover_fee.to_num().try_into().unwrap(); + dst.write_all(&raw_proxy_fee.to_be_bytes()).unwrap(); + dst.write_all(&raw_prover_fee.to_be_bytes()).unwrap(); + } + } + + pub fn total_fee(&self) -> Num { + self.proxy_fee.to_num() + self.prover_fee.to_num() + } +} + #[derive(Debug, Serialize, Deserialize, Clone)] pub enum TxType { - // fee, data, tx_outputs - Transfer(TokenAmount, Vec, Vec>), - // fee, data, deposit_amount - Deposit(TokenAmount, Vec, TokenAmount), - // fee, data, deposit_amount, deadline, holder + // operator, extra_data, tx_outputs + Transfer(TxOperator, Vec, Vec>), + // operator, extra_data, deposit_amount + Deposit(TxOperator, Vec, TokenAmount), + // operator, extra_data, deposit_amount, deadline, holder DepositPermittable( - TokenAmount, - Vec, + TxOperator, + Vec, TokenAmount, u64, Vec ), - // fee, data, withdraw_amount, to, native_amount, energy_amount + // operator, extra_data, withdraw_amount, to, native_amount, energy_amount Withdraw( - TokenAmount, - Vec, + TxOperator, + Vec, TokenAmount, Vec, TokenAmount, @@ -119,6 +157,7 @@ pub enum TxType { pub struct UserAccount { pub pool_id: u32, + pub is_obsolete_pool: bool, pub keys: Keys

, pub params: P, pub state: State, @@ -132,11 +171,12 @@ where P::Fr: 'static, { /// Initializes UserAccount with a spending key that has to be an element of the prime field Fs (p = 6554484396890773809930967563523245729705921265872317281365359162392183254199). - pub fn new(sk: Num, pool_id: u32, state: State, params: P) -> Self { + pub fn new(sk: Num, pool_id: u32, is_obsolete_pool: bool, state: State, params: P) -> Self { let keys = Keys::derive(sk, ¶ms); UserAccount { pool_id, + is_obsolete_pool, keys, state, params, @@ -145,9 +185,9 @@ where } /// Same as constructor but accepts arbitrary data as spending key. - pub fn from_seed(seed: &[u8], pool_id: u32, state: State, params: P) -> Self { + pub fn from_seed(seed: &[u8], pool_id: u32, is_obsolete_pool: bool, state: State, params: P) -> Self { let sk = reduce_sk(seed); - Self::new(sk, pool_id, state, params) + Self::new(sk, pool_id, is_obsolete_pool, state, params) } fn generate_address_components( @@ -328,37 +368,33 @@ where .unwrap_or_else(|| self.state.tree.next_index()) })); - let (fee, tx_data, user_data) = { + let (fee, tx_data, _user_data) = { let mut tx_data: Vec = vec![]; match &tx { - TxType::Deposit(fee, user_data, _) => { - let raw_fee: u64 = fee.to_num().try_into().unwrap(); - tx_data.write_all(&raw_fee.to_be_bytes()).unwrap(); - (fee, tx_data, user_data) - } - TxType::DepositPermittable(fee, user_data, _, deadline, holder) => { - let raw_fee: u64 = fee.to_num().try_into().unwrap(); + TxType::Deposit(operator, user_data, _) => { + operator.serialize(&mut tx_data, self.is_obsolete_pool); - tx_data.write_all(&raw_fee.to_be_bytes()).unwrap(); + (operator.total_fee(), tx_data, user_data) + } + TxType::DepositPermittable(operator, user_data, _, deadline, holder) => { + operator.serialize(&mut tx_data, self.is_obsolete_pool); tx_data.write_all(&deadline.to_be_bytes()).unwrap(); tx_data.append(&mut holder.clone()); - (fee, tx_data, user_data) + (operator.total_fee(), tx_data, user_data) } - TxType::Transfer(fee, user_data, _) => { - let raw_fee: u64 = fee.to_num().try_into().unwrap(); - tx_data.write_all(&raw_fee.to_be_bytes()).unwrap(); - (fee, tx_data, user_data) + TxType::Transfer(operator, user_data, _) => { + operator.serialize(&mut tx_data, self.is_obsolete_pool); + + (operator.total_fee(), tx_data, user_data) } - TxType::Withdraw(fee, user_data, _, reciever, native_amount, _) => { - let raw_fee: u64 = fee.to_num().try_into().unwrap(); + TxType::Withdraw(operator, user_data, _, reciever, native_amount, _) => { + operator.serialize(&mut tx_data, self.is_obsolete_pool); let raw_native_amount: u64 = native_amount.to_num().try_into().unwrap(); - - tx_data.write_all(&raw_fee.to_be_bytes()).unwrap(); tx_data.write_all(&raw_native_amount.to_be_bytes()).unwrap(); tx_data.append(&mut reciever.clone()); - (fee, tx_data, user_data) + (operator.total_fee(), tx_data, user_data) } } }; @@ -424,7 +460,7 @@ where (0, (0..).map(|_| zero_note()).take(constants::OUT).collect()) }; - let mut delta_value = -fee.as_num(); + let mut delta_value = -fee; // By default all account energy will be withdrawn on withdraw tx let mut delta_energy = Num::ZERO; @@ -438,11 +474,11 @@ where } let new_balance = match &tx { TxType::Transfer(_, _, _) => { - if input_value.to_uint() >= (output_value + fee.as_num()).to_uint() { - input_value - output_value - fee.as_num() + if input_value.to_uint() >= (output_value + fee).to_uint() { + input_value - output_value - fee } else { return Err(CreateTxError::InsufficientBalance( - (output_value + fee.as_num()).to_string(), + (output_value + fee).to_string(), input_value.to_string(), )); } @@ -499,7 +535,10 @@ 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.kappa, out_account, out_notes, &self.params) + match self.is_obsolete_pool { + true => cipher::_encrypt_old(&entropy, keys.eta, out_account, out_notes, &self.params), // ECDH scheme + false => cipher::encrypt(&entropy, &keys.kappa, out_account, out_notes, &self.params), // symmetric scheme + } }; // Hash input account + notes filling remaining space with non-hashed zeroes @@ -553,18 +592,25 @@ where let root: Num = tree.get_root_optimistic(&mut virtual_nodes, &update_boundaries); - // memo = tx_specific_data, ciphertext, user_defined_data + // TODO: prepare (encrypt) extra data + + // memo = tx_specific_data, ciphertext, extra_data let mut memo_data = { let tx_data_size = tx_data.len(); + let ciphertext_size_size = if self.is_obsolete_pool { 0 } else { 2 }; let ciphertext_size = ciphertext.len(); - let user_data_size = user_data.len(); - Vec::with_capacity(tx_data_size + ciphertext_size + user_data_size) + let user_data_size = 0; //user_data.len(); + Vec::with_capacity(tx_data_size + ciphertext_size_size + ciphertext_size + user_data_size) }; #[allow(clippy::redundant_clone)] memo_data.append(&mut tx_data.clone()); + if !self.is_obsolete_pool { + // add message size for new memo format + memo_data.extend((ciphertext.len() as u16).to_be_bytes()); + } memo_data.extend(&ciphertext); - memo_data.append(&mut user_data.clone()); + // TODO: append extra data here! let memo_hash = keccak256(&memo_data); let memo = Num::from_uint_reduced(NumRepr(Uint::from_big_endian(&memo_hash))); @@ -655,11 +701,18 @@ mod tests { #[test] fn test_create_tx_deposit_zero() { let state = State::init_test(POOL_PARAMS.clone()); - let acc = UserAccount::new(Num::ZERO, 0, state, POOL_PARAMS.clone()); + let acc = UserAccount::new(Num::ZERO, 0, false, state, POOL_PARAMS.clone()); + + let op = TxOperator { + proxy_address: vec![], + prover_address: vec![], + proxy_fee: BoundedNum::ZERO, + prover_fee: BoundedNum::ZERO, + }; acc.create_tx( TxType::Deposit( - BoundedNum::ZERO, + op, vec![], BoundedNum::ZERO, ), @@ -672,11 +725,18 @@ mod tests { #[test] fn test_create_tx_deposit_one() { let state = State::init_test(POOL_PARAMS.clone()); - let acc = UserAccount::new(Num::ZERO, 0, state, POOL_PARAMS.clone()); + let acc = UserAccount::new(Num::ZERO, 0, false, state, POOL_PARAMS.clone()); + + let op = TxOperator { + proxy_address: vec![], + prover_address: vec![], + proxy_fee: BoundedNum::ZERO, + prover_fee: BoundedNum::ONE, + }; acc.create_tx( TxType::Deposit( - BoundedNum::new(Num::ZERO), + op, vec![], BoundedNum::new(Num::ONE), ), @@ -690,7 +750,7 @@ mod tests { #[test] fn test_create_tx_transfer_zero() { let state = State::init_test(POOL_PARAMS.clone()); - let acc = UserAccount::new(Num::ZERO, 0, state, POOL_PARAMS.clone()); + let acc = UserAccount::new(Num::ZERO, 0, false, state, POOL_PARAMS.clone()); let addr = acc.generate_address(); @@ -699,8 +759,15 @@ mod tests { amount: BoundedNum::new(Num::ZERO), }; + let op = TxOperator { + proxy_address: vec![], + prover_address: vec![], + proxy_fee: BoundedNum::ZERO, + prover_fee: BoundedNum::ZERO, + }; + acc.create_tx( - TxType::Transfer(BoundedNum::new(Num::ZERO), vec![], vec![out]), + TxType::Transfer(op, vec![], vec![out]), None, None, ) @@ -711,7 +778,7 @@ mod tests { #[should_panic] fn test_create_tx_transfer_one_no_balance() { let state = State::init_test(POOL_PARAMS.clone()); - let acc = UserAccount::new(Num::ZERO, 0, state, POOL_PARAMS.clone()); + let acc = UserAccount::new(Num::ZERO, 0, false, state, POOL_PARAMS.clone()); let addr = acc.generate_address(); @@ -720,8 +787,15 @@ mod tests { amount: BoundedNum::new(Num::ONE), }; + let op = TxOperator { + proxy_address: vec![], + prover_address: vec![], + proxy_fee: BoundedNum::ZERO, + prover_fee: BoundedNum::ZERO, + }; + acc.create_tx( - TxType::Transfer(BoundedNum::new(Num::ZERO), vec![], vec![out]), + TxType::Transfer(op, vec![], vec![out]), None, None, ) @@ -733,12 +807,14 @@ mod tests { let acc_1 = UserAccount::new( Num::ZERO, 0xffff02, + false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone(), ); let acc_2 = UserAccount::new( Num::ONE, 0xffff02, + false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone(), ); @@ -761,6 +837,7 @@ mod tests { let mut user_account = UserAccount::new( Num::ZERO, 1, + false, state, POOL_PARAMS.clone() ); @@ -812,10 +889,10 @@ mod tests { #[test] fn test_chain_specific_addresses() { - let acc_polygon = UserAccount::new(Num::ZERO, 0, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); - let acc_sepolia = UserAccount::new(Num::ZERO, 0, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); - let acc_optimism = UserAccount::new(Num::ZERO, 1, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); - let acc_optimism_eth = UserAccount::new(Num::ZERO, 2, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); + let acc_polygon = UserAccount::new(Num::ZERO, 0, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); + let acc_sepolia = UserAccount::new(Num::ZERO, 0, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); + let acc_optimism = UserAccount::new(Num::ZERO, 1, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); + let acc_optimism_eth = UserAccount::new(Num::ZERO, 2, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); assert!(acc_polygon.validate_universal_address("PtfsqyJhA2yvmLtXBm55pkvFDX6XZrRMaib9F1GvwzmU8U4witUf8Jyse5kxBwa")); // generic without a prefix assert!(acc_polygon.validate_universal_address("zkbob:PtfsqyJhA2yvmLtXBm55pkvFDX6XZrRMaib9F1GvwzmU8U4witUf8Jyse5kxBwa")); // generic @@ -853,13 +930,13 @@ mod tests { #[test] fn test_chain_specific_address_ownable() { let accs = [ - UserAccount::new(Num::ZERO, 0, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), - UserAccount::new(Num::ZERO, 1, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), - UserAccount::new(Num::ZERO, 2, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), - UserAccount::new(Num::ZERO, 0xffff02, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), - UserAccount::new(Num::ZERO, 0xffff03, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), + UserAccount::new(Num::ZERO, 0, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), + UserAccount::new(Num::ZERO, 1, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), + UserAccount::new(Num::ZERO, 2, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), + UserAccount::new(Num::ZERO, 0xffff02, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), + UserAccount::new(Num::ZERO, 0xffff03, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()), ]; - let acc2 = UserAccount::new(Num::ONE, 1, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); + let acc2 = UserAccount::new(Num::ONE, 1, false, State::init_test(POOL_PARAMS.clone()), POOL_PARAMS.clone()); let pool_addresses: Vec = accs.iter().map(|acc| acc.generate_address()).collect(); let universal_addresses: Vec = accs.iter().map(|acc| acc.generate_universal_address()).collect();