diff --git a/nssa/core/src/account/commitment.rs b/nssa/core/src/account/commitment.rs index a0bcfee..8f1b072 100644 --- a/nssa/core/src/account/commitment.rs +++ b/nssa/core/src/account/commitment.rs @@ -1,2 +1,13 @@ -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +use serde::{Deserialize, Serialize}; + +use crate::account::{Account, NullifierPublicKey}; + + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub struct Commitment([u8; 32]); + +impl Commitment { + pub fn new(Npk: &NullifierPublicKey, account: &Account) -> Self { + todo!() + } +} diff --git a/nssa/core/src/account/mod.rs b/nssa/core/src/account/mod.rs index 32b32da..722a4fb 100644 --- a/nssa/core/src/account/mod.rs +++ b/nssa/core/src/account/mod.rs @@ -6,7 +6,7 @@ mod commitment; mod nullifier; pub use commitment::Commitment; -pub use nullifier::Nullifier; +pub use nullifier::{Nullifier, NullifierPublicKey, NullifierSecretKey}; pub type Nonce = u128; type Data = Vec; diff --git a/nssa/core/src/account/nullifier.rs b/nssa/core/src/account/nullifier.rs index 4479af0..600093c 100644 --- a/nssa/core/src/account/nullifier.rs +++ b/nssa/core/src/account/nullifier.rs @@ -1,2 +1,23 @@ -#[derive(Debug, Clone, PartialEq, Eq, Hash)] +use serde::{Deserialize, Serialize}; + +use crate::account::Commitment; + +#[derive(Debug, Serialize, Deserialize, PartialEq, Eq)] +pub struct NullifierPublicKey([u8; 32]); + +impl From<&NullifierSecretKey> for NullifierPublicKey { + fn from(_value: &NullifierSecretKey) -> Self { + todo!() + } +} + +pub type NullifierSecretKey = [u8; 32]; + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, Hash)] pub struct Nullifier([u8; 32]); + +impl Nullifier { + pub fn new(commitment: &Commitment, nsk: &NullifierSecretKey) -> Self { + todo!() + } +} diff --git a/nssa/core/src/lib.rs b/nssa/core/src/lib.rs index d20620e..54556e5 100644 --- a/nssa/core/src/lib.rs +++ b/nssa/core/src/lib.rs @@ -1,2 +1,79 @@ +use serde::{Deserialize, Serialize}; + +use crate::{ + account::{ + Account, AccountWithMetadata, Commitment, Nonce, Nullifier, NullifierPublicKey, + NullifierSecretKey, + }, + program::{ProgramId, ProgramOutput}, +}; + pub mod account; pub mod program; + +pub type CommitmentSetDigest = [u32; 8]; +pub type MembershipProof = Vec<[u8; 32]>; +pub fn verify_membership_proof( + commitment: &Commitment, + proof: &MembershipProof, + digest: &CommitmentSetDigest, +) -> bool { + todo!() +} + +pub type IncomingViewingPublicKey = [u8; 32]; +pub type EphemeralSecretKey = [u8; 32]; +pub struct EphemeralPublicKey; + +impl From<&EphemeralSecretKey> for EphemeralPublicKey { + fn from(value: &EphemeralSecretKey) -> Self { + todo!() + } +} + +pub struct Tag(u8); +impl Tag { + pub fn new(Npk: &NullifierPublicKey, Ipk: &IncomingViewingPublicKey) -> Self { + todo!() + } +} + +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +pub struct EncryptedAccountData; + +impl EncryptedAccountData { + pub fn new( + account: &Account, + esk: &EphemeralSecretKey, + Npk: &NullifierPublicKey, + Ivk: &IncomingViewingPublicKey, + ) -> Self { + // TODO: implement + Self + } +} + +#[derive(Serialize, Deserialize)] +pub struct PrivacyPreservingCircuitInput { + pub program_output: ProgramOutput, + pub visibility_mask: Vec, + pub private_account_data: Vec<( + Nonce, + NullifierPublicKey, + IncomingViewingPublicKey, + EphemeralSecretKey, + )>, + pub private_account_auth: Vec<(NullifierSecretKey, MembershipProof)>, + pub program_id: ProgramId, + pub commitment_set_digest: CommitmentSetDigest, +} + +#[derive(Serialize, Deserialize)] +pub struct PrivacyPreservingCircuitOutput { + pub public_pre_states: Vec, + pub public_post_states: Vec, + pub encrypted_private_post_states: Vec, + pub new_commitments: Vec, + pub new_nullifiers: Vec, + pub commitment_set_digest: CommitmentSetDigest, +} diff --git a/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs new file mode 100644 index 0000000..6caebee --- /dev/null +++ b/nssa/program_methods/guest/src/bin/privacy_preserving_circuit.rs @@ -0,0 +1,118 @@ +use risc0_zkvm::{guest::env, serde::to_vec}; + +use nssa_core::{ + account::{Account, AccountWithMetadata, Commitment, Nullifier, NullifierPublicKey}, + program::{validate_execution, ProgramOutput}, + verify_membership_proof, EncryptedAccountData, EphemeralPublicKey, EphemeralSecretKey, + IncomingViewingPublicKey, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, Tag, +}; + +fn main() { + let PrivacyPreservingCircuitInput { + program_output, + visibility_mask, + private_account_data, + private_account_auth, + program_id, + commitment_set_digest, + } = env::read(); + + // TODO: Check that `program_execution_proof` is one of the allowed built-in programs + // assert!(BUILTIN_PROGRAM_IDS.contains(executing_program_id)); + + // Check that `program_output` is consistent with the execution of the corresponding program. + env::verify(program_id, &to_vec(&program_output).unwrap()).unwrap(); + + let ProgramOutput { + pre_states, + post_states, + } = program_output; + + // Check that the program is well behaved. + // See the # Programs section for the definition of the `validate_execution` method. + validate_execution(&pre_states, &post_states, program_id); + + let n_accounts = pre_states.len(); + assert_eq!(visibility_mask.len(), n_accounts); + + let n_private_accounts = visibility_mask.iter().filter(|&&flag| flag != 0).count(); + assert_eq!(private_account_data.len(), n_private_accounts); + + let n_auth_private_accounts = visibility_mask.iter().filter(|&&flag| flag == 1).count(); + assert_eq!(private_account_auth.len(), n_auth_private_accounts); + + // These lists will be the public outputs of this circuit + // and will be populated next. + let mut public_pre_states: Vec = Vec::new(); + let mut public_post_states: Vec = Vec::new(); + let mut encrypted_private_post_states: Vec = Vec::new(); + let mut new_commitments: Vec = Vec::new(); + let mut new_nullifiers: Vec = Vec::new(); + + for i in 0..n_accounts { + // visibility_mask[i] equal to 0 means public + if visibility_mask[i] == 0 { + // If the account is marked as public, add the pre and post + // states to the corresponding lists. + public_pre_states.push(pre_states[i].clone()); + public_post_states.push(post_states[i].clone()); + } else { + let (new_nonce, Npk, Ipk, esk) = &private_account_data[i]; + + // Verify authentication + if visibility_mask[i] == 1 { + let (nsk, membership_proof) = &private_account_auth[i]; + + // 1. Compute Npk from the provided nsk and assert it is equal to the provided Npk + let expected_Npk = NullifierPublicKey::from(nsk); + assert_eq!(&expected_Npk, Npk); + // 2. Compute the commitment of the pre_state account using the provided Npk + let commitment_pre = Commitment::new(Npk, &pre_states[i].account); + // 3. Verify that the commitment belongs to the global commitment set + assert!(verify_membership_proof( + &commitment_pre, + membership_proof, + &commitment_set_digest, + )); + // At this point the account is correctly authenticated as a private account. + // Assert that `pre_states` marked this account as authenticated. + assert!(pre_states[i].is_authorized); + // Compute the nullifier of the pre state version of this private account + // and include it in the `new_nullifiers` list. + let nullifier = Nullifier::new(&commitment_pre, nsk); + new_nullifiers.push(nullifier); + } else if visibility_mask[i] == 2 { + assert_eq!(pre_states[i].account, Account::default()); + assert!(!pre_states[i].is_authorized); + } else { + panic!(); + } + + // Update the nonce for the post state of this private account. + let mut post_with_updated_nonce = post_states[i].clone(); + post_with_updated_nonce.nonce = *new_nonce; + + // Compute the commitment of the post state of the private account, + // with the updated nonce, and include it in the `new_commitments` list. + let commitment_post = Commitment::new(Npk, &post_with_updated_nonce); + new_commitments.push(commitment_post); + + // Encrypt the post state of the private account with the updated + // nonce and include it in the `encrypted_private_post_states` list. + // + let encrypted_account = EncryptedAccountData::new(&post_with_updated_nonce, esk, Npk, Ipk); + encrypted_private_post_states.push(encrypted_account); + } + } + + let output = PrivacyPreservingCircuitOutput { + public_pre_states, + public_post_states, + encrypted_private_post_states, + new_commitments, + new_nullifiers, + commitment_set_digest, + }; + + env::commit(&output); +} diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 5bbdcd9..08cad42 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -1,10 +1,10 @@ -use nssa_core::account::{Account, Commitment, Nonce, Nullifier}; +use nssa_core::{ + EncryptedAccountData, + account::{Account, Commitment, Nonce, Nullifier}, +}; use crate::Address; -#[derive(Debug, Clone, PartialEq, Eq)] -pub struct EncryptedAccountData; - #[derive(Debug, Clone, PartialEq, Eq)] pub struct Message { pub(crate) public_addresses: Vec
, diff --git a/nssa/src/privacy_preserving_transaction/transaction.rs b/nssa/src/privacy_preserving_transaction/transaction.rs index b0cdbe2..33b1aa5 100644 --- a/nssa/src/privacy_preserving_transaction/transaction.rs +++ b/nssa/src/privacy_preserving_transaction/transaction.rs @@ -1,10 +1,9 @@ use std::collections::{HashMap, HashSet}; +use nssa_core::{CommitmentSetDigest, EncryptedAccountData}; 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; @@ -45,7 +44,7 @@ impl PrivacyPreservingTransaction { )); } - // Check there are no duplicate commitments in the new_nullifiers list + // Check there are no duplicate commitments in the new_commitments list if n_unique(&message.new_commitments) != message.new_commitments.len() { return Err(NssaError::InvalidInput( "Duplicate commitments found in message".into(), @@ -61,7 +60,7 @@ impl PrivacyPreservingTransaction { } // Check the signatures are valid - if !witness_set.is_valid_for(message) { + if !witness_set.signatures_are_valid_for(message) { return Err(NssaError::InvalidInput( "Invalid signature for given message and public key".into(), )); @@ -89,7 +88,7 @@ impl PrivacyPreservingTransaction { let set_commitment = state.commitment_set_digest(); // 4. Proof verification - check_privacy_preserving_circuit_execution_proof_is_valid( + check_privacy_preserving_circuit_proof_is_valid( witness_set.proof, &public_pre_states, &message.public_post_states, @@ -130,7 +129,7 @@ impl PrivacyPreservingTransaction { } } -fn check_privacy_preserving_circuit_execution_proof_is_valid( +fn check_privacy_preserving_circuit_proof_is_valid( proof: (), public_pre_states: &[AccountWithMetadata], public_post_states: &[Account], diff --git a/nssa/src/privacy_preserving_transaction/witness_set.rs b/nssa/src/privacy_preserving_transaction/witness_set.rs index 7db63fd..190099d 100644 --- a/nssa/src/privacy_preserving_transaction/witness_set.rs +++ b/nssa/src/privacy_preserving_transaction/witness_set.rs @@ -15,7 +15,7 @@ impl WitnessSet { todo!() } - pub fn is_valid_for(&self, message: &Message) -> bool { + pub fn signatures_are_valid_for(&self, message: &Message) -> bool { todo!() } diff --git a/nssa/src/state.rs b/nssa/src/state.rs index 53e5c91..aa42f3d 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -4,13 +4,13 @@ use crate::{ public_transaction::PublicTransaction, }; use nssa_core::{ + CommitmentSetDigest, account::{Account, Commitment, Nullifier}, program::{DEFAULT_PROGRAM_ID, ProgramId}, }; use std::collections::{HashMap, HashSet}; struct CommitmentSet(HashSet); -pub type CommitmentSetDigest = [u32; 8]; impl CommitmentSet { fn extend(&mut self, commitments: Vec) { @@ -22,6 +22,7 @@ impl CommitmentSet { [0; 8] } } + type NullifierSet = HashSet; pub struct V01State {