diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 1585be1..5bbdcd9 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -3,14 +3,14 @@ use nssa_core::account::{Account, Commitment, Nonce, Nullifier}; use crate::Address; #[derive(Debug, Clone, PartialEq, Eq)] -struct EncryptedAccountData; +pub struct EncryptedAccountData; #[derive(Debug, Clone, PartialEq, Eq)] pub struct Message { pub(crate) public_addresses: Vec
, pub(crate) nonces: Vec, pub(crate) public_post_states: Vec, - pub(crate) encrypted_private_post_states: Vec, + pub(crate) encrypted_private_post_states: EncryptedAccountData, pub(crate) new_commitments: Vec, pub(crate) new_nullifiers: Vec, } diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index 0640454..b0cdbe2 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,8 +1,10 @@ -use std::collections::HashMap; +use std::collections::{HashMap, HashSet}; -use nssa_core::account::Account; +use nssa_core::account::{Account, AccountWithMetadata}; use crate::error::NssaError; +use crate::privacy_preserving_transaction::message::EncryptedAccountData; +use crate::state::CommitmentSetDigest; use crate::{Address, V01State}; use super::message::Message; @@ -15,11 +17,100 @@ pub struct PrivacyPreservingTransaction { } impl PrivacyPreservingTransaction { - pub(crate) fn validate( + pub(crate) fn validate_and_produce_public_state_diff( &self, - arg: &mut V01State, + state: &mut V01State, ) -> Result, NssaError> { - todo!() + let message = &self.message; + let witness_set = &self.witness_set; + + // 1. Commitments or nullifiers are non empty + if message.new_commitments.is_empty() && message.new_nullifiers.is_empty() { + return Err(NssaError::InvalidInput( + "Empty commitments and empty nullifiers found in message".into(), + )); + } + + // 2. Check there are no duplicate addresses in the public_addresses list. + if n_unique(&message.public_addresses) != message.public_addresses.len() { + return Err(NssaError::InvalidInput( + "Duplicate addresses found in message".into(), + )); + } + + // Check there are no duplicate nullifiers in the new_nullifiers list + if n_unique(&message.new_nullifiers) != message.new_nullifiers.len() { + return Err(NssaError::InvalidInput( + "Duplicate nullifiers found in message".into(), + )); + } + + // Check there are no duplicate commitments in the new_nullifiers list + if n_unique(&message.new_commitments) != message.new_commitments.len() { + return Err(NssaError::InvalidInput( + "Duplicate commitments found in message".into(), + )); + } + + // 3. Nonce checks and Valid signatures + // Check exactly one nonce is provided for each signature + if message.nonces.len() != witness_set.signatures_and_public_keys.len() { + return Err(NssaError::InvalidInput( + "Mismatch between number of nonces and signatures/public keys".into(), + )); + } + + // Check the signatures are valid + if !witness_set.is_valid_for(message) { + return Err(NssaError::InvalidInput( + "Invalid signature for given message and public key".into(), + )); + } + + let signer_addresses = self.signer_addresses(); + // Check nonces corresponds to the current nonces on the public state. + for (address, nonce) in signer_addresses.iter().zip(&message.nonces) { + let current_nonce = state.get_account_by_address(address).nonce; + if current_nonce != *nonce { + return Err(NssaError::InvalidInput("Nonce mismatch".into())); + } + } + + // Build pre_states for proof verification + let public_pre_states: Vec<_> = message + .public_addresses + .iter() + .map(|address| AccountWithMetadata { + account: state.get_account_by_address(address), + is_authorized: signer_addresses.contains(address), + }) + .collect(); + + let set_commitment = state.commitment_set_digest(); + + // 4. Proof verification + check_privacy_preserving_circuit_execution_proof_is_valid( + witness_set.proof, + &public_pre_states, + &message.public_post_states, + &message.encrypted_private_post_states, + &message.new_commitments, + &message.new_nullifiers, + set_commitment, + )?; + + // 5. Commitment freshness + state.check_commitments_are_new(&message.new_commitments)?; + + // 6. Nullifier uniqueness + state.check_nullifiers_are_new(&message.new_nullifiers)?; + + Ok(message + .public_addresses + .iter() + .cloned() + .zip(message.public_post_states.clone()) + .collect()) } pub fn message(&self) -> &Message { @@ -38,3 +129,21 @@ impl PrivacyPreservingTransaction { .collect() } } + +fn check_privacy_preserving_circuit_execution_proof_is_valid( + proof: (), + public_pre_states: &[AccountWithMetadata], + public_post_states: &[Account], + encrypted_private_post_states: &EncryptedAccountData, + new_commitments: &[nssa_core::account::Commitment], + new_nullifiers: &[nssa_core::account::Nullifier], + commitment_set_digest: CommitmentSetDigest, +) -> Result<(), NssaError> { + todo!() +} + +use std::hash::Hash; +fn n_unique(data: &[T]) -> usize { + let set: HashSet<&T> = data.iter().collect(); + set.len() +} diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 6a65cb6..7db63fd 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -1,8 +1,12 @@ use crate::{privacy_preserving_transaction::message::Message, PrivateKey, PublicKey, Signature}; + +type Proof = (); + #[derive(Debug, Clone, PartialEq, Eq)] pub struct WitnessSet { pub(super) signatures_and_public_keys: Vec<(Signature, PublicKey)>, + pub(super) proof: Proof } @@ -18,4 +22,8 @@ impl WitnessSet { pub fn signatures_and_public_keys(&self) -> &[(Signature, PublicKey)] { &self.signatures_and_public_keys } + + pub fn proof(&self) -> &Proof { + &self.proof + } } diff --git a/nssa/src/public_transaction/transaction.rs b/nssa/src/public_transaction/transaction.rs index bd16644..53d859c 100644 --- a/nssa/src/public_transaction/transaction.rs +++ b/nssa/src/public_transaction/transaction.rs @@ -50,7 +50,7 @@ impl PublicTransaction { hasher.finalize_fixed().into() } - pub(crate) fn validate_and_compute_post_states( + pub(crate) fn validate_and_produce_public_state_diff( &self, state: &V01State, ) -> Result, NssaError> { @@ -64,6 +64,7 @@ impl PublicTransaction { )); } + // Check exactly one nonce is provided for each signature if message.nonces.len() != witness_set.signatures_and_public_keys.len() { return Err(NssaError::InvalidInput( "Mismatch between number of nonces and signatures/public keys".into(), @@ -232,7 +233,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key1]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } @@ -252,7 +253,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } @@ -273,7 +274,7 @@ pub mod tests { let mut witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); witness_set.signatures_and_public_keys[0].0 = Signature::new_for_tests([1; 64]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } @@ -293,7 +294,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } @@ -309,7 +310,7 @@ pub mod tests { let witness_set = WitnessSet::for_message(&message, &[&key1, &key2]); let tx = PublicTransaction::new(message, witness_set); - let result = tx.validate_and_compute_post_states(&state); + let result = tx.validate_and_produce_public_state_diff(&state); assert!(matches!(result, Err(NssaError::InvalidInput(_)))) } } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 3e266d8..53e5c91 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -10,15 +10,16 @@ use nssa_core::{ use std::collections::{HashMap, HashSet}; struct CommitmentSet(HashSet); +pub type CommitmentSetDigest = [u32; 8]; impl CommitmentSet { - fn extend(&mut self, commitments: &[Commitment]) { + fn extend(&mut self, commitments: Vec) { self.0.extend(commitments) } - fn set_commitment(&self) -> [u8; 32] { + fn digest(&self) -> CommitmentSetDigest { // TODO: implement - [0; 32] + [0; 8] } } type NullifierSet = HashSet; @@ -65,7 +66,7 @@ impl V01State { &mut self, tx: &PublicTransaction, ) -> Result<(), NssaError> { - let state_diff = tx.validate_and_compute_post_states(self)?; + let state_diff = tx.validate_and_produce_public_state_diff(self)?; for (address, post) in state_diff.into_iter() { let current_account = self.get_account_by_address_mut(address); @@ -90,14 +91,15 @@ impl V01State { tx: &PrivacyPreservingTransaction, ) -> Result<(), NssaError> { // 1. Verify the transaction satisfies acceptance criteria - let public_state_diff = tx.validate(self)?; + let public_state_diff = tx.validate_and_produce_public_state_diff(self)?; + let message = tx.message(); // 2. Add new commitments - self.private_state.0.extend(message.new_commitments); + self.private_state.0.extend(message.new_commitments.clone()); // 3. Add new nullifiers - self.private_state.1.extend(message.new_nullifiers); + self.private_state.1.extend(message.new_nullifiers.clone()); // 4. Update public accounts for (address, post) in public_state_diff.into_iter() { @@ -127,6 +129,24 @@ impl V01State { pub(crate) fn builtin_programs(&self) -> &HashMap { &self.builtin_programs } + + pub fn commitment_set_digest(&self) -> CommitmentSetDigest { + self.private_state.0.digest() + } + + pub(crate) fn check_commitments_are_new( + &self, + new_commitments: &[Commitment], + ) -> Result<(), NssaError> { + todo!() + } + + pub(crate) fn check_nullifiers_are_new( + &self, + new_nullifiers: &[Nullifier], + ) -> Result<(), NssaError> { + todo!() + } } #[cfg(test)]