From 5d5dde33b776390a3f6eb74e9e745b3ae7635fc6 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Thu, 26 Jun 2025 07:21:31 +0300 Subject: [PATCH 1/5] fix: utxo nullifier calculation fix and circuits updates --- accounts/src/account_core/mod.rs | 18 ++ common/src/merkle_tree_public/merkle_tree.rs | 1 + sc_core/src/blob_utils.rs | 136 ++++++++ sc_core/src/cryptography.rs | 3 +- sc_core/src/lib.rs | 2 + sc_core/src/proofs_circuits.rs | 308 +++++++++++-------- sc_core/src/public_context.rs | 194 ++++++++++++ storage/src/sc_db_utils.rs | 178 ++++------- 8 files changed, 581 insertions(+), 259 deletions(-) create mode 100644 sc_core/src/blob_utils.rs create mode 100644 sc_core/src/public_context.rs diff --git a/accounts/src/account_core/mod.rs b/accounts/src/account_core/mod.rs index 192c158..079e725 100644 --- a/accounts/src/account_core/mod.rs +++ b/accounts/src/account_core/mod.rs @@ -93,6 +93,24 @@ pub struct AccountPublicMask { pub balance: u64, } +impl AccountPublicMask { + pub fn encrypt_data( + ephemeral_key_holder: &EphemeralKeyHolder, + viewing_public_key_receiver: AffinePoint, + data: &[u8], + ) -> (CipherText, Nonce) { + ephemeral_key_holder.encrypt_data(viewing_public_key_receiver, data) + } + + pub fn make_tag(&self) -> Tag { + self.address[0] + } + + pub fn produce_ephemeral_key_holder(&self) -> EphemeralKeyHolder { + EphemeralKeyHolder::new_os_random() + } +} + impl Account { pub fn new() -> Self { let key_holder = AddressKeyHolder::new_os_random(); diff --git a/common/src/merkle_tree_public/merkle_tree.rs b/common/src/merkle_tree_public/merkle_tree.rs index 3540b06..d8e99ec 100644 --- a/common/src/merkle_tree_public/merkle_tree.rs +++ b/common/src/merkle_tree_public/merkle_tree.rs @@ -11,6 +11,7 @@ use crate::{transaction::Transaction, utxo_commitment::UTXOCommitment}; use super::{hasher::OwnHasher, tree_leav_item::TreeLeavItem, TreeHashType}; +#[derive(Clone)] pub struct HashStorageMerkleTree { leaves: HashMap, hash_to_id_map: HashMap, diff --git a/sc_core/src/blob_utils.rs b/sc_core/src/blob_utils.rs new file mode 100644 index 0000000..d0a3485 --- /dev/null +++ b/sc_core/src/blob_utils.rs @@ -0,0 +1,136 @@ +use serde::Serialize; +use storage::{ + sc_db_utils::{produce_blob_from_fit_vec, DataBlob, DataBlobChangeVariant}, + SC_DATA_BLOB_SIZE, +}; + +///Creates blob list from generic serializable state +/// +///`ToDo`: Find a way to align data in a way, to minimize read and write operations in db +pub fn produce_blob_list_from_sc_public_state( + state: &S, +) -> Result, serde_json::Error> { + let mut blob_list = vec![]; + + let ser_data = serde_json::to_vec(state)?; + + //`ToDo` Replace with `next_chunk` usage, when feature stabilizes in Rust + for i in 0..=(ser_data.len() / SC_DATA_BLOB_SIZE) { + let next_chunk: Vec; + + if (i + 1) * SC_DATA_BLOB_SIZE < ser_data.len() { + next_chunk = ser_data[(i * SC_DATA_BLOB_SIZE)..((i + 1) * SC_DATA_BLOB_SIZE)] + .iter() + .cloned() + .collect(); + } else { + next_chunk = ser_data[(i * SC_DATA_BLOB_SIZE)..(ser_data.len())] + .iter() + .cloned() + .collect(); + } + + blob_list.push(produce_blob_from_fit_vec(next_chunk)); + } + + Ok(blob_list) +} + +///Compare two consecutive in time blob lists to produce list of modified ids +pub fn compare_blob_lists( + blob_list_old: &[DataBlob], + blob_list_new: &[DataBlob], +) -> Vec { + let mut changed_ids = vec![]; + let mut id_end = 0; + + let old_len = blob_list_old.len(); + let new_len = blob_list_new.len(); + + if old_len > new_len { + for id in new_len..old_len { + changed_ids.push(DataBlobChangeVariant::Deleted { id }); + } + } else if new_len > old_len { + for id in old_len..new_len { + changed_ids.push(DataBlobChangeVariant::Created { + id, + blob: blob_list_new[id], + }); + } + } + + loop { + let old_blob = blob_list_old.get(id_end); + let new_blob = blob_list_new.get(id_end); + + match (old_blob, new_blob) { + (Some(old), Some(new)) => { + if old != new { + changed_ids.push(DataBlobChangeVariant::Modified { + id: id_end, + blob_old: *old, + blob_new: *new, + }); + } + } + _ => break, + } + + id_end += 1; + } + + changed_ids +} + +#[cfg(test)] +mod tests { + use super::*; + use serde::Serialize; + + const TEST_BLOB_SIZE: usize = 256; // Define a test blob size for simplicity + static SC_DATA_BLOB_SIZE: usize = TEST_BLOB_SIZE; + + #[derive(Serialize)] + struct TestState { + a: u32, + b: u32, + } + + #[test] + fn test_produce_blob_list_from_sc_public_state() { + let state = TestState { a: 42, b: 99 }; + let result = produce_blob_list_from_sc_public_state(&state).unwrap(); + assert!(!result.is_empty()); + } + + #[test] + fn test_compare_blob_lists_created() { + let old_list: Vec = vec![]; + let new_list: Vec = vec![[1; SC_DATA_BLOB_SIZE].into()]; + + let changes = compare_blob_lists(&old_list, &new_list); + assert_eq!(changes.len(), 1); + assert!(matches!(changes[0], DataBlobChangeVariant::Created { .. })); + } + + #[test] + fn test_compare_blob_lists_deleted() { + let old_list: Vec = vec![[1; SC_DATA_BLOB_SIZE].into()]; + let new_list: Vec = vec![]; + + let changes = compare_blob_lists(&old_list, &new_list); + assert_eq!(changes.len(), 1); + assert!(matches!(changes[0], DataBlobChangeVariant::Deleted { .. })); + } + + #[test] + fn test_compare_blob_lists_modified() { + let old_list: Vec = vec![[1; SC_DATA_BLOB_SIZE].into()]; + let new_list: Vec = vec![[2; SC_DATA_BLOB_SIZE].into()]; + + let changes = compare_blob_lists(&old_list, &new_list); + assert_eq!(changes.len(), 1); + assert!(matches!(changes[0], DataBlobChangeVariant::Modified { .. })); + } +} diff --git a/sc_core/src/cryptography.rs b/sc_core/src/cryptography.rs index 23367c2..d93ce0e 100644 --- a/sc_core/src/cryptography.rs +++ b/sc_core/src/cryptography.rs @@ -1,8 +1,7 @@ use ark_bn254::Fr; use light_poseidon::{Poseidon, PoseidonBytesHasher}; -#[allow(unused)] -fn poseidon_hash(inputs: &[&[u8]]) -> anyhow::Result<[u8; 32]> { +pub fn poseidon_hash(inputs: &[&[u8]]) -> anyhow::Result<[u8; 32]> { let mut poseidon = Poseidon::::new_circom(2).unwrap(); let hash = poseidon.hash_bytes_be(inputs)?; diff --git a/sc_core/src/lib.rs b/sc_core/src/lib.rs index a41ab82..ed70205 100644 --- a/sc_core/src/lib.rs +++ b/sc_core/src/lib.rs @@ -1,3 +1,5 @@ +pub mod blob_utils; pub mod cryptography; pub mod proofs_circuits; +pub mod public_context; pub mod transaction_payloads_tools; diff --git a/sc_core/src/proofs_circuits.rs b/sc_core/src/proofs_circuits.rs index cbebe3e..03eea96 100644 --- a/sc_core/src/proofs_circuits.rs +++ b/sc_core/src/proofs_circuits.rs @@ -1,28 +1,42 @@ use bincode; -use k256::Scalar; +use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree; use rand::{thread_rng, RngCore}; use secp256k1_zkp::{CommitmentSecrets, Generator, PedersenCommitment, Tag, Tweak, SECP256K1}; use sha2::{Digest, Sha256}; +use std::collections::HashSet; use utxo::utxo_core::UTXO; +use crate::{cryptography::poseidon_hash, public_context::PublicSCContext}; + fn hash(input: &[u8]) -> Vec { Sha256::digest(input).to_vec() } -// Generate nullifiers - -// takes the input_utxo and nsk -// returns the nullifiers[i], where the nullifier[i] = hash(in_commitments[i] || nsk) where the hash function -pub fn generate_nullifiers(input_utxo: &UTXO, nsk: &[u8]) -> Vec { - let mut input = bincode::serialize(input_utxo).unwrap().to_vec(); - input.extend_from_slice(nsk); - hash(&input) +/// Generate nullifiers +/// +/// takes the input_utxo and npk +/// +/// returns the nullifiers[i], where the nullifiers[i] = poseidon_hash(in_commitments[i] || npk) +pub fn generate_nullifiers(input_utxo: &UTXO, npk: &[u8]) -> Vec { + let commitment = generate_commitment(input_utxo); + poseidon_hash(&[commitment.as_ref(), npk]).unwrap().to_vec() } -// Generate commitments for output UTXOs +/// Generate commitment for UTXO +/// +/// uses the input_utxo +/// +/// returns commitment here comminment is a hash(bincode(input_utxo)) +pub fn generate_commitment(input_utxo: &UTXO) -> Vec { + let serialized = bincode::serialize(input_utxo).unwrap(); // Serialize UTXO. + hash(&serialized) +} -// uses the list of input_utxos[] -// returns in_commitments[] where each in_commitments[i] = Commitment(in_utxos[i]) where the commitment +/// Generate commitments for UTXO +/// +/// uses the input_utxos +/// +/// returns commitments pub fn generate_commitments(input_utxos: &[UTXO]) -> Vec> { input_utxos .iter() @@ -33,60 +47,122 @@ pub fn generate_commitments(input_utxos: &[UTXO]) -> Vec> { .collect() } -// Validate inclusion proof for in_commitments - -// takes the in_commitments[i] as a leaf, the root hash root_commitment and the path in_commitments_proofs[i][], -// returns True if the in_commitments[i] is in the tree with root hash root_commitment otherwise returns False, as membership proof. +/// Validate inclusion proof for in_commitments +/// +/// ToDo: Solve it in more scalable way pub fn validate_in_commitments_proof( - _in_commitment: &Vec, - _root_commitment: Vec, - _in_commitments_proof: &[Vec], + in_commitment: &Vec, + commitment_tree: &UTXOCommitmentsMerkleTree, ) -> bool { - // ToDo: Implement correct check + let alighned_hash: [u8; 32] = in_commitment.clone().try_into().unwrap(); - todo!() + commitment_tree.get_proof(alighned_hash).is_some() } -// Validate that `nullifier` has not been present in set items before +/// Validate that `nullifier` has not been present in set items before pub fn validate_nullifier_not_present_in_set_items( nullifier: [u8; 32], - nullifiers_items: &[[u8; 32]], + nullifiers_set: &HashSet<[u8; 32]>, ) -> bool { - !nullifiers_items.contains(&nullifier) + !nullifiers_set.contains(&nullifier) } -#[allow(unused)] -fn private_kernel( - root_commitment: &[u8], - root_nullifier: [u8; 32], +/// Check, that input utxos balances is equal to out utxo balances +pub fn check_balances_private(in_utxos: &[UTXO], out_utxos: &[UTXO]) -> bool { + let in_sum = in_utxos.iter().fold(0, |prev, utxo| prev + utxo.amount); + let out_sum = out_utxos.iter().fold(0, |prev, utxo| prev + utxo.amount); + + in_sum == out_sum +} + +pub fn private_circuit( input_utxos: &[UTXO], - in_commitments_proof: &[Vec], - nullifiers_proof: &[[u8; 32]], - nullifier_secret_key: Scalar, -) -> (Vec, Vec>) { - let nullifiers: Vec<_> = input_utxos - .into_iter() - .map(|utxo| generate_nullifiers(&utxo, &nullifier_secret_key.to_bytes())) - .collect(); + output_utxos: &[UTXO], + public_context: &PublicSCContext, +) -> (Vec>, Vec>) { + assert!(check_balances_private(input_utxos, output_utxos)); let in_commitments = generate_commitments(&input_utxos); + let mut in_nullifiers = vec![]; + + for in_utxo in input_utxos { + let nullifier_public_key = public_context + .account_masks + .get(&in_utxo.owner) + .unwrap() + .nullifier_public_key; + + let key_ser = serde_json::to_vec(&nullifier_public_key).unwrap(); + + in_nullifiers.push(generate_nullifiers(in_utxo, &key_ser)); + } + for in_commitment in in_commitments { - validate_in_commitments_proof( + assert!(validate_in_commitments_proof( &in_commitment, - root_commitment.to_vec(), - in_commitments_proof, - ); + &public_context.commitments_tree, + )); } - for nullifier in nullifiers.iter() { - validate_nullifier_not_present_in_set_items( - nullifier[0..32].try_into().unwrap(), - nullifiers_proof, - ); + for nullifier in in_nullifiers.iter() { + assert!(validate_nullifier_not_present_in_set_items( + nullifier.clone().try_into().unwrap(), + &public_context.nullifiers_set, + )); } - (vec![], nullifiers) + (in_nullifiers, generate_commitments(&output_utxos)) +} + +/// Check balances DE +/// +/// takes the input_utxos[] and output_balance, +/// +/// returns the True if the token amount in output_balance matches the sum of all input_utxos[], otherwise return False. +pub fn check_balances_de(input_utxos: &[UTXO], output_balance: u128) -> bool { + let total_input: u128 = input_utxos.iter().map(|utxo| utxo.amount).sum(); + total_input == output_balance +} + +pub fn deshielded_circuit( + input_utxos: &[UTXO], + output_balance: u128, + public_context: &PublicSCContext, +) -> Vec> { + assert!(check_balances_de(input_utxos, output_balance)); + + let in_commitments = generate_commitments(&input_utxos); + + let mut in_nullifiers = vec![]; + + for in_utxo in input_utxos { + let nullifier_public_key = public_context + .account_masks + .get(&in_utxo.owner) + .unwrap() + .nullifier_public_key; + + let key_ser = serde_json::to_vec(&nullifier_public_key).unwrap(); + + in_nullifiers.push(generate_nullifiers(in_utxo, &key_ser)); + } + + for in_commitment in in_commitments { + assert!(validate_in_commitments_proof( + &in_commitment, + &public_context.commitments_tree, + )); + } + + for nullifier in in_nullifiers.iter() { + assert!(validate_nullifier_not_present_in_set_items( + nullifier.clone().try_into().unwrap(), + &public_context.nullifiers_set, + )); + } + + in_nullifiers } #[allow(unused)] @@ -114,16 +190,7 @@ pub fn commit(comm: &CommitmentSecrets, tag: Tag) -> PedersenCommitment { PedersenCommitment::new(SECP256K1, comm.value, comm.value_blinding_factor, generator) } -// Check balances - -// takes the public_info and output_utxos[], -// returns the True if the token amount in public_info matches the sum of all output_utxos[], otherwise return False. -pub fn check_balances(public_info: u128, output_utxos: &[UTXO]) -> bool { - let total_output: u128 = output_utxos.iter().map(|utxo| utxo.amount).sum(); - public_info == total_output -} - -// new_commitment for a Vec of values +/// new_commitment for a Vec of values pub fn pedersen_commitment_vec( public_info_vec: Vec, ) -> (Tweak, [u8; 32], Vec) { @@ -149,10 +216,11 @@ pub fn pedersen_commitment_vec( (generator_blinding_factor, random_val, vec_commitments) } -// Verify Pedersen commitment - -// takes the public_info, secret_r and pedersen_commitment and -// checks that commitment(public_info,secret_r) is equal pedersen_commitment where the commitment is pedersen commitment. +/// Verify Pedersen commitment +/// +/// takes the public_info, secret_r and pedersen_commitment and +/// +/// checks that commitment(public_info,secret_r) is equal pedersen_commitment where the commitment is pedersen commitment. pub fn verify_commitment( public_info: u64, secret_r: &[u8], @@ -170,95 +238,69 @@ pub fn verify_commitment( commitment == *pedersen_commitment } -#[allow(unused)] -fn de_kernel( - root_commitment: &[u8], - root_nullifier: [u8; 32], - public_info: u64, - input_utxos: &[UTXO], - in_commitments_proof: &[Vec], - nullifiers_proof: &[[u8; 32]], - nullifier_secret_key: Scalar, -) -> (Vec, Vec>) { - check_balances(public_info as u128, input_utxos); - - let nullifiers: Vec<_> = input_utxos - .into_iter() - .map(|utxo| generate_nullifiers(&utxo, &nullifier_secret_key.to_bytes())) - .collect(); - - let in_commitments = generate_commitments(&input_utxos); - - for in_commitment in in_commitments { - validate_in_commitments_proof( - &in_commitment, - root_commitment.to_vec(), - in_commitments_proof, - ); - } - - for nullifier in nullifiers.iter() { - validate_nullifier_not_present_in_set_items( - nullifier[0..32].try_into().unwrap(), - nullifiers_proof, - ); - } - - (vec![], nullifiers) -} - -// Validate inclusion proof for in_commitments - -// takes the pedersen_commitment as a leaf, the root hash root_commitment and the path in_commitments_proof[], -// returns True if the pedersen_commitment is in the tree with root hash root_commitment -// otherwise -// returns False, as membership proof. +/// Validate inclusion proof for pedersen_commitment +/// +/// ToDo: Solve it in more scalable way pub fn validate_in_commitments_proof_se( - _pedersen_commitment: &PedersenCommitment, - _root_commitment: Vec, - _in_commitments_proof: &[Vec], + pedersen_commitment: &PedersenCommitment, + commitment_tree: &UTXOCommitmentsMerkleTree, ) -> bool { - // ToDo: Implement correct check + let alighned_hash: [u8; 32] = pedersen_commitment.serialize()[0..32].try_into().unwrap(); - todo!() + commitment_tree.get_proof(alighned_hash).is_some() } -// Generate nullifiers SE +/// Generate nullifier SE +/// +/// takes the pedersen_commitment and npk then +/// returns a nullifier, where the nullifier = poseidon_hash(pedersen_commitment || npk) +pub fn generate_nullifiers_se(pedersen_commitment: &PedersenCommitment, npk: &[u8]) -> Vec { + let commitment_ser = pedersen_commitment.serialize().to_vec(); -// takes the pedersen_commitment and nsk then -// returns a list of nullifiers, where the nullifier = hash(pedersen_commitment || nsk) where the hash function will be determined - -pub fn generate_nullifiers_se(pedersen_commitment: &PedersenCommitment, nsk: &[u8]) -> Vec { - let mut input = pedersen_commitment.serialize().to_vec(); - input.extend_from_slice(nsk); - hash(&input) + poseidon_hash(&[&commitment_ser, npk]).unwrap().to_vec() } -#[allow(unused)] -fn se_kernel( - root_commitment: &[u8], - root_nullifier: [u8; 32], +/// Check balances SE +/// +/// takes the input_balance and output_utxos[], +/// +/// returns the True if the token amount in input_balance matches the sum of all output_utxos[], otherwise return False. +pub fn check_balances_se(input_balance: u128, output_utxos: &[UTXO]) -> bool { + let total_output: u128 = output_utxos.iter().map(|utxo| utxo.amount).sum(); + total_output == input_balance +} + +pub fn shielded_circuit( public_info: u64, + output_utxos: &[UTXO], pedersen_commitment: PedersenCommitment, secret_r: &[u8], - output_utxos: &[UTXO], - in_commitments_proof: &[Vec], - nullifiers_proof: &[[u8; 32]], - nullifier_secret_key: Scalar, -) -> (Vec, Vec>, Vec) { - check_balances(public_info as u128, output_utxos); + public_context: &PublicSCContext, +) -> (Vec>, Vec) { + assert!(check_balances_se(public_info as u128, output_utxos)); let out_commitments = generate_commitments(output_utxos); - let nullifier = generate_nullifiers_se(&pedersen_commitment, &nullifier_secret_key.to_bytes()); + let nullifier_public_key = public_context + .account_masks + .get(&public_context.caller_address) + .unwrap() + .nullifier_public_key; - validate_in_commitments_proof_se( + let key_ser = serde_json::to_vec(&nullifier_public_key).unwrap(); + + let nullifier = generate_nullifiers_se(&pedersen_commitment, &key_ser); + + assert!(validate_in_commitments_proof_se( &pedersen_commitment, - root_commitment.to_vec(), - in_commitments_proof, - ); + &public_context.commitments_tree, + )); - verify_commitment(public_info, secret_r, &pedersen_commitment); + assert!(verify_commitment( + public_info, + secret_r, + &pedersen_commitment + )); - (vec![], out_commitments, nullifier) + (out_commitments, nullifier) } diff --git a/sc_core/src/public_context.rs b/sc_core/src/public_context.rs new file mode 100644 index 0000000..9694fbc --- /dev/null +++ b/sc_core/src/public_context.rs @@ -0,0 +1,194 @@ +use std::collections::{BTreeMap, HashSet}; + +use accounts::account_core::{AccountAddress, AccountPublicMask}; +use common::merkle_tree_public::{merkle_tree::UTXOCommitmentsMerkleTree, TreeHashType}; +use serde::{ser::SerializeStruct, Serialize}; + +pub const PUBLIC_SC_CONTEXT: &str = "PublicSCContext"; +pub const CALLER_ADDRESS: &str = "caller_address"; +pub const CALLER_BALANCE: &str = "caller_balance"; +pub const ACCOUNT_MASKS_KEYS_SORTED: &str = "account_masks_keys_sorted"; +pub const ACCOUNT_MASKS_VALUES_SORTED: &str = "account_masks_values_sorted"; +pub const COMMITMENT_STORE_ROOT: &str = "commitment_store_root"; +pub const PUT_TX_STORE_ROOT: &str = "put_tx_store_root"; +pub const COMMITMENT_TREE: &str = "commitments_tree"; +pub const NULLIFIERS_SET: &str = "nullifiers_set"; + +///Strucutre, representing context, given to a smart contract on a call +pub struct PublicSCContext { + pub caller_address: AccountAddress, + pub caller_balance: u64, + pub account_masks: BTreeMap, + pub comitment_store_root: TreeHashType, + pub pub_tx_store_root: TreeHashType, + pub commitments_tree: UTXOCommitmentsMerkleTree, + pub nullifiers_set: HashSet<[u8; 32]>, +} + +impl Serialize for PublicSCContext { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + let mut account_masks_keys: Vec<[u8; 32]> = self.account_masks.keys().cloned().collect(); + account_masks_keys.sort(); + + let mut account_mask_values: Vec = + self.account_masks.values().cloned().collect(); + account_mask_values.sort_by(|left, right| left.address.cmp(&right.address)); + + let mut s = serializer.serialize_struct(PUBLIC_SC_CONTEXT, 7)?; + + s.serialize_field(CALLER_ADDRESS, &self.caller_address)?; + s.serialize_field(CALLER_BALANCE, &self.caller_balance)?; + s.serialize_field(ACCOUNT_MASKS_KEYS_SORTED, &account_masks_keys)?; + s.serialize_field(ACCOUNT_MASKS_VALUES_SORTED, &account_mask_values)?; + s.serialize_field(COMMITMENT_STORE_ROOT, &self.comitment_store_root)?; + s.serialize_field(PUT_TX_STORE_ROOT, &self.pub_tx_store_root)?; + s.serialize_field(COMMITMENT_TREE, &self.commitments_tree)?; + s.serialize_field(NULLIFIERS_SET, &self.nullifiers_set)?; + + s.end() + } +} + +impl PublicSCContext { + ///Produces `u64` from bytes in a vector + /// + /// Assumes, that vector of le_bytes + pub fn produce_u64_from_fit_vec(data: Vec) -> u64 { + let data_len = data.len(); + + assert!(data_len <= 8); + let mut le_bytes: [u8; 8] = [0; 8]; + + for (idx, item) in data.into_iter().enumerate() { + le_bytes[idx] = item + } + + u64::from_le_bytes(le_bytes) + } + + ///Produces vector of `u64` from context + pub fn produce_u64_list_from_context(&self) -> Result, serde_json::Error> { + let mut u64_list = vec![]; + + let ser_data = serde_json::to_vec(self)?; + + //`ToDo` Replace with `next_chunk` usage, when feature stabilizes in Rust + for i in 0..=(ser_data.len() / 8) { + let next_chunk: Vec; + + if (i + 1) * 8 < ser_data.len() { + next_chunk = ser_data[(i * 8)..((i + 1) * 8)].iter().cloned().collect(); + } else { + next_chunk = ser_data[(i * 8)..(ser_data.len())] + .iter() + .cloned() + .collect(); + } + + u64_list.push(PublicSCContext::produce_u64_from_fit_vec(next_chunk)); + } + + Ok(u64_list) + } +} + +#[cfg(test)] +mod tests { + use accounts::account_core::Account; + use common::utxo_commitment::UTXOCommitment; + + use super::*; + + fn create_test_context() -> PublicSCContext { + let caller_address = [1; 32]; + let comitment_store_root = [3; 32]; + let pub_tx_store_root = [4; 32]; + + let commitments_tree = + UTXOCommitmentsMerkleTree::new(vec![UTXOCommitment { hash: [5; 32] }]); + let mut nullifiers_set = HashSet::new(); + nullifiers_set.insert([6; 32]); + + let mut account_masks = BTreeMap::new(); + + let acc_1 = Account::new(); + let acc_2 = Account::new(); + let acc_3 = Account::new(); + + account_masks.insert(acc_1.address, acc_1.make_account_public_mask()); + account_masks.insert(acc_2.address, acc_2.make_account_public_mask()); + account_masks.insert(acc_3.address, acc_3.make_account_public_mask()); + + PublicSCContext { + caller_address, + caller_balance: 100, + account_masks, + comitment_store_root, + pub_tx_store_root, + commitments_tree, + nullifiers_set, + } + } + + #[test] + fn bin_ser_stability_test() { + let test_context = create_test_context(); + + let serialization_1 = serde_json::to_vec(&test_context).unwrap(); + let serialization_2 = serde_json::to_vec(&test_context).unwrap(); + + assert_eq!(serialization_1, serialization_2); + } + + #[test] + fn correct_u64_production_from_fit_vec() { + let le_vec = vec![1, 1, 1, 1, 2, 1, 1, 1]; + + let num = PublicSCContext::produce_u64_from_fit_vec(le_vec); + + assert_eq!(num, 72340177133043969); + } + + #[test] + fn correct_u64_production_from_small_vec() { + //7 items instead of 8 + let le_vec = vec![1, 1, 1, 1, 2, 1, 1]; + + let num = PublicSCContext::produce_u64_from_fit_vec(le_vec); + + assert_eq!(num, 282583095116033); + } + + #[test] + fn correct_u64_production_from_small_vec_le_bytes() { + //7 items instead of 8 + let le_vec = vec![1, 1, 1, 1, 2, 1, 1]; + let le_vec_res = [1, 1, 1, 1, 2, 1, 1, 0]; + + let num = PublicSCContext::produce_u64_from_fit_vec(le_vec); + + assert_eq!(num.to_le_bytes(), le_vec_res); + } + + #[test] + #[should_panic] + fn correct_u64_production_from_unfit_vec_should_panic() { + //9 items instead of 8 + let le_vec = vec![1, 1, 1, 1, 2, 1, 1, 1, 1]; + + PublicSCContext::produce_u64_from_fit_vec(le_vec); + } + + #[test] + fn consistent_len_of_context_commitments() { + let test_context = create_test_context(); + + let context_num_vec1 = test_context.produce_u64_list_from_context().unwrap(); + let context_num_vec2 = test_context.produce_u64_list_from_context().unwrap(); + + assert_eq!(context_num_vec1.len(), context_num_vec2.len()); + } +} diff --git a/storage/src/sc_db_utils.rs b/storage/src/sc_db_utils.rs index b059de7..3ef12c5 100644 --- a/storage/src/sc_db_utils.rs +++ b/storage/src/sc_db_utils.rs @@ -49,7 +49,7 @@ impl DataBlob { } } -#[derive(Debug, Clone, Copy, Serialize, Deserialize)] +#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)] pub enum DataBlobChangeVariant { Created { id: usize, @@ -83,93 +83,66 @@ pub fn produce_blob_from_fit_vec(data: Vec) -> DataBlob { blob } -///Creates blob list from generic serializable state -/// -///`ToDo`: Find a way to align data in a way, to minimize read and write operations in db -pub fn produce_blob_list_from_sc_public_state( - state: &S, -) -> Result, serde_json::Error> { - let mut blob_list = vec![]; - - let ser_data = serde_json::to_vec(state)?; - - //`ToDo` Replace with `next_chunk` usage, when feature stabilizes in Rust - for i in 0..=(ser_data.len() / SC_DATA_BLOB_SIZE) { - let next_chunk: Vec; - - if (i + 1) * SC_DATA_BLOB_SIZE < ser_data.len() { - next_chunk = ser_data[(i * SC_DATA_BLOB_SIZE)..((i + 1) * SC_DATA_BLOB_SIZE)] - .iter() - .cloned() - .collect(); - } else { - next_chunk = ser_data[(i * SC_DATA_BLOB_SIZE)..(ser_data.len())] - .iter() - .cloned() - .collect(); - } - - blob_list.push(produce_blob_from_fit_vec(next_chunk)); - } - - Ok(blob_list) -} - -///Compare two consecutive in time blob lists to produce list of modified ids -pub fn compare_blob_lists( - blob_list_old: &[DataBlob], - blob_list_new: &[DataBlob], -) -> Vec { - let mut changed_ids = vec![]; - let mut id_end = 0; - - let old_len = blob_list_old.len(); - let new_len = blob_list_new.len(); - - if old_len > new_len { - for id in new_len..old_len { - changed_ids.push(DataBlobChangeVariant::Deleted { id }); - } - } else if new_len > old_len { - for id in old_len..new_len { - changed_ids.push(DataBlobChangeVariant::Created { - id, - blob: blob_list_new[id], - }); - } - } - - loop { - let old_blob = blob_list_old.get(id_end); - let new_blob = blob_list_new.get(id_end); - - match (old_blob, new_blob) { - (Some(old), Some(new)) => { - if old != new { - changed_ids.push(DataBlobChangeVariant::Modified { - id: id_end, - blob_old: *old, - blob_new: *new, - }); - } - } - _ => break, - } - - id_end += 1; - } - - changed_ids -} - #[cfg(test)] mod tests { use super::*; - use serde::Serialize; + use serde_json; const TEST_BLOB_SIZE: usize = 256; // Define a test blob size for simplicity static SC_DATA_BLOB_SIZE: usize = TEST_BLOB_SIZE; + fn sample_vec() -> Vec { + (0..SC_DATA_BLOB_SIZE) + .collect::>() + .iter() + .map(|&x| x as u8) + .collect() + } + + fn sample_data_blob() -> DataBlob { + let vec: Vec = sample_vec(); + produce_blob_from_fit_vec(vec) + } + + #[test] + fn test_serialize_data_blob() { + let blob = sample_data_blob(); + let json = serde_json::to_string(&blob).unwrap(); + + let expected_json = serde_json::to_string(&sample_vec()).unwrap(); + assert_eq!(json, expected_json); + } + + #[test] + fn test_deserialize_data_blob() { + let data = sample_vec(); + let json = serde_json::to_string(&data).unwrap(); + let deserialized: DataBlob = serde_json::from_str(&json).unwrap(); + assert_eq!(deserialized.to_vec(), data); + } + + #[test] + fn test_serialize_deserialize_data_blob_change_variant() { + let blob1 = sample_data_blob(); + let blob2 = produce_blob_from_fit_vec((50..50 + SC_DATA_BLOB_SIZE as u8).collect()); + + let variants = vec![ + DataBlobChangeVariant::Created { id: 1, blob: blob1 }, + DataBlobChangeVariant::Modified { + id: 2, + blob_old: blob1, + blob_new: blob2, + }, + DataBlobChangeVariant::Deleted { id: 3 }, + ]; + + for variant in variants { + let json = serde_json::to_string(&variant).unwrap(); + let deserialized: DataBlobChangeVariant = serde_json::from_str(&json).unwrap(); + assert_eq!(variant, deserialized); + } + } + #[test] fn test_produce_blob_from_fit_vec() { let data = (0..0 + 255).collect(); @@ -183,47 +156,4 @@ mod tests { let data = vec![0; SC_DATA_BLOB_SIZE + 1]; let _ = produce_blob_from_fit_vec(data); } - - #[derive(Serialize)] - struct TestState { - a: u32, - b: u32, - } - - #[test] - fn test_produce_blob_list_from_sc_public_state() { - let state = TestState { a: 42, b: 99 }; - let result = produce_blob_list_from_sc_public_state(&state).unwrap(); - assert!(!result.is_empty()); - } - - #[test] - fn test_compare_blob_lists_created() { - let old_list: Vec = vec![]; - let new_list: Vec = vec![[1; SC_DATA_BLOB_SIZE].into()]; - - let changes = compare_blob_lists(&old_list, &new_list); - assert_eq!(changes.len(), 1); - assert!(matches!(changes[0], DataBlobChangeVariant::Created { .. })); - } - - #[test] - fn test_compare_blob_lists_deleted() { - let old_list: Vec = vec![[1; SC_DATA_BLOB_SIZE].into()]; - let new_list: Vec = vec![]; - - let changes = compare_blob_lists(&old_list, &new_list); - assert_eq!(changes.len(), 1); - assert!(matches!(changes[0], DataBlobChangeVariant::Deleted { .. })); - } - - #[test] - fn test_compare_blob_lists_modified() { - let old_list: Vec = vec![[1; SC_DATA_BLOB_SIZE].into()]; - let new_list: Vec = vec![[2; SC_DATA_BLOB_SIZE].into()]; - - let changes = compare_blob_lists(&old_list, &new_list); - assert_eq!(changes.len(), 1); - assert!(matches!(changes[0], DataBlobChangeVariant::Modified { .. })); - } } From 90205c6330eaba881bb49dbd1b846bbe4b7a6e71 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Tue, 15 Jul 2025 12:49:19 +0300 Subject: [PATCH 2/5] fix: account masks updates to safe state --- accounts/src/account_core/mod.rs | 22 ++- accounts/src/key_management/mod.rs | 14 +- node_core/src/chain_storage/mod.rs | 9 +- node_core/src/chain_storage/public_context.rs | 180 ------------------ node_core/src/lib.rs | 17 +- sc_core/src/transaction_payloads_tools.rs | 4 +- 6 files changed, 37 insertions(+), 209 deletions(-) delete mode 100644 node_core/src/chain_storage/public_context.rs diff --git a/accounts/src/account_core/mod.rs b/accounts/src/account_core/mod.rs index 079e725..b1b4a91 100644 --- a/accounts/src/account_core/mod.rs +++ b/accounts/src/account_core/mod.rs @@ -85,6 +85,8 @@ impl<'de> Deserialize<'de> for Account { ///A strucure, which represents all the visible(public) information /// /// known to each node about account `address` +/// +/// Main usage is to encode data for other account #[derive(Serialize, Clone)] pub struct AccountPublicMask { pub nullifier_public_key: AffinePoint, @@ -99,16 +101,13 @@ impl AccountPublicMask { viewing_public_key_receiver: AffinePoint, data: &[u8], ) -> (CipherText, Nonce) { - ephemeral_key_holder.encrypt_data(viewing_public_key_receiver, data) + //Using of parent Account fuction + Account::encrypt_data(ephemeral_key_holder, viewing_public_key_receiver, data) } pub fn make_tag(&self) -> Tag { self.address[0] } - - pub fn produce_ephemeral_key_holder(&self) -> EphemeralKeyHolder { - EphemeralKeyHolder::new_os_random() - } } impl Account { @@ -139,10 +138,6 @@ impl Account { } } - pub fn produce_ephemeral_key_holder(&self) -> EphemeralKeyHolder { - self.key_holder.produce_ephemeral_key_holder() - } - pub fn encrypt_data( ephemeral_key_holder: &EphemeralKeyHolder, viewing_public_key_receiver: AffinePoint, @@ -267,4 +262,13 @@ mod tests { assert!(result.is_ok()); assert_eq!(account.utxos.len(), 1); } + + #[test] + fn accounts_accounts_mask_tag_consistency() { + let account = Account::new(); + + let account_mask = account.make_account_public_mask(); + + assert_eq!(account.make_tag(), account_mask.make_tag()); + } } diff --git a/accounts/src/key_management/mod.rs b/accounts/src/key_management/mod.rs index 69b8ce0..24ac045 100644 --- a/accounts/src/key_management/mod.rs +++ b/accounts/src/key_management/mod.rs @@ -2,7 +2,6 @@ use aes_gcm::{aead::Aead, Aes256Gcm, KeyInit}; use common::merkle_tree_public::TreeHashType; use constants_types::{CipherText, Nonce}; use elliptic_curve::point::AffineCoordinates; -use ephemeral_key_holder::EphemeralKeyHolder; use k256::AffinePoint; use log::info; use secret_holders::{SeedHolder, TopSecretKeyHolder, UTXOSecretKeyHolder}; @@ -55,10 +54,6 @@ impl AddressKeyHolder { (ephemeral_public_key_sender * self.utxo_secret_key_holder.viewing_secret_key).into() } - pub fn produce_ephemeral_key_holder(&self) -> EphemeralKeyHolder { - EphemeralKeyHolder::new_os_random() - } - pub fn decrypt_data( &self, ephemeral_public_key_sender: AffinePoint, @@ -114,6 +109,8 @@ mod tests { use elliptic_curve::point::AffineCoordinates; use k256::{AffinePoint, ProjectivePoint, Scalar}; + use crate::key_management::ephemeral_key_holder::EphemeralKeyHolder; + use super::*; #[test] @@ -136,7 +133,7 @@ mod tests { // Generate a random ephemeral public key sender let scalar = Scalar::random(&mut OsRng); - let ephemeral_public_key_sender = (ProjectivePoint::generator() * scalar).to_affine(); + let ephemeral_public_key_sender = (ProjectivePoint::GENERATOR * scalar).to_affine(); // Calculate shared secret let shared_secret = @@ -151,9 +148,8 @@ mod tests { let address_key_holder = AddressKeyHolder::new_os_random(); // Generate an ephemeral key and shared secret - let ephemeral_public_key_sender = address_key_holder - .produce_ephemeral_key_holder() - .generate_ephemeral_public_key(); + let ephemeral_public_key_sender = + EphemeralKeyHolder::new_os_random().generate_ephemeral_public_key(); let shared_secret = address_key_holder.calculate_shared_secret_receiver(ephemeral_public_key_sender); diff --git a/node_core/src/chain_storage/mod.rs b/node_core/src/chain_storage/mod.rs index f6b1343..d8e8065 100644 --- a/node_core/src/chain_storage/mod.rs +++ b/node_core/src/chain_storage/mod.rs @@ -11,7 +11,7 @@ use common::{ }; use k256::AffinePoint; use log::{info, warn}; -use public_context::PublicSCContext; +use sc_core::public_context::PublicSCContext; use serde::{Deserialize, Serialize}; use utxo::utxo_core::UTXO; @@ -19,7 +19,6 @@ use crate::{config::NodeConfig, ActionData}; pub mod accounts_store; pub mod block_store; -pub mod public_context; #[derive(Deserialize, Serialize)] pub struct AccMap { @@ -267,6 +266,12 @@ impl NodeChainStore { account_masks, comitment_store_root: self.utxo_commitments_store.get_root().unwrap_or([0; 32]), pub_tx_store_root: self.pub_tx_store.get_root().unwrap_or([0; 32]), + nullifiers_set: self + .nullifier_store + .iter() + .map(|item| item.utxo_hash) + .collect(), + commitments_tree: self.utxo_commitments_store.clone(), } } } diff --git a/node_core/src/chain_storage/public_context.rs b/node_core/src/chain_storage/public_context.rs deleted file mode 100644 index 438fa50..0000000 --- a/node_core/src/chain_storage/public_context.rs +++ /dev/null @@ -1,180 +0,0 @@ -use std::collections::BTreeMap; - -use accounts::account_core::{AccountAddress, AccountPublicMask}; -use common::merkle_tree_public::TreeHashType; -use serde::{ser::SerializeStruct, Serialize}; - -pub const PUBLIC_SC_CONTEXT: &str = "PublicSCContext"; -pub const CALLER_ADDRESS: &str = "caller_address"; -pub const CALLER_BALANCE: &str = "caller_balance"; -pub const ACCOUNT_MASKS_KEYS_SORTED: &str = "account_masks_keys_sorted"; -pub const ACCOUNT_MASKS_VALUES_SORTED: &str = "account_masks_values_sorted"; -pub const COMMITMENT_STORE_ROOT: &str = "commitment_store_root"; -pub const PUT_TX_STORE_ROOT: &str = "put_tx_store_root"; - -///Strucutre, representing context, given to a smart contract on a call -pub struct PublicSCContext { - pub caller_address: AccountAddress, - pub caller_balance: u64, - pub account_masks: BTreeMap, - pub comitment_store_root: TreeHashType, - pub pub_tx_store_root: TreeHashType, -} - -impl Serialize for PublicSCContext { - fn serialize(&self, serializer: S) -> Result - where - S: serde::Serializer, - { - let mut account_masks_keys: Vec<[u8; 32]> = self.account_masks.keys().cloned().collect(); - account_masks_keys.sort(); - - let mut account_mask_values: Vec = - self.account_masks.values().cloned().collect(); - account_mask_values.sort_by(|left, right| left.address.cmp(&right.address)); - - let mut s = serializer.serialize_struct(PUBLIC_SC_CONTEXT, 7)?; - - s.serialize_field(CALLER_ADDRESS, &self.caller_address)?; - s.serialize_field(CALLER_BALANCE, &self.caller_balance)?; - s.serialize_field(ACCOUNT_MASKS_KEYS_SORTED, &account_masks_keys)?; - s.serialize_field(ACCOUNT_MASKS_VALUES_SORTED, &account_mask_values)?; - s.serialize_field(COMMITMENT_STORE_ROOT, &self.comitment_store_root)?; - s.serialize_field(PUT_TX_STORE_ROOT, &self.pub_tx_store_root)?; - - s.end() - } -} - -impl PublicSCContext { - ///Produces `u64` from bytes in a vector - /// - /// Assumes, that vector of le_bytes - pub fn produce_u64_from_fit_vec(data: Vec) -> u64 { - let data_len = data.len(); - - assert!(data_len <= 8); - let mut le_bytes: [u8; 8] = [0; 8]; - - for (idx, item) in data.into_iter().enumerate() { - le_bytes[idx] = item - } - - u64::from_le_bytes(le_bytes) - } - - ///Produces vector of `u64` from context - pub fn produce_u64_list_from_context(&self) -> Result, serde_json::Error> { - let mut u64_list = vec![]; - - let ser_data = serde_json::to_vec(self)?; - - //`ToDo` Replace with `next_chunk` usage, when feature stabilizes in Rust - for i in 0..=(ser_data.len() / 8) { - let next_chunk: Vec; - - if (i + 1) * 8 < ser_data.len() { - next_chunk = ser_data[(i * 8)..((i + 1) * 8)].iter().cloned().collect(); - } else { - next_chunk = ser_data[(i * 8)..(ser_data.len())] - .iter() - .cloned() - .collect(); - } - - u64_list.push(PublicSCContext::produce_u64_from_fit_vec(next_chunk)); - } - - Ok(u64_list) - } -} - -#[cfg(test)] -mod tests { - use accounts::account_core::Account; - - use super::*; - - fn create_test_context() -> PublicSCContext { - let caller_address = [1; 32]; - let comitment_store_root = [3; 32]; - let pub_tx_store_root = [4; 32]; - - let mut account_masks = BTreeMap::new(); - - let acc_1 = Account::new(); - let acc_2 = Account::new(); - let acc_3 = Account::new(); - - account_masks.insert(acc_1.address, acc_1.make_account_public_mask()); - account_masks.insert(acc_2.address, acc_2.make_account_public_mask()); - account_masks.insert(acc_3.address, acc_3.make_account_public_mask()); - - PublicSCContext { - caller_address, - caller_balance: 100, - account_masks, - comitment_store_root, - pub_tx_store_root, - } - } - - #[test] - fn bin_ser_stability_test() { - let test_context = create_test_context(); - - let serialization_1 = serde_json::to_vec(&test_context).unwrap(); - let serialization_2 = serde_json::to_vec(&test_context).unwrap(); - - assert_eq!(serialization_1, serialization_2); - } - - #[test] - fn correct_u64_production_from_fit_vec() { - let le_vec = vec![1, 1, 1, 1, 2, 1, 1, 1]; - - let num = PublicSCContext::produce_u64_from_fit_vec(le_vec); - - assert_eq!(num, 72340177133043969); - } - - #[test] - fn correct_u64_production_from_small_vec() { - //7 items instead of 8 - let le_vec = vec![1, 1, 1, 1, 2, 1, 1]; - - let num = PublicSCContext::produce_u64_from_fit_vec(le_vec); - - assert_eq!(num, 282583095116033); - } - - #[test] - fn correct_u64_production_from_small_vec_le_bytes() { - //7 items instead of 8 - let le_vec = vec![1, 1, 1, 1, 2, 1, 1]; - let le_vec_res = [1, 1, 1, 1, 2, 1, 1, 0]; - - let num = PublicSCContext::produce_u64_from_fit_vec(le_vec); - - assert_eq!(num.to_le_bytes(), le_vec_res); - } - - #[test] - #[should_panic] - fn correct_u64_production_from_unfit_vec_should_panic() { - //9 items instead of 8 - let le_vec = vec![1, 1, 1, 1, 2, 1, 1, 1, 1]; - - PublicSCContext::produce_u64_from_fit_vec(le_vec); - } - - #[test] - fn consistent_len_of_context_commitments() { - let test_context = create_test_context(); - - let context_num_vec1 = test_context.produce_u64_list_from_context().unwrap(); - let context_num_vec2 = test_context.produce_u64_list_from_context().unwrap(); - - assert_eq!(context_num_vec1.len(), context_num_vec2.len()); - } -} diff --git a/node_core/src/lib.rs b/node_core/src/lib.rs index 49f1285..2ef3595 100644 --- a/node_core/src/lib.rs +++ b/node_core/src/lib.rs @@ -5,7 +5,10 @@ use std::sync::{ use common::ExecutionFailureKind; -use accounts::account_core::{Account, AccountAddress}; +use accounts::{ + account_core::{Account, AccountAddress}, + key_management::ephemeral_key_holder::EphemeralKeyHolder, +}; use anyhow::Result; use chain_storage::NodeChainStore; use common::transaction::{Transaction, TransactionPayload, TxKind}; @@ -197,7 +200,7 @@ impl NodeCore { let account = acc_map_read_guard.acc_map.get(&acc).unwrap(); - let ephm_key_holder = &account.produce_ephemeral_key_holder(); + let ephm_key_holder = EphemeralKeyHolder::new_os_random(); ephm_key_holder.log(); let eph_pub_key = @@ -286,7 +289,7 @@ impl NodeCore { let account = acc_map_read_guard.acc_map.get(&acc).unwrap(); - let ephm_key_holder = &account.produce_ephemeral_key_holder(); + let ephm_key_holder = EphemeralKeyHolder::new_os_random(); ephm_key_holder.log(); let eph_pub_key = @@ -401,7 +404,7 @@ impl NodeCore { .map(|(utxo, _)| utxo.clone()) .collect(); - let ephm_key_holder = &account.produce_ephemeral_key_holder(); + let ephm_key_holder = EphemeralKeyHolder::new_os_random(); ephm_key_holder.log(); let eph_pub_key = @@ -523,7 +526,7 @@ impl NodeCore { .map(|utxo| utxo.hash) .collect(); - let ephm_key_holder = &account.produce_ephemeral_key_holder(); + let ephm_key_holder = EphemeralKeyHolder::new_os_random(); ephm_key_holder.log(); let eph_pub_key = @@ -669,7 +672,7 @@ impl NodeCore { .map(|(utxo, _)| utxo.clone()) .collect(); - let ephm_key_holder = &account.produce_ephemeral_key_holder(); + let ephm_key_holder = EphemeralKeyHolder::new_os_random(); ephm_key_holder.log(); let eph_pub_key = @@ -1387,7 +1390,7 @@ impl NodeCore { .map(|(utxo, _)| utxo.clone()) .collect(); - let ephm_key_holder = &account.produce_ephemeral_key_holder(); + let ephm_key_holder = EphemeralKeyHolder::new_os_random(); ephm_key_holder.log(); let eph_pub_key = diff --git a/sc_core/src/transaction_payloads_tools.rs b/sc_core/src/transaction_payloads_tools.rs index 24ad8f2..cba56dc 100644 --- a/sc_core/src/transaction_payloads_tools.rs +++ b/sc_core/src/transaction_payloads_tools.rs @@ -1,4 +1,4 @@ -use accounts::account_core::Account; +use accounts::{account_core::Account, key_management::ephemeral_key_holder::EphemeralKeyHolder}; use anyhow::Result; use common::transaction::{TransactionPayload, TxKind}; use rand::thread_rng; @@ -40,7 +40,7 @@ pub fn encode_utxos_to_receivers( let mut all_encoded_data = vec![]; for (utxo, receiver) in utxos_receivers { - let ephm_key_holder = &receiver.produce_ephemeral_key_holder(); + let ephm_key_holder = EphemeralKeyHolder::new_os_random(); let encoded_data = Account::encrypt_data( &ephm_key_holder, From eee7cc13979a875ddb8fad1b293d4baf3bb03436 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Wed, 16 Jul 2025 08:10:49 +0300 Subject: [PATCH 3/5] fix: comments fix --- sc_core/src/proofs_circuits.rs | 35 ++++++++++++---------------------- 1 file changed, 12 insertions(+), 23 deletions(-) diff --git a/sc_core/src/proofs_circuits.rs b/sc_core/src/proofs_circuits.rs index 03eea96..86b5886 100644 --- a/sc_core/src/proofs_circuits.rs +++ b/sc_core/src/proofs_circuits.rs @@ -3,7 +3,6 @@ use common::merkle_tree_public::merkle_tree::UTXOCommitmentsMerkleTree; use rand::{thread_rng, RngCore}; use secp256k1_zkp::{CommitmentSecrets, Generator, PedersenCommitment, Tag, Tweak, SECP256K1}; use sha2::{Digest, Sha256}; -use std::collections::HashSet; use utxo::utxo_core::UTXO; use crate::{cryptography::poseidon_hash, public_context::PublicSCContext}; @@ -26,7 +25,7 @@ pub fn generate_nullifiers(input_utxo: &UTXO, npk: &[u8]) -> Vec { /// /// uses the input_utxo /// -/// returns commitment here comminment is a hash(bincode(input_utxo)) +/// returns commitment here commitment is a hash(bincode(input_utxo)) pub fn generate_commitment(input_utxo: &UTXO) -> Vec { let serialized = bincode::serialize(input_utxo).unwrap(); // Serialize UTXO. hash(&serialized) @@ -50,7 +49,7 @@ pub fn generate_commitments(input_utxos: &[UTXO]) -> Vec> { /// Validate inclusion proof for in_commitments /// /// ToDo: Solve it in more scalable way -pub fn validate_in_commitments_proof( +pub fn validate_in_commitments_tree( in_commitment: &Vec, commitment_tree: &UTXOCommitmentsMerkleTree, ) -> bool { @@ -59,14 +58,6 @@ pub fn validate_in_commitments_proof( commitment_tree.get_proof(alighned_hash).is_some() } -/// Validate that `nullifier` has not been present in set items before -pub fn validate_nullifier_not_present_in_set_items( - nullifier: [u8; 32], - nullifiers_set: &HashSet<[u8; 32]>, -) -> bool { - !nullifiers_set.contains(&nullifier) -} - /// Check, that input utxos balances is equal to out utxo balances pub fn check_balances_private(in_utxos: &[UTXO], out_utxos: &[UTXO]) -> bool { let in_sum = in_utxos.iter().fold(0, |prev, utxo| prev + utxo.amount); @@ -99,17 +90,16 @@ pub fn private_circuit( } for in_commitment in in_commitments { - assert!(validate_in_commitments_proof( + assert!(validate_in_commitments_tree( &in_commitment, &public_context.commitments_tree, )); } for nullifier in in_nullifiers.iter() { - assert!(validate_nullifier_not_present_in_set_items( - nullifier.clone().try_into().unwrap(), - &public_context.nullifiers_set, - )); + let nullifier: [u8; 32] = nullifier.clone().try_into().unwrap(); + + assert!(!public_context.nullifiers_set.contains(&nullifier)); } (in_nullifiers, generate_commitments(&output_utxos)) @@ -149,17 +139,16 @@ pub fn deshielded_circuit( } for in_commitment in in_commitments { - assert!(validate_in_commitments_proof( + assert!(validate_in_commitments_tree( &in_commitment, &public_context.commitments_tree, )); } for nullifier in in_nullifiers.iter() { - assert!(validate_nullifier_not_present_in_set_items( - nullifier.clone().try_into().unwrap(), - &public_context.nullifiers_set, - )); + let nullifier: [u8; 32] = nullifier.clone().try_into().unwrap(); + + assert!(!public_context.nullifiers_set.contains(&nullifier)); } in_nullifiers @@ -241,7 +230,7 @@ pub fn verify_commitment( /// Validate inclusion proof for pedersen_commitment /// /// ToDo: Solve it in more scalable way -pub fn validate_in_commitments_proof_se( +pub fn validate_in_commitments_tree_se( pedersen_commitment: &PedersenCommitment, commitment_tree: &UTXOCommitmentsMerkleTree, ) -> bool { @@ -291,7 +280,7 @@ pub fn shielded_circuit( let nullifier = generate_nullifiers_se(&pedersen_commitment, &key_ser); - assert!(validate_in_commitments_proof_se( + assert!(validate_in_commitments_tree_se( &pedersen_commitment, &public_context.commitments_tree, )); From 38b57df8831a298e73383e82366f4be230a80e7a Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Wed, 16 Jul 2025 08:18:44 +0300 Subject: [PATCH 4/5] fix: rate limit refetch --- sc_core/src/proofs_circuits.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/sc_core/src/proofs_circuits.rs b/sc_core/src/proofs_circuits.rs index 86b5886..b5627f0 100644 --- a/sc_core/src/proofs_circuits.rs +++ b/sc_core/src/proofs_circuits.rs @@ -5,6 +5,8 @@ use secp256k1_zkp::{CommitmentSecrets, Generator, PedersenCommitment, Tag, Tweak use sha2::{Digest, Sha256}; use utxo::utxo_core::UTXO; +// + use crate::{cryptography::poseidon_hash, public_context::PublicSCContext}; fn hash(input: &[u8]) -> Vec { From 19de364958ce76f04611059dfaab663b8a8f0b69 Mon Sep 17 00:00:00 2001 From: Oleksandr Pravdyvyi Date: Thu, 17 Jul 2025 07:53:03 +0300 Subject: [PATCH 5/5] fix: zkvm version bump --- Cargo.lock | 40 ++++++++++++++++++------------------ common/Cargo.toml | 2 +- node_core/Cargo.toml | 2 +- sc_core/Cargo.toml | 2 +- zkvm/Cargo.toml | 2 +- zkvm/test_methods/Cargo.toml | 2 +- 6 files changed, 25 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 0200d46..72b6e1c 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -767,7 +767,7 @@ dependencies = [ [[package]] name = "bonsai-sdk" version = "1.4.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "duplicate", "maybe-async", @@ -3716,7 +3716,7 @@ checksum = "3df6368f71f205ff9c33c076d170dd56ebf68e8161c733c0caa07a7a5509ed53" [[package]] name = "risc0-binfmt" version = "2.0.2" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "anyhow", "borsh", @@ -3733,8 +3733,8 @@ dependencies = [ [[package]] name = "risc0-build" -version = "2.2.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +version = "2.3.0" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "anyhow", "cargo_metadata", @@ -3757,7 +3757,7 @@ dependencies = [ [[package]] name = "risc0-build-kernel" version = "2.0.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "cc", "directories", @@ -3771,7 +3771,7 @@ dependencies = [ [[package]] name = "risc0-circuit-keccak" version = "3.0.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "anyhow", "bytemuck", @@ -3792,7 +3792,7 @@ dependencies = [ [[package]] name = "risc0-circuit-keccak-sys" version = "3.0.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "cc", "cust", @@ -3807,7 +3807,7 @@ dependencies = [ [[package]] name = "risc0-circuit-recursion" version = "3.0.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "anyhow", "bytemuck", @@ -3832,7 +3832,7 @@ dependencies = [ [[package]] name = "risc0-circuit-recursion-sys" version = "3.0.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "glob", "risc0-build-kernel", @@ -3844,7 +3844,7 @@ dependencies = [ [[package]] name = "risc0-circuit-rv32im" version = "3.0.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "anyhow", "auto_ops", @@ -3875,7 +3875,7 @@ dependencies = [ [[package]] name = "risc0-circuit-rv32im-sys" version = "3.0.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "cc", "cust", @@ -3890,7 +3890,7 @@ dependencies = [ [[package]] name = "risc0-core" version = "2.0.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "bytemuck", "bytemuck_derive", @@ -3902,7 +3902,7 @@ dependencies = [ [[package]] name = "risc0-groth16" version = "2.0.2" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "anyhow", "ark-bn254", @@ -3926,7 +3926,7 @@ dependencies = [ [[package]] name = "risc0-sys" version = "1.4.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "anyhow", "cust", @@ -3937,7 +3937,7 @@ dependencies = [ [[package]] name = "risc0-zkos-v1compat" version = "2.0.1" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "include_bytes_aligned", "no_std_strings", @@ -3946,7 +3946,7 @@ dependencies = [ [[package]] name = "risc0-zkp" version = "2.0.2" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "anyhow", "blake2", @@ -3976,8 +3976,8 @@ dependencies = [ [[package]] name = "risc0-zkvm" -version = "2.2.0" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +version = "2.3.0" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "addr2line 0.22.0", "anyhow", @@ -4023,7 +4023,7 @@ dependencies = [ [[package]] name = "risc0-zkvm-platform" version = "2.0.3" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "bytemuck", "cfg-if", @@ -4185,7 +4185,7 @@ checksum = "28d3b2b1366ec20994f1fd18c3c594f05c5dd4bc44d8bb0c1c632c8d6829481f" [[package]] name = "rzup" version = "0.4.1" -source = "git+https://github.com/risc0/risc0.git?branch=release-2.2#eff3c74bf9992401c2c68bea95eb6c93b27999ec" +source = "git+https://github.com/risc0/risc0.git?branch=release-2.3#c6297fc2075cb66aadb733ee677223b5a7f8c85a" dependencies = [ "semver", "serde", diff --git a/common/Cargo.toml b/common/Cargo.toml index b1988ad..bc929e3 100644 --- a/common/Cargo.toml +++ b/common/Cargo.toml @@ -9,7 +9,7 @@ thiserror.workspace = true serde_json.workspace = true serde.workspace = true reqwest.workspace = true -risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.2" } +risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" } rs_merkle.workspace = true sha2.workspace = true diff --git a/node_core/Cargo.toml b/node_core/Cargo.toml index c85e0e0..969e4ec 100644 --- a/node_core/Cargo.toml +++ b/node_core/Cargo.toml @@ -18,7 +18,7 @@ reqwest.workspace = true thiserror.workspace = true tokio.workspace = true tempfile.workspace = true -risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.2" } +risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" } hex.workspace = true actix-rt.workspace = true diff --git a/sc_core/Cargo.toml b/sc_core/Cargo.toml index 3d6a213..bc6bade 100644 --- a/sc_core/Cargo.toml +++ b/sc_core/Cargo.toml @@ -19,7 +19,7 @@ light-poseidon.workspace = true ark-bn254.workspace = true ark-ff.workspace = true -risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.2" } +risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" } [dependencies.accounts] path = "../accounts" diff --git a/zkvm/Cargo.toml b/zkvm/Cargo.toml index a0701cb..d87d142 100644 --- a/zkvm/Cargo.toml +++ b/zkvm/Cargo.toml @@ -12,7 +12,7 @@ serde.workspace = true thiserror.workspace = true rand.workspace = true -risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.2" } +risc0-zkvm = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" } test-methods = { path = "test_methods" } [dependencies.accounts] diff --git a/zkvm/test_methods/Cargo.toml b/zkvm/test_methods/Cargo.toml index eb771c5..48f27b8 100644 --- a/zkvm/test_methods/Cargo.toml +++ b/zkvm/test_methods/Cargo.toml @@ -4,7 +4,7 @@ version = "0.1.0" edition = "2021" [build-dependencies] -risc0-build = { git = "https://github.com/risc0/risc0.git", branch = "release-2.2" } +risc0-build = { git = "https://github.com/risc0/risc0.git", branch = "release-2.3" } [package.metadata.risc0] methods = ["guest"]