mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-07-02 03:50:16 +00:00
refactor(privacy_preserving_circuit): introduce helper functions to shorten long functions (#545)
* refactor(privacy_preserving_circuit): extract functions for readability * refactor(privacy_preserving_circuit): address PR review comments Bundle shared handle_* arguments into PrivateOutputHandler struct in output.rs and fix misplaced docstring on resolve_external_seed in execution_state.rs. * feat: update commitment mechanism for new private account (#546) * refactor(privacy_preserving_circuit): extract functions for readability * feat: update commitment mechanism for new private accounts Allow init accounts to optionally use a real membership proof for DUMMY_COMMITMENT instead of hardcoding DUMMY_COMMITMENT_HASH as the CommitmentSetDigest. The wallet fetches the proof from the sequencer and passes it through the circuit. * fix: address clippy lints and fix integration test visibility * add tests * refactor: removed duplicated code * refactor: simplify init nullifier mechanism Replace Option<MembershipProof> with Option<CommitmentSetDigest> on init variants (PrivateAuthorizedInit, PrivateUnauthorized, PrivatePdaInit). The circuit now receives the commitment tree root directly instead of recomputing it from a Merkle proof. * refactor: use CommitmentSetDigest directly instead of Option for init commitment root Address PR #546 review feedback: the circuit now accepts CommitmentSetDigest directly on init variants (PrivateAuthorizedInit, PrivateUnauthorized, PrivatePdaInit), with callers providing DUMMY_COMMITMENT_HASH as the default. Also fixes duplicate resolve_external_seed from rebase and rebuilds artifacts. * style: run cargo +nightly fmt
This commit is contained in:
parent
3b3857594f
commit
f8d859394b
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.
@ -11,8 +11,10 @@ use lee::{
|
||||
privacy_preserving_transaction::circuit::ProgramWithDependencies, program::Program,
|
||||
};
|
||||
use lee_core::{
|
||||
EncryptedAccountData, InputAccountIdentity, ML_KEM_768_CIPHERTEXT_LEN, NullifierPublicKey,
|
||||
account::AccountWithMetadata,
|
||||
DUMMY_COMMITMENT, DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity, Nullifier,
|
||||
NullifierPublicKey,
|
||||
account::{Account, AccountWithMetadata},
|
||||
compute_digest_for_path,
|
||||
encryption::{EphemeralPublicKey, ViewingPublicKey},
|
||||
};
|
||||
use log::info;
|
||||
@ -710,6 +712,7 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> {
|
||||
npk,
|
||||
ssk,
|
||||
identifier: 1337,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
},
|
||||
],
|
||||
@ -720,3 +723,100 @@ async fn ppt_cant_chain_call_faucet() -> Result<()> {
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
async fn prove_init_with_commitment_root(
|
||||
ctx: &TestContext,
|
||||
commitment_root: lee_core::CommitmentSetDigest,
|
||||
) -> Result<lee_core::PrivacyPreservingCircuitOutput> {
|
||||
let program = programs::authenticated_transfer();
|
||||
let sender_id = ctx.existing_public_accounts()[0];
|
||||
let sender_pre = AccountWithMetadata::new(
|
||||
ctx.sequencer_client().get_account(sender_id).await?,
|
||||
true,
|
||||
sender_id,
|
||||
);
|
||||
|
||||
let nsk: lee_core::NullifierSecretKey = [7; 32];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let vpk = ViewingPublicKey::from_bytes(vec![4_u8; 1184]).unwrap();
|
||||
let ssk = SharedSecretKey([55_u8; 32]);
|
||||
let recipient_account_id = AccountId::for_regular_private_account(&npk, 0);
|
||||
let recipient = AccountWithMetadata::new(Account::default(), false, recipient_account_id);
|
||||
|
||||
let (output, _) = execute_and_prove(
|
||||
vec![sender_pre, recipient],
|
||||
Program::serialize_instruction(authenticated_transfer_core::Instruction::Transfer {
|
||||
amount: 1,
|
||||
})?,
|
||||
vec![
|
||||
InputAccountIdentity::Public,
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: EphemeralPublicKey(Vec::new()),
|
||||
view_tag: EncryptedAccountData::compute_view_tag(&npk, &vpk),
|
||||
npk,
|
||||
ssk,
|
||||
identifier: 0,
|
||||
commitment_root,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
)?;
|
||||
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn init_with_dummy_commitment_root_produces_valid_root() -> Result<()> {
|
||||
let ctx = TestContext::new().await?;
|
||||
|
||||
let dummy_proof = ctx
|
||||
.sequencer_client()
|
||||
.get_proof_for_commitment(DUMMY_COMMITMENT)
|
||||
.await?
|
||||
.expect("DUMMY_COMMITMENT must be in genesis commitment set");
|
||||
let expected_digest = compute_digest_for_path(&DUMMY_COMMITMENT, &dummy_proof);
|
||||
|
||||
let nsk: lee_core::NullifierSecretKey = [7; 32];
|
||||
let npk = NullifierPublicKey::from(&nsk);
|
||||
let recipient_account_id = AccountId::for_regular_private_account(&npk, 0);
|
||||
|
||||
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!(
|
||||
*nullifier,
|
||||
Nullifier::for_account_initialization(&recipient_account_id)
|
||||
);
|
||||
assert_eq!(*digest, expected_digest);
|
||||
assert_ne!(*digest, DUMMY_COMMITMENT_HASH);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
async fn init_nullifier_digest_is_bound_to_commitment_root() -> Result<()> {
|
||||
let ctx = TestContext::new().await?;
|
||||
|
||||
let dummy_proof = ctx
|
||||
.sequencer_client()
|
||||
.get_proof_for_commitment(DUMMY_COMMITMENT)
|
||||
.await?
|
||||
.expect("DUMMY_COMMITMENT must be in genesis commitment set");
|
||||
let expected_digest = compute_digest_for_path(&DUMMY_COMMITMENT, &dummy_proof);
|
||||
|
||||
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_without_root.new_nullifiers[0].1,
|
||||
DUMMY_COMMITMENT_HASH
|
||||
);
|
||||
assert_ne!(
|
||||
output_with_root.new_nullifiers[0].1,
|
||||
output_without_root.new_nullifiers[0].1,
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@ -22,7 +22,7 @@ use lee::{
|
||||
program::Program,
|
||||
};
|
||||
use lee_core::{
|
||||
EncryptedAccountData, InputAccountIdentity, NullifierPublicKey,
|
||||
DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity, NullifierPublicKey,
|
||||
account::{Account, AccountWithMetadata},
|
||||
encryption::ViewingPublicKey,
|
||||
program::PdaSeed,
|
||||
@ -78,6 +78,7 @@ async fn fund_private_pda(
|
||||
npk,
|
||||
ssk,
|
||||
identifier,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: Some((seed, authority_program_id)),
|
||||
},
|
||||
];
|
||||
|
||||
@ -23,7 +23,8 @@ use lee::{
|
||||
public_transaction as putx,
|
||||
};
|
||||
use lee_core::{
|
||||
EncryptedAccountData, InputAccountIdentity, MembershipProof, NullifierPublicKey,
|
||||
DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity, MembershipProof,
|
||||
NullifierPublicKey,
|
||||
account::{AccountWithMetadata, Nonce, data::Data},
|
||||
encryption::ViewingPublicKey,
|
||||
};
|
||||
@ -314,6 +315,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction {
|
||||
npk: recipient_npk,
|
||||
ssk: recipient_ss,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
|
||||
@ -59,46 +59,9 @@ impl ExecutionState {
|
||||
program_id: ProgramId,
|
||||
program_outputs: Vec<ProgramOutput>,
|
||||
) -> Self {
|
||||
// Build position → (npk, identifier) map for private-PDA pre_states, indexed by position
|
||||
// in `account_identities`. The vec is documented as 1:1 with the program's pre_state
|
||||
// order, so position here matches `pre_state_position` used downstream in
|
||||
// `validate_and_sync_states`.
|
||||
let mut private_pda_npk_by_position: HashMap<usize, (NullifierPublicKey, Identifier)> =
|
||||
HashMap::new();
|
||||
for (pos, account_identity) in account_identities.iter().enumerate() {
|
||||
if let Some((npk, identifier)) = account_identity.npk_if_private_pda() {
|
||||
private_pda_npk_by_position.insert(pos, (npk, identifier));
|
||||
}
|
||||
}
|
||||
|
||||
let block_valid_from = program_outputs
|
||||
.iter()
|
||||
.filter_map(|output| output.block_validity_window.start())
|
||||
.max();
|
||||
let block_valid_until = program_outputs
|
||||
.iter()
|
||||
.filter_map(|output| output.block_validity_window.end())
|
||||
.min();
|
||||
let ts_valid_from = program_outputs
|
||||
.iter()
|
||||
.filter_map(|output| output.timestamp_validity_window.start())
|
||||
.max();
|
||||
let ts_valid_until = program_outputs
|
||||
.iter()
|
||||
.filter_map(|output| output.timestamp_validity_window.end())
|
||||
.min();
|
||||
|
||||
let block_validity_window: BlockValidityWindow = (block_valid_from, block_valid_until)
|
||||
.try_into()
|
||||
.expect(
|
||||
"There should be non empty intersection in the program output block validity windows",
|
||||
);
|
||||
let timestamp_validity_window: TimestampValidityWindow =
|
||||
(ts_valid_from, ts_valid_until)
|
||||
.try_into()
|
||||
.expect(
|
||||
"There should be non empty intersection in the program output timestamp validity windows",
|
||||
);
|
||||
let private_pda_npk_by_position = build_private_pda_npk_map(account_identities);
|
||||
let (block_validity_window, timestamp_validity_window) =
|
||||
intersect_validity_windows(&program_outputs);
|
||||
|
||||
let mut execution_state = Self {
|
||||
pre_states: Vec::new(),
|
||||
@ -136,49 +99,7 @@ impl ExecutionState {
|
||||
panic!("Insufficient program outputs for chained calls");
|
||||
};
|
||||
|
||||
// Check that instruction data in chained call is the instruction data in program output
|
||||
assert_eq!(
|
||||
chained_call.instruction_data, program_output.instruction_data,
|
||||
"Mismatched instruction data between chained call and program output"
|
||||
);
|
||||
|
||||
// Check that `program_output` is consistent with the execution of the corresponding
|
||||
// program.
|
||||
let program_output_words =
|
||||
&to_vec(&program_output).expect("program_output must be serializable");
|
||||
env::verify(chained_call.program_id, program_output_words).unwrap_or_else(
|
||||
|_: Infallible| unreachable!("Infallible error is never constructed"),
|
||||
);
|
||||
|
||||
// Verify that the program output's self_program_id matches the expected program ID.
|
||||
// This ensures the proof commits to which program produced the output.
|
||||
assert_eq!(
|
||||
program_output.self_program_id, chained_call.program_id,
|
||||
"Program output self_program_id does not match chained call program_id"
|
||||
);
|
||||
|
||||
// Verify that the program output's caller_program_id matches the actual caller.
|
||||
// This prevents a malicious user from privately executing an internal function
|
||||
// by spoofing caller_program_id (e.g. passing caller_program_id = self_program_id
|
||||
// to bypass access control checks).
|
||||
assert_eq!(
|
||||
program_output.caller_program_id, caller_program_id,
|
||||
"Program output caller_program_id does not match actual caller"
|
||||
);
|
||||
|
||||
// Check that the program is well behaved.
|
||||
// See the # Programs section for the definition of the `validate_execution` method.
|
||||
let validated_execution = validate_execution(
|
||||
&program_output.pre_states,
|
||||
&program_output.post_states,
|
||||
chained_call.program_id,
|
||||
);
|
||||
if let Err(err) = validated_execution {
|
||||
panic!(
|
||||
"Invalid program behavior in program {:?}: {err}",
|
||||
chained_call.program_id
|
||||
);
|
||||
}
|
||||
verify_program_output(&chained_call, caller_program_id, &program_output);
|
||||
|
||||
for next_call in program_output.chained_calls.iter().rev() {
|
||||
chained_calls.push_front((next_call.clone(), Some(chained_call.program_id)));
|
||||
@ -202,41 +123,8 @@ impl ExecutionState {
|
||||
"Inner call without a chained call found",
|
||||
);
|
||||
|
||||
// Every private-PDA pre_state must have had its npk bound to its account_id, either via
|
||||
// a `Claim::Pda(seed)` in some program's post_state or via a caller's `pda_seeds`
|
||||
// matching the private derivation. An unbound private-PDA pre_state has no
|
||||
// cryptographic link between the supplied npk and the account_id, and must be rejected.
|
||||
for (pos, account_identity) in account_identities.iter().enumerate() {
|
||||
if account_identity.is_private_pda() {
|
||||
assert!(
|
||||
execution_state
|
||||
.private_pda_bound_positions
|
||||
.contains_key(&pos),
|
||||
"private PDA pre_state at position {pos} has no proven (seed, npk) binding via Claim::Pda or caller pda_seeds"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Check that all modified uninitialized accounts were claimed
|
||||
for (account_id, post) in execution_state
|
||||
.pre_states
|
||||
.iter()
|
||||
.filter(|a| a.account.program_owner == DEFAULT_PROGRAM_ID)
|
||||
.map(|a| {
|
||||
let post = execution_state
|
||||
.post_states
|
||||
.get(&a.account_id)
|
||||
.expect("Post state must exist for pre state");
|
||||
(a, post)
|
||||
})
|
||||
.filter(|(pre_default, post)| pre_default.account != **post)
|
||||
.map(|(pre, post)| (pre.account_id, post))
|
||||
{
|
||||
assert_ne!(
|
||||
post.program_owner, DEFAULT_PROGRAM_ID,
|
||||
"Account {account_id} was modified but not claimed"
|
||||
);
|
||||
}
|
||||
execution_state.assert_all_pda_positions_bound(account_identities);
|
||||
execution_state.assert_modified_accounts_claimed();
|
||||
|
||||
execution_state
|
||||
}
|
||||
@ -254,200 +142,185 @@ impl ExecutionState {
|
||||
for (pre, mut post) in output_pre_states.into_iter().zip(output_post_states) {
|
||||
let pre_account_id = pre.account_id;
|
||||
let pre_is_authorized = pre.is_authorized;
|
||||
let post_states_entry = self.post_states.entry(pre.account_id);
|
||||
match &post_states_entry {
|
||||
Entry::Occupied(occupied) => {
|
||||
#[expect(
|
||||
clippy::shadow_unrelated,
|
||||
reason = "Shadowing is intentional to use all fields"
|
||||
)]
|
||||
let AccountWithMetadata {
|
||||
account: pre_account,
|
||||
account_id: pre_account_id,
|
||||
is_authorized: pre_is_authorized,
|
||||
} = pre;
|
||||
|
||||
// Ensure that new pre state is the same as known post state
|
||||
assert_eq!(
|
||||
occupied.get(),
|
||||
&pre_account,
|
||||
"Inconsistent pre state for account {pre_account_id}",
|
||||
if let Some(existing) = self.post_states.get(&pre.account_id) {
|
||||
#[expect(
|
||||
clippy::shadow_unrelated,
|
||||
reason = "Shadowing is intentional to use all fields"
|
||||
)]
|
||||
let AccountWithMetadata {
|
||||
account: pre_account,
|
||||
account_id: pre_account_id,
|
||||
is_authorized: pre_is_authorized,
|
||||
} = pre;
|
||||
|
||||
assert_eq!(
|
||||
existing, &pre_account,
|
||||
"Inconsistent pre state for account {pre_account_id}",
|
||||
);
|
||||
|
||||
let (previous_is_authorized, pre_state_position) = self
|
||||
.pre_states
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, acc)| acc.account_id == pre_account_id)
|
||||
.map_or_else(
|
||||
|| panic!(
|
||||
"Pre state must exist in execution state for account {pre_account_id}",
|
||||
),
|
||||
|(pos, acc)| (acc.is_authorized, pos),
|
||||
);
|
||||
|
||||
let (previous_is_authorized, pre_state_position) = self
|
||||
.pre_states
|
||||
.iter()
|
||||
.enumerate()
|
||||
.find(|(_, acc)| acc.account_id == pre_account_id)
|
||||
.map_or_else(
|
||||
|| panic!(
|
||||
"Pre state must exist in execution state for account {pre_account_id}",
|
||||
),
|
||||
|(pos, acc)| (acc.is_authorized, pos)
|
||||
);
|
||||
let is_authorized = resolve_authorization_and_record_bindings(
|
||||
&mut self.pda_family_binding,
|
||||
&mut self.private_pda_bound_positions,
|
||||
&self.private_pda_npk_by_position,
|
||||
&mut self.authorized_accounts,
|
||||
pre_account_id,
|
||||
pre_state_position,
|
||||
caller_program_id,
|
||||
caller_pda_seeds,
|
||||
previous_is_authorized,
|
||||
);
|
||||
|
||||
let is_authorized = resolve_authorization_and_record_bindings(
|
||||
&mut self.pda_family_binding,
|
||||
&mut self.private_pda_bound_positions,
|
||||
&self.private_pda_npk_by_position,
|
||||
&mut self.authorized_accounts,
|
||||
pre_account_id,
|
||||
pre_state_position,
|
||||
caller_program_id,
|
||||
caller_pda_seeds,
|
||||
previous_is_authorized,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
pre_is_authorized, is_authorized,
|
||||
"Inconsistent authorization for account {pre_account_id}",
|
||||
);
|
||||
}
|
||||
Entry::Vacant(_) => {
|
||||
// Pre state for the initial call
|
||||
let pre_state_position = self.pre_states.len();
|
||||
let external_seed = match account_identities.get(pre_state_position) {
|
||||
Some(InputAccountIdentity::PrivatePdaInit {
|
||||
npk,
|
||||
identifier,
|
||||
seed: Some((seed, authority_program_id)),
|
||||
..
|
||||
}) => {
|
||||
let expected = AccountId::for_private_pda(
|
||||
authority_program_id,
|
||||
seed,
|
||||
npk,
|
||||
*identifier,
|
||||
);
|
||||
assert_eq!(
|
||||
pre_account_id, expected,
|
||||
"External seed mismatch for PrivatePdaInit at position {pre_state_position}"
|
||||
);
|
||||
Some((*seed, *authority_program_id))
|
||||
}
|
||||
Some(InputAccountIdentity::PrivatePdaUpdate {
|
||||
nsk,
|
||||
identifier,
|
||||
seed: Some((seed, authority_program_id)),
|
||||
..
|
||||
}) => {
|
||||
let npk = NullifierPublicKey::from(nsk);
|
||||
let expected = AccountId::for_private_pda(
|
||||
authority_program_id,
|
||||
seed,
|
||||
&npk,
|
||||
*identifier,
|
||||
);
|
||||
assert_eq!(
|
||||
pre_account_id, expected,
|
||||
"External seed mismatch for PrivatePdaUpdate at position {pre_state_position}"
|
||||
);
|
||||
Some((*seed, *authority_program_id))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
// External seed is only consulted the first time the account is seen.
|
||||
// Subsequent calls need no re-check because the entry is already recorded on
|
||||
// private_pda_bound_positions.
|
||||
if let Some((seed, authority_program_id)) = external_seed {
|
||||
assert!(
|
||||
!pre.is_authorized,
|
||||
"Private PDA with externally-provided seed must not be authorized at position {pre_state_position}"
|
||||
);
|
||||
bind_private_pda_position(
|
||||
&mut self.private_pda_bound_positions,
|
||||
pre_state_position,
|
||||
authority_program_id,
|
||||
seed,
|
||||
);
|
||||
assert_family_binding(
|
||||
&mut self.pda_family_binding,
|
||||
authority_program_id,
|
||||
seed,
|
||||
pre_account_id,
|
||||
);
|
||||
}
|
||||
self.pre_states.push(pre);
|
||||
}
|
||||
assert_eq!(
|
||||
pre_is_authorized, is_authorized,
|
||||
"Inconsistent authorization for account {pre_account_id}",
|
||||
);
|
||||
} else {
|
||||
let pre_state_position = self.pre_states.len();
|
||||
resolve_external_seed(
|
||||
account_identities,
|
||||
pre_state_position,
|
||||
pre_account_id,
|
||||
pre.is_authorized,
|
||||
&mut self.private_pda_bound_positions,
|
||||
&mut self.pda_family_binding,
|
||||
);
|
||||
self.pre_states.push(pre);
|
||||
}
|
||||
|
||||
if let Some(claim) = post.required_claim() {
|
||||
// The invoked program can only claim accounts with default program id.
|
||||
assert_eq!(
|
||||
post.account().program_owner,
|
||||
DEFAULT_PROGRAM_ID,
|
||||
"Cannot claim an initialized account {pre_account_id}"
|
||||
self.process_claim(
|
||||
account_identities,
|
||||
&mut post,
|
||||
pre_account_id,
|
||||
pre_is_authorized,
|
||||
program_id,
|
||||
claim,
|
||||
);
|
||||
|
||||
let pre_state_position = self
|
||||
.pre_states
|
||||
.iter()
|
||||
.position(|acc| acc.account_id == pre_account_id)
|
||||
.expect("Pre state must exist at this point");
|
||||
|
||||
let account_identity = &account_identities[pre_state_position];
|
||||
if account_identity.is_public() {
|
||||
match claim {
|
||||
Claim::Authorized => {
|
||||
// Note: no need to check authorized pdas because we have already
|
||||
// checked consistency of authorization above.
|
||||
assert!(
|
||||
pre_is_authorized,
|
||||
"Cannot claim unauthorized account {pre_account_id}"
|
||||
);
|
||||
}
|
||||
Claim::Pda(seed) => {
|
||||
let pda = AccountId::for_public_pda(&program_id, &seed);
|
||||
assert_eq!(
|
||||
pre_account_id, pda,
|
||||
"Invalid PDA claim for account {pre_account_id} which does not match derived PDA {pda}"
|
||||
);
|
||||
assert_family_binding(
|
||||
&mut self.pda_family_binding,
|
||||
program_id,
|
||||
seed,
|
||||
pre_account_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Private accounts: don't enforce the claim semantics. Unauthorized private
|
||||
// claiming is intentionally allowed
|
||||
match claim {
|
||||
Claim::Authorized => {}
|
||||
Claim::Pda(seed) => {
|
||||
let (npk, identifier) = self
|
||||
.private_pda_npk_by_position
|
||||
.get(&pre_state_position)
|
||||
.expect(
|
||||
"private PDA pre_state must have an npk in the position map",
|
||||
);
|
||||
let pda =
|
||||
AccountId::for_private_pda(&program_id, &seed, npk, *identifier);
|
||||
assert_eq!(
|
||||
pre_account_id, pda,
|
||||
"Invalid private PDA claim for account {pre_account_id}"
|
||||
);
|
||||
bind_private_pda_position(
|
||||
&mut self.private_pda_bound_positions,
|
||||
pre_state_position,
|
||||
program_id,
|
||||
seed,
|
||||
);
|
||||
assert_family_binding(
|
||||
&mut self.pda_family_binding,
|
||||
program_id,
|
||||
seed,
|
||||
pre_account_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post.account_mut().program_owner = program_id;
|
||||
}
|
||||
|
||||
post_states_entry.insert_entry(post.into_account());
|
||||
self.post_states.insert(pre_account_id, post.into_account());
|
||||
}
|
||||
}
|
||||
|
||||
fn process_claim(
|
||||
&mut self,
|
||||
account_identities: &[InputAccountIdentity],
|
||||
post: &mut AccountPostState,
|
||||
pre_account_id: AccountId,
|
||||
pre_is_authorized: bool,
|
||||
program_id: ProgramId,
|
||||
claim: Claim,
|
||||
) {
|
||||
assert_eq!(
|
||||
post.account().program_owner,
|
||||
DEFAULT_PROGRAM_ID,
|
||||
"Cannot claim an initialized account {pre_account_id}"
|
||||
);
|
||||
|
||||
let pre_state_position = self
|
||||
.pre_states
|
||||
.iter()
|
||||
.position(|acc| acc.account_id == pre_account_id)
|
||||
.expect("Pre state must exist at this point");
|
||||
|
||||
let account_identity = &account_identities[pre_state_position];
|
||||
if account_identity.is_public() {
|
||||
match claim {
|
||||
Claim::Authorized => {
|
||||
assert!(
|
||||
pre_is_authorized,
|
||||
"Cannot claim unauthorized account {pre_account_id}"
|
||||
);
|
||||
}
|
||||
Claim::Pda(seed) => {
|
||||
let pda = AccountId::for_public_pda(&program_id, &seed);
|
||||
assert_eq!(
|
||||
pre_account_id, pda,
|
||||
"Invalid PDA claim for account {pre_account_id} which does not match derived PDA {pda}"
|
||||
);
|
||||
assert_family_binding(
|
||||
&mut self.pda_family_binding,
|
||||
program_id,
|
||||
seed,
|
||||
pre_account_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
match claim {
|
||||
Claim::Authorized => {}
|
||||
Claim::Pda(seed) => {
|
||||
let (npk, identifier) = self
|
||||
.private_pda_npk_by_position
|
||||
.get(&pre_state_position)
|
||||
.expect("private PDA pre_state must have an npk in the position map");
|
||||
let pda = AccountId::for_private_pda(&program_id, &seed, npk, *identifier);
|
||||
assert_eq!(
|
||||
pre_account_id, pda,
|
||||
"Invalid private PDA claim for account {pre_account_id}"
|
||||
);
|
||||
bind_private_pda_position(
|
||||
&mut self.private_pda_bound_positions,
|
||||
pre_state_position,
|
||||
program_id,
|
||||
seed,
|
||||
);
|
||||
assert_family_binding(
|
||||
&mut self.pda_family_binding,
|
||||
program_id,
|
||||
seed,
|
||||
pre_account_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post.account_mut().program_owner = program_id;
|
||||
}
|
||||
|
||||
fn assert_all_pda_positions_bound(&self, account_identities: &[InputAccountIdentity]) {
|
||||
for (pos, account_identity) in account_identities.iter().enumerate() {
|
||||
if account_identity.is_private_pda() {
|
||||
assert!(
|
||||
self.private_pda_bound_positions.contains_key(&pos),
|
||||
"private PDA pre_state at position {pos} has no proven (seed, npk) binding via Claim::Pda or caller pda_seeds"
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn assert_modified_accounts_claimed(&self) {
|
||||
for (account_id, post) in self
|
||||
.pre_states
|
||||
.iter()
|
||||
.filter(|a| a.account.program_owner == DEFAULT_PROGRAM_ID)
|
||||
.map(|a| {
|
||||
let post = self
|
||||
.post_states
|
||||
.get(&a.account_id)
|
||||
.expect("Post state must exist for pre state");
|
||||
(a, post)
|
||||
})
|
||||
.filter(|(pre_default, post)| pre_default.account != **post)
|
||||
.map(|(pre, post)| (pre.account_id, post))
|
||||
{
|
||||
assert_ne!(
|
||||
post.program_owner, DEFAULT_PROGRAM_ID,
|
||||
"Account {account_id} was modified but not claimed"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -486,6 +359,150 @@ impl ExecutionState {
|
||||
}
|
||||
}
|
||||
|
||||
fn verify_program_output(
|
||||
chained_call: &ChainedCall,
|
||||
caller_program_id: Option<ProgramId>,
|
||||
program_output: &ProgramOutput,
|
||||
) {
|
||||
assert_eq!(
|
||||
chained_call.instruction_data, program_output.instruction_data,
|
||||
"Mismatched instruction data between chained call and program output"
|
||||
);
|
||||
|
||||
let program_output_words =
|
||||
&to_vec(program_output).expect("program_output must be serializable");
|
||||
env::verify(chained_call.program_id, program_output_words)
|
||||
.unwrap_or_else(|_: Infallible| unreachable!("Infallible error is never constructed"));
|
||||
|
||||
assert_eq!(
|
||||
program_output.self_program_id, chained_call.program_id,
|
||||
"Program output self_program_id does not match chained call program_id"
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
program_output.caller_program_id, caller_program_id,
|
||||
"Program output caller_program_id does not match actual caller"
|
||||
);
|
||||
|
||||
if let Err(err) = validate_execution(
|
||||
&program_output.pre_states,
|
||||
&program_output.post_states,
|
||||
chained_call.program_id,
|
||||
) {
|
||||
panic!(
|
||||
"Invalid program behavior in program {:?}: {err}",
|
||||
chained_call.program_id
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
fn build_private_pda_npk_map(
|
||||
account_identities: &[InputAccountIdentity],
|
||||
) -> HashMap<usize, (NullifierPublicKey, Identifier)> {
|
||||
account_identities
|
||||
.iter()
|
||||
.enumerate()
|
||||
.filter_map(|(pos, identity)| {
|
||||
identity
|
||||
.npk_if_private_pda()
|
||||
.map(|(npk, identifier)| (pos, (npk, identifier)))
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn intersect_validity_windows(
|
||||
program_outputs: &[ProgramOutput],
|
||||
) -> (BlockValidityWindow, TimestampValidityWindow) {
|
||||
let block_valid_from = program_outputs
|
||||
.iter()
|
||||
.filter_map(|output| output.block_validity_window.start())
|
||||
.max();
|
||||
let block_valid_until = program_outputs
|
||||
.iter()
|
||||
.filter_map(|output| output.block_validity_window.end())
|
||||
.min();
|
||||
let ts_valid_from = program_outputs
|
||||
.iter()
|
||||
.filter_map(|output| output.timestamp_validity_window.start())
|
||||
.max();
|
||||
let ts_valid_until = program_outputs
|
||||
.iter()
|
||||
.filter_map(|output| output.timestamp_validity_window.end())
|
||||
.min();
|
||||
|
||||
let block_validity_window: BlockValidityWindow =
|
||||
(block_valid_from, block_valid_until).try_into().expect(
|
||||
"There should be non empty intersection in the program output block validity windows",
|
||||
);
|
||||
let timestamp_validity_window: TimestampValidityWindow = (ts_valid_from, ts_valid_until)
|
||||
.try_into()
|
||||
.expect(
|
||||
"There should be non empty intersection in the program output timestamp validity windows",
|
||||
);
|
||||
|
||||
(block_validity_window, timestamp_validity_window)
|
||||
}
|
||||
|
||||
fn resolve_external_seed(
|
||||
account_identities: &[InputAccountIdentity],
|
||||
pre_state_position: usize,
|
||||
pre_account_id: AccountId,
|
||||
is_authorized: bool,
|
||||
private_pda_bound_positions: &mut HashMap<usize, (ProgramId, PdaSeed)>,
|
||||
pda_family_binding: &mut HashMap<(ProgramId, PdaSeed), AccountId>,
|
||||
) {
|
||||
let external_seed = match account_identities.get(pre_state_position) {
|
||||
Some(InputAccountIdentity::PrivatePdaInit {
|
||||
npk,
|
||||
identifier,
|
||||
seed: Some((seed, authority_program_id)),
|
||||
..
|
||||
}) => {
|
||||
let expected = AccountId::for_private_pda(authority_program_id, seed, npk, *identifier);
|
||||
assert_eq!(
|
||||
pre_account_id, expected,
|
||||
"External seed mismatch for PrivatePdaInit at position {pre_state_position}"
|
||||
);
|
||||
Some((*seed, *authority_program_id))
|
||||
}
|
||||
Some(InputAccountIdentity::PrivatePdaUpdate {
|
||||
nsk,
|
||||
identifier,
|
||||
seed: Some((seed, authority_program_id)),
|
||||
..
|
||||
}) => {
|
||||
let npk = NullifierPublicKey::from(nsk);
|
||||
let expected =
|
||||
AccountId::for_private_pda(authority_program_id, seed, &npk, *identifier);
|
||||
assert_eq!(
|
||||
pre_account_id, expected,
|
||||
"External seed mismatch for PrivatePdaUpdate at position {pre_state_position}"
|
||||
);
|
||||
Some((*seed, *authority_program_id))
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
|
||||
if let Some((seed, authority_program_id)) = external_seed {
|
||||
assert!(
|
||||
!is_authorized,
|
||||
"Private PDA with externally-provided seed must not be authorized at position {pre_state_position}"
|
||||
);
|
||||
bind_private_pda_position(
|
||||
private_pda_bound_positions,
|
||||
pre_state_position,
|
||||
authority_program_id,
|
||||
seed,
|
||||
);
|
||||
assert_family_binding(
|
||||
pda_family_binding,
|
||||
authority_program_id,
|
||||
seed,
|
||||
pre_account_id,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// Record or re-verify the `(program_id, seed) → account_id` family binding for the
|
||||
/// transaction. Any claim or caller-seed authorization that resolves a `pre_state` under
|
||||
/// `(program_id, seed)` must agree with every prior resolution of the same pair; otherwise a
|
||||
|
||||
@ -1,13 +1,244 @@
|
||||
use lee_core::{
|
||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT_HASH, EncryptedAccountData, EncryptionScheme,
|
||||
EphemeralPublicKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey,
|
||||
NullifierSecretKey, PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey,
|
||||
Commitment, CommitmentSetDigest, EncryptedAccountData, EncryptionScheme, EphemeralPublicKey,
|
||||
InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, NullifierSecretKey,
|
||||
PrivacyPreservingCircuitOutput, PrivateAccountKind, SharedSecretKey,
|
||||
account::{Account, AccountId, Nonce},
|
||||
compute_digest_for_path,
|
||||
};
|
||||
|
||||
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,
|
||||
view_tag: u8,
|
||||
ssk: &'ctx SharedSecretKey,
|
||||
identifier: u128,
|
||||
}
|
||||
|
||||
impl PrivateOutputHandler<'_> {
|
||||
fn authorized_init(self, nsk: &NullifierSecretKey, commitment_root: &CommitmentSetDigest) {
|
||||
let npk = NullifierPublicKey::from(nsk);
|
||||
let account_id =
|
||||
derive_and_verify_account_id(&npk, self.identifier, self.pre_state.account_id);
|
||||
|
||||
assert!(
|
||||
self.pre_state.is_authorized,
|
||||
"Pre-state not authorized for authenticated private account"
|
||||
);
|
||||
assert_eq!(
|
||||
self.pre_state.account,
|
||||
Account::default(),
|
||||
"Found new private account with non default values"
|
||||
);
|
||||
|
||||
let (new_nullifier, new_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);
|
||||
}
|
||||
|
||||
fn authorized_update(self, nsk: &NullifierSecretKey, membership_proof: &MembershipProof) {
|
||||
let npk = NullifierPublicKey::from(nsk);
|
||||
let account_id =
|
||||
derive_and_verify_account_id(&npk, self.identifier, self.pre_state.account_id);
|
||||
|
||||
assert!(
|
||||
self.pre_state.is_authorized,
|
||||
"Pre-state not authorized for authenticated private account"
|
||||
);
|
||||
|
||||
let new_nullifier = compute_update_nullifier_and_set_digest(
|
||||
membership_proof,
|
||||
&self.pre_state.account,
|
||||
&account_id,
|
||||
nsk,
|
||||
);
|
||||
let new_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);
|
||||
}
|
||||
|
||||
fn unauthorized(self, npk: &NullifierPublicKey, commitment_root: &CommitmentSetDigest) {
|
||||
let account_id =
|
||||
derive_and_verify_account_id(npk, self.identifier, self.pre_state.account_id);
|
||||
|
||||
assert_eq!(
|
||||
self.pre_state.account,
|
||||
Account::default(),
|
||||
"Found new private account with non default values",
|
||||
);
|
||||
assert!(
|
||||
!self.pre_state.is_authorized,
|
||||
"Found new private account marked as authorized."
|
||||
);
|
||||
|
||||
let (new_nullifier, new_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);
|
||||
}
|
||||
|
||||
fn pda_init(
|
||||
self,
|
||||
commitment_root: &CommitmentSetDigest,
|
||||
pos: usize,
|
||||
pda_seed_by_position: &std::collections::HashMap<
|
||||
usize,
|
||||
(lee_core::program::ProgramId, lee_core::program::PdaSeed),
|
||||
>,
|
||||
) {
|
||||
// The npk-to-account_id binding is established upstream in
|
||||
// `validate_and_sync_states` via `Claim::Pda(seed)` or a caller `pda_seeds`
|
||||
// match. Here we only enforce the init pre-conditions.
|
||||
assert!(
|
||||
!self.pre_state.is_authorized,
|
||||
"PrivatePdaInit requires unauthorized pre_state"
|
||||
);
|
||||
assert_eq!(
|
||||
self.pre_state.account,
|
||||
Account::default(),
|
||||
"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 (authority_program_id, seed) = pda_seed_by_position
|
||||
.get(&pos)
|
||||
.expect("PrivatePdaInit position must be in pda_seed_by_position");
|
||||
let kind = PrivateAccountKind::Pda {
|
||||
program_id: *authority_program_id,
|
||||
seed: *seed,
|
||||
identifier: self.identifier,
|
||||
};
|
||||
|
||||
self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce);
|
||||
}
|
||||
|
||||
fn pda_update(
|
||||
self,
|
||||
nsk: &NullifierSecretKey,
|
||||
membership_proof: &MembershipProof,
|
||||
external_seed: Option<&(lee_core::program::PdaSeed, lee_core::program::ProgramId)>,
|
||||
pos: usize,
|
||||
pda_seed_by_position: &std::collections::HashMap<
|
||||
usize,
|
||||
(lee_core::program::ProgramId, lee_core::program::PdaSeed),
|
||||
>,
|
||||
) {
|
||||
// With an external seed the binding comes from the circuit input and the
|
||||
// pre_state is intentionally unauthorized; without one the binding comes from
|
||||
// a Claim or caller pda_seeds, so the pre_state must already be authorized.
|
||||
assert!(
|
||||
self.pre_state.is_authorized ^ external_seed.is_some(),
|
||||
"PrivatePdaUpdate requires authorized pre_state or external seed"
|
||||
);
|
||||
|
||||
let new_nullifier = compute_update_nullifier_and_set_digest(
|
||||
membership_proof,
|
||||
&self.pre_state.account,
|
||||
&self.pre_state.account_id,
|
||||
nsk,
|
||||
);
|
||||
let new_nonce = self
|
||||
.pre_state
|
||||
.account
|
||||
.nonce
|
||||
.private_account_nonce_increment(nsk);
|
||||
|
||||
let account_id = self.pre_state.account_id;
|
||||
let (authority_program_id, seed) = pda_seed_by_position
|
||||
.get(&pos)
|
||||
.expect("PrivatePdaUpdate position must be in pda_seed_by_position");
|
||||
let kind = PrivateAccountKind::Pda {
|
||||
program_id: *authority_program_id,
|
||||
seed: *seed,
|
||||
identifier: self.identifier,
|
||||
};
|
||||
|
||||
self.emit_private_output(&account_id, &kind, new_nullifier, new_nonce);
|
||||
}
|
||||
|
||||
fn emit_private_output(
|
||||
self,
|
||||
account_id: &AccountId,
|
||||
kind: &PrivateAccountKind,
|
||||
new_nullifier: (Nullifier, CommitmentSetDigest),
|
||||
new_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;
|
||||
|
||||
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,
|
||||
);
|
||||
|
||||
self.output.new_commitments.push(commitment_post);
|
||||
self.output
|
||||
.encrypted_private_post_states
|
||||
.push(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::for_account_initialization(account_id),
|
||||
*commitment_root,
|
||||
);
|
||||
let nonce = Nonce::private_account_nonce_init(account_id);
|
||||
(nullifier, nonce)
|
||||
}
|
||||
|
||||
fn derive_and_verify_account_id(
|
||||
npk: &NullifierPublicKey,
|
||||
identifier: u128,
|
||||
pre_state_account_id: AccountId,
|
||||
) -> AccountId {
|
||||
let account_id = AccountId::for_regular_private_account(npk, identifier);
|
||||
assert_eq!(account_id, pre_state_account_id, "AccountId mismatch");
|
||||
account_id
|
||||
}
|
||||
|
||||
fn compute_update_nullifier_and_set_digest(
|
||||
membership_proof: &MembershipProof,
|
||||
pre_account: &Account,
|
||||
account_id: &AccountId,
|
||||
nsk: &NullifierSecretKey,
|
||||
) -> (Nullifier, CommitmentSetDigest) {
|
||||
let commitment_pre = Commitment::new(account_id, pre_account);
|
||||
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
|
||||
let nullifier = Nullifier::for_account_update(&commitment_pre, nsk);
|
||||
(nullifier, set_digest)
|
||||
}
|
||||
|
||||
pub fn compute_circuit_output(
|
||||
execution_state: ExecutionState,
|
||||
account_identities: &[InputAccountIdentity],
|
||||
@ -45,40 +276,18 @@ pub fn compute_circuit_output(
|
||||
ssk,
|
||||
nsk,
|
||||
identifier,
|
||||
} => {
|
||||
let npk = NullifierPublicKey::from(nsk);
|
||||
let account_id = AccountId::for_regular_private_account(&npk, *identifier);
|
||||
|
||||
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
|
||||
assert!(
|
||||
pre_state.is_authorized,
|
||||
"Pre-state not authorized for authenticated private account"
|
||||
);
|
||||
assert_eq!(
|
||||
pre_state.account,
|
||||
Account::default(),
|
||||
"Found new private account with non default values"
|
||||
);
|
||||
|
||||
let new_nullifier = (
|
||||
Nullifier::for_account_initialization(&account_id),
|
||||
DUMMY_COMMITMENT_HASH,
|
||||
);
|
||||
let new_nonce = Nonce::private_account_nonce_init(&account_id);
|
||||
|
||||
emit_private_output(
|
||||
&mut output,
|
||||
&mut output_index,
|
||||
post_state,
|
||||
&account_id,
|
||||
&PrivateAccountKind::Regular(*identifier),
|
||||
ssk,
|
||||
epk,
|
||||
*view_tag,
|
||||
new_nullifier,
|
||||
new_nonce,
|
||||
);
|
||||
commitment_root,
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
}
|
||||
.authorized_init(nsk, commitment_root),
|
||||
InputAccountIdentity::PrivateAuthorizedUpdate {
|
||||
epk,
|
||||
view_tag,
|
||||
@ -86,127 +295,54 @@ pub fn compute_circuit_output(
|
||||
nsk,
|
||||
membership_proof,
|
||||
identifier,
|
||||
} => {
|
||||
let npk = NullifierPublicKey::from(nsk);
|
||||
let account_id = AccountId::for_regular_private_account(&npk, *identifier);
|
||||
|
||||
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
|
||||
assert!(
|
||||
pre_state.is_authorized,
|
||||
"Pre-state not authorized for authenticated private account"
|
||||
);
|
||||
|
||||
let new_nullifier = compute_update_nullifier_and_set_digest(
|
||||
membership_proof,
|
||||
&pre_state.account,
|
||||
&account_id,
|
||||
nsk,
|
||||
);
|
||||
let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk);
|
||||
|
||||
emit_private_output(
|
||||
&mut output,
|
||||
&mut output_index,
|
||||
post_state,
|
||||
&account_id,
|
||||
&PrivateAccountKind::Regular(*identifier),
|
||||
ssk,
|
||||
epk,
|
||||
*view_tag,
|
||||
new_nullifier,
|
||||
new_nonce,
|
||||
);
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
}
|
||||
.authorized_update(nsk, membership_proof),
|
||||
InputAccountIdentity::PrivateUnauthorized {
|
||||
epk,
|
||||
view_tag,
|
||||
npk,
|
||||
ssk,
|
||||
identifier,
|
||||
} => {
|
||||
let account_id = AccountId::for_regular_private_account(npk, *identifier);
|
||||
|
||||
assert_eq!(account_id, pre_state.account_id, "AccountId mismatch");
|
||||
assert_eq!(
|
||||
pre_state.account,
|
||||
Account::default(),
|
||||
"Found new private account with non default values",
|
||||
);
|
||||
assert!(
|
||||
!pre_state.is_authorized,
|
||||
"Found new private account marked as authorized."
|
||||
);
|
||||
|
||||
let new_nullifier = (
|
||||
Nullifier::for_account_initialization(&account_id),
|
||||
DUMMY_COMMITMENT_HASH,
|
||||
);
|
||||
let new_nonce = Nonce::private_account_nonce_init(&account_id);
|
||||
|
||||
emit_private_output(
|
||||
&mut output,
|
||||
&mut output_index,
|
||||
post_state,
|
||||
&account_id,
|
||||
&PrivateAccountKind::Regular(*identifier),
|
||||
ssk,
|
||||
epk,
|
||||
*view_tag,
|
||||
new_nullifier,
|
||||
new_nonce,
|
||||
);
|
||||
commitment_root,
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
}
|
||||
.unauthorized(npk, commitment_root),
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
epk,
|
||||
view_tag,
|
||||
npk: _,
|
||||
ssk,
|
||||
identifier,
|
||||
commitment_root,
|
||||
seed: _,
|
||||
} => {
|
||||
// The npk-to-account_id binding is established upstream in
|
||||
// `validate_and_sync_states` via `Claim::Pda(seed)` or a caller `pda_seeds`
|
||||
// match. Here we only enforce the init pre-conditions. The supplied npk on
|
||||
// the variant has been recorded into `private_pda_npk_by_position` and used
|
||||
// for the binding check; we use `pre_state.account_id` directly for nullifier
|
||||
// and commitment derivation.
|
||||
assert!(
|
||||
!pre_state.is_authorized,
|
||||
"PrivatePdaInit requires unauthorized pre_state"
|
||||
);
|
||||
assert_eq!(
|
||||
pre_state.account,
|
||||
Account::default(),
|
||||
"New private PDA must be default"
|
||||
);
|
||||
|
||||
let new_nullifier = (
|
||||
Nullifier::for_account_initialization(&pre_state.account_id),
|
||||
DUMMY_COMMITMENT_HASH,
|
||||
);
|
||||
let new_nonce = Nonce::private_account_nonce_init(&pre_state.account_id);
|
||||
|
||||
let account_id = pre_state.account_id;
|
||||
let (authority_program_id, seed) = pda_seed_by_position
|
||||
.get(&pos)
|
||||
.expect("PrivatePdaInit position must be in pda_seed_by_position");
|
||||
emit_private_output(
|
||||
&mut output,
|
||||
&mut output_index,
|
||||
post_state,
|
||||
&account_id,
|
||||
&PrivateAccountKind::Pda {
|
||||
program_id: *authority_program_id,
|
||||
seed: *seed,
|
||||
identifier: *identifier,
|
||||
},
|
||||
ssk,
|
||||
epk,
|
||||
*view_tag,
|
||||
new_nullifier,
|
||||
new_nonce,
|
||||
);
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
}
|
||||
.pda_init(commitment_root, pos, &pda_seed_by_position),
|
||||
InputAccountIdentity::PrivatePdaUpdate {
|
||||
epk,
|
||||
view_tag,
|
||||
@ -215,103 +351,25 @@ pub fn compute_circuit_output(
|
||||
membership_proof,
|
||||
identifier,
|
||||
seed: external_seed,
|
||||
} => {
|
||||
// With an external seed the binding comes from the circuit input and the
|
||||
// pre_state is intentionally unauthorized; without one the binding comes from
|
||||
// a Claim or caller pda_seeds, so the pre_state must already be authorized.
|
||||
// When `external_seed` is `Some`, execution_state already asserted
|
||||
// `!pre_state.is_authorized`.
|
||||
assert!(
|
||||
pre_state.is_authorized ^ external_seed.is_some(),
|
||||
"PrivatePdaUpdate requires authorized pre_state or external seed"
|
||||
);
|
||||
|
||||
let new_nullifier = compute_update_nullifier_and_set_digest(
|
||||
membership_proof,
|
||||
&pre_state.account,
|
||||
&pre_state.account_id,
|
||||
nsk,
|
||||
);
|
||||
let new_nonce = pre_state.account.nonce.private_account_nonce_increment(nsk);
|
||||
|
||||
let account_id = pre_state.account_id;
|
||||
let (authority_program_id, seed) = pda_seed_by_position
|
||||
.get(&pos)
|
||||
.expect("PrivatePdaUpdate position must be in pda_seed_by_position");
|
||||
emit_private_output(
|
||||
&mut output,
|
||||
&mut output_index,
|
||||
post_state,
|
||||
&account_id,
|
||||
&PrivateAccountKind::Pda {
|
||||
program_id: *authority_program_id,
|
||||
seed: *seed,
|
||||
identifier: *identifier,
|
||||
},
|
||||
ssk,
|
||||
epk,
|
||||
*view_tag,
|
||||
new_nullifier,
|
||||
new_nonce,
|
||||
);
|
||||
} => PrivateOutputHandler {
|
||||
output: &mut output,
|
||||
output_index: &mut output_index,
|
||||
pre_state: &pre_state,
|
||||
post_state,
|
||||
epk,
|
||||
view_tag: *view_tag,
|
||||
ssk,
|
||||
identifier: *identifier,
|
||||
}
|
||||
.pda_update(
|
||||
nsk,
|
||||
membership_proof,
|
||||
external_seed.as_ref(),
|
||||
pos,
|
||||
&pda_seed_by_position,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
#[expect(
|
||||
clippy::too_many_arguments,
|
||||
reason = "Inputs are distinct concerns from the variant arms; bundling would be artificial"
|
||||
)]
|
||||
fn emit_private_output(
|
||||
output: &mut PrivacyPreservingCircuitOutput,
|
||||
output_index: &mut u32,
|
||||
post_state: Account,
|
||||
account_id: &AccountId,
|
||||
kind: &PrivateAccountKind,
|
||||
shared_secret: &SharedSecretKey,
|
||||
epk: &EphemeralPublicKey,
|
||||
view_tag: u8,
|
||||
new_nullifier: (Nullifier, CommitmentSetDigest),
|
||||
new_nonce: Nonce,
|
||||
) {
|
||||
output.new_nullifiers.push(new_nullifier);
|
||||
|
||||
let mut post_with_updated_nonce = post_state;
|
||||
post_with_updated_nonce.nonce = new_nonce;
|
||||
|
||||
let commitment_post = Commitment::new(account_id, &post_with_updated_nonce);
|
||||
let encrypted_account = EncryptionScheme::encrypt(
|
||||
&post_with_updated_nonce,
|
||||
kind,
|
||||
shared_secret,
|
||||
&commitment_post,
|
||||
*output_index,
|
||||
);
|
||||
|
||||
output.new_commitments.push(commitment_post);
|
||||
output
|
||||
.encrypted_private_post_states
|
||||
.push(EncryptedAccountData {
|
||||
ciphertext: encrypted_account,
|
||||
epk: epk.clone(),
|
||||
view_tag,
|
||||
});
|
||||
*output_index = output_index
|
||||
.checked_add(1)
|
||||
.unwrap_or_else(|| panic!("Too many private accounts, output index overflow"));
|
||||
}
|
||||
|
||||
fn compute_update_nullifier_and_set_digest(
|
||||
membership_proof: &MembershipProof,
|
||||
pre_account: &Account,
|
||||
account_id: &AccountId,
|
||||
nsk: &NullifierSecretKey,
|
||||
) -> (Nullifier, CommitmentSetDigest) {
|
||||
let commitment_pre = Commitment::new(account_id, pre_account);
|
||||
let set_digest = compute_digest_for_path(&commitment_pre, membership_proof);
|
||||
let nullifier = Nullifier::for_account_update(&commitment_pre, nsk);
|
||||
(nullifier, set_digest)
|
||||
}
|
||||
|
||||
@ -38,6 +38,7 @@ pub enum InputAccountIdentity {
|
||||
ssk: SharedSecretKey,
|
||||
nsk: NullifierSecretKey,
|
||||
identifier: Identifier,
|
||||
commitment_root: CommitmentSetDigest,
|
||||
},
|
||||
/// Update of an authorized standalone private account: existing on-chain commitment, with
|
||||
/// membership proof.
|
||||
@ -57,6 +58,7 @@ pub enum InputAccountIdentity {
|
||||
npk: NullifierPublicKey,
|
||||
ssk: SharedSecretKey,
|
||||
identifier: Identifier,
|
||||
commitment_root: CommitmentSetDigest,
|
||||
},
|
||||
/// Init of a private PDA, unauthorized. The npk-to-account_id binding is proven upstream
|
||||
/// via `Claim::Pda(seed)` or a caller's `pda_seeds` match. The identifier diversifies the
|
||||
@ -68,6 +70,7 @@ pub enum InputAccountIdentity {
|
||||
npk: NullifierPublicKey,
|
||||
ssk: SharedSecretKey,
|
||||
identifier: Identifier,
|
||||
commitment_root: CommitmentSetDigest,
|
||||
/// When `Some((seed, authority_program_id))`, the circuit binds this position via the
|
||||
/// external derivation check
|
||||
/// `AccountId::for_private_pda(authority_program_id, seed, npk, identifier) ==
|
||||
|
||||
@ -273,6 +273,7 @@ mod tests {
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&crate::test_methods::simple_balance_transfer().into(),
|
||||
@ -387,6 +388,7 @@ mod tests {
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: shared_secret_2,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
@ -460,6 +462,7 @@ mod tests {
|
||||
npk: account_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
&program_with_deps,
|
||||
);
|
||||
@ -491,6 +494,7 @@ mod tests {
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
}],
|
||||
&program.clone().into(),
|
||||
@ -540,6 +544,7 @@ mod tests {
|
||||
npk,
|
||||
ssk: shared_secret_pda,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
}],
|
||||
&program_with_deps,
|
||||
@ -595,6 +600,7 @@ mod tests {
|
||||
npk,
|
||||
ssk: shared_secret_pda,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
},
|
||||
InputAccountIdentity::Public,
|
||||
@ -653,6 +659,7 @@ mod tests {
|
||||
npk: shared_npk,
|
||||
ssk: shared_secret,
|
||||
identifier: shared_identifier,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
@ -683,6 +690,7 @@ mod tests {
|
||||
ssk,
|
||||
nsk: keys.nsk,
|
||||
identifier,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
&program.into(),
|
||||
)
|
||||
@ -714,6 +722,7 @@ mod tests {
|
||||
npk: keys.npk(),
|
||||
ssk,
|
||||
identifier,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
&program.into(),
|
||||
)
|
||||
@ -848,6 +857,7 @@ mod tests {
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: 99,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
}],
|
||||
&program.into(),
|
||||
|
||||
@ -327,8 +327,8 @@ pub mod tests {
|
||||
use std::collections::HashMap;
|
||||
|
||||
use lee_core::{
|
||||
BlockId, Commitment, EncryptedAccountData, InputAccountIdentity, Nullifier,
|
||||
NullifierPublicKey, NullifierSecretKey, SharedSecretKey, Timestamp,
|
||||
BlockId, Commitment, DUMMY_COMMITMENT_HASH, EncryptedAccountData, InputAccountIdentity,
|
||||
Nullifier, NullifierPublicKey, NullifierSecretKey, SharedSecretKey, Timestamp,
|
||||
account::{Account, AccountId, AccountWithMetadata, Nonce, data::Data},
|
||||
encryption::{EphemeralPublicKey, ML_KEM_768_CIPHERTEXT_LEN, ViewingPublicKey},
|
||||
program::{
|
||||
@ -1192,6 +1192,7 @@ pub mod tests {
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&crate::test_methods::simple_balance_transfer().into(),
|
||||
@ -1259,6 +1260,7 @@ pub mod tests {
|
||||
npk: recipient_keys.npk(),
|
||||
ssk: shared_secret_2,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
},
|
||||
],
|
||||
&program.into(),
|
||||
@ -1908,6 +1910,7 @@ pub mod tests {
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
@ -1974,6 +1977,7 @@ pub mod tests {
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
@ -2040,6 +2044,7 @@ pub mod tests {
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
@ -2106,6 +2111,7 @@ pub mod tests {
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
@ -2172,6 +2178,7 @@ pub mod tests {
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
@ -2236,6 +2243,7 @@ pub mod tests {
|
||||
0,
|
||||
)
|
||||
.0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
identifier: 0,
|
||||
},
|
||||
],
|
||||
@ -2279,6 +2287,7 @@ pub mod tests {
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
},
|
||||
],
|
||||
@ -2314,6 +2323,7 @@ pub mod tests {
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
}],
|
||||
&program.into(),
|
||||
@ -2357,6 +2367,7 @@ pub mod tests {
|
||||
npk: npk_b,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
}],
|
||||
&program.into(),
|
||||
@ -2396,6 +2407,7 @@ pub mod tests {
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
}],
|
||||
&program_with_deps,
|
||||
@ -2438,6 +2450,7 @@ pub mod tests {
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
}],
|
||||
&program_with_deps,
|
||||
@ -2479,6 +2492,7 @@ pub mod tests {
|
||||
npk: keys_a.npk(),
|
||||
ssk: shared_a,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
},
|
||||
InputAccountIdentity::PrivatePdaInit {
|
||||
@ -2487,6 +2501,7 @@ pub mod tests {
|
||||
npk: keys_b.npk(),
|
||||
ssk: shared_b,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
},
|
||||
],
|
||||
@ -2531,6 +2546,7 @@ pub mod tests {
|
||||
npk,
|
||||
ssk: shared_secret,
|
||||
identifier: u128::MAX,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: None,
|
||||
}],
|
||||
&program.into(),
|
||||
@ -3302,6 +3318,7 @@ pub mod tests {
|
||||
ssk: shared_secret,
|
||||
nsk: private_keys.nsk,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
&program.into(),
|
||||
)
|
||||
@ -3349,6 +3366,7 @@ pub mod tests {
|
||||
npk: private_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
&program.into(),
|
||||
)
|
||||
@ -3400,6 +3418,7 @@ pub mod tests {
|
||||
ssk: shared_secret,
|
||||
nsk: private_keys.nsk,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
&claimer_program.into(),
|
||||
)
|
||||
@ -3446,6 +3465,7 @@ pub mod tests {
|
||||
ssk: shared_secret2,
|
||||
nsk: private_keys.nsk,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
&noop_program.into(),
|
||||
);
|
||||
@ -3788,6 +3808,7 @@ pub mod tests {
|
||||
npk: account_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
&validity_window_program.into(),
|
||||
)
|
||||
@ -3856,6 +3877,7 @@ pub mod tests {
|
||||
npk: account_keys.npk(),
|
||||
ssk: shared_secret,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
}],
|
||||
&validity_window_program.into(),
|
||||
)
|
||||
@ -4200,6 +4222,7 @@ pub mod tests {
|
||||
npk: alice_npk,
|
||||
ssk: alice_shared_0,
|
||||
identifier: 0,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: Some((seed, proxy_id)),
|
||||
},
|
||||
],
|
||||
@ -4240,6 +4263,7 @@ pub mod tests {
|
||||
npk: alice_npk,
|
||||
ssk: alice_shared_1,
|
||||
identifier: 1,
|
||||
commitment_root: DUMMY_COMMITMENT_HASH,
|
||||
seed: Some((seed, proxy_id)),
|
||||
},
|
||||
],
|
||||
|
||||
@ -5,8 +5,8 @@ use key_protocol::key_management::ephemeral_key_holder::EphemeralKeyHolder;
|
||||
use keycard_wallet::{KeycardWallet, python_path};
|
||||
use lee::{AccountId, PrivateKey, PublicKey, Signature};
|
||||
use lee_core::{
|
||||
Identifier, InputAccountIdentity, MembershipProof, NullifierPublicKey, NullifierSecretKey,
|
||||
SharedSecretKey,
|
||||
CommitmentSetDigest, DUMMY_COMMITMENT_HASH, Identifier, InputAccountIdentity, MembershipProof,
|
||||
NullifierPublicKey, NullifierSecretKey, SharedSecretKey,
|
||||
account::{AccountWithMetadata, Nonce},
|
||||
encryption::{EncryptedAccountData, EphemeralPublicKey, ViewingPublicKey},
|
||||
};
|
||||
@ -187,6 +187,7 @@ enum State {
|
||||
pub struct AccountManager {
|
||||
states: Vec<State>,
|
||||
pin: Option<String>,
|
||||
dummy_commitment_root: CommitmentSetDigest,
|
||||
}
|
||||
|
||||
impl AccountManager {
|
||||
@ -340,7 +341,24 @@ impl AccountManager {
|
||||
states.push(state);
|
||||
}
|
||||
|
||||
Ok(Self { states, pin })
|
||||
let has_init_account = states
|
||||
.iter()
|
||||
.any(|s| matches!(s, State::Private(pre) if pre.proof.is_none()));
|
||||
let dummy_commitment_root = if has_init_account {
|
||||
wallet
|
||||
.get_commitment_root()
|
||||
.await
|
||||
.map_err(ExecutionFailureKind::SequencerError)?
|
||||
.unwrap_or(DUMMY_COMMITMENT_HASH)
|
||||
} else {
|
||||
DUMMY_COMMITMENT_HASH
|
||||
};
|
||||
|
||||
Ok(Self {
|
||||
states,
|
||||
pin,
|
||||
dummy_commitment_root,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn pre_states(&self) -> Vec<AccountWithMetadata> {
|
||||
@ -404,6 +422,7 @@ impl AccountManager {
|
||||
npk: pre.npk,
|
||||
ssk: pre.ssk,
|
||||
identifier: pre.identifier,
|
||||
commitment_root: self.dummy_commitment_root,
|
||||
seed: None,
|
||||
},
|
||||
},
|
||||
@ -424,6 +443,7 @@ impl AccountManager {
|
||||
ssk: pre.ssk,
|
||||
nsk,
|
||||
identifier: pre.identifier,
|
||||
commitment_root: self.dummy_commitment_root,
|
||||
},
|
||||
(None, _) => InputAccountIdentity::PrivateUnauthorized {
|
||||
epk: pre.epk.clone(),
|
||||
@ -431,6 +451,7 @@ impl AccountManager {
|
||||
npk: pre.npk,
|
||||
ssk: pre.ssk,
|
||||
identifier: pre.identifier,
|
||||
commitment_root: self.dummy_commitment_root,
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -22,7 +22,8 @@ use lee::{
|
||||
},
|
||||
};
|
||||
use lee_core::{
|
||||
Commitment, MembershipProof, SharedSecretKey, account::Nonce, program::InstructionData,
|
||||
Commitment, CommitmentSetDigest, DUMMY_COMMITMENT, MembershipProof, SharedSecretKey,
|
||||
account::Nonce, compute_digest_for_path, program::InstructionData,
|
||||
};
|
||||
use log::info;
|
||||
use sequencer_service_rpc::{RpcClient as _, SequencerClient, SequencerClientBuilder};
|
||||
@ -508,6 +509,14 @@ impl WalletCore {
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn get_commitment_root(&self) -> Result<Option<CommitmentSetDigest>> {
|
||||
let proof = self
|
||||
.sequencer_client
|
||||
.get_proof_for_commitment(DUMMY_COMMITMENT)
|
||||
.await?;
|
||||
Ok(proof.map(|p| compute_digest_for_path(&DUMMY_COMMITMENT, &p)))
|
||||
}
|
||||
|
||||
pub fn decode_insert_privacy_preserving_transaction_results(
|
||||
&mut self,
|
||||
tx: &lee::privacy_preserving_transaction::PrivacyPreservingTransaction,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user