Skip to content

Commit

Permalink
Merge branch 'develop' into enhancement/memo-format
Browse files Browse the repository at this point in the history
  • Loading branch information
EvgenKor committed Jan 10, 2024
2 parents a81a185 + 218887b commit 519efe6
Show file tree
Hide file tree
Showing 7 changed files with 159 additions and 307 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.5.0"
version = "1.6.0"
authors = ["Dmitry Vdovin <[email protected]>"]
repository = "https://github.com/zkBob/libzkbob-rs/"
license = "MIT OR Apache-2.0"
Expand Down
50 changes: 27 additions & 23 deletions libzkbob-rs-wasm/src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,6 @@ use libzkbob_rs::{
client::{TxType as NativeTxType, UserAccount as NativeUserAccount, StateFragment},
merkle::{Hash, Node},
address::{parse_address, parse_address_ext},
pools::Pool
};

use serde::Serialize;
Expand Down Expand Up @@ -64,28 +63,13 @@ 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, network: &str) -> Result<UserAccount, JsValue> {
crate::utils::set_panic_hook();

let pool = if network.to_lowercase() == "sepolia" && pool_id == Pool::SepoliaBOB.pool_id() {
// A workaround related with Sepolia pool_id issue
// (pool_id for Sepolia BOB pool is equal to Polygon BOB pool)
Ok(Pool::SepoliaBOB)
} else {
Pool::from_pool_id(pool_id)
.ok_or_else(|| js_err!("Unsupported pool with ID {}", pool_id))
}?;

UserAccount::create_internal(sk, pool, state)
}

fn create_internal(sk: &[u8], pool: Pool, state: UserState) -> Result<UserAccount, JsValue> {
pub fn new(sk: &[u8], pool_id: u32, state: UserState) -> Result<UserAccount, JsValue> {
crate::utils::set_panic_hook();

let sk = Num::<Fs>::from_uint(NumRepr(Uint::from_little_endian(sk)))
.ok_or_else(|| js_err!("Invalid spending key"))?;

let account = NativeUserAccount::new(sk, pool, state.inner, POOL_PARAMS.clone());
let account = NativeUserAccount::new(sk, pool_id, state.inner, POOL_PARAMS.clone());

Ok(UserAccount {
inner: Rc::new(RefCell::new(account)),
Expand All @@ -109,11 +93,21 @@ impl UserAccount {
self.inner.borrow().gen_address_for_seed(seed)
}

#[wasm_bindgen(js_name = "generateUniversalAddressForSeed")]
pub fn generate_universal_address_for_seed(&self, seed: &[u8]) -> String {
self.inner.borrow().gen_universal_address_for_seed(seed)
}

#[wasm_bindgen(js_name = "validateAddress")]
pub fn validate_address(&self, address: &str) -> bool {
self.inner.borrow().validate_address(address)
}

#[wasm_bindgen(js_name = "validateUniversalAddress")]
pub fn validate_universal_address(&self, address: &str) -> bool {
self.inner.borrow().validate_universal_address(address)
}

#[wasm_bindgen(js_name = "assembleAddress")]
pub fn assemble_address(&self, d: &str, p_d: &str) -> String {
let d = Num::from_str(d).unwrap();
Expand All @@ -123,18 +117,28 @@ impl UserAccount {
self.inner.borrow().generate_address_from_components(d, p_d)
}

#[wasm_bindgen(js_name = "assembleUniversalAddress")]
pub fn assemble_universal_address(&self, d: &str, p_d: &str) -> String {
let d = Num::from_str(d).unwrap();
let d = BoundedNum::new(d);
let p_d = Num::from_str(p_d).unwrap();

self.inner.borrow().generate_universal_address_from_components(d, p_d)
}

#[wasm_bindgen(js_name = "convertAddressToChainSpecific")]
pub fn convert_address_to_chain_specific(&self, address: &str) -> Result<String, JsValue> {
let (d, p_d, _) =
parse_address::<PoolParams>(address, &POOL_PARAMS).map_err(|err| js_err!(&err.to_string()))?;
parse_address::<PoolParams>(address, &POOL_PARAMS, self.inner.borrow().pool_id).map_err(|err| js_err!(&err.to_string()))?;

Ok(self.inner.borrow().generate_address_from_components(d, p_d))
}

#[wasm_bindgen(js_name = "parseAddress")]
pub fn parse_address(&self, address: &str) -> Result<IAddressComponents, JsValue> {
pub fn parse_address(&self, address: &str, pool_id: Option<u32>) -> Result<IAddressComponents, JsValue> {
let desired_pool_id = pool_id.unwrap_or(self.inner.borrow().pool_id);
let (d, p_d, pool, format, checksum) =
parse_address_ext::<PoolParams>(address, &POOL_PARAMS).map_err(|err| js_err!(&err.to_string()))?;
parse_address_ext::<PoolParams>(address, &POOL_PARAMS, desired_pool_id).map_err(|err| js_err!(&err.to_string()))?;

#[derive(Serialize)]
struct Address {
Expand All @@ -152,9 +156,9 @@ impl UserAccount {
d: d.to_num().to_string(),
p_d: p_d.to_string(),
checksum,
pool_id: if let Some(pool) = pool { format!("{}", pool.pool_id()) } else { "any".to_string() },
pool_id: if let Some(pool) = pool { format!("{}", pool) } else { "any".to_string() },
derived_from_our_sk: self.inner.borrow().is_derived_from_our_sk(d, p_d),
is_pool_valid: if let Some(pool) = pool { pool == self.inner.borrow().pool } else { true },
is_pool_valid: if let Some(pool) = pool { pool == self.inner.borrow().pool_id } else { true },
};

Ok(serde_wasm_bindgen::to_value(&address)
Expand Down
2 changes: 1 addition & 1 deletion 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.4.3"
version = "1.5.0"
authors = ["Dmitry Vdovin <[email protected]>"]
repository = "https://github.com/zkBob/libzkbob-rs/"
license = "MIT OR Apache-2.0"
Expand Down
122 changes: 53 additions & 69 deletions libzkbob-rs/src/address.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
use std::convert::{TryInto};
use std::convert::TryInto;

use crate::pools::Pool;
use crate::{utils::keccak256, pools::{GENERIC_ADDRESS_PREFIX, POOL_ID_BITS}};
const POOL_ID_BITS: usize = 24;
const POOL_ID_BYTES: usize = POOL_ID_BITS >> 3;

use crate::utils::keccak256;
use libzeropool::{
constants,
fawkes_crypto::{
borsh::{BorshDeserialize, BorshSerialize},
ff_uint::Num, native::ecc::EdwardsPoint,
ff_uint::{Num, Uint, NumRepr},

Check warning on line 11 in libzkbob-rs/src/address.rs

View workflow job for this annotation

GitHub Actions / clippy

unused imports: `NumRepr`, `Uint`

warning: unused imports: `NumRepr`, `Uint` --> libzkbob-rs/src/address.rs:11:24 | 11 | ff_uint::{Num, Uint, NumRepr}, | ^^^^ ^^^^^^^ | = note: `#[warn(unused_imports)]` on by default
native::ecc::EdwardsPoint,
},
native::boundednum::BoundedNum,
native::params::PoolParams,
Expand All @@ -31,16 +34,15 @@ pub enum AddressParseError {
DeserializationError(#[from] std::io::Error),
}

#[derive(PartialEq)]
pub enum AddressFormat {
Old,
PoolSpecific,
Generic,
}

impl AddressFormat {
pub fn name(&self) -> &str {
match self {
AddressFormat::Old => "old",
AddressFormat::PoolSpecific => "pool",
AddressFormat::Generic => "generic",
}
Expand All @@ -50,83 +52,64 @@ impl AddressFormat {
pub fn parse_address<P: PoolParams>(
address: &str,
params: &P,
pool_id: u32, // current pool id
) -> Result<
(
BoundedNum<P::Fr, { constants::DIVERSIFIER_SIZE_BITS }>, // d
Num<P::Fr>, // p_d
Option<Pool>, // None for generic addresses
AddressFormat,
),
AddressParseError,
>{
let (d, p_d, pool, _, _) = parse_address_ext(address, params)?;
Ok((d, p_d, pool))
let (d, p_d, _, format, _) = parse_address_ext(address, params, pool_id)?;
Ok((d, p_d, format))
}

pub fn parse_address_ext<P: PoolParams>(
address: &str,
params: &P,
pool_id: u32,
) -> Result<
(
BoundedNum<P::Fr, { constants::DIVERSIFIER_SIZE_BITS }>, // d
Num<P::Fr>, // p_d
Option<Pool>, // None for generic addresses
Option<u32>, // None for generic addresses (otherwse pool id)
AddressFormat,
[u8; 4], // checksum
),
AddressParseError,
>{
if address.find(':').is_some() {
// address with prefix
let addr_components: Vec<&str> = address.split(':').collect();
if addr_components.len() == 2 {
let pool = Pool::from_prefix(addr_components[0]);
// ignoring any prefixes, just try to validate checksum
let addr_components: Vec<&str> = address.split(':').filter(|s| !s.is_empty()).collect();
match addr_components.last() {
Some(addr) => {
// parse address
let (d,
p_d,
addr_hash,
checksum) = parse_address_raw(addr_components[1], params)?;

match pool {
Some(pool) => {
// pool-specific address
const POOL_ID_BYTES: usize = POOL_ID_BITS >> 3;
let mut hash_src: [u8; POOL_ID_BYTES + 32] = [0; POOL_ID_BYTES + 32];
pool.pool_id_bytes_be::<P::Fr>().serialize(& mut &mut hash_src[0..POOL_ID_BYTES]).unwrap();
hash_src[POOL_ID_BYTES..POOL_ID_BYTES + 32].clone_from_slice(&addr_hash);

if keccak256(&hash_src)[0..=3] != checksum {
return Err(AddressParseError::InvalidChecksum);
}
return Ok((d, p_d, Some(pool), AddressFormat::PoolSpecific, checksum));
},
None => {
if addr_components[0].to_lowercase() == GENERIC_ADDRESS_PREFIX {
// generic address
if addr_hash[0..=3] != checksum {
return Err(AddressParseError::InvalidChecksum);
}
return Ok((d, p_d, None, AddressFormat::Generic, checksum));
} else {
return Err(AddressParseError::InvalidPrefix(addr_components[0].to_string()))
}
},
};
}
checksum) = parse_address_raw(addr, params)?;

Err(AddressParseError::InvalidFormat)
} else {
// old format without prefix
let (d,
p_d,
addr_hash,
checksum) = parse_address_raw(address, params)?;

if addr_hash[0..=3] != checksum {
return Err(AddressParseError::InvalidChecksum);
}

// the old format should be acceptable on the Polygon BOB pool only
Ok((d, p_d, Some(Pool::PolygonUSDC), AddressFormat::Old, checksum))
if addr_hash[0..=3] != checksum {
// calcing checksum [pool-specific format]
let mut hash_src: [u8; POOL_ID_BYTES + 32] = [0; POOL_ID_BYTES + 32];
pool_id_to_bytes_be::<P>(pool_id).serialize(& mut &mut hash_src[0..POOL_ID_BYTES]).unwrap();
hash_src[POOL_ID_BYTES..POOL_ID_BYTES + 32].clone_from_slice(&addr_hash);

if keccak256(&hash_src)[0..=3] == checksum {
Ok((d, p_d, Some(pool_id), AddressFormat::PoolSpecific, checksum))
} else {
Err(AddressParseError::InvalidChecksum)
}
} else {
// generic format
Ok((d, p_d, None, AddressFormat::Generic, checksum))
}

},
None => Err(AddressParseError::InvalidFormat),
}

}

fn parse_address_raw<P: PoolParams>(
Expand Down Expand Up @@ -154,36 +137,37 @@ fn parse_address_raw<P: PoolParams>(
}
}

// generates shielded address in format "pool_prefix:base58(d ++ p_d ++ checksum)"
// generates shielded address in format "base58(d ++ p_d ++ checksum)"
// pool prefix doesn't append here
// both address types (pool-specific\generic) can generated here
pub fn format_address<P: PoolParams>(
d: BoundedNum<P::Fr, { constants::DIVERSIFIER_SIZE_BITS }>,
p_d: Num<P::Fr>,
pool: Option<Pool>, // set pool to None to generate universal address for all pools
pool_id: Option<u32>, // set pool_id to None to generate universal address for all pools
) -> String {
let mut buf: [u8; ADDR_LEN] = [0; ADDR_LEN];

d.serialize(&mut &mut buf[0..10]).unwrap();
p_d.serialize(&mut &mut buf[10..42]).unwrap();

// there are two ways for checksum calculation
let (checksum_hash, address_prefix) = match pool {
let checksum_hash = match pool_id {
// pool-specific format
Some(pool) => {
const POOL_ID_BYTES: usize = POOL_ID_BITS >> 3;
Some(pool_id) => {
let mut hash_src: [u8; POOL_ID_BYTES + 32] = [0; POOL_ID_BYTES + 32];
//let pool_id = pool.pool_id_num::<P::Fr>().to_num().to_uint().0.to_big_endian();
//let pool_id_be: [u8; POOL_ID_BYTES] = pool_id[32 - POOL_ID_BYTES..32].try_into().unwrap();
pool.pool_id_bytes_be::<P::Fr>().serialize(& mut &mut hash_src[0..POOL_ID_BYTES]).unwrap();
pool_id_to_bytes_be::<P>(pool_id).serialize(& mut &mut hash_src[0..POOL_ID_BYTES]).unwrap();
hash_src[POOL_ID_BYTES..POOL_ID_BYTES + 32].clone_from_slice(&keccak256(&buf[0..42]));
(keccak256(&hash_src), pool.address_prefix().to_owned())
keccak256(&hash_src)
},
// generic format (for all pools, when pool_id isn't specified)
None => (keccak256(&buf[0..42]), GENERIC_ADDRESS_PREFIX.to_string()),
None => keccak256(&buf[0..42]),
};
buf[42..ADDR_LEN].clone_from_slice(&checksum_hash[0..4]);

let address_part = bs58::encode(buf).into_string();

format!("{}:{}", address_prefix, address_part)
bs58::encode(buf).into_string()
}

fn pool_id_to_bytes_be<P: PoolParams>(pool_id: u32) -> [u8; POOL_ID_BYTES] {
// preparing pool id for checksum validation
pool_id.to_be_bytes()[4 - POOL_ID_BYTES..].try_into().unwrap()
}
Loading

0 comments on commit 519efe6

Please sign in to comment.