Skip to content

Commit

Permalink
Feature/compliance (#46)
Browse files Browse the repository at this point in the history
* Extracting decryption keys

* Retrieving tx inputs (account and notes)

* Add unit test for retrieving tx inputs, fix few issues

* Fix issues, public methods for acccount/note decrypt

* Export method to calculate nullifier from the account

* Exporting intermediate nullifier hashes

* fix comment line

* Fix review issues

* Fixed compilation errors

* Fix errors

* Fix deprecation warnings

* Apply suggestions from code review

removing commented code

Co-authored-by: Alexander Filippov <aleksander.fill@gmail.com>

* Increasing version (libzkbob-rs\libzkbob-rs-wasm -> 1.3.0), switching to the actual libzeropool-zkbob

---------

Co-authored-by: Alexander Filippov <aleksander.fill@gmail.com>
  • Loading branch information
EvgenKor and AllFi authored May 11, 2023
1 parent 3334d84 commit 3e3b0a4
Show file tree
Hide file tree
Showing 8 changed files with 341 additions and 38 deletions.
2 changes: 1 addition & 1 deletion libzkbob-rs-wasm/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "libzkbob-rs-wasm"
description = "A higher level zkBob API for Wasm"
version = "1.2.0"
version = "1.3.0"
authors = ["Dmitry Vdovin <voidxnull@gmail.com>"]
repository = "https://github.com/zkBob/libzkbob-rs/"
license = "MIT OR Apache-2.0"
Expand Down
72 changes: 63 additions & 9 deletions libzkbob-rs-wasm/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@ use std::rc::Rc;
use std::{cell::RefCell, convert::TryInto};
use std::str::FromStr;

use libzkbob_rs::address::parse_address_ext;
#[cfg(feature = "multicore")]
use rayon::prelude::*;

Expand All @@ -19,21 +18,21 @@ use libzkbob_rs::libzeropool::{
account::Account as NativeAccount,
note::Note as NativeNote,
boundednum::BoundedNum,
tx::{parse_delta, TransferPub as NativeTransferPub, TransferSec as NativeTransferSec}
tx::{parse_delta, nullifier, TransferPub as NativeTransferPub, TransferSec as NativeTransferSec}
},
};
use libzkbob_rs::{
client::{TxType as NativeTxType, UserAccount as NativeUserAccount, StateFragment},
merkle::{Hash, Node},
address::parse_address,
address::{parse_address, parse_address_ext},
pools::Pool
};

use serde::Serialize;
use wasm_bindgen::{prelude::*, JsCast };
use wasm_bindgen_futures::future_to_promise;

use crate::{ParseTxsColdStorageResult, IAddressComponents};
use crate::{ParseTxsColdStorageResult, IAddressComponents, TxInput, TxInputNodes, IndexedAccount};
use crate::client::tx_parser::StateUpdate;

use crate::database::Database;
Expand Down Expand Up @@ -163,7 +162,27 @@ impl UserAccount {
.unchecked_into::<IAddressComponents>())
}

#[wasm_bindgen(js_name = "decryptNotes")]
#[wasm_bindgen(js_name = "calculateNullifier")]
/// Calculate nullifier from the account
pub fn calculate_nullifier(&self, account: Account, index: u64) -> Result<JsHash, JsValue> {
let in_account: NativeAccount<Fr> = serde_wasm_bindgen::from_value(account.into())?;

let params = &self.inner.borrow().params;
let eta = &self.inner.borrow().keys.eta;
let in_account_hash = in_account.hash(params);
let nullifier = nullifier(
in_account_hash,
*eta,
index.into(),
params,
);

Ok(serde_wasm_bindgen::to_value(&nullifier)
.unwrap()
.unchecked_into::<JsHash>())
}

#[wasm_bindgen(js_name = decryptNotes)]
/// Attempts to decrypt notes.
pub fn decrypt_notes(&self, data: Vec<u8>) -> Result<IndexedNotes, JsValue> {
let notes = self
Expand Down Expand Up @@ -293,7 +312,7 @@ impl UserAccount {

#[wasm_bindgen(js_name = "createTransferOptimistic")]
pub fn create_tranfer_optimistic(&self, transfer: ITransferData, new_state: &JsValue) -> Result<Promise, JsValue> {
let new_state: StateUpdate = new_state.into_serde().map_err(|err| js_err!(&err.to_string()))?;
let new_state: StateUpdate = serde_wasm_bindgen::from_value(new_state.to_owned()).map_err(|err| js_err!(&err.to_string()))?;
Ok(self.construct_tx_data(transfer.to_native()?, Some(new_state)))
}

Expand All @@ -304,7 +323,7 @@ impl UserAccount {

#[wasm_bindgen(js_name = "createWithdrawalOptimistic")]
pub fn create_withdraw_optimistic(&self, withdraw: IWithdrawData, new_state: &JsValue) -> Result<Promise, JsValue> {
let new_state: StateUpdate = new_state.into_serde().map_err(|err| js_err!(&err.to_string()))?;
let new_state: StateUpdate = serde_wasm_bindgen::from_value(new_state.to_owned()).map_err(|err| js_err!(&err.to_string()))?;
Ok(self.construct_tx_data(withdraw.to_native()?, Some(new_state)))
}

Expand Down Expand Up @@ -386,9 +405,9 @@ impl UserAccount {

#[wasm_bindgen(js_name = "updateState")]
pub fn update_state(&mut self, state_update: &JsValue, siblings: Option<TreeNodes>) -> Result<(), JsValue> {
let state_update: StateUpdate = state_update.into_serde().map_err(|err| js_err!(&err.to_string()))?;
let state_update: StateUpdate = serde_wasm_bindgen::from_value(state_update.to_owned()).map_err(|err| js_err!(&err.to_string()))?;
let siblings: Option<Vec<Node<Fr>>> = match siblings {
Some(val) => val.into_serde().map_err(|err| js_err!(&err.to_string()))?,
Some(val) => serde_wasm_bindgen::from_value(val.unchecked_into()).map_err(|err| js_err!(&err.to_string()))?,
None => None
};

Expand Down Expand Up @@ -551,6 +570,41 @@ impl UserAccount {
serde_wasm_bindgen::to_value(&data).unwrap()
}

#[wasm_bindgen(js_name = "getTxInputs")]
/// Returns transaction inputs: account and notes
pub fn get_tx_inputs(&self, index: u64) -> Result<TxInput, JsValue> {
if index & constants::OUT as u64 != 0 {
return Err(js_err!(&format!("Account index should be multiple of {}", constants::OUT + 1)));
}

let inputs = self
.inner
.borrow()
.get_tx_input(index)
.ok_or_else(|| js_err!("No account found at index {}", index))?;

let res = TxInputNodes {
account: IndexedAccount {
index: inputs.account.0,
account: inputs.account.1
},
intermediate_nullifier: inputs.intermediate_nullifier,
notes: inputs
.notes.into_iter()
.map(|note| {
IndexedNote {
index: note.0,
note: note.1,
}
})
.collect()
};

Ok(serde_wasm_bindgen::to_value(&res)
.unwrap()
.unchecked_into::<TxInput>())
}

#[wasm_bindgen(js_name = "nextTreeIndex")]
pub fn next_tree_index(&self) -> u64 {
self.inner.borrow().state.tree.next_index()
Expand Down
101 changes: 94 additions & 7 deletions libzkbob-rs-wasm/src/client/tx_parser.rs
Original file line number Diff line number Diff line change
@@ -1,16 +1,45 @@
use byteorder::{LittleEndian, ReadBytesExt};
use libzkbob_rs::{libzeropool::{native::{account::Account, note::Note, cipher, key::{self, derive_key_p_d}}, fawkes_crypto::ff_uint::{Num, NumRepr, Uint}, constants}, delegated_deposit::{MEMO_DELEGATED_DEPOSIT_SIZE, MemoDelegatedDeposit}, utils::zero_account};
use libzkbob_rs::{merkle::Hash, keys::Keys, delegated_deposit::DELEGATED_DEPOSIT_FLAG};
use libzkbob_rs::libzeropool::{
native::{
account::Account as NativeAccount,
note::Note as NativeNote,
cipher::{
self,
symcipher_decryption_keys,
decrypt_account_no_validate,
decrypt_note_no_validate
},
key::{
self,derive_key_p_d
}
},
fawkes_crypto::ff_uint::{ Num, NumRepr, Uint },
constants,
};
use libzkbob_rs::{
merkle::Hash,
keys::Keys,
utils::zero_account,
delegated_deposit::{
DELEGATED_DEPOSIT_FLAG,
MEMO_DELEGATED_DEPOSIT_SIZE,
MemoDelegatedDeposit
}
};
use wasm_bindgen::{prelude::*, JsCast};
use serde::{Serialize, Deserialize};
use std::iter::IntoIterator;
use thiserror::Error;
use web_sys::console;
use crate::{ Account, Note };

#[cfg(feature = "multicore")]
use rayon::prelude::*;

use crate::{PoolParams, Fr, IndexedNote, IndexedTx, Fs, ParseTxsResult, POOL_PARAMS, helpers::vec_into_iter};
use crate::{PoolParams, Fr, IndexedNote, IndexedTx, Fs,
ParseTxsResult, POOL_PARAMS, helpers::vec_into_iter,
TxMemoChunk,
};

#[derive(Debug, Error)]
pub enum ParseError {
Expand All @@ -36,15 +65,15 @@ pub struct StateUpdate {
#[serde(rename = "newCommitments")]
pub new_commitments: Vec<(u64, Hash<Fr>)>,
#[serde(rename = "newAccounts")]
pub new_accounts: Vec<(u64, Account<Fr>)>,
pub new_accounts: Vec<(u64, NativeAccount<Fr>)>,
#[serde(rename = "newNotes")]
pub new_notes: Vec<Vec<(u64, Note<Fr>)>>
pub new_notes: Vec<Vec<(u64, NativeNote<Fr>)>>
}

#[derive(Serialize, Deserialize, Clone, Default, Debug)]
pub struct DecMemo {
pub index: u64,
pub acc: Option<Account<Fr>>,
pub acc: Option<NativeAccount<Fr>>,
#[serde(rename = "inNotes")]
pub in_notes: Vec<IndexedNote>,
#[serde(rename = "outNotes")]
Expand All @@ -70,6 +99,14 @@ pub struct ParseColdStorageResult {
pub decrypted_leafs_cnt: usize,
}

/// Describes one memo chunk (account\note) along with decryption key
#[derive(Serialize, Deserialize, Clone, Default)]
pub struct MemoChunk {
pub index: u64,
pub encrypted: Vec<u8>,
pub key: Vec<u8>,
}

#[wasm_bindgen]
pub struct TxParser {
#[wasm_bindgen(skip)]
Expand All @@ -92,7 +129,7 @@ impl TxParser {
let params = &self.params;
let eta = Keys::derive(sk, params).eta;

let txs: Vec<IndexedTx> = txs.into_serde().map_err(|err| js_err!(&err.to_string()))?;
let txs: Vec<IndexedTx> = 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<ParseResult, ParseError> {
Expand Down Expand Up @@ -137,6 +174,56 @@ impl TxParser {
Err(js_err!("The following txs cannot be processed: {:?}", all_errs))
}
}

#[wasm_bindgen(js_name = "extractDecryptKeys")]
pub fn extract_decrypt_keys(
&self,
sk: &[u8],
index: u64,
memo: &[u8],
) -> Result<Vec<TxMemoChunk>, JsValue> {
let sk = Num::<Fs>::from_uint(NumRepr(Uint::from_little_endian(sk)))
.ok_or_else(|| js_err!("Invalid spending key"))?;
let eta = Keys::derive(sk, &self.params).eta;
//(index, chunk, key)
let result = symcipher_decryption_keys(eta, memo, &self.params).unwrap_or(vec![]);

let chunks = result
.iter()
.map(|(chunk_idx, chunk, key)| {
let res = MemoChunk {
index: index + chunk_idx,
encrypted: chunk.clone(),
key: key.clone()
};

serde_wasm_bindgen::to_value(&res)
.unwrap()
.unchecked_into::<TxMemoChunk>()
})
.collect();

Ok(chunks)

}

#[wasm_bindgen(js_name = "symcipherDecryptAcc")]
pub fn symcipher_decrypt_acc(&self, sym_key: &[u8], encrypted: &[u8] ) -> Result<Account, JsValue> {
let acc = decrypt_account_no_validate(sym_key, encrypted, &self.params)
.ok_or_else(|| js_err!("Unable to decrypt account"))?;

Ok(serde_wasm_bindgen::to_value(&acc).unwrap().unchecked_into::<Account>())
}

#[wasm_bindgen(js_name = "symcipherDecryptNote")]
pub fn symcipher_decrypt_note(&self, sym_key: &[u8], encrypted: &[u8] ) -> Result<Note, JsValue> {
let note = decrypt_note_no_validate(sym_key, encrypted, &self.params)
.ok_or_else(|| js_err!("Unable to decrypt note"))?;

Ok(serde_wasm_bindgen::to_value(&note).unwrap().unchecked_into::<Note>())
}


}

pub fn parse_tx(
Expand Down
42 changes: 39 additions & 3 deletions libzkbob-rs-wasm/src/ts_types.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
use libzkbob_rs::libzeropool::native::note::Note as NativeNote;
use fawkes_crypto::ff_uint::Num;
use libzkbob_rs::libzeropool::native::{
note::Note as NativeNote,
account::Account as NativeAccount,
};
use serde::{Deserialize, Serialize};
use wasm_bindgen::prelude::*;

Expand All @@ -21,11 +25,11 @@ export interface Note {
}
export interface Account {
eta: string;
d: string;
p_d: string;
i: string;
b: string;
e: string;
t: string;
}
export interface TransferPub {
Expand Down Expand Up @@ -173,6 +177,18 @@ export interface ParseTxsColdStorageResult {
decryptedLeafsCnt: number;
}
export interface TxMemoChunk {
index: number;
encrypted: Uint8Array;
key: Uint8Array;
}
export interface TxInput {
account: { index: number, account: Account };
intermediateNullifier: string
notes: { index: number, note: Note }[];
}
"#;

#[wasm_bindgen]
Expand Down Expand Up @@ -282,16 +298,36 @@ extern "C" {
#[wasm_bindgen(typescript_type = "ParseTxsColdStorageResult")]
pub type ParseTxsColdStorageResult;

#[wasm_bindgen(typescript_type = "TxMemoChunk")]
pub type TxMemoChunk;

#[wasm_bindgen(typescript_type = "TxInput")]
pub type TxInput;

#[wasm_bindgen(typescript_type = "IAddressComponents")]
pub type IAddressComponents;
}

#[derive(Serialize, Deserialize, Clone)]
pub struct IndexedAccount {
pub index: u64,
pub account: NativeAccount<Fr>,
}

#[derive(Serialize, Deserialize, Clone, Debug)]
pub struct IndexedNote {
pub index: u64,
pub note: NativeNote<Fr>,
}

#[derive(Serialize, Deserialize, Clone)]
pub struct TxInputNodes {
pub account: IndexedAccount,
#[serde(rename = "intermediateNullifier")]
pub intermediate_nullifier: Num<Fr>, // intermediate nullifier hash
pub notes: Vec<IndexedNote>,
}

#[derive(Serialize, Deserialize)]
pub struct IndexedTx {
pub index: u64,
Expand Down
4 changes: 2 additions & 2 deletions libzkbob-rs/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[package]
name = "libzkbob-rs"
description = "A higher level zkBob API"
version = "1.1.0"
version = "1.3.0"
authors = ["Dmitry Vdovin <voidxnull@gmail.com>"]
repository = "https://github.com/zkBob/libzkbob-rs/"
license = "MIT OR Apache-2.0"
Expand All @@ -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.1.0"
version = "1.2.0"
default-features = false
features = ["in3out127"]

Expand Down
1 change: 0 additions & 1 deletion libzkbob-rs/src/address.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ use std::convert::{TryInto};

use crate::pools::Pool;
use crate::{utils::keccak256, pools::{GENERIC_ADDRESS_PREFIX, POOL_ID_BITS}};
use libzeropool::fawkes_crypto::ff_uint::Uint;
use libzeropool::{
constants,
fawkes_crypto::{
Expand Down
Loading

0 comments on commit 3e3b0a4

Please sign in to comment.