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)]