mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-07-17 13:20:17 +00:00
refactor: bundle private/public i/o into action struccts
This commit is contained in:
parent
2e669e76f8
commit
e409b3ac16
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -81,9 +81,9 @@ 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!(tx.message.new_commitments.contains(&new_commitment1));
|
||||
assert!(tx.message.commitments().contains(&new_commitment1));
|
||||
|
||||
for commitment in tx.message.new_commitments {
|
||||
for commitment in tx.message.commitments() {
|
||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||
}
|
||||
|
||||
@ -169,9 +169,9 @@ 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!(tx.message.new_commitments.contains(&sender_commitment));
|
||||
assert!(tx.message.commitments().contains(&sender_commitment));
|
||||
|
||||
for commitment in tx.message.new_commitments {
|
||||
for commitment in tx.message.commitments() {
|
||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||
}
|
||||
|
||||
@ -245,7 +245,7 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> {
|
||||
|
||||
let acc_1_balance = account_balance(&ctx, from).await?;
|
||||
|
||||
for commitment in tx.message.new_commitments {
|
||||
for commitment in tx.message.commitments() {
|
||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||
}
|
||||
|
||||
@ -301,7 +301,7 @@ 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
|
||||
for commitment in tx.message.new_commitments {
|
||||
for commitment in tx.message.commitments() {
|
||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||
}
|
||||
|
||||
@ -657,8 +657,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)
|
||||
@ -678,14 +679,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(())
|
||||
|
||||
@ -201,12 +201,10 @@ async fn private_bridge_deposit_invocation_is_dropped() -> anyhow::Result<()> {
|
||||
.context("Failed to execute/prove bridge deposit")?;
|
||||
|
||||
// Create privacy-preserving transaction from circuit output
|
||||
let message = privacy_preserving_transaction::Message::try_from_circuit_output(
|
||||
vec![bridge_account_id, recipient_vault_id],
|
||||
let message = privacy_preserving_transaction::Message::from_circuit_output(
|
||||
vec![bridge_pre.account.nonce, vault_pre.account.nonce],
|
||||
output,
|
||||
)
|
||||
.context("Failed to build privacy-preserving bridge deposit message")?;
|
||||
);
|
||||
|
||||
let witness_set = privacy_preserving_transaction::WitnessSet::for_message(&message, proof, &[]);
|
||||
let attack_tx = LeeTransaction::PrivacyPreserving(lee::PrivacyPreservingTransaction::new(
|
||||
|
||||
@ -71,9 +71,9 @@ 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!(tx.message.new_commitments.contains(&new_commitment1));
|
||||
assert!(tx.message.commitments().contains(&new_commitment1));
|
||||
|
||||
for commitment in tx.message.new_commitments {
|
||||
for commitment in tx.message.commitments() {
|
||||
assert!(verify_commitment_is_in_state(commitment, ctx.sequencer_client()).await);
|
||||
}
|
||||
|
||||
|
||||
@ -83,9 +83,7 @@ async fn fund_private_pda(
|
||||
)
|
||||
.map_err(|e| anyhow::anyhow!("circuit proving failed: {e}"))?;
|
||||
|
||||
let message =
|
||||
Message::try_from_circuit_output(vec![sender], vec![sender_account.nonce], output)
|
||||
.map_err(|e| anyhow::anyhow!("message build failed: {e}"))?;
|
||||
let message = Message::from_circuit_output(vec![sender_account.nonce], output);
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[sender_sk]);
|
||||
let tx = PrivacyPreservingTransaction::new(message, witness_set);
|
||||
|
||||
@ -26,9 +26,7 @@ async fn private_transaction_pads_notes_to_max() -> Result<()> {
|
||||
|
||||
let tx = fetch_privacy_preserving_tx(ctx.sequencer_client(), tx_hash).await;
|
||||
|
||||
assert_eq!(tx.message.new_commitments.len(), 7);
|
||||
assert_eq!(tx.message.new_nullifiers.len(), 7);
|
||||
assert_eq!(tx.message.encrypted_private_post_states.len(), 7);
|
||||
assert_eq!(tx.message.private_actions.len(), 7);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -310,7 +310,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
&program.into(),
|
||||
)
|
||||
.unwrap();
|
||||
let message = pptx::message::Message::try_from_circuit_output(vec![], vec![], output).unwrap();
|
||||
let message = pptx::message::Message::from_circuit_output(vec![], output);
|
||||
let witness_set = pptx::witness_set::WitnessSet::for_message(&message, proof, &[]);
|
||||
pptx::PrivacyPreservingTransaction::new(message, witness_set)
|
||||
}
|
||||
|
||||
@ -1,7 +1,8 @@
|
||||
use lee_core::{
|
||||
Commitment, CommitmentSetDigest, DummyInput, EncryptedAccountData, EncryptionScheme,
|
||||
EphemeralSecretKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey,
|
||||
NullifierSecretKey, PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey,
|
||||
NullifierSecretKey, PrivacyPreservingCircuitOutput, PrivateAccountKind, PrivateAction,
|
||||
PublicAction, SharedSecretKey,
|
||||
account::{Account, AccountId, Nonce},
|
||||
compute_digest_for_path,
|
||||
encryption::{ViewTag, ViewingPublicKey},
|
||||
@ -17,11 +18,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,
|
||||
};
|
||||
@ -37,8 +35,10 @@ pub fn compute_circuit_output(
|
||||
{
|
||||
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 {
|
||||
vpk,
|
||||
@ -267,16 +267,20 @@ pub fn compute_circuit_output(
|
||||
}
|
||||
|
||||
fn obfuscate_output_ordering(output: &mut PrivacyPreservingCircuitOutput) {
|
||||
output
|
||||
.new_commitments
|
||||
.sort_unstable_by_key(Commitment::to_byte_array);
|
||||
|
||||
let mut notes: Vec<_> = core::mem::take(&mut output.new_nullifiers)
|
||||
.into_iter()
|
||||
.zip(core::mem::take(&mut output.encrypted_private_post_states))
|
||||
let mut commitments: Vec<_> = output
|
||||
.private_actions
|
||||
.iter()
|
||||
.map(|action| action.commitment.clone())
|
||||
.collect();
|
||||
notes.sort_unstable_by_key(|((nullifier, _), _)| nullifier.to_byte_array());
|
||||
(output.new_nullifiers, output.encrypted_private_post_states) = notes.into_iter().unzip();
|
||||
commitments.sort_unstable_by_key(Commitment::to_byte_array);
|
||||
|
||||
output
|
||||
.private_actions
|
||||
.sort_unstable_by_key(|action| action.nullifier.to_byte_array());
|
||||
|
||||
for (action, commitment) in output.private_actions.iter_mut().zip(commitments) {
|
||||
action.commitment = commitment;
|
||||
}
|
||||
}
|
||||
|
||||
fn emit_dummy_output(output: &mut PrivacyPreservingCircuitOutput, dummy: DummyInput) {
|
||||
@ -284,17 +288,18 @@ fn emit_dummy_output(output: &mut PrivacyPreservingCircuitOutput, dummy: DummyIn
|
||||
// The prover is responsible for their randomness.
|
||||
let nullifier = Nullifier::for_dummy(&dummy.nullifier_seed);
|
||||
let commitment = Commitment::for_dummy(&nullifier, &dummy.commitment_seed);
|
||||
output
|
||||
.new_nullifiers
|
||||
.push((nullifier, dummy.commitment_root));
|
||||
output.new_commitments.push(commitment);
|
||||
// Note: the encrypted post states are pushed as fed into the circuit.
|
||||
// That means that the prover is responsible for managing the randomness
|
||||
// so as to not reveal the padding.
|
||||
//
|
||||
// In particular, it is recommended to generate the ML KEM ciphertext
|
||||
// explicitly as these are not uniformly random.
|
||||
output.encrypted_private_post_states.push(dummy.note);
|
||||
output.private_actions.push(PrivateAction {
|
||||
nullifier,
|
||||
root: dummy.commitment_root,
|
||||
commitment,
|
||||
encrypted_post_state: dummy.note,
|
||||
});
|
||||
}
|
||||
|
||||
#[expect(
|
||||
@ -327,15 +332,16 @@ fn emit_private_output(
|
||||
&new_nullifier.0,
|
||||
);
|
||||
|
||||
output.new_nullifiers.push(new_nullifier);
|
||||
output.new_commitments.push(commitment_post);
|
||||
output
|
||||
.encrypted_private_post_states
|
||||
.push(EncryptedAccountData {
|
||||
output.private_actions.push(PrivateAction {
|
||||
nullifier: new_nullifier.0,
|
||||
root: new_nullifier.1,
|
||||
commitment: commitment_post,
|
||||
encrypted_post_state: EncryptedAccountData {
|
||||
ciphertext: encrypted_account,
|
||||
epk,
|
||||
view_tag,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
fn compute_update_nullifier_and_set_digest(
|
||||
@ -358,7 +364,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
|
||||
fn note(tag: u8) -> (Nullifier, Commitment, EncryptedAccountData) {
|
||||
fn note(tag: u8) -> PrivateAction {
|
||||
let nullifier = Nullifier::for_dummy(&[tag; 32]);
|
||||
let commitment = Commitment::for_dummy(&nullifier, &[tag; 32]);
|
||||
let ciphertext = EncryptionScheme::encrypt(
|
||||
@ -367,37 +373,36 @@ mod tests {
|
||||
&SharedSecretKey([0; 32]),
|
||||
&nullifier,
|
||||
);
|
||||
let encrypted = EncryptedAccountData {
|
||||
ciphertext,
|
||||
epk: EphemeralPublicKey(vec![tag]),
|
||||
view_tag: 0,
|
||||
};
|
||||
(nullifier, commitment, encrypted)
|
||||
PrivateAction {
|
||||
nullifier,
|
||||
root: DUMMY_COMMITMENT_HASH,
|
||||
commitment,
|
||||
encrypted_post_state: EncryptedAccountData {
|
||||
ciphertext,
|
||||
epk: EphemeralPublicKey(vec![tag]),
|
||||
view_tag: 0,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn obfuscate_byte_sorts_commitments_and_nullifiers() {
|
||||
let mut output = PrivacyPreservingCircuitOutput::default();
|
||||
for tag in 0..3 {
|
||||
let (nullifier, commitment, encrypted) = note(tag);
|
||||
output
|
||||
.new_nullifiers
|
||||
.push((nullifier, DUMMY_COMMITMENT_HASH));
|
||||
output.new_commitments.push(commitment);
|
||||
output.encrypted_private_post_states.push(encrypted);
|
||||
output.private_actions.push(note(tag));
|
||||
}
|
||||
|
||||
obfuscate_output_ordering(&mut output);
|
||||
|
||||
assert!(
|
||||
output
|
||||
.new_commitments
|
||||
.is_sorted_by_key(Commitment::to_byte_array)
|
||||
.private_actions
|
||||
.is_sorted_by_key(|action| action.nullifier.to_byte_array())
|
||||
);
|
||||
assert!(
|
||||
output
|
||||
.new_nullifiers
|
||||
.is_sorted_by_key(|(nullifier, _)| nullifier.to_byte_array())
|
||||
.private_actions
|
||||
.is_sorted_by_key(|action| action.commitment.to_byte_array())
|
||||
);
|
||||
}
|
||||
|
||||
@ -405,27 +410,26 @@ mod tests {
|
||||
fn obfuscate_keeps_each_nullifier_with_its_ciphertext() {
|
||||
let mut output = PrivacyPreservingCircuitOutput::default();
|
||||
for tag in 0..3 {
|
||||
let (nullifier, _, encrypted) = note(tag);
|
||||
output
|
||||
.new_nullifiers
|
||||
.push((nullifier, DUMMY_COMMITMENT_HASH));
|
||||
output.encrypted_private_post_states.push(encrypted);
|
||||
output.private_actions.push(note(tag));
|
||||
}
|
||||
let paired: HashMap<[u8; 32], EphemeralPublicKey> = output
|
||||
.new_nullifiers
|
||||
.private_actions
|
||||
.iter()
|
||||
.zip(&output.encrypted_private_post_states)
|
||||
.map(|((nullifier, _), note)| (nullifier.to_byte_array(), note.epk.clone()))
|
||||
.map(|action| {
|
||||
(
|
||||
action.nullifier.to_byte_array(),
|
||||
action.encrypted_post_state.epk.clone(),
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
|
||||
obfuscate_output_ordering(&mut output);
|
||||
|
||||
for ((nullifier, _), note) in output
|
||||
.new_nullifiers
|
||||
.iter()
|
||||
.zip(&output.encrypted_private_post_states)
|
||||
{
|
||||
assert_eq!(paired[&nullifier.to_byte_array()], note.epk);
|
||||
for action in &output.private_actions {
|
||||
assert_eq!(
|
||||
paired[&action.nullifier.to_byte_array()],
|
||||
action.encrypted_post_state.epk
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
use borsh::{BorshDeserialize, BorshSerialize};
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
use crate::{
|
||||
@ -147,18 +148,53 @@ impl InputAccountIdentity {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
#[cfg_attr(
|
||||
any(feature = "host", test),
|
||||
derive(Debug, Clone, Default, 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, Default))]
|
||||
pub struct PrivacyPreservingCircuitOutput {
|
||||
pub public_pre_states: Vec<AccountWithMetadata>,
|
||||
pub public_post_states: Vec<Account>,
|
||||
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
||||
pub new_commitments: Vec<Commitment>,
|
||||
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
||||
pub public_actions: Vec<PublicAction>,
|
||||
pub private_actions: Vec<PrivateAction>,
|
||||
pub block_validity_window: BlockValidityWindow,
|
||||
pub timestamp_validity_window: TimestampValidityWindow,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "host", test))]
|
||||
impl PrivacyPreservingCircuitOutput {
|
||||
#[must_use]
|
||||
pub fn commitments(&self) -> Vec<Commitment> {
|
||||
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.
|
||||
@ -183,50 +219,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(),
|
||||
};
|
||||
|
||||
@ -32,10 +32,10 @@ pub const DUMMY_COMMITMENT_HASH: [u8; 32] = [
|
||||
129, 241, 118, 39, 41, 253, 141, 171, 184, 71, 8, 41,
|
||||
];
|
||||
|
||||
#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
#[derive(Clone, Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
#[cfg_attr(
|
||||
any(feature = "host", test),
|
||||
derive(Clone, PartialEq, Eq, Hash, PartialOrd, Ord)
|
||||
derive(Default, PartialEq, Eq, Hash, PartialOrd, Ord)
|
||||
)]
|
||||
pub struct Commitment(pub(super) [u8; 32]);
|
||||
|
||||
|
||||
@ -45,13 +45,15 @@ pub struct SharedSecretKey(pub [u8; 32]);
|
||||
|
||||
/// The ML-KEM-768 ciphertext produced during encapsulation; transmitted on-wire in place of the
|
||||
/// former ECDH ephemeral public key. Always `ML_KEM_768_CIPHERTEXT_LEN` (1088) bytes.
|
||||
#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
#[derive(
|
||||
Serialize, Deserialize, Clone, Debug, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize,
|
||||
)]
|
||||
pub struct EphemeralPublicKey(pub Vec<u8>);
|
||||
|
||||
pub struct EncryptionScheme;
|
||||
|
||||
#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Clone, PartialEq, Eq))]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Clone, Default, PartialEq, Eq))]
|
||||
pub struct Ciphertext(pub(crate) Vec<u8>);
|
||||
|
||||
#[cfg(any(feature = "host", test))]
|
||||
@ -71,7 +73,10 @@ pub type ViewTag = u8;
|
||||
|
||||
/// Encrypted private-account note for one output.
|
||||
#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
#[cfg_attr(any(feature = "host", test), derive(Debug, Clone, PartialEq, Eq))]
|
||||
#[cfg_attr(
|
||||
any(feature = "host", test),
|
||||
derive(Debug, Clone, Default, PartialEq, Eq)
|
||||
)]
|
||||
pub struct EncryptedAccountData {
|
||||
pub ciphertext: Ciphertext,
|
||||
pub epk: EphemeralPublicKey,
|
||||
|
||||
@ -4,7 +4,8 @@
|
||||
)]
|
||||
|
||||
pub use circuit_io::{
|
||||
DummyInput, InputAccountIdentity, PrivacyPreservingCircuitInput, PrivacyPreservingCircuitOutput,
|
||||
DummyInput, InputAccountIdentity, PrivacyPreservingCircuitInput,
|
||||
PrivacyPreservingCircuitOutput, PrivateAction, PublicAction,
|
||||
};
|
||||
pub use commitment::{
|
||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, MembershipProof,
|
||||
|
||||
@ -72,7 +72,7 @@ pub type NullifierSecretKey = [u8; 32];
|
||||
#[derive(Serialize, Deserialize, BorshSerialize, BorshDeserialize)]
|
||||
#[cfg_attr(
|
||||
any(feature = "host", test),
|
||||
derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)
|
||||
derive(Clone, Copy, Default, PartialEq, Eq, PartialOrd, Ord, Hash)
|
||||
)]
|
||||
pub struct Nullifier(pub(super) [u8; 32]);
|
||||
|
||||
|
||||
@ -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_nullifiers[idx].0,
|
||||
&output.private_actions[idx].nullifier,
|
||||
)
|
||||
.unwrap();
|
||||
kind
|
||||
@ -102,18 +102,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_nullifiers[0].0,
|
||||
&output.private_actions[0].nullifier,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(recipient_post, expected_recipient_post);
|
||||
@ -216,43 +214,46 @@ 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!(output.public_actions.is_empty());
|
||||
let sender_nullifier = expected_new_nullifiers[0].0;
|
||||
let recipient_nullifier = expected_new_nullifiers[1].0;
|
||||
|
||||
let mut expected_new_commitments = expected_new_commitments;
|
||||
expected_new_commitments.sort_unstable_by_key(Commitment::to_byte_array);
|
||||
assert_eq!(output.new_commitments, expected_new_commitments);
|
||||
let mut sorted_commitments = expected_new_commitments;
|
||||
sorted_commitments.sort_unstable_by_key(Commitment::to_byte_array);
|
||||
assert_eq!(output.commitments(), sorted_commitments);
|
||||
|
||||
let mut expected_new_nullifiers = expected_new_nullifiers;
|
||||
expected_new_nullifiers.sort_unstable_by_key(|(nullifier, _)| nullifier.to_byte_array());
|
||||
assert_eq!(output.new_nullifiers, expected_new_nullifiers);
|
||||
let mut sorted_nullifiers = expected_new_nullifiers;
|
||||
sorted_nullifiers.sort_unstable_by_key(|(nullifier, _)| nullifier.to_byte_array());
|
||||
assert_eq!(output.nullifiers(), sorted_nullifiers);
|
||||
|
||||
assert_eq!(output.encrypted_private_post_states.len(), 2);
|
||||
assert_eq!(output.private_actions.len(), 2);
|
||||
|
||||
let sender_slot = output
|
||||
.new_nullifiers
|
||||
.private_actions
|
||||
.iter()
|
||||
.position(|(nullifier, _)| *nullifier == sender_nullifier)
|
||||
.position(|action| action.nullifier == sender_nullifier)
|
||||
.unwrap();
|
||||
let (_identifier, sender_post) = EncryptionScheme::decrypt(
|
||||
&output.encrypted_private_post_states[sender_slot].ciphertext,
|
||||
&output.private_actions[sender_slot]
|
||||
.encrypted_post_state
|
||||
.ciphertext,
|
||||
&shared_secret_1,
|
||||
&output.new_nullifiers[sender_slot].0,
|
||||
&output.private_actions[sender_slot].nullifier,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(sender_post, expected_private_account_1);
|
||||
|
||||
let recipient_slot = output
|
||||
.new_nullifiers
|
||||
.private_actions
|
||||
.iter()
|
||||
.position(|(nullifier, _)| *nullifier == recipient_nullifier)
|
||||
.position(|action| action.nullifier == recipient_nullifier)
|
||||
.unwrap();
|
||||
let (_identifier, recipient_post) = EncryptionScheme::decrypt(
|
||||
&output.encrypted_private_post_states[recipient_slot].ciphertext,
|
||||
&output.private_actions[recipient_slot]
|
||||
.encrypted_post_state
|
||||
.ciphertext,
|
||||
&shared_secret_2,
|
||||
&output.new_nullifiers[recipient_slot].0,
|
||||
&output.private_actions[recipient_slot].nullifier,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(recipient_post, expected_private_account_2);
|
||||
@ -281,9 +282,9 @@ fn init_note_view_tag_is_derived_from_account_keys() {
|
||||
.unwrap();
|
||||
|
||||
assert!(proof.is_valid_for(&output));
|
||||
assert_eq!(output.encrypted_private_post_states.len(), 1);
|
||||
assert_eq!(output.private_actions.len(), 1);
|
||||
assert_eq!(
|
||||
output.encrypted_private_post_states[0].view_tag,
|
||||
output.private_actions[0].encrypted_post_state.view_tag,
|
||||
EncryptedAccountData::compute_view_tag(&keys.npk(), &keys.vpk()),
|
||||
);
|
||||
}
|
||||
@ -324,8 +325,11 @@ fn update_note_view_tag_is_the_supplied_value() {
|
||||
.unwrap();
|
||||
|
||||
assert!(proof.is_valid_for(&output));
|
||||
assert_eq!(output.encrypted_private_post_states.len(), 1);
|
||||
assert_eq!(output.encrypted_private_post_states[0].view_tag, fed_tag);
|
||||
assert_eq!(output.private_actions.len(), 1);
|
||||
assert_eq!(
|
||||
output.private_actions[0].encrypted_post_state.view_tag,
|
||||
fed_tag
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -448,7 +452,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.
|
||||
@ -502,7 +506,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,
|
||||
@ -554,7 +558,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
|
||||
|
||||
@ -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 PublicActionWithID {
|
||||
pub account_id: AccountId,
|
||||
pub post_state: Account,
|
||||
}
|
||||
|
||||
#[derive(Clone, Default, PartialEq, Eq, BorshSerialize, BorshDeserialize)]
|
||||
pub struct Message {
|
||||
pub public_account_ids: Vec<AccountId>,
|
||||
pub public_actions: Vec<PublicActionWithID>,
|
||||
pub nonces: Vec<Nonce>,
|
||||
pub public_post_states: Vec<Account>,
|
||||
pub encrypted_private_post_states: Vec<EncryptedAccountData>,
|
||||
pub new_commitments: Vec<Commitment>,
|
||||
pub new_nullifiers: Vec<(Nullifier, CommitmentSetDigest)>,
|
||||
pub private_actions: Vec<PrivateAction>,
|
||||
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<AccountId>,
|
||||
nonces: Vec<Nonce>,
|
||||
output: PrivacyPreservingCircuitOutput,
|
||||
) -> Result<Self, LeeError> {
|
||||
Ok(Self {
|
||||
public_account_ids,
|
||||
#[must_use]
|
||||
pub fn from_circuit_output(nonces: Vec<Nonce>, output: PrivacyPreservingCircuitOutput) -> Self {
|
||||
let public_actions = output
|
||||
.public_actions
|
||||
.into_iter()
|
||||
.map(|action| PublicActionWithID {
|
||||
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<Commitment> {
|
||||
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<AccountId> {
|
||||
self.public_actions
|
||||
.iter()
|
||||
.map(|action| action.account_id)
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
pub fn public_post_states(&self) -> Vec<Account> {
|
||||
self.public_actions
|
||||
.iter()
|
||||
.map(|action| action.post_state.clone())
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[must_use]
|
||||
@ -84,34 +122,20 @@ impl Message {
|
||||
|
||||
Sha256::digest(bytes).into()
|
||||
}
|
||||
|
||||
/// Ensure that the commitments, nullifiers, and ciphertexts agree.
|
||||
pub fn validate_note_lengths(&self) -> Result<usize, LeeError> {
|
||||
let count = self.new_nullifiers.len();
|
||||
if self.new_commitments.len() != count || self.encrypted_private_post_states.len() != count
|
||||
{
|
||||
return Err(LeeError::InvalidInput(format!(
|
||||
"Note vectors disagree in length with {count} nullifiers, {} commitments, and {} ciphertexts",
|
||||
self.new_commitments.len(),
|
||||
self.encrypted_private_post_states.len(),
|
||||
)));
|
||||
}
|
||||
Ok(count)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub mod tests {
|
||||
use lee_core::{
|
||||
Commitment, EncryptionScheme, EphemeralSecretKey, Nullifier, NullifierPublicKey,
|
||||
PrivateAccountKind, SharedSecretKey,
|
||||
Commitment, EncryptionScheme, EphemeralPublicKey, EphemeralSecretKey, 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, PublicActionWithID};
|
||||
|
||||
#[must_use]
|
||||
pub fn message_for_tests() -> Message {
|
||||
@ -125,78 +149,57 @@ pub mod tests {
|
||||
let npk2 = NullifierPublicKey::from(&nsk2);
|
||||
let vpk = ViewingPublicKey::from_seed(&[7; 32], &[8; 32]);
|
||||
|
||||
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, &vpk, 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, &vpk, 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![PublicActionWithID {
|
||||
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(),
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_note_lengths_accepts_matching_and_rejects_mismatched() {
|
||||
assert_eq!(Message::default().validate_note_lengths().unwrap(), 0);
|
||||
|
||||
let mismatched = Message {
|
||||
new_commitments: vec![Commitment::new(
|
||||
&AccountId::new([0; 32]),
|
||||
&Account::default(),
|
||||
)],
|
||||
..Default::default()
|
||||
};
|
||||
assert!(mismatched.validate_note_lengths().is_err());
|
||||
}
|
||||
|
||||
#[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<u8> = [
|
||||
&[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
|
||||
]
|
||||
|
||||
@ -53,7 +53,12 @@ impl PrivacyPreservingTransaction {
|
||||
.signer_account_ids()
|
||||
.into_iter()
|
||||
.collect::<HashSet<_>>();
|
||||
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()
|
||||
}
|
||||
|
||||
@ -402,11 +402,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
|
||||
@ -482,8 +479,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
|
||||
@ -756,7 +752,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, &[]);
|
||||
|
||||
@ -801,7 +797,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);
|
||||
@ -851,7 +847,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);
|
||||
@ -961,8 +957,7 @@ fn two_private_pda_family_members_receive_and_spend() {
|
||||
&simple_transfer.clone().into(),
|
||||
)
|
||||
.unwrap();
|
||||
let message =
|
||||
Message::try_from_circuit_output(vec![funder_id], vec![funder_nonce], output).unwrap();
|
||||
let message = 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(
|
||||
@ -997,8 +992,7 @@ fn two_private_pda_family_members_receive_and_spend() {
|
||||
&simple_transfer.into(),
|
||||
)
|
||||
.unwrap();
|
||||
let message =
|
||||
Message::try_from_circuit_output(vec![funder_id], vec![funder_nonce], output).unwrap();
|
||||
let message = 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(
|
||||
@ -1041,8 +1035,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![Nonce(0)], output).unwrap();
|
||||
let message = 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(
|
||||
@ -1079,7 +1072,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(
|
||||
@ -1130,9 +1123,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(
|
||||
|
||||
@ -341,9 +341,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);
|
||||
@ -466,7 +464,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);
|
||||
|
||||
|
||||
@ -290,12 +290,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)
|
||||
@ -349,7 +344,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, &[]);
|
||||
|
||||
@ -398,8 +393,7 @@ fn deshielded_balance_transfer_for_tests(
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let message =
|
||||
Message::try_from_circuit_output(vec![*recipient_account_id], vec![], output).unwrap();
|
||||
let message = Message::from_circuit_output(vec![], output);
|
||||
|
||||
let witness_set = WitnessSet::for_message(&message, proof, &[]);
|
||||
|
||||
|
||||
@ -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
|
||||
@ -128,7 +128,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!(
|
||||
@ -154,7 +154,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!(
|
||||
|
||||
@ -149,7 +149,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)
|
||||
@ -214,7 +214,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)
|
||||
|
||||
@ -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<Self, LeeError> {
|
||||
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::<Vec<_>>()
|
||||
) == message.new_nullifiers.len(),
|
||||
n_unique(&nullifiers.iter().map(|(n, _)| n).collect::<Vec<_>>()) == 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,
|
||||
};
|
||||
|
||||
@ -188,12 +188,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);
|
||||
@ -350,12 +348,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);
|
||||
@ -480,8 +476,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},
|
||||
};
|
||||
|
||||
@ -503,12 +500,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(),
|
||||
};
|
||||
|
||||
@ -282,27 +282,30 @@ impl From<PublicMessage> for lee::public_transaction::Message {
|
||||
impl From<lee::privacy_preserving_transaction::message::Message> 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
|
||||
public_post_states: public_actions
|
||||
.into_iter()
|
||||
.map(Into::into)
|
||||
.map(|a| a.post_state.into())
|
||||
.collect(),
|
||||
new_commitments: new_commitments.into_iter().map(Into::into).collect(),
|
||||
new_nullifiers: new_nullifiers
|
||||
encrypted_private_post_states: private_actions
|
||||
.iter()
|
||||
.map(|a| a.encrypted_post_state.clone().into())
|
||||
.collect(),
|
||||
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(),
|
||||
@ -324,26 +327,50 @@ impl TryFrom<PrivacyPreservingMessage> 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::<Result<Vec<lee_core::account::Account>, _>>()
|
||||
.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::PublicActionWithID {
|
||||
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::<Result<Vec<_>, _>>()
|
||||
.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}")))?,
|
||||
|
||||
@ -875,8 +875,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 =
|
||||
|
||||
@ -383,7 +383,6 @@ impl WalletCore {
|
||||
let LeeTransaction::PrivacyPreserving(pp_tx) = &tx else {
|
||||
continue;
|
||||
};
|
||||
pp_tx.message.validate_note_lengths()?;
|
||||
// Sync updates while watching only the init nullifier.
|
||||
self.storage
|
||||
.key_chain_mut()
|
||||
@ -550,7 +549,7 @@ impl WalletCore {
|
||||
tx: &lee::privacy_preserving_transaction::PrivacyPreservingTransaction,
|
||||
acc_decode_mask: &[AccDecodeData],
|
||||
) -> Result<()> {
|
||||
let note_count = tx.message.validate_note_lengths()?;
|
||||
let note_count = tx.message.private_actions.len();
|
||||
anyhow::ensure!(
|
||||
note_count >= acc_decode_mask.len(),
|
||||
"Decode mask has {} entries but the transaction has {note_count} notes",
|
||||
@ -655,12 +654,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
|
||||
@ -785,7 +782,6 @@ impl WalletCore {
|
||||
let LeeTransaction::PrivacyPreserving(pp_tx) = &tx else {
|
||||
continue;
|
||||
};
|
||||
pp_tx.message.validate_note_lengths()?;
|
||||
// Eagerly decrypt note updates using expected nullifiers.
|
||||
let handled = self
|
||||
.storage
|
||||
@ -824,18 +820,19 @@ impl WalletCore {
|
||||
&key_chain.viewing_public_key,
|
||||
);
|
||||
message
|
||||
.encrypted_private_post_states
|
||||
.private_actions
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter(move |(ciph_id, encrypted_data)| {
|
||||
.filter(move |(ciph_id, action)| {
|
||||
// If we have not decrypted the update using the nullifiers,
|
||||
// the note may be an initialized one, for which we should
|
||||
// scan.
|
||||
!handled.contains(ciph_id) && encrypted_data.view_tag == view_tag
|
||||
!handled.contains(ciph_id)
|
||||
&& action.encrypted_post_state.view_tag == view_tag
|
||||
})
|
||||
.filter_map(move |(ciph_id, encrypted_data)| {
|
||||
let shared_secret =
|
||||
key_chain.calculate_shared_secret_receiver(&encrypted_data.epk)?;
|
||||
.filter_map(move |(ciph_id, action)| {
|
||||
let shared_secret = key_chain
|
||||
.calculate_shared_secret_receiver(&action.encrypted_post_state.epk)?;
|
||||
|
||||
decrypt_note_at(message, ciph_id, &shared_secret).map(|(kind, res_acc)| {
|
||||
let npk = &key_chain.nullifier_public_key;
|
||||
@ -892,16 +889,14 @@ impl WalletCore {
|
||||
for (account_id, npk, vpk, vsk, nsk) in shared_keys {
|
||||
let view_tag = EncryptedAccountData::compute_view_tag(&npk, &vpk);
|
||||
|
||||
for (ciph_id, encrypted_data) in
|
||||
message.encrypted_private_post_states.iter().enumerate()
|
||||
{
|
||||
for (ciph_id, action) in message.private_actions.iter().enumerate() {
|
||||
// If already decrypted or the tag does not match, skip.
|
||||
if handled.contains(&ciph_id) || encrypted_data.view_tag != view_tag {
|
||||
if handled.contains(&ciph_id) || action.encrypted_post_state.view_tag != view_tag {
|
||||
continue;
|
||||
}
|
||||
|
||||
let Some(shared_secret) =
|
||||
SharedSecretKey::decapsulate(&encrypted_data.epk, &vsk.d, &vsk.z)
|
||||
SharedSecretKey::decapsulate(&action.encrypted_post_state.epk, &vsk.d, &vsk.z)
|
||||
else {
|
||||
continue;
|
||||
};
|
||||
@ -938,9 +933,9 @@ fn decrypt_note_at(
|
||||
secret: &SharedSecretKey,
|
||||
) -> Option<(lee_core::PrivateAccountKind, Account)> {
|
||||
lee_core::EncryptionScheme::decrypt(
|
||||
&message.encrypted_private_post_states[i].ciphertext,
|
||||
&message.private_actions[i].encrypted_post_state.ciphertext,
|
||||
secret,
|
||||
&message.new_nullifiers[i].0,
|
||||
&message.private_actions[i].nullifier,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -390,9 +390,9 @@ impl UserKeyChain {
|
||||
index: &mut NullifierIndex,
|
||||
) -> HashSet<usize> {
|
||||
let mut handled = HashSet::new();
|
||||
for (i, (old_nullifier, _)) in message.new_nullifiers.iter().enumerate() {
|
||||
for (i, action) in message.private_actions.iter().enumerate() {
|
||||
// Get the nullifier information if awaiting the nullifier.
|
||||
let Some(account_id) = index.account_for(old_nullifier) else {
|
||||
let Some(account_id) = index.account_for(&action.nullifier) else {
|
||||
continue;
|
||||
};
|
||||
// Try decrypting the commitment connected to the nullifier and get the next
|
||||
@ -400,7 +400,7 @@ impl UserKeyChain {
|
||||
if let Some(new_nullifier) = self.apply_nullifier_update(account_id, message, i) {
|
||||
// Update the index to await for the new state of the account, i.e.
|
||||
// the new nullifier.
|
||||
index.update(old_nullifier, new_nullifier, account_id);
|
||||
index.update(&action.nullifier, new_nullifier, account_id);
|
||||
// Record that this nullifier's position can be skipped for scanning.
|
||||
handled.insert(i);
|
||||
}
|
||||
@ -416,7 +416,7 @@ impl UserKeyChain {
|
||||
message: &Message,
|
||||
i: usize,
|
||||
) -> Option<Nullifier> {
|
||||
let encrypted = &message.encrypted_private_post_states[i];
|
||||
let encrypted = &message.private_actions[i].encrypted_post_state;
|
||||
|
||||
let (nsk, secret, is_shared) = if let Some(entry) = self.shared_private_account(account_id)
|
||||
{
|
||||
@ -460,10 +460,9 @@ impl UserKeyChain {
|
||||
&acc.key_chain.private_key_holder.nullifier_secret_key,
|
||||
)
|
||||
});
|
||||
message
|
||||
.new_nullifiers
|
||||
.iter()
|
||||
.position(|(nullifier, _)| *nullifier == init || Some(nullifier) == update.as_ref())
|
||||
message.private_actions.iter().position(|action| {
|
||||
action.nullifier == init || Some(&action.nullifier) == update.as_ref()
|
||||
})
|
||||
}
|
||||
|
||||
pub fn add_imported_public_account(&mut self, private_key: lee::PrivateKey) {
|
||||
@ -876,7 +875,7 @@ impl Default for UserKeyChain {
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lee_core::{EncryptionScheme, encryption::EncryptedAccountData};
|
||||
use lee_core::{EncryptionScheme, PrivateAction, encryption::EncryptedAccountData};
|
||||
|
||||
use super::*;
|
||||
|
||||
@ -921,9 +920,12 @@ mod tests {
|
||||
);
|
||||
|
||||
let message = Message {
|
||||
encrypted_private_post_states: vec![note],
|
||||
new_commitments: vec![new_commitment],
|
||||
new_nullifiers: vec![(old_nullifier, [0; 32])],
|
||||
private_actions: vec![PrivateAction {
|
||||
nullifier: old_nullifier,
|
||||
commitment: new_commitment,
|
||||
encrypted_post_state: note,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -985,9 +987,12 @@ mod tests {
|
||||
);
|
||||
let note = EncryptedAccountData::new(ciphertext, &npk, &vpk, epk);
|
||||
let message = Message {
|
||||
encrypted_private_post_states: vec![note],
|
||||
new_commitments: vec![new_commitment],
|
||||
new_nullifiers: vec![(old_nullifier, [0; 32])],
|
||||
private_actions: vec![PrivateAction {
|
||||
nullifier: old_nullifier,
|
||||
commitment: new_commitment,
|
||||
encrypted_post_state: note,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
@ -1047,9 +1052,12 @@ mod tests {
|
||||
);
|
||||
let note = EncryptedAccountData::new(ciphertext, &npk, &vpk, epk);
|
||||
Message {
|
||||
encrypted_private_post_states: vec![note],
|
||||
new_commitments: vec![commitment],
|
||||
new_nullifiers: vec![(spent, [0; 32])],
|
||||
private_actions: vec![PrivateAction {
|
||||
nullifier: spent,
|
||||
commitment,
|
||||
encrypted_post_state: note,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
}
|
||||
};
|
||||
@ -1110,7 +1118,10 @@ mod tests {
|
||||
&[9; 32],
|
||||
);
|
||||
let message = Message {
|
||||
new_nullifiers: vec![(unindexed, [0; 32])],
|
||||
private_actions: vec![PrivateAction {
|
||||
nullifier: unindexed,
|
||||
..Default::default()
|
||||
}],
|
||||
..Default::default()
|
||||
};
|
||||
|
||||
|
||||
Binary file not shown.
Loading…
x
Reference in New Issue
Block a user