add root history

This commit is contained in:
Sergio Chouhy 2025-08-25 09:22:59 -03:00
parent 9ff6a5bc85
commit 20897596b0
8 changed files with 91 additions and 79 deletions

View File

@ -38,11 +38,10 @@ pub mod error;
pub type CommitmentSetDigest = [u8; 32]; pub type CommitmentSetDigest = [u8; 32];
pub type MembershipProof = (usize, Vec<[u8; 32]>); pub type MembershipProof = (usize, Vec<[u8; 32]>);
pub fn verify_membership_proof( pub fn compute_root_associated_to_path(
commitment: &Commitment, commitment: &Commitment,
proof: &MembershipProof, proof: &MembershipProof,
digest: &CommitmentSetDigest, ) -> CommitmentSetDigest {
) -> bool {
let value_bytes = commitment.to_byte_array(); let value_bytes = commitment.to_byte_array();
let mut result: [u8; 32] = Impl::hash_bytes(&value_bytes) let mut result: [u8; 32] = Impl::hash_bytes(&value_bytes)
.as_bytes() .as_bytes()
@ -64,7 +63,7 @@ pub fn verify_membership_proof(
} }
level_index >>= 1; level_index >>= 1;
} }
&result == digest result
} }
pub type EphemeralPublicKey = Secp256k1Point; pub type EphemeralPublicKey = Secp256k1Point;
@ -243,7 +242,6 @@ pub struct PrivacyPreservingCircuitInput {
)>, )>,
pub private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>, pub private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>,
pub program_id: ProgramId, pub program_id: ProgramId,
pub commitment_set_digest: CommitmentSetDigest,
} }
#[derive(Serialize, Deserialize)] #[derive(Serialize, Deserialize)]
@ -253,8 +251,7 @@ pub struct PrivacyPreservingCircuitOutput {
pub public_post_states: Vec<Account>, pub public_post_states: Vec<Account>,
pub encrypted_private_post_states: Vec<EncryptedAccountData>, pub encrypted_private_post_states: Vec<EncryptedAccountData>,
pub new_commitments: Vec<Commitment>, pub new_commitments: Vec<Commitment>,
pub new_nullifiers: Vec<Nullifier>, pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
pub commitment_set_digest: CommitmentSetDigest,
} }
#[cfg(feature = "host")] #[cfg(feature = "host")]
@ -313,11 +310,13 @@ mod tests {
&NullifierPublicKey::from(&[1; 32]), &NullifierPublicKey::from(&[1; 32]),
&Account::default(), &Account::default(),
)], )],
new_nullifiers: vec![Nullifier::new( new_nullifiers: vec![(
&Commitment::new(&NullifierPublicKey::from(&[2; 32]), &Account::default()), Nullifier::new(
&[1; 32], &Commitment::new(&NullifierPublicKey::from(&[2; 32]), &Account::default()),
&[1; 32],
),
[0xab; 32],
)], )],
commitment_set_digest: [0xab; 32],
}; };
let bytes = output.to_bytes(); let bytes = output.to_bytes();
let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap(); let output_from_slice: PrivacyPreservingCircuitOutput = from_slice(&bytes).unwrap();

View File

@ -2,8 +2,9 @@ use risc0_zkvm::{guest::env, serde::to_vec};
use nssa_core::{ use nssa_core::{
account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey},
compute_root_associated_to_path,
program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID}, program::{validate_execution, ProgramOutput, DEFAULT_PROGRAM_ID},
verify_membership_proof, EncryptedAccountData, EphemeralPublicKey, EphemeralSecretKey, CommitmentSetDigest, EncryptedAccountData, EphemeralPublicKey, EphemeralSecretKey,
IncomingViewingPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, IncomingViewingPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
}; };
@ -15,7 +16,6 @@ fn main() {
private_account_keys, private_account_keys,
private_account_auth, private_account_auth,
program_id, program_id,
commitment_set_digest,
} = env::read(); } = env::read();
// TODO: Check that `program_execution_proof` is one of the allowed built-in programs // TODO: Check that `program_execution_proof` is one of the allowed built-in programs
@ -44,7 +44,7 @@ fn main() {
let mut public_post_states: Vec<Account> = Vec::new(); let mut public_post_states: Vec<Account> = Vec::new();
let mut encrypted_private_post_states: Vec<EncryptedAccountData> = Vec::new(); let mut encrypted_private_post_states: Vec<EncryptedAccountData> = Vec::new();
let mut new_commitments: Vec<Commitment> = Vec::new(); let mut new_commitments: Vec<Commitment> = Vec::new();
let mut new_nullifiers: Vec<Nullifier> = Vec::new(); let mut new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)> = Vec::new();
let mut private_nonces_iter = private_account_nonces.iter(); let mut private_nonces_iter = private_account_nonces.iter();
let mut private_keys_iter = private_account_keys.iter(); let mut private_keys_iter = private_account_keys.iter();
@ -80,15 +80,10 @@ fn main() {
panic!("Npk mismatch"); panic!("Npk mismatch");
} }
// Verify pre-state commitment membership // Compute commitment set digest associated with provided auth path
let commitment_pre = Commitment::new(Npk, &pre_states[i].account); let commitment_pre = Commitment::new(Npk, &pre_states[i].account);
if !verify_membership_proof( let set_digest =
&commitment_pre, compute_root_associated_to_path(&commitment_pre, membership_proof);
membership_proof,
&commitment_set_digest,
) {
panic!("Membership proof invalid");
}
// Check pre_state authorization // Check pre_state authorization
if !pre_states[i].is_authorized { if !pre_states[i].is_authorized {
@ -97,7 +92,7 @@ fn main() {
// Compute nullifier // Compute nullifier
let nullifier = Nullifier::new(&commitment_pre, nsk); let nullifier = Nullifier::new(&commitment_pre, nsk);
new_nullifiers.push(nullifier); new_nullifiers.push((nullifier, set_digest));
} else { } else {
if pre_states[i].account != Account::default() { if pre_states[i].account != Account::default() {
panic!("Found new private account with non default values."); panic!("Found new private account with non default values.");
@ -156,7 +151,6 @@ fn main() {
encrypted_private_post_states, encrypted_private_post_states,
new_commitments, new_commitments,
new_nullifiers, new_nullifiers,
commitment_set_digest,
}; };
env::commit(&output); env::commit(&output);

View File

@ -173,7 +173,7 @@ fn verify_authentication_path(value: &Value, index: usize, path: &[Node], root:
fn prev_power_of_two(x: usize) -> usize { fn prev_power_of_two(x: usize) -> usize {
if x == 0 { if x == 0 {
return 0; // define as 0 return 0;
} }
1 << (usize::BITS as usize - x.leading_zeros() as usize - 1) 1 << (usize::BITS as usize - x.leading_zeros() as usize - 1)
} }
@ -314,10 +314,10 @@ mod tests {
let expected_root = let expected_root =
hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de"); hex!("48c73f7821a58a8d2a703e5b39c571c0aa20cf14abcd0af8f2b955bc202998de");
tree.insert(values[0]); assert_eq!(0, tree.insert(values[0]));
tree.insert(values[1]); assert_eq!(1, tree.insert(values[1]));
tree.insert(values[2]); assert_eq!(2, tree.insert(values[2]));
tree.insert(values[3]); assert_eq!(3, tree.insert(values[3]));
assert_eq!(tree.root(), expected_root); assert_eq!(tree.root(), expected_root);
} }
@ -331,9 +331,9 @@ mod tests {
let expected_root = let expected_root =
hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568"); hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568");
tree.insert(values[0]); assert_eq!(0, tree.insert(values[0]));
tree.insert(values[1]); assert_eq!(1, tree.insert(values[1]));
tree.insert(values[2]); assert_eq!(2, tree.insert(values[2]));
assert_eq!(tree.root(), expected_root); assert_eq!(tree.root(), expected_root);
} }
@ -347,9 +347,9 @@ mod tests {
let expected_root = let expected_root =
hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568"); hex!("c8d3d8d2b13f27ceeccdc699119871f9f32ea7ed86ff45d0ad11f77b28cd7568");
tree.insert(values[0]); assert_eq!(0, tree.insert(values[0]));
tree.insert(values[1]); assert_eq!(1, tree.insert(values[1]));
tree.insert(values[2]); assert_eq!(2, tree.insert(values[2]));
assert_eq!(tree.root(), expected_root); assert_eq!(tree.root(), expected_root);
} }
@ -361,9 +361,9 @@ mod tests {
let values = [[1; 32], [2; 32], [3; 32]]; let values = [[1; 32], [2; 32], [3; 32]];
let expected_tree = MerkleTree::new(&values); let expected_tree = MerkleTree::new(&values);
tree.insert(values[0]); assert_eq!(0, tree.insert(values[0]));
tree.insert(values[1]); assert_eq!(1, tree.insert(values[1]));
tree.insert(values[2]); assert_eq!(2, tree.insert(values[2]));
assert_eq!(expected_tree, tree); assert_eq!(expected_tree, tree);
} }
@ -375,10 +375,10 @@ mod tests {
let values = [[1; 32], [2; 32], [3; 32], [4; 32]]; let values = [[1; 32], [2; 32], [3; 32], [4; 32]];
let expected_tree = MerkleTree::new(&values); let expected_tree = MerkleTree::new(&values);
tree.insert(values[0]); assert_eq!(0, tree.insert(values[0]));
tree.insert(values[1]); assert_eq!(1, tree.insert(values[1]));
tree.insert(values[2]); assert_eq!(2, tree.insert(values[2]));
tree.insert(values[3]); assert_eq!(3, tree.insert(values[3]));
assert_eq!(expected_tree, tree); assert_eq!(expected_tree, tree);
} }

View File

@ -33,7 +33,6 @@ pub fn execute_and_prove(
)], )],
private_account_auth: &[(NullifierSecretKey, MembershipProof)], private_account_auth: &[(NullifierSecretKey, MembershipProof)],
program: &Program, program: &Program,
commitment_set_digest: &CommitmentSetDigest,
) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> { ) -> Result<(PrivacyPreservingCircuitOutput, Proof), NssaError> {
let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?; let inner_receipt = execute_and_prove_program(program, pre_states, instruction_data)?;
@ -49,7 +48,6 @@ pub fn execute_and_prove(
private_account_keys: private_account_keys.to_vec(), private_account_keys: private_account_keys.to_vec(),
private_account_auth: private_account_auth.to_vec(), private_account_auth: private_account_auth.to_vec(),
program_id: program.id(), program_id: program.id(),
commitment_set_digest: *commitment_set_digest,
}; };
// Prove circuit. // Prove circuit.
@ -157,7 +155,6 @@ mod tests {
&[(recipient_keys.npk(), recipient_keys.ivk(), [3; 32])], &[(recipient_keys.npk(), recipient_keys.ivk(), [3; 32])],
&[], &[],
&Program::authenticated_transfer_program(), &Program::authenticated_transfer_program(),
&[99; 32],
) )
.unwrap(); .unwrap();
@ -169,7 +166,6 @@ mod tests {
assert_eq!(sender_post, expected_sender_post); assert_eq!(sender_post, expected_sender_post);
assert_eq!(output.new_commitments.len(), 1); assert_eq!(output.new_commitments.len(), 1);
assert_eq!(output.new_nullifiers.len(), 0); assert_eq!(output.new_nullifiers.len(), 0);
assert_eq!(output.commitment_set_digest, [99; 32]);
assert_eq!(output.encrypted_private_post_states.len(), 1); assert_eq!(output.encrypted_private_post_states.len(), 1);
let recipient_post = output.encrypted_private_post_states[0] let recipient_post = output.encrypted_private_post_states[0]
@ -199,7 +195,13 @@ mod tests {
}; };
let balance_to_move: u128 = 37; let balance_to_move: u128 = 37;
let expected_new_nullifiers = vec![Nullifier::new(&commitment_sender, &sender_keys.nsk)]; let mut commitment_set = CommitmentSet::with_capacity(2);
commitment_set.extend(&[commitment_sender.clone()]);
let expected_new_nullifiers = vec![(
Nullifier::new(&commitment_sender, &sender_keys.nsk),
commitment_set.digest(),
)];
let program = Program::authenticated_transfer_program(); let program = Program::authenticated_transfer_program();
@ -220,9 +222,6 @@ mod tests {
Commitment::new(&recipient_keys.npk(), &expected_private_account_2), Commitment::new(&recipient_keys.npk(), &expected_private_account_2),
]; ];
let mut commitment_set = CommitmentSet::with_capacity(2);
commitment_set.extend(&[commitment_sender.clone()]);
let (output, proof) = execute_and_prove( let (output, proof) = execute_and_prove(
&[sender_pre.clone(), recipient], &[sender_pre.clone(), recipient],
&Program::serialize_instruction(balance_to_move).unwrap(), &Program::serialize_instruction(balance_to_move).unwrap(),
@ -237,7 +236,6 @@ mod tests {
commitment_set.get_proof_for(&commitment_sender).unwrap(), commitment_set.get_proof_for(&commitment_sender).unwrap(),
)], )],
&program, &program,
&commitment_set.digest(),
) )
.unwrap(); .unwrap();
@ -246,7 +244,6 @@ mod tests {
assert!(output.public_post_states.is_empty()); assert!(output.public_post_states.is_empty());
assert_eq!(output.new_commitments, expected_new_commitments); assert_eq!(output.new_commitments, expected_new_commitments);
assert_eq!(output.new_nullifiers, expected_new_nullifiers); assert_eq!(output.new_nullifiers, expected_new_nullifiers);
assert_eq!(output.commitment_set_digest, commitment_set.digest());
assert_eq!(output.encrypted_private_post_states.len(), 2); assert_eq!(output.encrypted_private_post_states.len(), 2);
let recipient_post_1 = output.encrypted_private_post_states[0] let recipient_post_1 = output.encrypted_private_post_states[0]

View File

@ -12,8 +12,7 @@ use crate::{Address, error::NssaError};
use super::message::Message; use super::message::Message;
const MESSAGE_ENCODING_PREFIX_LEN: usize = 22; const MESSAGE_ENCODING_PREFIX_LEN: usize = 22;
const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = const MESSAGE_ENCODING_PREFIX: &[u8; MESSAGE_ENCODING_PREFIX_LEN] = b"\x01/NSSA/v0.1/TxMessage/";
b"\x01/NSSA/v0.1/TxMessage/";
impl Message { impl Message {
pub(crate) fn to_bytes(&self) -> Vec<u8> { pub(crate) fn to_bytes(&self) -> Vec<u8> {
@ -56,9 +55,11 @@ impl Message {
// New nullifiers // New nullifiers
let new_nullifiers_len: u32 = self.new_nullifiers.len() as u32; let new_nullifiers_len: u32 = self.new_nullifiers.len() as u32;
bytes.extend_from_slice(&new_nullifiers_len.to_le_bytes()); bytes.extend_from_slice(&new_nullifiers_len.to_le_bytes());
for nullifier in &self.new_nullifiers { for (nullifier, commitment_set_digest) in &self.new_nullifiers {
bytes.extend_from_slice(&nullifier.to_byte_array()); bytes.extend_from_slice(&nullifier.to_byte_array());
bytes.extend_from_slice(commitment_set_digest);
} }
bytes bytes
} }
@ -125,7 +126,10 @@ impl Message {
let new_nullifiers_len = u32::from_le_bytes(len_bytes) as usize; let new_nullifiers_len = u32::from_le_bytes(len_bytes) as usize;
let mut new_nullifiers = Vec::with_capacity(new_nullifiers_len); let mut new_nullifiers = Vec::with_capacity(new_nullifiers_len);
for _ in 0..new_nullifiers_len { for _ in 0..new_nullifiers_len {
new_nullifiers.push(Nullifier::from_cursor(cursor)?); let nullifier = Nullifier::from_cursor(cursor)?;
let mut commitment_set_digest = [0; 32];
cursor.read_exact(&mut commitment_set_digest);
new_nullifiers.push((nullifier, commitment_set_digest));
} }
Ok(Self { Ok(Self {

View File

@ -1,5 +1,5 @@
use nssa_core::{ use nssa_core::{
EncryptedAccountData, CommitmentSetDigest, EncryptedAccountData,
account::{Account, Commitment, Nonce, Nullifier}, account::{Account, Commitment, Nonce, Nullifier},
}; };
@ -12,7 +12,7 @@ pub struct Message {
pub(crate) public_post_states: Vec<Account>, pub(crate) public_post_states: Vec<Account>,
pub(crate) encrypted_private_post_states: Vec<EncryptedAccountData>, pub(crate) encrypted_private_post_states: Vec<EncryptedAccountData>,
pub(crate) new_commitments: Vec<Commitment>, pub(crate) new_commitments: Vec<Commitment>,
pub(crate) new_nullifiers: Vec<Nullifier>, pub(crate) new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
} }
impl Message { impl Message {
@ -22,7 +22,7 @@ impl Message {
public_post_states: Vec<Account>, public_post_states: Vec<Account>,
encrypted_private_post_states: Vec<EncryptedAccountData>, encrypted_private_post_states: Vec<EncryptedAccountData>,
new_commitments: Vec<Commitment>, new_commitments: Vec<Commitment>,
new_nullifiers: Vec<Nullifier>, new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
) -> Self { ) -> Self {
Self { Self {
public_addresses, public_addresses,
@ -66,7 +66,7 @@ pub mod tests {
let new_commitments = vec![Commitment::new(&Npk2, &account2)]; let new_commitments = vec![Commitment::new(&Npk2, &account2)];
let old_commitment = Commitment::new(&Npk1, &account1); let old_commitment = Commitment::new(&Npk1, &account1);
let new_nullifiers = vec![Nullifier::new(&old_commitment, &nsk1)]; let new_nullifiers = vec![(Nullifier::new(&old_commitment, &nsk1), [0; 32])];
Message { Message {
public_addresses: public_addresses.clone(), public_addresses: public_addresses.clone(),

View File

@ -1,6 +1,6 @@
use std::collections::{HashMap, HashSet}; use std::collections::{HashMap, HashSet};
use nssa_core::account::{Account, AccountWithMetadata}; use nssa_core::account::{Account, AccountWithMetadata, Commitment, Nullifier};
use nssa_core::{CommitmentSetDigest, EncryptedAccountData, PrivacyPreservingCircuitOutput}; use nssa_core::{CommitmentSetDigest, EncryptedAccountData, PrivacyPreservingCircuitOutput};
use crate::error::NssaError; use crate::error::NssaError;
@ -93,8 +93,6 @@ impl PrivacyPreservingTransaction {
}) })
.collect(); .collect();
let set_commitment = state.commitment_set_digest();
// 4. Proof verification // 4. Proof verification
check_privacy_preserving_circuit_proof_is_valid( check_privacy_preserving_circuit_proof_is_valid(
&witness_set.proof, &witness_set.proof,
@ -103,14 +101,13 @@ impl PrivacyPreservingTransaction {
&message.encrypted_private_post_states, &message.encrypted_private_post_states,
&message.new_commitments, &message.new_commitments,
&message.new_nullifiers, &message.new_nullifiers,
set_commitment,
)?; )?;
// 5. Commitment freshness // 5. Commitment freshness
state.check_commitments_are_new(&message.new_commitments)?; state.check_commitments_are_new(&message.new_commitments)?;
// 6. Nullifier uniqueness // 6. Nullifier uniqueness
state.check_nullifiers_are_new(&message.new_nullifiers)?; state.check_nullifiers_are_valid(&message.new_nullifiers)?;
Ok(message Ok(message
.public_addresses .public_addresses
@ -142,9 +139,8 @@ fn check_privacy_preserving_circuit_proof_is_valid(
public_pre_states: &[AccountWithMetadata], public_pre_states: &[AccountWithMetadata],
public_post_states: &[Account], public_post_states: &[Account],
encrypted_private_post_states: &[EncryptedAccountData], encrypted_private_post_states: &[EncryptedAccountData],
new_commitments: &[nssa_core::account::Commitment], new_commitments: &[Commitment],
new_nullifiers: &[nssa_core::account::Nullifier], new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
commitment_set_digest: CommitmentSetDigest,
) -> Result<(), NssaError> { ) -> Result<(), NssaError> {
let output = PrivacyPreservingCircuitOutput { let output = PrivacyPreservingCircuitOutput {
public_pre_states: public_pre_states.to_vec(), public_pre_states: public_pre_states.to_vec(),
@ -152,7 +148,6 @@ fn check_privacy_preserving_circuit_proof_is_valid(
encrypted_private_post_states: encrypted_private_post_states.to_vec(), encrypted_private_post_states: encrypted_private_post_states.to_vec(),
new_commitments: new_commitments.to_vec(), new_commitments: new_commitments.to_vec(),
new_nullifiers: new_nullifiers.to_vec(), new_nullifiers: new_nullifiers.to_vec(),
commitment_set_digest,
}; };
proof proof
.is_valid_for(&output) .is_valid_for(&output)

View File

@ -13,6 +13,7 @@ use std::collections::{HashMap, HashSet};
pub(crate) struct CommitmentSet { pub(crate) struct CommitmentSet {
merkle_tree: MerkleTree, merkle_tree: MerkleTree,
commitments: HashMap<Commitment, usize>, commitments: HashMap<Commitment, usize>,
pub root_history: HashSet<CommitmentSetDigest>,
} }
impl CommitmentSet { impl CommitmentSet {
@ -22,9 +23,11 @@ impl CommitmentSet {
pub(crate) fn get_proof_for(&self, commitment: &Commitment) -> Option<MembershipProof> { pub(crate) fn get_proof_for(&self, commitment: &Commitment) -> Option<MembershipProof> {
let index = *self.commitments.get(commitment)?; let index = *self.commitments.get(commitment)?;
self.merkle_tree let proof = self
.merkle_tree
.get_authentication_path_for(index) .get_authentication_path_for(index)
.map(|path| (index, path)) .map(|path| (index, path));
proof
} }
pub(crate) fn extend(&mut self, commitments: &[Commitment]) { pub(crate) fn extend(&mut self, commitments: &[Commitment]) {
@ -32,6 +35,7 @@ impl CommitmentSet {
let index = self.merkle_tree.insert(commitment.to_byte_array()); let index = self.merkle_tree.insert(commitment.to_byte_array());
self.commitments.insert(commitment, index); self.commitments.insert(commitment, index);
} }
self.root_history.insert(self.digest());
} }
fn contains(&self, commitment: &Commitment) -> bool { fn contains(&self, commitment: &Commitment) -> bool {
@ -42,6 +46,7 @@ impl CommitmentSet {
Self { Self {
merkle_tree: MerkleTree::with_capacity(capacity), merkle_tree: MerkleTree::with_capacity(capacity),
commitments: HashMap::new(), commitments: HashMap::new(),
root_history: HashSet::new(),
} }
} }
} }
@ -50,7 +55,7 @@ type NullifierSet = HashSet<Nullifier>;
pub struct V01State { pub struct V01State {
public_state: HashMap<Address, Account>, public_state: HashMap<Address, Account>,
private_state: (CommitmentSet, NullifierSet), pub private_state: (CommitmentSet, NullifierSet),
builtin_programs: HashMap<ProgramId, Program>, builtin_programs: HashMap<ProgramId, Program>,
} }
@ -122,7 +127,13 @@ impl V01State {
self.private_state.0.extend(&message.new_commitments); self.private_state.0.extend(&message.new_commitments);
// 3. Add new nullifiers // 3. Add new nullifiers
self.private_state.1.extend(message.new_nullifiers.clone()); let new_nullifiers = message
.new_nullifiers
.iter()
.cloned()
.map(|(nullifier, _)| nullifier)
.collect::<Vec<Nullifier>>();
self.private_state.1.extend(new_nullifiers);
// 4. Update public accounts // 4. Update public accounts
for (address, post) in public_state_diff.into_iter() { for (address, post) in public_state_diff.into_iter() {
@ -172,19 +183,34 @@ impl V01State {
Ok(()) Ok(())
} }
pub(crate) fn check_nullifiers_are_new( pub(crate) fn check_nullifiers_are_valid(
&self, &self,
new_nullifiers: &[Nullifier], new_nullifiers: &[(Nullifier, CommitmentSetDigest)],
) -> Result<(), NssaError> { ) -> Result<(), NssaError> {
for nullifier in new_nullifiers.iter() { for (nullifier, digest) in new_nullifiers.iter() {
if self.private_state.1.contains(nullifier) { if self.private_state.1.contains(nullifier) {
return Err(NssaError::InvalidInput( return Err(NssaError::InvalidInput(
"Nullifier already seen".to_string(), "Nullifier already seen".to_string(),
)); ));
} }
if !self.private_state.0.root_history.contains(digest) {
return Err(NssaError::InvalidInput(
"Unrecognized commitment set digest".to_string(),
));
}
} }
Ok(()) Ok(())
} }
pub(crate) fn check_commitment_set_digest_is_valid(
&self,
commitment_set_digest: &CommitmentSetDigest,
) -> bool {
self.private_state
.0
.root_history
.contains(commitment_set_digest)
}
} }
#[cfg(test)] #[cfg(test)]
@ -774,7 +800,6 @@ pub mod tests {
&[(recipient_keys.npk(), recipient_keys.ivk(), esk)], &[(recipient_keys.npk(), recipient_keys.ivk(), esk)],
&[], &[],
&Program::authenticated_transfer_program(), &Program::authenticated_transfer_program(),
&state.commitment_set_digest(),
) )
.unwrap(); .unwrap();
@ -828,7 +853,6 @@ pub mod tests {
.unwrap(), .unwrap(),
)], )],
&program, &program,
&state.private_state.0.digest(),
) )
.unwrap(); .unwrap();
@ -880,7 +904,6 @@ pub mod tests {
.unwrap(), .unwrap(),
)], )],
&program, &program,
&state.private_state.0.digest(),
) )
.unwrap(); .unwrap();