diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index b543a2f6..d22cb002 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -83,10 +83,10 @@ async fn private_transfer_to_foreign_account() -> Result<()> { .context("Failed to get private account commitment for sender")?; let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await; - assert_eq!(tx.message.new_commitments[0], new_commitment1); + assert_eq!(tx.message.private_actions[0].commitment, new_commitment1); - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments { + assert_eq!(tx.message.private_actions.len(), 2); + for commitment in tx.message.commitments() { assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); } @@ -172,10 +172,10 @@ async fn private_transfer_to_owned_account_using_claiming_path() -> Result<()> { .wallet() .get_private_account_commitment(from) .context("Failed to get private account commitment for sender")?; - assert_eq!(tx.message.new_commitments[0], sender_commitment); + assert_eq!(tx.message.private_actions[0].commitment, sender_commitment); - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments { + assert_eq!(tx.message.private_actions.len(), 2); + for commitment in tx.message.commitments() { assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); } @@ -251,7 +251,7 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> { assert!( verify_commitment_is_in_state( - tx.message.new_commitments[0].clone(), + tx.message.private_actions[0].commitment.clone(), ctx.sequencer_client() ) .await @@ -309,8 +309,8 @@ async fn private_transfer_to_owned_account_continuous_run_path() -> Result<()> { tokio::time::sleep(Duration::from_secs(TIME_TO_WAIT_FOR_BLOCK_SECONDS)).await; // Verify commitments are in state - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments { + assert_eq!(tx.message.private_actions.len(), 2); + for commitment in tx.message.commitments() { assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); } @@ -675,8 +675,9 @@ async fn init_with_dummy_commitment_root_produces_valid_root() -> Result<()> { let output = prove_init_with_commitment_root(&ctx, expected_digest).await?; - assert_eq!(output.new_nullifiers.len(), 1); - let (nullifier, digest) = &output.new_nullifiers[0]; + assert_eq!(output.private_actions.len(), 1); + let action = &output.private_actions[0]; + let (nullifier, digest) = (&action.nullifier, &action.root); assert_eq!( *nullifier, Nullifier::for_account_initialization(&recipient_account_id) @@ -701,14 +702,14 @@ async fn init_nullifier_digest_is_bound_to_commitment_root() -> Result<()> { let output_with_root = prove_init_with_commitment_root(&ctx, expected_digest).await?; let output_without_root = prove_init_with_commitment_root(&ctx, DUMMY_COMMITMENT_HASH).await?; - assert_eq!(output_with_root.new_nullifiers[0].1, expected_digest); + assert_eq!(output_with_root.private_actions[0].root, expected_digest); assert_eq!( - output_without_root.new_nullifiers[0].1, + output_without_root.private_actions[0].root, DUMMY_COMMITMENT_HASH ); assert_ne!( - output_with_root.new_nullifiers[0].1, - output_without_root.new_nullifiers[0].1, + output_with_root.private_actions[0].root, + output_without_root.private_actions[0].root, ); Ok(()) diff --git a/integration_tests/tests/keys.rs b/integration_tests/tests/keys.rs index 7fc73c62..21e1cde9 100644 --- a/integration_tests/tests/keys.rs +++ b/integration_tests/tests/keys.rs @@ -71,10 +71,10 @@ async fn sync_private_account_with_non_zero_chain_index() -> Result<()> { .wallet() .get_private_account_commitment(from) .context("Failed to get private account commitment for sender")?; - assert_eq!(tx.message.new_commitments[0], new_commitment1); + assert_eq!(tx.message.private_actions[0].commitment, new_commitment1); - assert_eq!(tx.message.new_commitments.len(), 2); - for commitment in tx.message.new_commitments { + assert_eq!(tx.message.private_actions.len(), 2); + for commitment in tx.message.commitments() { assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await); } diff --git a/lee/privacy_preserving_circuit/src/output.rs b/lee/privacy_preserving_circuit/src/output.rs index 74c1cccc..58258528 100644 --- a/lee/privacy_preserving_circuit/src/output.rs +++ b/lee/privacy_preserving_circuit/src/output.rs @@ -1,7 +1,8 @@ use lee_core::{ Commitment, CommitmentSetDigest, EncryptedAccountData, EncryptionScheme, EphemeralPublicKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey, - PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey, + PrivacyPreservingCircuitOutput, PrivateAccountKind, PrivateAction, PublicAction, + SharedSecretKey, account::{Account, AccountId, Nonce}, compute_digest_for_path, }; @@ -10,7 +11,6 @@ use crate::execution_state::ExecutionState; struct PrivateOutputHandler<'ctx> { output: &'ctx mut PrivacyPreservingCircuitOutput, - output_index: &'ctx mut u32, pre_state: &'ctx lee_core::account::AccountWithMetadata, post_state: Account, epk: &'ctx EphemeralPublicKey, @@ -35,10 +35,10 @@ impl PrivateOutputHandler<'_> { "Found new private account with non default values" ); - let (new_nullifier, new_nonce) = init_nullifier_and_nonce(&account_id, commitment_root); + let (nullifier, root, nonce) = init_nullifier_and_nonce(&account_id, commitment_root); let kind = PrivateAccountKind::Regular(self.identifier); - self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce); + self.emit_private_output(&account_id, &kind, nullifier, root, nonce); } fn authorized_update(self, nsk: &NullifierSecretKey, membership_proof: &MembershipProof) { @@ -51,20 +51,20 @@ impl PrivateOutputHandler<'_> { "Pre-state not authorized for authenticated private account" ); - let new_nullifier = compute_update_nullifier_and_set_digest( + let (nullifier, root) = compute_update_nullifier_and_set_digest( membership_proof, &self.pre_state.account, &account_id, nsk, ); - let new_nonce = self + let nonce = self .pre_state .account .nonce .private_account_nonce_increment(nsk); let kind = PrivateAccountKind::Regular(self.identifier); - self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce); + self.emit_private_output(&account_id, &kind, nullifier, root, nonce); } fn unauthorized(self, npk: &NullifierPublicKey, commitment_root: &CommitmentSetDigest) { @@ -81,10 +81,10 @@ impl PrivateOutputHandler<'_> { "Found new private account marked as authorized." ); - let (new_nullifier, new_nonce) = init_nullifier_and_nonce(&account_id, commitment_root); + let (nullifier, root, nonce) = init_nullifier_and_nonce(&account_id, commitment_root); let kind = PrivateAccountKind::Regular(self.identifier); - self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce); + self.emit_private_output(&account_id, &kind, nullifier, root, nonce); } fn pda_init( @@ -109,10 +109,8 @@ impl PrivateOutputHandler<'_> { "New private PDA must be default" ); - let (new_nullifier, new_nonce) = - init_nullifier_and_nonce(&self.pre_state.account_id, commitment_root); - let account_id = self.pre_state.account_id; + let (nullifier, root, nonce) = init_nullifier_and_nonce(&account_id, commitment_root); let (authority_program_id, seed) = pda_seed_by_position .get(&pos) .expect("PrivatePdaInit position must be in pda_seed_by_position"); @@ -122,7 +120,7 @@ impl PrivateOutputHandler<'_> { identifier: self.identifier, }; - self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce); + self.emit_private_output(&account_id, &kind, nullifier, root, nonce); } fn pda_update( @@ -144,13 +142,13 @@ impl PrivateOutputHandler<'_> { "PrivatePdaUpdate requires authorized pre_state or external seed" ); - let new_nullifier = compute_update_nullifier_and_set_digest( + let (nullifier, root) = compute_update_nullifier_and_set_digest( membership_proof, &self.pre_state.account, &self.pre_state.account_id, nsk, ); - let new_nonce = self + let nonce = self .pre_state .account .nonce @@ -166,55 +164,53 @@ impl PrivateOutputHandler<'_> { identifier: self.identifier, }; - self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce); + self.emit_private_output(&account_id, &kind, nullifier, root, nonce); } fn emit_private_output( self, account_id: &AccountId, kind: &PrivateAccountKind, - new_nullifier: (Nullifier, CommitmentSetDigest), - new_nonce: Nonce, + nullifier: Nullifier, + root: CommitmentSetDigest, + nonce: Nonce, ) { - self.output.new_nullifiers.push(new_nullifier); - let mut post_with_updated_nonce = self.post_state; - post_with_updated_nonce.nonce = new_nonce; + post_with_updated_nonce.nonce = nonce; + let output_index = u32::try_from(self.output.private_actions.len()) + .expect("Too many private accounts, output index overflow"); let commitment_post = Commitment::new(account_id, &post_with_updated_nonce); let encrypted_account = EncryptionScheme::encrypt( &post_with_updated_nonce, kind, self.ssk, &commitment_post, - *self.output_index, + output_index, ); - self.output.new_commitments.push(commitment_post); - self.output - .encrypted_private_post_states - .push(EncryptedAccountData { + self.output.private_actions.push(PrivateAction { + nullifier, + root, + commitment: commitment_post, + encrypted_post_state: EncryptedAccountData { ciphertext: encrypted_account, epk: self.epk.clone(), view_tag: self.view_tag, - }); - *self.output_index = self - .output_index - .checked_add(1) - .unwrap_or_else(|| panic!("Too many private accounts, output index overflow")); + }, + }); } } fn init_nullifier_and_nonce( account_id: &AccountId, commitment_root: &CommitmentSetDigest, -) -> ((Nullifier, CommitmentSetDigest), Nonce) { - let nullifier = ( +) -> (Nullifier, CommitmentSetDigest, Nonce) { + ( Nullifier::for_account_initialization(account_id), *commitment_root, - ); - let nonce = Nonce::private_account_nonce_init(account_id); - (nullifier, nonce) + Nonce::private_account_nonce_init(account_id), + ) } fn derive_and_verify_account_id( @@ -246,11 +242,8 @@ pub fn compute_circuit_output( let (block_validity_window, timestamp_validity_window, pda_seed_by_position, states_iter) = execution_state.into_parts(); let mut output = PrivacyPreservingCircuitOutput { - public_pre_states: Vec::new(), - public_post_states: Vec::new(), - encrypted_private_post_states: Vec::new(), - new_commitments: Vec::new(), - new_nullifiers: Vec::new(), + public_actions: Vec::new(), + private_actions: Vec::new(), block_validity_window, timestamp_validity_window, }; @@ -261,14 +254,15 @@ pub fn compute_circuit_output( "Invalid account_identities length" ); - let mut output_index = 0; for (pos, (account_identity, (pre_state, post_state))) in account_identities.iter().zip(states_iter).enumerate() { match account_identity { InputAccountIdentity::Public => { - output.public_pre_states.push(pre_state); - output.public_post_states.push(post_state); + output.public_actions.push(PublicAction { + pre: pre_state, + post: post_state, + }); } InputAccountIdentity::PrivateAuthorizedInit { epk, @@ -279,7 +273,6 @@ pub fn compute_circuit_output( commitment_root, } => PrivateOutputHandler { output: &mut output, - output_index: &mut output_index, pre_state: &pre_state, post_state, epk, @@ -297,7 +290,6 @@ pub fn compute_circuit_output( identifier, } => PrivateOutputHandler { output: &mut output, - output_index: &mut output_index, pre_state: &pre_state, post_state, epk, @@ -315,7 +307,6 @@ pub fn compute_circuit_output( commitment_root, } => PrivateOutputHandler { output: &mut output, - output_index: &mut output_index, pre_state: &pre_state, post_state, epk, @@ -334,7 +325,6 @@ pub fn compute_circuit_output( seed: _, } => PrivateOutputHandler { output: &mut output, - output_index: &mut output_index, pre_state: &pre_state, post_state, epk, @@ -353,7 +343,6 @@ pub fn compute_circuit_output( seed: external_seed, } => PrivateOutputHandler { output: &mut output, - output_index: &mut output_index, pre_state: &pre_state, post_state, epk, diff --git a/lee/state_machine/core/src/circuit_io.rs b/lee/state_machine/core/src/circuit_io.rs index 88b214d4..67021e2f 100644 --- a/lee/state_machine/core/src/circuit_io.rs +++ b/lee/state_machine/core/src/circuit_io.rs @@ -1,3 +1,4 @@ +use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use crate::{ @@ -131,18 +132,50 @@ impl InputAccountIdentity { } } +#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)] +#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))] +pub struct PrivateAction { + pub nullifier: Nullifier, + pub root: CommitmentSetDigest, + pub commitment: Commitment, + pub encrypted_post_state: EncryptedAccountData, +} + +#[derive(Serialize, Deserialize)] +#[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] +pub struct PublicAction { + pub pre: AccountWithMetadata, + pub post: Account, +} + #[derive(Serialize, Deserialize)] #[cfg_attr(any(feature = "host", test), derive(Debug, PartialEq, Eq))] 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<(Nullifier, CommitmentSetDigest)>, + pub public_actions: Vec, + pub private_actions: Vec, pub block_validity_window: BlockValidityWindow, pub timestamp_validity_window: TimestampValidityWindow, } +#[cfg(any(feature = "host", test))] +impl PrivacyPreservingCircuitOutput { + #[must_use] + pub fn commitments(&self) -> Vec { + self.private_actions + .iter() + .map(|action| action.commitment.clone()) + .collect() + } + + #[must_use] + pub fn nullifiers(&self) -> Vec<(Nullifier, CommitmentSetDigest)> { + self.private_actions + .iter() + .map(|action| (action.nullifier, action.root)) + .collect() + } +} + #[cfg(feature = "host")] impl PrivacyPreservingCircuitOutput { /// Serializes the circuit output to a byte vector. @@ -167,50 +200,57 @@ mod tests { #[test] fn privacy_preserving_circuit_output_to_bytes_is_compatible_with_from_slice() { let output = PrivacyPreservingCircuitOutput { - public_pre_states: vec![ - AccountWithMetadata::new( - Account { + public_actions: vec![ + PublicAction { + pre: AccountWithMetadata::new( + Account { + program_owner: [1, 2, 3, 4, 5, 6, 7, 8], + balance: 12_345_678_901_234_567_890, + data: b"test data".to_vec().try_into().unwrap(), + nonce: Nonce(0xFFFF_FFFF_FFFF_FFFE), + }, + true, + AccountId::new([0; 32]), + ), + post: Account { program_owner: [1, 2, 3, 4, 5, 6, 7, 8], - balance: 12_345_678_901_234_567_890, - data: b"test data".to_vec().try_into().unwrap(), - nonce: Nonce(0xFFFF_FFFF_FFFF_FFFE), + balance: 100, + data: b"post state data".to_vec().try_into().unwrap(), + nonce: Nonce(0xFFFF_FFFF_FFFF_FFFF), }, - true, - AccountId::new([0; 32]), - ), - AccountWithMetadata::new( - Account { - program_owner: [9, 9, 9, 8, 8, 8, 7, 7], - balance: 123_123_123_456_456_567_112, - data: b"test data".to_vec().try_into().unwrap(), - nonce: Nonce(9_999_999_999_999_999_999_999), + }, + PublicAction { + pre: AccountWithMetadata::new( + Account { + program_owner: [9, 9, 9, 8, 8, 8, 7, 7], + balance: 123_123_123_456_456_567_112, + data: b"test data".to_vec().try_into().unwrap(), + nonce: Nonce(9_999_999_999_999_999_999_999), + }, + false, + AccountId::new([1; 32]), + ), + post: Account { + program_owner: [2, 3, 4, 5, 6, 7, 8, 9], + balance: 200, + data: b"post state data 2".to_vec().try_into().unwrap(), + nonce: Nonce(0xFFFF_FFFF_FFFF_FFFD), }, - false, - AccountId::new([1; 32]), - ), + }, ], - public_post_states: vec![Account { - program_owner: [1, 2, 3, 4, 5, 6, 7, 8], - balance: 100, - data: b"post state data".to_vec().try_into().unwrap(), - nonce: Nonce(0xFFFF_FFFF_FFFF_FFFF), - }], - encrypted_private_post_states: vec![EncryptedAccountData { - ciphertext: Ciphertext(vec![255, 255, 1, 1, 2, 2]), - epk: EphemeralPublicKey(vec![9, 9, 9]), - view_tag: 42, - }], - new_commitments: vec![Commitment::new( - &AccountId::new([1; 32]), - &Account::default(), - )], - new_nullifiers: vec![( - Nullifier::for_account_update( + private_actions: vec![PrivateAction { + nullifier: Nullifier::for_account_update( &Commitment::new(&AccountId::new([2; 32]), &Account::default()), &[1; 32], ), - [0xab; 32], - )], + root: [0xab; 32], + commitment: Commitment::new(&AccountId::new([1; 32]), &Account::default()), + encrypted_post_state: EncryptedAccountData { + ciphertext: Ciphertext(vec![255, 255, 1, 1, 2, 2]), + epk: EphemeralPublicKey(vec![9, 9, 9]), + view_tag: 42, + }, + }], block_validity_window: (1..).into(), timestamp_validity_window: TimestampValidityWindow::new_unbounded(), }; diff --git a/lee/state_machine/core/src/lib.rs b/lee/state_machine/core/src/lib.rs index 900de667..a2f329d5 100644 --- a/lee/state_machine/core/src/lib.rs +++ b/lee/state_machine/core/src/lib.rs @@ -5,6 +5,7 @@ pub use circuit_io::{ InputAccountIdentity, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput, + PrivateAction, PublicAction, }; pub use commitment::{ Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, MembershipProof, diff --git a/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs b/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs index 970bfbd4..1b2cd7cc 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs @@ -24,9 +24,9 @@ fn decrypt_kind( idx: usize, ) -> PrivateAccountKind { let (kind, _) = EncryptionScheme::decrypt( - &output.encrypted_private_post_states[idx].ciphertext, + &output.private_actions[idx].encrypted_post_state.ciphertext, ssk, - &output.new_commitments[idx], + &output.private_actions[idx].commitment, u32::try_from(idx).expect("idx fits in u32"), ) .unwrap(); @@ -105,18 +105,16 @@ fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() assert!(proof.is_valid_for(&output)); - let [sender_pre] = output.public_pre_states.try_into().unwrap(); - let [sender_post] = output.public_post_states.try_into().unwrap(); + let [action] = output.public_actions.try_into().unwrap(); + let (sender_pre, sender_post) = (action.pre, action.post); assert_eq!(sender_pre, expected_sender_pre); assert_eq!(sender_post, expected_sender_post); - assert_eq!(output.new_commitments.len(), 1); - assert_eq!(output.new_nullifiers.len(), 1); - assert_eq!(output.encrypted_private_post_states.len(), 1); + assert_eq!(output.private_actions.len(), 1); let (_identifier, recipient_post) = EncryptionScheme::decrypt( - &output.encrypted_private_post_states[0].ciphertext, + &output.private_actions[0].encrypted_post_state.ciphertext, &shared_secret, - &output.new_commitments[0], + &output.private_actions[0].commitment, 0, ) .unwrap(); @@ -219,14 +217,13 @@ fn prove_privacy_preserving_execution_circuit_fully_private() { .unwrap(); assert!(proof.is_valid_for(&output)); - assert!(output.public_pre_states.is_empty()); - assert!(output.public_post_states.is_empty()); - assert_eq!(output.new_commitments, expected_new_commitments); - assert_eq!(output.new_nullifiers, expected_new_nullifiers); - assert_eq!(output.encrypted_private_post_states.len(), 2); + assert!(output.public_actions.is_empty()); + assert_eq!(output.commitments(), expected_new_commitments); + assert_eq!(output.nullifiers(), expected_new_nullifiers); + assert_eq!(output.private_actions.len(), 2); let (_identifier, sender_post) = EncryptionScheme::decrypt( - &output.encrypted_private_post_states[0].ciphertext, + &output.private_actions[0].encrypted_post_state.ciphertext, &shared_secret_1, &expected_new_commitments[0], 0, @@ -235,7 +232,7 @@ fn prove_privacy_preserving_execution_circuit_fully_private() { assert_eq!(sender_post, expected_private_account_1); let (_identifier, recipient_post) = EncryptionScheme::decrypt( - &output.encrypted_private_post_states[1].ciphertext, + &output.private_actions[1].encrypted_post_state.ciphertext, &shared_secret_2, &expected_new_commitments[1], 1, @@ -373,7 +370,7 @@ fn private_pda_init() { ); let (output, _proof) = result.expect("PDA init should succeed"); - assert_eq!(output.new_commitments.len(), 1); + assert_eq!(output.private_actions.len(), 1); } /// PDA withdraw: chains to `simple_balance_transfer` to move balance from PDA to recipient. @@ -431,7 +428,7 @@ fn private_pda_withdraw() { ); let (output, _proof) = result.expect("PDA withdraw should succeed"); - assert_eq!(output.new_commitments.len(), 1); + assert_eq!(output.private_actions.len(), 1); } /// Shared regular private account: receives funds via `authenticated_transfer` directly, @@ -486,7 +483,7 @@ fn shared_account_receives_via_simple_transfer() { let (output, _proof) = result.expect("shared account receive should succeed"); // Sender is public (no commitment), recipient is private (1 commitment) - assert_eq!(output.new_commitments.len(), 1); + assert_eq!(output.private_actions.len(), 1); } /// `PrivateAuthorizedInit` with a non-default identifier produces a ciphertext that decrypts diff --git a/lee/state_machine/src/privacy_preserving_transaction/message.rs b/lee/state_machine/src/privacy_preserving_transaction/message.rs index b2594912..61112b42 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/message.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/message.rs @@ -1,24 +1,27 @@ use borsh::{BorshDeserialize, BorshSerialize}; use lee_core::{ - Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput, + Commitment, CommitmentSetDigest, Nullifier, PrivacyPreservingCircuitOutput, PrivateAction, account::{Account, Nonce}, program::{BlockValidityWindow, TimestampValidityWindow}, }; pub use lee_core::{EncryptedAccountData, ViewTag}; use sha2::{Digest as _, Sha256}; -use crate::{AccountId, error::LeeError}; +use crate::AccountId; const PREFIX: &[u8; 32] = b"/LEE/v0.3/Message/Privacy/\x00\x00\x00\x00\x00\x00"; +#[derive(Debug, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +pub struct PublicActionWithOwner { + pub account_id: AccountId, + pub post_state: Account, +} + #[derive(Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] pub struct Message { - pub public_account_ids: Vec, + pub public_actions: Vec, pub nonces: Vec, - pub public_post_states: Vec, - pub encrypted_private_post_states: Vec, - pub new_commitments: Vec, - pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>, + pub private_actions: Vec, pub block_validity_window: BlockValidityWindow, pub timestamp_validity_window: TimestampValidityWindow, } @@ -31,21 +34,22 @@ impl std::fmt::Debug for Message { write!(f, "{}", hex::encode(self.0)) } } - let nullifiers: Vec<_> = self - .new_nullifiers + let private_actions: Vec<_> = self + .private_actions .iter() - .map(|(n, d)| (n, HexDigest(d))) + .map(|a| { + ( + &a.nullifier, + HexDigest(&a.root), + &a.commitment, + &a.encrypted_post_state, + ) + }) .collect(); f.debug_struct("Message") - .field("public_account_ids", &self.public_account_ids) + .field("public_actions", &self.public_actions) .field("nonces", &self.nonces) - .field("public_post_states", &self.public_post_states) - .field( - "encrypted_private_post_states", - &self.encrypted_private_post_states, - ) - .field("new_commitments", &self.new_commitments) - .field("new_nullifiers", &nullifiers) + .field("private_actions", &private_actions) .field("block_validity_window", &self.block_validity_window) .field("timestamp_validity_window", &self.timestamp_validity_window) .finish() @@ -53,21 +57,55 @@ impl std::fmt::Debug for Message { } impl Message { - pub fn try_from_circuit_output( - public_account_ids: Vec, - nonces: Vec, - output: PrivacyPreservingCircuitOutput, - ) -> Result { - Ok(Self { - public_account_ids, + #[must_use] + pub fn from_circuit_output(nonces: Vec, output: PrivacyPreservingCircuitOutput) -> Self { + let public_actions = output + .public_actions + .into_iter() + .map(|action| PublicActionWithOwner { + account_id: action.pre.account_id, + post_state: action.post, + }) + .collect(); + Self { + public_actions, nonces, - public_post_states: output.public_post_states, - encrypted_private_post_states: output.encrypted_private_post_states, - new_commitments: output.new_commitments, - new_nullifiers: output.new_nullifiers, + private_actions: output.private_actions, block_validity_window: output.block_validity_window, timestamp_validity_window: output.timestamp_validity_window, - }) + } + } + + #[must_use] + pub fn commitments(&self) -> Vec { + self.private_actions + .iter() + .map(|action| action.commitment.clone()) + .collect() + } + + #[must_use] + pub fn nullifiers(&self) -> Vec<(Nullifier, CommitmentSetDigest)> { + self.private_actions + .iter() + .map(|action| (action.nullifier, action.root)) + .collect() + } + + #[must_use] + pub fn public_account_ids(&self) -> Vec { + self.public_actions + .iter() + .map(|action| action.account_id) + .collect() + } + + #[must_use] + pub fn public_post_states(&self) -> Vec { + self.public_actions + .iter() + .map(|action| action.post_state.clone()) + .collect() } #[must_use] @@ -89,15 +127,15 @@ impl Message { #[cfg(test)] pub mod tests { use lee_core::{ - Commitment, EncryptionScheme, Nullifier, NullifierPublicKey, PrivateAccountKind, - SharedSecretKey, + Commitment, EncryptionScheme, EphemeralPublicKey, Nullifier, NullifierPublicKey, + PrivateAccountKind, PrivateAction, SharedSecretKey, account::{Account, AccountId, Nonce}, - encryption::ViewingPublicKey, + encryption::{Ciphertext, ViewingPublicKey}, program::{BlockValidityWindow, TimestampValidityWindow}, }; use sha2::{Digest as _, Sha256}; - use super::{EncryptedAccountData, Message, PREFIX}; + use super::{EncryptedAccountData, Message, PREFIX, PublicActionWithOwner}; #[must_use] pub fn message_for_tests() -> Message { @@ -110,31 +148,31 @@ pub mod tests { let npk1 = NullifierPublicKey::from(&nsk1); let npk2 = NullifierPublicKey::from(&nsk2); - let public_account_ids = vec![AccountId::new([1; 32])]; - let nonces = vec![1_u128.into(), 2_u128.into(), 3_u128.into()]; - let public_post_states = vec![Account::default()]; - - let encrypted_private_post_states = Vec::new(); - let account_id2 = lee_core::account::AccountId::for_regular_private_account(&npk2, 0); - let new_commitments = vec![Commitment::new(&account_id2, &account2)]; + let commitment = Commitment::new(&account_id2, &account2); let account_id1 = lee_core::account::AccountId::for_regular_private_account(&npk1, 0); let old_commitment = Commitment::new(&account_id1, &account1); - let new_nullifiers = vec![( - Nullifier::for_account_update(&old_commitment, &nsk1), - [0; 32], - )]; + let nullifier = Nullifier::for_account_update(&old_commitment, &nsk1); Message { - public_account_ids, + public_actions: vec![PublicActionWithOwner { + account_id: AccountId::new([1; 32]), + post_state: Account::default(), + }], nonces, - public_post_states, - encrypted_private_post_states, - new_commitments, - new_nullifiers, + private_actions: vec![PrivateAction { + nullifier, + root: [0; 32], + commitment, + encrypted_post_state: EncryptedAccountData { + ciphertext: Ciphertext::from_inner(vec![]), + epk: EphemeralPublicKey(vec![]), + view_tag: 0, + }, + }], block_validity_window: BlockValidityWindow::new_unbounded(), timestamp_validity_window: TimestampValidityWindow::new_unbounded(), } @@ -143,31 +181,24 @@ pub mod tests { #[test] fn hash_privacy_pinned() { let msg = Message { - public_account_ids: vec![AccountId::new([42_u8; 32])], + public_actions: vec![], nonces: vec![Nonce(5)], - public_post_states: vec![], - encrypted_private_post_states: vec![], - new_commitments: vec![], - new_nullifiers: vec![], + private_actions: vec![], block_validity_window: BlockValidityWindow::new_unbounded(), timestamp_validity_window: TimestampValidityWindow::new_unbounded(), }; - let public_account_ids_bytes: &[u8] = &[42_u8; 32]; + // empty vec fields: u32 len=0 + let public_actions_bytes: &[u8] = &[0, 0, 0, 0]; let nonces_bytes: &[u8] = &[1, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; - // all remaining vec fields are empty: u32 len=0 - let empty_vec_bytes: &[u8] = &[0_u8; 4]; + let private_actions_bytes: &[u8] = &[0, 0, 0, 0]; // validity windows: unbounded = {from: None (0_u8), to: None (0_u8)} - let unbounded_window_bytes: &[u8] = &[0_u8; 2]; + let unbounded_window_bytes: &[u8] = &[0, 0]; let expected_borsh_vec: Vec = [ - &[1_u8, 0, 0, 0], // public_account_ids - public_account_ids_bytes, + public_actions_bytes, nonces_bytes, - empty_vec_bytes, // public_post_state - empty_vec_bytes, // encrypted_private_post_states - empty_vec_bytes, // new_commitments - empty_vec_bytes, // new_nullifiers + private_actions_bytes, unbounded_window_bytes, // block_validity_window unbounded_window_bytes, // timestamp_validity_window ] diff --git a/lee/state_machine/src/privacy_preserving_transaction/transaction.rs b/lee/state_machine/src/privacy_preserving_transaction/transaction.rs index 055d65c1..5b5dbfec 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/transaction.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/transaction.rs @@ -53,7 +53,7 @@ impl PrivacyPreservingTransaction { .signer_account_ids() .into_iter() .collect::>(); - acc_set.extend(&self.message.public_account_ids); + acc_set.extend(self.message.public_actions.iter().map(|action| action.account_id)); acc_set.into_iter().collect() } diff --git a/lee/state_machine/src/state/tests/circuit.rs b/lee/state_machine/src/state/tests/circuit.rs index 0860e98b..ea2ff50c 100644 --- a/lee/state_machine/src/state/tests/circuit.rs +++ b/lee/state_machine/src/state/tests/circuit.rs @@ -481,11 +481,8 @@ fn private_pda_claim_succeeds() { ); let (output, _proof) = result.expect("private PDA claim should succeed"); - assert_eq!(output.new_nullifiers.len(), 1); - assert_eq!(output.new_commitments.len(), 1); - assert_eq!(output.encrypted_private_post_states.len(), 1); - assert!(output.public_pre_states.is_empty()); - assert!(output.public_post_states.is_empty()); + assert_eq!(output.private_actions.len(), 1); + assert!(output.public_actions.is_empty()); } /// An npk is supplied that does not match the `pre_state`'s `account_id` under @@ -563,8 +560,7 @@ fn caller_pda_seeds_authorize_private_pda_for_callee() { let (output, _proof) = result.expect("caller-seeds authorization of private PDA should succeed"); - assert_eq!(output.new_commitments.len(), 1); - assert_eq!(output.new_nullifiers.len(), 1); + assert_eq!(output.private_actions.len(), 1); } /// The delegator chains with a different seed than the one it claimed with. In the callee @@ -843,7 +839,7 @@ fn private_authorized_uninitialized_account() { .unwrap(); // Create message from circuit output - let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); + let message = Message::from_circuit_output(vec![], output); let witness_set = WitnessSet::for_message(&message, proof, &[]); @@ -890,7 +886,7 @@ fn private_unauthorized_uninitialized_account_can_still_be_claimed() { ) .unwrap(); - let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); + let message = Message::from_circuit_output(vec![], output); let witness_set = WitnessSet::for_message(&message, proof, &[]); let tx = PrivacyPreservingTransaction::new(message, witness_set); @@ -942,7 +938,7 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() { ) .unwrap(); - let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); + let message = Message::from_circuit_output(vec![], output); let witness_set = WitnessSet::for_message(&message, proof, &[]); let tx = PrivacyPreservingTransaction::new(message, witness_set); @@ -1062,7 +1058,7 @@ fn two_private_pda_family_members_receive_and_spend() { ) .unwrap(); let message = - Message::try_from_circuit_output(vec![funder_id], vec![funder_nonce], output).unwrap(); + Message::from_circuit_output(vec![funder_nonce], output); let witness_set = WitnessSet::for_message(&message, proof, &[&funder_keys.signing_key]); state .transition_from_privacy_preserving_transaction( @@ -1099,7 +1095,7 @@ fn two_private_pda_family_members_receive_and_spend() { ) .unwrap(); let message = - Message::try_from_circuit_output(vec![funder_id], vec![funder_nonce], output).unwrap(); + Message::from_circuit_output(vec![funder_nonce], output); let witness_set = WitnessSet::for_message(&message, proof, &[&funder_keys.signing_key]); state .transition_from_privacy_preserving_transaction( @@ -1143,7 +1139,7 @@ fn two_private_pda_family_members_receive_and_spend() { ) .unwrap(); let message = - Message::try_from_circuit_output(vec![recipient_id], vec![Nonce(0)], output).unwrap(); + Message::from_circuit_output(vec![Nonce(0)], output); let witness_set = WitnessSet::for_message(&message, proof, &[&recipient_signing_key]); state .transition_from_privacy_preserving_transaction( @@ -1180,7 +1176,7 @@ fn two_private_pda_family_members_receive_and_spend() { &spend_with_deps, ) .unwrap(); - let message = Message::try_from_circuit_output(vec![recipient_id], vec![], output).unwrap(); + let message = Message::from_circuit_output(vec![], output); let witness_set = WitnessSet::for_message(&message, proof, &[]); state .transition_from_privacy_preserving_transaction( @@ -1232,9 +1228,7 @@ fn two_private_pda_family_members_receive_and_spend() { &crate::test_methods::simple_balance_transfer().into(), ) .unwrap(); - let message = - Message::try_from_circuit_output(vec![recipient_id], vec![recipient_nonce], output) - .unwrap(); + let message = Message::from_circuit_output(vec![recipient_nonce], output); let witness_set = WitnessSet::for_message(&message, proof, &[&recipient_signing_key]); state .transition_from_privacy_preserving_transaction( diff --git a/lee/state_machine/src/state/tests/claiming.rs b/lee/state_machine/src/state/tests/claiming.rs index aba5c447..55457fa8 100644 --- a/lee/state_machine/src/state/tests/claiming.rs +++ b/lee/state_machine/src/state/tests/claiming.rs @@ -342,9 +342,7 @@ fn authorized_public_account_claiming_succeeds_when_executed_privately() { ) .unwrap(); - let message = - Message::try_from_circuit_output(vec![recipient_account_id], vec![Nonce(0)], output) - .unwrap(); + let message = Message::from_circuit_output(vec![Nonce(0)], output); let witness_set = WitnessSet::for_message(&message, proof, &[&recipient_private_key]); let tx = PrivacyPreservingTransaction::new(message, witness_set); @@ -475,7 +473,7 @@ fn private_chained_call(number_of_calls: u32) { ) .unwrap(); - let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); + let message = Message::from_circuit_output(vec![], output); let witness_set = WitnessSet::for_message(&message, proof, &[]); let transaction = PrivacyPreservingTransaction::new(message, witness_set); diff --git a/lee/state_machine/src/state/tests/mod.rs b/lee/state_machine/src/state/tests/mod.rs index f6b56be6..499f5167 100644 --- a/lee/state_machine/src/state/tests/mod.rs +++ b/lee/state_machine/src/state/tests/mod.rs @@ -293,12 +293,7 @@ fn shielded_balance_transfer_for_tests( ) .unwrap(); - let message = Message::try_from_circuit_output( - vec![sender_keys.account_id()], - vec![sender_nonce], - output, - ) - .unwrap(); + let message = Message::from_circuit_output(vec![sender_nonce], output); let witness_set = WitnessSet::for_message(&message, proof, &[&sender_keys.signing_key]); PrivacyPreservingTransaction::new(message, witness_set) @@ -361,7 +356,7 @@ fn private_balance_transfer_for_tests( ) .unwrap(); - let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); + let message = Message::from_circuit_output(vec![], output); let witness_set = WitnessSet::for_message(&message, proof, &[]); @@ -416,7 +411,7 @@ fn deshielded_balance_transfer_for_tests( .unwrap(); let message = - Message::try_from_circuit_output(vec![*recipient_account_id], vec![], output).unwrap(); + Message::from_circuit_output(vec![], output); let witness_set = WitnessSet::for_message(&message, proof, &[]); diff --git a/lee/state_machine/src/state/tests/privacy_preserving.rs b/lee/state_machine/src/state/tests/privacy_preserving.rs index 0205b5fd..2606ae2b 100644 --- a/lee/state_machine/src/state/tests/privacy_preserving.rs +++ b/lee/state_machine/src/state/tests/privacy_preserving.rs @@ -26,7 +26,7 @@ fn transition_from_privacy_preserving_transaction_shielded() { this }; - let [expected_new_commitment] = tx.message().new_commitments.clone().try_into().unwrap(); + let [expected_new_commitment] = tx.message().commitments().try_into().unwrap(); assert!(!state.private_state.0.contains(&expected_new_commitment)); state @@ -126,7 +126,7 @@ fn privacy_tampered_epk_is_rejected() { ); // Flip a byte of the first note's epk - tx.message.encrypted_private_post_states[0].epk.0[0] ^= 0xFF; + tx.message.private_actions[0].encrypted_post_state.epk.0[0] ^= 0xFF; assert!( matches!( @@ -152,7 +152,7 @@ fn privacy_tampered_view_tag_is_rejected() { ); // Flip the first note's view_tag - tx.message.encrypted_private_post_states[0].view_tag ^= 0xFF; + tx.message.private_actions[0].encrypted_post_state.view_tag ^= 0xFF; assert!( matches!( diff --git a/lee/state_machine/src/state/tests/validity_window.rs b/lee/state_machine/src/state/tests/validity_window.rs index 5a46bd5b..ba89fbc2 100644 --- a/lee/state_machine/src/state/tests/validity_window.rs +++ b/lee/state_machine/src/state/tests/validity_window.rs @@ -152,7 +152,7 @@ fn validity_window_works_in_privacy_preserving_transactions( ) .unwrap(); - let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); + let message = Message::from_circuit_output(vec![], output); let witness_set = WitnessSet::for_message(&message, proof, &[]); PrivacyPreservingTransaction::new(message, witness_set) @@ -220,7 +220,7 @@ fn timestamp_validity_window_works_in_privacy_preserving_transactions( ) .unwrap(); - let message = Message::try_from_circuit_output(vec![], vec![], output).unwrap(); + let message = Message::from_circuit_output(vec![], output); let witness_set = WitnessSet::for_message(&message, proof, &[]); PrivacyPreservingTransaction::new(message, witness_set) diff --git a/lee/state_machine/src/validated_state_diff/mod.rs b/lee/state_machine/src/validated_state_diff/mod.rs index 8fae4fee..755d2f12 100644 --- a/lee/state_machine/src/validated_state_diff/mod.rs +++ b/lee/state_machine/src/validated_state_diff/mod.rs @@ -4,7 +4,7 @@ use std::{ }; use lee_core::{ - BlockId, Commitment, Nullifier, PrivacyPreservingCircuitOutput, Timestamp, + BlockId, Commitment, Nullifier, PrivacyPreservingCircuitOutput, PublicAction, Timestamp, account::{Account, AccountId, AccountWithMetadata}, program::{ ChainedCall, Claim, DEFAULT_PROGRAM_ID, ProgramId, compute_public_authorized_pdas, @@ -310,10 +310,13 @@ impl ValidatedStateDiff { ) -> Result { let message = &tx.message; let witness_set = &tx.witness_set; + let commitments = message.commitments(); + let nullifiers = message.nullifiers(); + let public_account_ids = message.public_account_ids(); // 1. Commitments or nullifiers are non empty ensure!( - !message.new_commitments.is_empty() || !message.new_nullifiers.is_empty(), + !message.private_actions.is_empty(), LeeError::InvalidInput( "Empty commitments and empty nullifiers found in message".into(), ) @@ -321,25 +324,19 @@ impl ValidatedStateDiff { // 2. Check there are no duplicate account_ids in the public_account_ids list. ensure!( - n_unique(&message.public_account_ids) == message.public_account_ids.len(), + n_unique(&public_account_ids) == public_account_ids.len(), LeeError::InvalidInput("Duplicate account_ids found in message".into()) ); // Check there are no duplicate nullifiers in the new_nullifiers list ensure!( - n_unique( - &message - .new_nullifiers - .iter() - .map(|(n, _)| n) - .collect::>() - ) == message.new_nullifiers.len(), + n_unique(&nullifiers.iter().map(|(n, _)| n).collect::>()) == nullifiers.len(), LeeError::InvalidInput("Duplicate nullifiers found in message".into()) ); // Check there are no duplicate commitments in the new_commitments list ensure!( - n_unique(&message.new_commitments) == message.new_commitments.len(), + n_unique(&commitments) == commitments.len(), LeeError::InvalidInput("Duplicate commitments found in message".into()) ); @@ -376,8 +373,7 @@ impl ValidatedStateDiff { ); // Build pre_states for proof verification - let public_pre_states: Vec<_> = message - .public_account_ids + let public_pre_states: Vec<_> = public_account_ids .iter() .map(|account_id| { AccountWithMetadata::new( @@ -396,28 +392,22 @@ impl ValidatedStateDiff { )?; // 5. Commitment freshness - state.check_commitments_are_new(&message.new_commitments)?; + state.check_commitments_are_new(&commitments)?; // 6. Nullifier uniqueness - state.check_nullifiers_are_valid(&message.new_nullifiers)?; + state.check_nullifiers_are_valid(&nullifiers)?; - let public_diff = message - .public_account_ids + let public_diff = public_account_ids .iter() .copied() - .zip(message.public_post_states.clone()) - .collect(); - let new_nullifiers = message - .new_nullifiers - .iter() - .copied() - .map(|(nullifier, _)| nullifier) + .zip(message.public_post_states()) .collect(); + let new_nullifiers = nullifiers.iter().map(|(nullifier, _)| *nullifier).collect(); Ok(Self(StateDiff { signer_account_ids, public_diff, - new_commitments: message.new_commitments.clone(), + new_commitments: commitments, new_nullifiers, program: None, })) @@ -498,11 +488,13 @@ fn check_privacy_preserving_circuit_proof_is_valid( message: &Message, ) -> Result<(), LeeError> { let output = PrivacyPreservingCircuitOutput { - public_pre_states: public_pre_states.to_vec(), - public_post_states: message.public_post_states.clone(), - encrypted_private_post_states: message.encrypted_private_post_states.clone(), - new_commitments: message.new_commitments.clone(), - new_nullifiers: message.new_nullifiers.clone(), + public_actions: public_pre_states + .iter() + .cloned() + .zip(message.public_post_states()) + .map(|(pre, post)| PublicAction { pre, post }) + .collect(), + private_actions: message.private_actions.clone(), block_validity_window: message.block_validity_window, timestamp_validity_window: message.timestamp_validity_window, }; diff --git a/lee/state_machine/src/validated_state_diff/tests.rs b/lee/state_machine/src/validated_state_diff/tests.rs index 3cb61e7d..7690abb8 100644 --- a/lee/state_machine/src/validated_state_diff/tests.rs +++ b/lee/state_machine/src/validated_state_diff/tests.rs @@ -191,12 +191,10 @@ fn privacy_malicious_programs_cannot_drain_public_victim() { // public_account_ids lists the Public entries from account_identities, in order. // The single ciphertext belongs to attacker's private account update. - let message = Message::try_from_circuit_output( - vec![victim_id, recipient_id], + let message = Message::from_circuit_output( vec![], // no public signers, no nonces circuit_output, - ) - .unwrap(); + ); let witness_set = WitnessSet::for_message(&message, proof, &[]); // no signatures let tx = PrivacyPreservingTransaction::new(message, witness_set); @@ -355,12 +353,10 @@ fn privacy_malicious_programs_cannot_drain_private_victim() { // public_account_ids lists the Public entries from account_identities, in order. // The single ciphertext belongs to attacker's private account update. - let message = Message::try_from_circuit_output( - vec![victim_id, recipient_id], + let message = Message::from_circuit_output( vec![], // no public signers, no nonces circuit_output, - ) - .unwrap(); + ); let witness_set = WitnessSet::for_message(&message, proof, &[]); // no signatures let tx = PrivacyPreservingTransaction::new(message, witness_set); @@ -485,8 +481,9 @@ fn malicious_programs_cannot_drain_victim_without_signature() { #[test] fn privacy_garbage_proof_is_rejected() { use lee_core::{ - Commitment, + Commitment, EncryptedAccountData, Nullifier, PrivateAction, account::Account, + encryption::{Ciphertext, EphemeralPublicKey}, program::{BlockValidityWindow, TimestampValidityWindow}, }; @@ -508,12 +505,18 @@ fn privacy_garbage_proof_is_rejected() { )); let commitment = Commitment::new(&account_id, &Account::default()); let message = Message { - public_account_ids: vec![], + public_actions: vec![], nonces: vec![], - public_post_states: vec![], - encrypted_private_post_states: vec![], - new_commitments: vec![commitment], - new_nullifiers: vec![], + private_actions: vec![PrivateAction { + nullifier: Nullifier::for_account_initialization(&account_id), + root: [0; 32], + commitment, + encrypted_post_state: EncryptedAccountData { + ciphertext: Ciphertext::from_inner(vec![]), + epk: EphemeralPublicKey(vec![]), + view_tag: 0, + }, + }], block_validity_window: BlockValidityWindow::new_unbounded(), timestamp_validity_window: TimestampValidityWindow::new_unbounded(), }; diff --git a/lez/indexer/service/protocol/src/convert.rs b/lez/indexer/service/protocol/src/convert.rs index cd0bff7e..85593ba7 100644 --- a/lez/indexer/service/protocol/src/convert.rs +++ b/lez/indexer/service/protocol/src/convert.rs @@ -281,27 +281,24 @@ impl From for lee::public_transaction::Message { impl From for PrivacyPreservingMessage { fn from(value: lee::privacy_preserving_transaction::message::Message) -> Self { let lee::privacy_preserving_transaction::message::Message { - public_account_ids, + public_actions, nonces, - public_post_states, - encrypted_private_post_states, - new_commitments, - new_nullifiers, + private_actions, block_validity_window, timestamp_validity_window, } = value; Self { - public_account_ids: public_account_ids.into_iter().map(Into::into).collect(), + public_account_ids: public_actions.iter().map(|a| a.account_id.into()).collect(), nonces: nonces.iter().map(|x| x.0).collect(), - public_post_states: public_post_states.into_iter().map(Into::into).collect(), - encrypted_private_post_states: encrypted_private_post_states - .into_iter() - .map(Into::into) + public_post_states: public_actions.into_iter().map(|a| a.post_state.into()).collect(), + encrypted_private_post_states: private_actions + .iter() + .map(|a| a.encrypted_post_state.clone().into()) .collect(), - new_commitments: new_commitments.into_iter().map(Into::into).collect(), - new_nullifiers: new_nullifiers + new_commitments: private_actions.iter().map(|a| a.commitment.clone().into()).collect(), + new_nullifiers: private_actions .into_iter() - .map(|(n, d)| (n.into(), d.into())) + .map(|a| (a.nullifier.into(), a.root.into())) .collect(), block_validity_window: block_validity_window.into(), timestamp_validity_window: timestamp_validity_window.into(), @@ -323,26 +320,50 @@ impl TryFrom for lee::privacy_preserving_transaction:: block_validity_window, timestamp_validity_window, } = value; + + let public_post_states = public_post_states + .into_iter() + .map(TryInto::try_into) + .collect::, _>>() + .map_err(|e| lee::error::LeeError::InvalidInput(format!("{e}")))?; + let public_actions = public_account_ids + .into_iter() + .map(Into::into) + .zip(public_post_states) + .map(|(account_id, post_state)| { + lee::privacy_preserving_transaction::message::PublicActionWithOwner { + account_id, + post_state, + } + }) + .collect(); + + let private_actions = new_commitments + .into_iter() + .map(Into::into) + .zip( + new_nullifiers + .into_iter() + .map(|(n, d)| (n.into(), d.into())), + ) + .zip(encrypted_private_post_states.into_iter().map(Into::into)) + .map(|((commitment, (nullifier, root)), encrypted_post_state)| { + lee_core::PrivateAction { + nullifier, + root, + commitment, + encrypted_post_state, + } + }) + .collect(); + Ok(Self { - public_account_ids: public_account_ids.into_iter().map(Into::into).collect(), + public_actions, nonces: nonces .iter() .map(|x| lee_core::account::Nonce(*x)) .collect(), - public_post_states: public_post_states - .into_iter() - .map(TryInto::try_into) - .collect::, _>>() - .map_err(|e| lee::error::LeeError::InvalidInput(format!("{e}")))?, - encrypted_private_post_states: encrypted_private_post_states - .into_iter() - .map(Into::into) - .collect(), - new_commitments: new_commitments.into_iter().map(Into::into).collect(), - new_nullifiers: new_nullifiers - .into_iter() - .map(|(n, d)| (n.into(), d.into())) - .collect(), + private_actions, block_validity_window: block_validity_window .try_into() .map_err(|e| lee::error::LeeError::InvalidInput(format!("{e}")))?, diff --git a/lez/sequencer/core/src/tests.rs b/lez/sequencer/core/src/tests.rs index a5c535a0..c0682af7 100644 --- a/lez/sequencer/core/src/tests.rs +++ b/lez/sequencer/core/src/tests.rs @@ -860,8 +860,7 @@ fn private_bridge_withdraw_invocation_is_dropped() { ) .expect("Execution should succeed"); - let message = Message::try_from_circuit_output(vec![bridge_account_id], vec![], output) - .expect("Message construction should succeed"); + let message = Message::from_circuit_output(vec![], output); let witness_set = lee::privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]); let tx = diff --git a/lez/wallet/src/lib.rs b/lez/wallet/src/lib.rs index 923f55ae..f11891d1 100644 --- a/lez/wallet/src/lib.rs +++ b/lez/wallet/src/lib.rs @@ -525,13 +525,12 @@ impl WalletCore { for (output_index, acc_decode_data) in acc_decode_mask.iter().enumerate() { match acc_decode_data { AccDecodeData::Decode(secret, acc_account_id) => { - let acc_ead = tx.message.encrypted_private_post_states[output_index].clone(); - let acc_comm = tx.message.new_commitments[output_index].clone(); + let action = &tx.message.private_actions[output_index]; let (kind, res_acc) = lee_core::EncryptionScheme::decrypt( - &acc_ead.ciphertext, + &action.encrypted_post_state.ciphertext, secret, - &acc_comm, + &action.commitment, output_index .try_into() .expect("Output index is expected to fit in u32"), @@ -617,12 +616,10 @@ impl WalletCore { &program.to_owned(), )?; - let message = - lee::privacy_preserving_transaction::message::Message::try_from_circuit_output( - acc_manager.public_account_ids(), - acc_manager.public_account_nonces(), - output, - )?; + let message = lee::privacy_preserving_transaction::message::Message::from_circuit_output( + acc_manager.public_account_nonces(), + output, + ); let message_hash = message.hash(); let signatures_public_keys = acc_manager @@ -773,18 +770,16 @@ impl WalletCore { &key_chain.nullifier_public_key, &key_chain.viewing_public_key, ); - let new_commitments = &tx.message.new_commitments; - tx.message() - .encrypted_private_post_states + .private_actions .iter() .enumerate() - .filter(move |(_, encrypted_data)| encrypted_data.view_tag == view_tag) - .filter_map(move |(ciph_id, encrypted_data)| { - let ciphertext = &encrypted_data.ciphertext; - let commitment = &new_commitments[ciph_id]; - let shared_secret = - key_chain.calculate_shared_secret_receiver(&encrypted_data.epk)?; + .filter(move |(_, action)| action.encrypted_post_state.view_tag == view_tag) + .filter_map(move |(ciph_id, action)| { + let ciphertext = &action.encrypted_post_state.ciphertext; + let commitment = &action.commitment; + let shared_secret = key_chain + .calculate_shared_secret_receiver(&action.encrypted_post_state.epk)?; lee_core::EncryptionScheme::decrypt( ciphertext, @@ -856,25 +851,22 @@ impl WalletCore { for (account_id, npk, vpk, vsk) in shared_keys { let view_tag = EncryptedAccountData::compute_view_tag(&npk, &vpk); - for (ciph_id, encrypted_data) in tx - .message() - .encrypted_private_post_states - .iter() - .enumerate() - { - if encrypted_data.view_tag != view_tag { + for (ciph_id, action) in tx.message().private_actions.iter().enumerate() { + if action.encrypted_post_state.view_tag != view_tag { continue; } - let Some(shared_secret) = - SharedSecretKey::decapsulate(&encrypted_data.epk, &vsk.d, &vsk.z) - else { + let Some(shared_secret) = SharedSecretKey::decapsulate( + &action.encrypted_post_state.epk, + &vsk.d, + &vsk.z, + ) else { continue; }; - let commitment = &tx.message.new_commitments[ciph_id]; + let commitment = &action.commitment; if let Some((_kind, new_acc)) = lee_core::EncryptionScheme::decrypt( - &encrypted_data.ciphertext, + &action.encrypted_post_state.ciphertext, &shared_secret, commitment, ciph_id