Merge pull request #58 from vacp2p/Pravdyvy/deterministic-serilization-of-context-fix

Deterministic serialization of public sc context
This commit is contained in:
tyshko-rostyslav 2025-04-16 12:27:19 -04:00 committed by GitHub
commit e430c02c7f
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 255 additions and 91 deletions

View File

@ -29,7 +29,7 @@ pub struct Account {
///A strucure, which represents all the visible(public) information
///
/// known to each node about account `address`
#[derive(Serialize)]
#[derive(Serialize, Clone)]
pub struct AccountPublicMask {
pub nullifier_public_key: AffinePoint,
pub viewing_public_key: AffinePoint,

View File

@ -9,12 +9,9 @@ use ::storage::transaction::{Transaction, TransactionPayload, TxKind};
use accounts::account_core::{Account, AccountAddress};
use anyhow::Result;
use config::NodeConfig;
use executions::{
de::new_commitment_vec,
private_exec::{generate_commitments, generate_nullifiers},
};
use executions::private_exec::{generate_commitments, generate_nullifiers};
use log::info;
use rand::Rng;
use sc_core::proofs_circuits::pedersen_commitment_vec;
use sequencer_client::{json::SendTxResponse, SequencerClient};
use serde::{Deserialize, Serialize};
use storage::NodeChainStore;
@ -216,8 +213,6 @@ impl NodeCore {
// TODO: fix address when correspoding method will be added
let sc_addr = "";
let mut rng = rand::thread_rng();
let secret_r: [u8; 32] = rng.gen();
let sc_state = acc_map_read_guard
.block_store
@ -231,15 +226,13 @@ impl NodeCore {
let context = acc_map_read_guard.produce_context(account.address);
let serialized_context = serde_json::to_vec(&context).unwrap();
let serialized_context_u64 = vec_u8_to_vec_u64(serialized_context);
vec_values_u64.push(serialized_context_u64);
//Will not panic, as PublicScContext is serializable
let context_public_info: Vec<u64> = context.produce_u64_list_from_context().unwrap();
vec_values_u64.push(context_public_info);
let vec_public_info: Vec<u64> = vec_values_u64.into_iter().flatten().collect();
let (tweak, secret_r, commitment) = new_commitment_vec(vec_public_info, &secret_r);
let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info);
Ok((
TransactionPayload {
@ -260,7 +253,7 @@ impl NodeCore {
ephemeral_pub_key: eph_pub_key.to_vec(),
commitment,
tweak,
secret_r: secret_r.try_into().unwrap(),
secret_r,
}
.into(),
result_hash,
@ -305,8 +298,6 @@ impl NodeCore {
// TODO: fix address when correspoding method will be added
let sc_addr = "";
let mut rng = rand::thread_rng();
let secret_r: [u8; 32] = rng.gen();
let sc_state = acc_map_read_guard
.block_store
@ -320,15 +311,13 @@ impl NodeCore {
let context = acc_map_read_guard.produce_context(account.address);
let serialized_context = serde_json::to_vec(&context).unwrap();
let serialized_context_u64 = vec_u8_to_vec_u64(serialized_context);
vec_values_u64.push(serialized_context_u64);
//Will not panic, as PublicScContext is serializable
let context_public_info: Vec<u64> = context.produce_u64_list_from_context().unwrap();
vec_values_u64.push(context_public_info);
let vec_public_info: Vec<u64> = vec_values_u64.into_iter().flatten().collect();
let (tweak, secret_r, commitment) = new_commitment_vec(vec_public_info, &secret_r);
let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info);
Ok((
TransactionPayload {
@ -349,7 +338,7 @@ impl NodeCore {
ephemeral_pub_key: eph_pub_key.to_vec(),
commitment,
tweak,
secret_r: secret_r.try_into().unwrap(),
secret_r,
}
.into(),
result_hashes,
@ -414,8 +403,6 @@ impl NodeCore {
// TODO: fix address when correspoding method will be added
let sc_addr = "";
let mut rng = rand::thread_rng();
let secret_r: [u8; 32] = rng.gen();
let sc_state = acc_map_read_guard
.block_store
@ -429,15 +416,13 @@ impl NodeCore {
let context = acc_map_read_guard.produce_context(account.address);
let serialized_context = serde_json::to_vec(&context).unwrap();
let serialized_context_u64 = vec_u8_to_vec_u64(serialized_context);
vec_values_u64.push(serialized_context_u64);
//Will not panic, as PublicScContext is serializable
let context_public_info: Vec<u64> = context.produce_u64_list_from_context().unwrap();
vec_values_u64.push(context_public_info);
let vec_public_info: Vec<u64> = vec_values_u64.into_iter().flatten().collect();
let (tweak, secret_r, commitment) = new_commitment_vec(vec_public_info, &secret_r);
let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info);
Ok((
TransactionPayload {
@ -458,7 +443,7 @@ impl NodeCore {
ephemeral_pub_key: eph_pub_key.to_vec(),
commitment,
tweak,
secret_r: secret_r.try_into().unwrap(),
secret_r,
}
.into(),
utxo_hashes,
@ -551,8 +536,6 @@ impl NodeCore {
// TODO: fix address when correspoding method will be added
let sc_addr = "";
let mut rng = rand::thread_rng();
let secret_r: [u8; 32] = rng.gen();
let sc_state = acc_map_read_guard
.block_store
@ -566,15 +549,13 @@ impl NodeCore {
let context = acc_map_read_guard.produce_context(account.address);
let serialized_context = serde_json::to_vec(&context).unwrap();
let serialized_context_u64 = vec_u8_to_vec_u64(serialized_context);
vec_values_u64.push(serialized_context_u64);
//Will not panic, as PublicScContext is serializable
let context_public_info: Vec<u64> = context.produce_u64_list_from_context().unwrap();
vec_values_u64.push(context_public_info);
let vec_public_info: Vec<u64> = vec_values_u64.into_iter().flatten().collect();
let (tweak, secret_r, commitment) = new_commitment_vec(vec_public_info, &secret_r);
let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info);
Ok((
TransactionPayload {
@ -595,7 +576,7 @@ impl NodeCore {
ephemeral_pub_key: eph_pub_key.to_vec(),
commitment,
tweak,
secret_r: secret_r.try_into().unwrap(),
secret_r,
}
.into(),
utxo_hashes_receiver,
@ -668,9 +649,6 @@ impl NodeCore {
// TODO: fix address when correspoding method will be added
let sc_addr = "";
let mut rng = rand::thread_rng();
let secret_r: [u8; 32] = rng.gen();
let sc_state = acc_map_read_guard
.block_store
.get_sc_sc_state(sc_addr)
@ -681,15 +659,15 @@ impl NodeCore {
.map(|slice| vec_u8_to_vec_u64(slice.to_vec()))
.collect();
let serialized_context_u64 = vec_u8_to_vec_u64(
serde_json::to_vec(&acc_map_read_guard.produce_context(account.address)).unwrap(),
);
let context = acc_map_read_guard.produce_context(account.address);
vec_values_u64.push(serialized_context_u64);
//Will not panic, as PublicScContext is serializable
let context_public_info: Vec<u64> = context.produce_u64_list_from_context().unwrap();
vec_values_u64.push(context_public_info);
let vec_public_info: Vec<u64> = vec_values_u64.into_iter().flatten().collect();
let (tweak, secret_r, commitment) = new_commitment_vec(vec_public_info, &secret_r);
let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info);
Ok((
TransactionPayload {
@ -716,7 +694,7 @@ impl NodeCore {
ephemeral_pub_key: eph_pub_key.to_vec(),
commitment,
tweak,
secret_r: secret_r.try_into().unwrap(),
secret_r,
}
.into(),
utxo_hashes,
@ -753,8 +731,6 @@ impl NodeCore {
// TODO: fix address when correspoding method will be added
let sc_addr = "";
let mut rng = rand::thread_rng();
let secret_r: [u8; 32] = rng.gen();
let sc_state = acc_map_read_guard
.block_store
@ -768,15 +744,13 @@ impl NodeCore {
let context = acc_map_read_guard.produce_context(account.address);
let serialized_context = serde_json::to_vec(&context).unwrap();
let serialized_context_u64 = vec_u8_to_vec_u64(serialized_context);
vec_values_u64.push(serialized_context_u64);
//Will not panic, as PublicScContext is serializable
let context_public_info: Vec<u64> = context.produce_u64_list_from_context().unwrap();
vec_values_u64.push(context_public_info);
let vec_public_info: Vec<u64> = vec_values_u64.into_iter().flatten().collect();
let (tweak, secret_r, commitment) = new_commitment_vec(vec_public_info, &secret_r);
let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info);
Ok(TransactionPayload {
tx_kind: TxKind::Deshielded,
@ -796,7 +770,7 @@ impl NodeCore {
ephemeral_pub_key: vec![],
commitment,
tweak,
secret_r: secret_r.try_into().unwrap(),
secret_r,
}
.into())
}
@ -862,6 +836,17 @@ impl NodeCore {
//Considering proof time, needs to be done before proof
let tx_roots = self.get_roots().await;
let public_context = {
let read_guard = self.storage.read().await;
read_guard.produce_context(acc)
};
let (tweak, secret_r, commitment) = pedersen_commitment_vec(
//Will not panic, as public context is serializable
public_context.produce_u64_list_from_context().unwrap(),
);
let tx: Transaction =
sc_core::transaction_payloads_tools::create_public_transaction_payload(
serde_json::to_vec(&ActionData::MintMoneyPublicTx(MintMoneyPublicTx {
@ -869,6 +854,9 @@ impl NodeCore {
amount,
}))
.unwrap(),
commitment,
tweak,
secret_r,
)
.into();
tx.log();
@ -1367,8 +1355,6 @@ impl NodeCore {
// TODO: fix address when correspoding method will be added
let sc_addr = "";
let mut rng = rand::thread_rng();
let secret_r: [u8; 32] = rng.gen();
let sc_state = acc_map_read_guard
.block_store
@ -1382,15 +1368,13 @@ impl NodeCore {
let context = acc_map_read_guard.produce_context(account.address);
let serialized_context = serde_json::to_vec(&context).unwrap();
let serialized_context_u64 = vec_u8_to_vec_u64(serialized_context);
vec_values_u64.push(serialized_context_u64);
//Will not panic, as PublicScContext is serializable
let context_public_info: Vec<u64> = context.produce_u64_list_from_context().unwrap();
vec_values_u64.push(context_public_info);
let vec_public_info: Vec<u64> = vec_values_u64.into_iter().flatten().collect();
let (tweak, secret_r, commitment) = new_commitment_vec(vec_public_info, &secret_r);
let (tweak, secret_r, commitment) = pedersen_commitment_vec(vec_public_info);
Ok((
TransactionPayload {
@ -1412,7 +1396,7 @@ impl NodeCore {
ephemeral_pub_key: eph_pub_key.to_vec(),
commitment,
tweak,
secret_r: secret_r.try_into().unwrap(),
secret_r,
}
.into(),
utxo_hashes,

View File

@ -1,11 +1,10 @@
use std::collections::BTreeMap;
use accounts::account_core::{AccountAddress, AccountPublicMask};
use serde::Serialize;
use serde::{ser::SerializeStruct, Serialize};
use storage::merkle_tree_public::TreeHashType;
///Strucutre, representing context, given to a smart contract on a call
#[derive(Serialize)]
pub struct PublicSCContext {
pub caller_address: AccountAddress,
pub caller_balance: u64,
@ -14,3 +13,164 @@ pub struct PublicSCContext {
pub comitment_store_root: TreeHashType,
pub pub_tx_store_root: TreeHashType,
}
impl Serialize for PublicSCContext {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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<AccountPublicMask> =
self.account_masks.values().cloned().collect();
account_mask_values.sort_by(|left, right| left.address.cmp(&right.address));
let mut s = serializer.serialize_struct("PublicSCContext", 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("nullifier_store_root", &self.nullifier_store_root)?;
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<u8>) -> 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<Vec<u64>, 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<u8>;
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 nullifier_store_root = [2; 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,
nullifier_store_root,
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());
}
}

View File

@ -2,7 +2,7 @@ use bincode;
use k256::Scalar;
use monotree::hasher::Blake3;
use monotree::{Hasher, Monotree};
use rand::thread_rng;
use rand::{thread_rng, RngCore};
use secp256k1_zkp::{CommitmentSecrets, Generator, PedersenCommitment, Tag, Tweak, SECP256K1};
use sha2::{Digest, Sha256};
use storage::{
@ -168,6 +168,32 @@ pub fn check_balances(public_info: u128, output_utxos: &[UTXO]) -> bool {
public_info == total_output
}
// new_commitment for a Vec of values
pub fn pedersen_commitment_vec(
public_info_vec: Vec<u64>,
) -> (Tweak, [u8; 32], Vec<PedersenCommitment>) {
let mut random_val: [u8; 32] = [0; 32];
thread_rng().fill_bytes(&mut random_val);
let generator_blinding_factor = Tweak::new(&mut thread_rng());
let tag = tag_random();
let vec_commitments = public_info_vec
.into_iter()
.map(|public_info| {
let commitment_secrets = CommitmentSecrets {
value: public_info,
value_blinding_factor: Tweak::from_slice(&random_val).unwrap(),
generator_blinding_factor,
};
commit(&commitment_secrets, tag)
})
.collect();
(generator_blinding_factor, random_val, vec_commitments)
}
// Verify Pedersen commitment
// takes the public_info, secret_r and pedersen_commitment and

View File

@ -2,18 +2,18 @@ use accounts::account_core::Account;
use anyhow::Result;
use rand::thread_rng;
use risc0_zkvm::Receipt;
use secp256k1_zkp::{CommitmentSecrets, Generator, PedersenCommitment, Tweak, SECP256K1};
use secp256k1_zkp::{CommitmentSecrets, PedersenCommitment, Tweak};
use storage::transaction::{TransactionPayload, TxKind};
use utxo::utxo_core::UTXO;
use crate::proofs_circuits::{commit, generate_nullifiers, tag_random};
pub fn create_public_transaction_payload(execution_input: Vec<u8>) -> TransactionPayload {
let tag = tag_random();
let unblinded_gen = Generator::new_unblinded(SECP256K1, tag);
let mut rng = rand::thread_rng();
pub fn create_public_transaction_payload(
execution_input: Vec<u8>,
commitment: Vec<PedersenCommitment>,
tweak: Tweak,
secret_r: [u8; 32],
) -> TransactionPayload {
TransactionPayload {
tx_kind: TxKind::Public,
execution_input,
@ -24,13 +24,9 @@ pub fn create_public_transaction_payload(execution_input: Vec<u8>) -> Transactio
execution_proof_private: "".to_string(),
encoded_data: vec![],
ephemeral_pub_key: vec![],
commitment: vec![PedersenCommitment::new_unblinded(
SECP256K1,
0,
unblinded_gen,
)],
tweak: Tweak::new(&mut rng),
secret_r: [0; 32],
commitment,
tweak,
secret_r,
}
}

View File

@ -1,5 +1,5 @@
use log::info;
use secp256k1_zkp::{rand, PedersenCommitment, Tweak};
use secp256k1_zkp::{PedersenCommitment, Tweak};
use serde::{Deserialize, Serialize};
use sha2::{digest::FixedOutput, Digest};
@ -90,8 +90,6 @@ impl From<TransactionPayload> for Transaction {
let hash = <TreeHashType>::from(hasher.finalize_fixed());
let mut rng = rand::thread_rng();
Self {
hash,
tx_kind: value.tx_kind,
@ -104,8 +102,8 @@ impl From<TransactionPayload> for Transaction {
encoded_data: value.encoded_data,
ephemeral_pub_key: value.ephemeral_pub_key,
commitment: value.commitment,
tweak: Tweak::new(&mut rng),
secret_r: [0; 32],
tweak: value.tweak,
secret_r: value.secret_r,
}
}
}