From 279a9b8313856eed1d723ec06e9915882f5f1f9b Mon Sep 17 00:00:00 2001 From: Artem Gureev Date: Mon, 3 Aug 2026 13:33:56 +0000 Subject: [PATCH] feat!(lee): derive private authorization from an authorization secret key BREAKING! Before: Providing `nsk` was the same as authorizing a regular private account. After: A separate `ask` is required, making unauthorized private updates possible. --- examples/program_deployment/README.md | 6 +- .../tests/auth_transfer/private.rs | 2 +- integration_tests/tests/tps.rs | 8 +- lee/privacy_preserving_circuit/src/output.rs | 39 ++- lee/state_machine/core/src/circuit_io.rs | 9 +- .../circuit/tests.rs | 249 +++++++++++++++++- .../src/state/tests/changer_claimer.rs | 8 +- lee/state_machine/src/state/tests/circuit.rs | 72 +++-- lee/state_machine/src/state/tests/claiming.rs | 12 +- lee/state_machine/src/state/tests/mod.rs | 16 +- .../src/state/tests/privacy_preserving.rs | 4 +- .../src/state/tests/validity_window.rs | 8 +- .../src/validated_state_diff/tests.rs | 8 +- lez/wallet-ffi/src/keys.rs | 1 + lez/wallet-ffi/src/types.rs | 27 +- lez/wallet-ffi/wallet_ffi.h | 1 + lez/wallet/src/account_manager.rs | 130 +++++---- lez/wallet/src/lib.rs | 9 +- tools/crypto_primitives_bench/README.md | 2 +- 19 files changed, 480 insertions(+), 131 deletions(-) diff --git a/examples/program_deployment/README.md b/examples/program_deployment/README.md index 240079a5..35a1cd5b 100644 --- a/examples/program_deployment/README.md +++ b/examples/program_deployment/README.md @@ -348,9 +348,9 @@ Check the `run_hello_world_private.rs` file to see how it is used. # 8. Account authorization mechanism The Hello world example does not enforce any authorization on the input account. This means any user can execute it on any account, regardless of ownership. -LEE provides a mechanism for programs to enforce proper authorization before an execution can succeed. The meaning of authorization differs between public and private accounts: -- Public accounts: authorization requires that the transaction is signed with the account’s signing key. -- Private accounts: authorization requires that the circuit verifies knowledge of the account’s nullifier secret key. +LEE provides a mechanism for programs to enforce proper authorization before an execution can succeed. For both private and public accounts, the authorization is checked against knowledge of a secret key, yet the check is different: +- Public accounts: the transaction is signed with the account’s signing key. +- Private accounts: the circuit verifies knowledge of the account’s authorization secret key (`ask`), the key from which the account’s nullifier secret key is derived. From the program development perspective it is very simple: input accounts come with a flag indicating whether they has been properly authorized. And so, the only difference between the program `hello_world.rs` and `hello_world_with_authorization.rs` is in the lines diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index 419fa7f5..b60ee956 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -680,7 +680,7 @@ async fn prove_init_with_commitment_root( vpk, random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { ask: Some(ask) }, nullifier: NullifierWitness::Init { npk, commitment_root, diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index fa4da751..ab08ffdc 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -298,7 +298,9 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { vpk: sender_vpk, random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_nsk, @@ -309,7 +311,9 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { vpk: recipient_vpk, random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_ask), + }, nullifier: NullifierWitness::Init { npk: recipient_npk, commitment_root: DUMMY_COMMITMENT_HASH, diff --git a/lee/privacy_preserving_circuit/src/output.rs b/lee/privacy_preserving_circuit/src/output.rs index 3ff6d9cb..a01deef2 100644 --- a/lee/privacy_preserving_circuit/src/output.rs +++ b/lee/privacy_preserving_circuit/src/output.rs @@ -1,8 +1,8 @@ use lee_core::{ Commitment, CommitmentSetDigest, DummyInput, EncryptedAccountData, EncryptionScheme, - EphemeralSecretKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierSecretKey, - NullifierWitness, PrivacyPreservingCircuitOutput, PrivateAccountKind, PrivateAction, - PrivateWitness, PublicAction, SharedSecretKey, WitnessKind, + EphemeralSecretKey, InputAccountIdentity, MembershipProof, Nullifier, NullifierPublicKey, + NullifierSecretKey, NullifierWitness, PrivacyPreservingCircuitOutput, PrivateAccountKind, + PrivateAction, PrivateWitness, PublicAction, SharedSecretKey, WitnessKind, account::{Account, AccountId, Nonce}, compute_digest_for_path, encryption::{ViewTag, ViewingPublicKey}, @@ -48,7 +48,7 @@ pub fn compute_circuit_output( nullifier, }) => { let account_id = match kind { - WitnessKind::Regular => { + WitnessKind::Regular { .. } => { let derived = AccountId::for_regular_private_account( &nullifier.npk(), vpk, @@ -68,12 +68,31 @@ pub fn compute_circuit_output( match (kind, nullifier) { ( - WitnessKind::Regular, + WitnessKind::Regular { ask }, NullifierWitness::Init { .. } | NullifierWitness::Update { .. }, - ) => assert!( - pre_state.is_authorized, - "Regular private account pre-state must be authorized" - ), + ) => { + if let Some(ask) = ask { + let derived = NullifierSecretKey::from(ask); + match nullifier { + // Check that the authorization key is actually bound to the + // account Id. + NullifierWitness::Update { nsk, .. } => assert_eq!( + derived, *nsk, + "Authorization secret key does not derive this account's nullifier secret key" + ), + NullifierWitness::Init { npk, .. } => assert_eq!( + NullifierPublicKey::from(&derived), + *npk, + "Authorization secret key does not derive this account's nullifier public key" + ), + } + } + assert_eq!( + pre_state.is_authorized, + ask.is_some(), + "Regular private account authorization must match the supplied credential" + ); + } (WitnessKind::Pda { .. }, NullifierWitness::Init { .. }) => assert!( !pre_state.is_authorized, "Private PDA init requires unauthorized pre_state" @@ -126,7 +145,7 @@ pub fn compute_circuit_output( }; let account_kind = match kind { - WitnessKind::Regular => PrivateAccountKind::Regular(*identifier), + WitnessKind::Regular { .. } => PrivateAccountKind::Regular(*identifier), WitnessKind::Pda { .. } => { let (authority_program_id, seed) = pda_seed_by_position .get(&pos) diff --git a/lee/state_machine/core/src/circuit_io.rs b/lee/state_machine/core/src/circuit_io.rs index 1e3b3515..e3016290 100644 --- a/lee/state_machine/core/src/circuit_io.rs +++ b/lee/state_machine/core/src/circuit_io.rs @@ -2,8 +2,8 @@ use borsh::{BorshDeserialize, BorshSerialize}; use serde::{Deserialize, Serialize}; use crate::{ - Commitment, CommitmentSetDigest, Identifier, MembershipProof, Nullifier, NullifierPublicKey, - NullifierSecretKey, + AuthorizationSecretKey, Commitment, CommitmentSetDigest, Identifier, MembershipProof, + Nullifier, NullifierPublicKey, NullifierSecretKey, account::{Account, AccountWithMetadata}, encryption::{EncryptedAccountData, ViewTag, ViewingPublicKey}, program::{BlockValidityWindow, PdaSeed, ProgramId, ProgramOutput, TimestampValidityWindow}, @@ -48,8 +48,9 @@ pub struct PrivateWitness { pub enum WitnessKind { /// Standalone private account. The `account_id` is derived as /// `AccountId::for_regular_private_account(&npk, vpk, identifier)` and matched against - /// `pre_state.account_id`. - Regular, + /// `pre_state.account_id`. An honest authorized account's `npk` for Id computation gets + /// derived from the supplied `ask`. + Regular { ask: Option }, /// Private PDA. The npk-to-account_id binding is proven upstream via `Claim::Pda(seed)` or a /// caller's `pda_seeds` match. The identifier diversifies the PDA within the /// `(program_id, seed, npk)` family: `AccountId::for_private_pda` uses it as the 4th input. diff --git a/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs b/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs index fe238815..c2129fb9 100644 --- a/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs +++ b/lee/state_machine/src/privacy_preserving_transaction/circuit/tests.rs @@ -93,7 +93,9 @@ fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -199,7 +201,9 @@ fn prove_privacy_preserving_execution_circuit_fully_private() { vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -212,7 +216,9 @@ fn prove_privacy_preserving_execution_circuit_fully_private() { vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -284,7 +290,9 @@ fn init_note_view_tag_is_derived_from_account_keys() { vpk: keys.vpk(), random_seed: [0; 32], identifier, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(keys.ask), + }, nullifier: NullifierWitness::Init { npk: keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -329,7 +337,9 @@ fn update_note_view_tag_is_the_supplied_value() { vpk: keys.vpk(), random_seed: [0; 32], identifier, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: fed_tag, nsk: keys.nsk(), @@ -381,7 +391,9 @@ fn circuit_fails_when_chained_validity_windows_have_empty_intersection() { vpk: account_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(account_keys.ask), + }, nullifier: NullifierWitness::Init { npk: account_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -574,7 +586,9 @@ fn shared_account_receives_via_simple_transfer() { vpk: shared_keys.vpk(), random_seed: [0; 32], identifier: shared_identifier, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(shared_keys.ask), + }, nullifier: NullifierWitness::Init { npk: shared_npk, commitment_root: DUMMY_COMMITMENT_HASH, @@ -613,7 +627,9 @@ fn private_authorized_init_encrypts_regular_kind_with_identifier() { vpk: keys.vpk(), random_seed: [0; 32], identifier, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(keys.ask), + }, nullifier: NullifierWitness::Init { npk: NullifierPublicKey::from(&keys.nsk()), commitment_root: DUMMY_COMMITMENT_HASH, @@ -653,7 +669,9 @@ fn private_foreign_init_encrypts_regular_kind_with_identifier() { vpk: keys.vpk(), random_seed: [0; 32], identifier, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(keys.ask), + }, nullifier: NullifierWitness::Init { npk: keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -701,7 +719,9 @@ fn private_authorized_update_encrypts_regular_kind_with_identifier() { vpk: keys.vpk(), random_seed: [0; 32], identifier, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: keys.nsk(), @@ -718,6 +738,215 @@ fn private_authorized_update_encrypts_regular_kind_with_identifier() { ); } +/// Builds an on-chain regular private account owned by `program`, returning its id, pre-state +/// and a membership proof for its commitment. +fn seeded_regular_account( + keys: &crate::state::tests::TestPrivateKeys, + program: &Program, + identifier: u128, +) -> (AccountId, AccountWithMetadata, lee_core::MembershipProof) { + let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), identifier); + let account = Account { + program_owner: program.id(), + balance: 1, + ..Account::default() + }; + let commitment = Commitment::new(&account_id, &account); + let mut commitment_set = CommitmentSet::with_capacity(1); + commitment_set.extend(std::slice::from_ref(&commitment)); + let proof = commitment_set.get_proof_for(&commitment).unwrap(); + ( + account_id, + AccountWithMetadata::new(account, false, account_id), + proof, + ) +} + +/// Spending without consenting. The witness carries no `ask`, so the pre-state is unauthorized, +/// and the nullifier is still produced from the `nsk`. +#[test] +fn private_regular_update_without_ask_is_spendable() { + let program = crate::test_methods::noop(); + let keys = test_private_account_keys_1(); + let (_, pre, membership_proof) = seeded_regular_account(&keys, &program, 0); + assert!(!pre.is_authorized); + + execute_and_prove( + vec![pre], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::Private(PrivateWitness { + vpk: keys.vpk(), + random_seed: [0; 32], + identifier: 0, + kind: WitnessKind::Regular { ask: None }, + nullifier: NullifierWitness::Update { + view_tag: 0, + nsk: keys.nsk(), + membership_proof, + }, + })], + &program.into(), + ) + .unwrap(); +} + +/// Claiming authorization without supplying an `ask` is rejected. +#[test] +fn private_regular_witness_without_ask_cannot_assert_authorization() { + let program = crate::test_methods::noop(); + let keys = test_private_account_keys_1(); + let (account_id, pre, membership_proof) = seeded_regular_account(&keys, &program, 0); + let pre = AccountWithMetadata::new(pre.account, true, account_id); + + let result = execute_and_prove( + vec![pre], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::Private(PrivateWitness { + vpk: keys.vpk(), + random_seed: [0; 32], + identifier: 0, + kind: WitnessKind::Regular { ask: None }, + nullifier: NullifierWitness::Update { + view_tag: 0, + nsk: keys.nsk(), + membership_proof, + }, + })], + &program.into(), + ); + + assert!(matches!(result, Err(LeeError::CircuitProvingError(_)))); +} + +/// An `ask` that does not derive this account's `nsk` is not a credential for it. +#[test] +fn regular_update_with_wrong_ask_nsk_is_rejected() { + let program = crate::test_methods::noop(); + let keys = test_private_account_keys_1(); + let foreign = test_private_account_keys_2(); + let (account_id, pre, membership_proof) = seeded_regular_account(&keys, &program, 0); + let pre = AccountWithMetadata::new(pre.account, true, account_id); + + let result = execute_and_prove( + vec![pre], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::Private(PrivateWitness { + vpk: keys.vpk(), + random_seed: [0; 32], + identifier: 0, + kind: WitnessKind::Regular { + ask: Some(foreign.ask), + }, + nullifier: NullifierWitness::Update { + view_tag: 0, + nsk: keys.nsk(), + membership_proof, + }, + })], + &program.into(), + ); + + assert!(matches!(result, Err(LeeError::CircuitProvingError(_)))); +} + +/// An `ask` tthat does not derive this account's `npk` is not a credential for it. +#[test] +fn regular_init_with_non_chaining_ask_npk_is_rejected() { + let program = crate::test_methods::claimer(); + let keys = test_private_account_keys_1(); + let foreign = test_private_account_keys_2(); + let account_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), 0); + let pre = AccountWithMetadata::new(Account::default(), true, account_id); + + let result = execute_and_prove( + vec![pre], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::Private(PrivateWitness { + vpk: keys.vpk(), + random_seed: [0; 32], + identifier: 0, + kind: WitnessKind::Regular { + ask: Some(foreign.ask), + }, + nullifier: NullifierWitness::Init { + npk: keys.npk(), + commitment_root: DUMMY_COMMITMENT_HASH, + }, + })], + &program.into(), + ); + + assert!(matches!(result, Err(LeeError::CircuitProvingError(_)))); +} + +#[test] +fn unauthorized_private_init_can_be_claimed() { + let program = crate::test_methods::claimer(); + let program_id = program.id(); + let keys = test_private_account_keys_1(); + let recipient_id = AccountId::for_regular_private_account(&keys.npk(), &keys.vpk(), 0); + let recipient = AccountWithMetadata::new(Account::default(), false, recipient_id); + let esk = EphemeralSecretKey::new( + &recipient_id, + &[0; 32], + &Nonce::private_account_nonce_init(&recipient_id), + ); + let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &esk).0; + + let (output, _) = execute_and_prove( + vec![recipient], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::Private(PrivateWitness { + vpk: keys.vpk(), + random_seed: [0; 32], + identifier: 0, + kind: WitnessKind::Regular { ask: None }, + nullifier: NullifierWitness::Init { + npk: keys.npk(), + commitment_root: DUMMY_COMMITMENT_HASH, + }, + })], + &program.into(), + ) + .unwrap(); + + let (_, claimed) = EncryptionScheme::decrypt( + &output.private_actions[0].encrypted_post_state.ciphertext, + &ssk, + &output.private_actions[0].nullifier, + ) + .unwrap(); + assert_eq!(claimed.program_owner, program_id); +} + +/// A program that asserts authorization over its pre-states rejects a regular private account +/// whose witness supplied no `ask`. +#[test] +fn auth_asserting_program_rejects_unauthorized_regular_private_account() { + let program = crate::test_methods::auth_asserting_noop(); + let keys = test_private_account_keys_1(); + let (_, pre, membership_proof) = seeded_regular_account(&keys, &program, 0); + + let result = execute_and_prove( + vec![pre], + Program::serialize_instruction(()).unwrap(), + vec![InputAccountIdentity::Private(PrivateWitness { + vpk: keys.vpk(), + random_seed: [0; 32], + identifier: 0, + kind: WitnessKind::Regular { ask: None }, + nullifier: NullifierWitness::Update { + view_tag: 0, + nsk: keys.nsk(), + membership_proof, + }, + })], + &program.into(), + ); + + assert!(matches!(result, Err(LeeError::ProgramProveFailed(_)))); +} + /// A private-PDA update with a non-default identifier produces a ciphertext that decrypts /// to `PrivateAccountKind::Pda` carrying the correct `(program_id, seed, identifier)`. #[test] diff --git a/lee/state_machine/src/state/tests/changer_claimer.rs b/lee/state_machine/src/state/tests/changer_claimer.rs index 8e2aba9c..b422d119 100644 --- a/lee/state_machine/src/state/tests/changer_claimer.rs +++ b/lee/state_machine/src/state/tests/changer_claimer.rs @@ -75,7 +75,9 @@ fn private_changer_claimer_no_data_change_no_claim_succeeds() { vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -109,7 +111,9 @@ fn private_changer_claimer_data_change_no_claim_fails() { vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), diff --git a/lee/state_machine/src/state/tests/circuit.rs b/lee/state_machine/src/state/tests/circuit.rs index a2f41fb8..758a7c85 100644 --- a/lee/state_machine/src/state/tests/circuit.rs +++ b/lee/state_machine/src/state/tests/circuit.rs @@ -65,7 +65,9 @@ fn circuit_fails_if_invalid_auth_keys_are_provided() { vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: recipient_keys.nsk(), @@ -76,7 +78,9 @@ fn circuit_fails_if_invalid_auth_keys_are_provided() { vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -121,7 +125,9 @@ fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provid vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -132,7 +138,9 @@ fn circuit_should_fail_if_new_private_account_with_non_default_balance_is_provid vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -177,7 +185,9 @@ fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_ vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -188,7 +198,9 @@ fn circuit_should_fail_if_new_private_account_with_non_default_program_owner_is_ vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -233,7 +245,9 @@ fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided( vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -244,7 +258,9 @@ fn circuit_should_fail_if_new_private_account_with_non_default_data_is_provided( vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -289,7 +305,9 @@ fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -300,7 +318,9 @@ fn circuit_should_fail_if_new_private_account_with_non_default_nonce_is_provided vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -343,7 +363,9 @@ fn circuit_should_fail_if_new_private_account_is_provided_with_default_values_bu vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -354,7 +376,9 @@ fn circuit_should_fail_if_new_private_account_is_provided_with_default_values_bu vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -749,7 +773,9 @@ fn circuit_should_fail_if_there_are_repeated_ids() { vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -760,7 +786,9 @@ fn circuit_should_fail_if_there_are_repeated_ids() { vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -802,7 +830,9 @@ fn private_authorized_uninitialized_account() { vpk: private_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(private_keys.ask), + }, nullifier: NullifierWitness::Init { npk: NullifierPublicKey::from(&private_keys.nsk()), commitment_root: DUMMY_COMMITMENT_HASH, @@ -851,7 +881,9 @@ fn private_unauthorized_uninitialized_account_can_still_be_claimed() { vpk: private_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(private_keys.ask), + }, nullifier: NullifierWitness::Init { npk: private_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -904,7 +936,9 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() { vpk: private_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(private_keys.ask), + }, nullifier: NullifierWitness::Init { npk: NullifierPublicKey::from(&private_keys.nsk()), commitment_root: DUMMY_COMMITMENT_HASH, @@ -949,7 +983,9 @@ fn private_account_claimed_then_used_without_init_flag_should_fail() { vpk: private_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(private_keys.ask), + }, nullifier: NullifierWitness::Init { npk: NullifierPublicKey::from(&private_keys.nsk()), commitment_root: DUMMY_COMMITMENT_HASH, diff --git a/lee/state_machine/src/state/tests/claiming.rs b/lee/state_machine/src/state/tests/claiming.rs index 1a70c2b8..1d63fdb3 100644 --- a/lee/state_machine/src/state/tests/claiming.rs +++ b/lee/state_machine/src/state/tests/claiming.rs @@ -329,7 +329,9 @@ fn authorized_public_account_claiming_succeeds_when_executed_privately() { vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -446,7 +448,9 @@ fn private_chained_call(number_of_calls: u32) { vpk: from_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(from_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: from_keys.nsk(), @@ -459,7 +463,9 @@ fn private_chained_call(number_of_calls: u32) { vpk: to_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(to_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: to_keys.nsk(), diff --git a/lee/state_machine/src/state/tests/mod.rs b/lee/state_machine/src/state/tests/mod.rs index 4ad7c107..b1c67b9b 100644 --- a/lee/state_machine/src/state/tests/mod.rs +++ b/lee/state_machine/src/state/tests/mod.rs @@ -288,7 +288,9 @@ fn shielded_balance_transfer_for_tests( vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -335,7 +337,9 @@ fn private_balance_transfer_for_tests( vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), @@ -348,7 +352,9 @@ fn private_balance_transfer_for_tests( vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Init { npk: recipient_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -396,7 +402,9 @@ fn deshielded_balance_transfer_for_tests( vpk: sender_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(sender_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: sender_keys.nsk(), diff --git a/lee/state_machine/src/state/tests/privacy_preserving.rs b/lee/state_machine/src/state/tests/privacy_preserving.rs index 13aa3c91..d3262d9b 100644 --- a/lee/state_machine/src/state/tests/privacy_preserving.rs +++ b/lee/state_machine/src/state/tests/privacy_preserving.rs @@ -525,7 +525,9 @@ fn malicious_authorization_changer_should_fail_in_privacy_preserving_circuit() { vpk: recipient_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(recipient_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: recipient_keys.nsk(), diff --git a/lee/state_machine/src/state/tests/validity_window.rs b/lee/state_machine/src/state/tests/validity_window.rs index c39571ea..7953c671 100644 --- a/lee/state_machine/src/state/tests/validity_window.rs +++ b/lee/state_machine/src/state/tests/validity_window.rs @@ -142,7 +142,9 @@ fn validity_window_works_in_privacy_preserving_transactions( vpk: account_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(account_keys.ask), + }, nullifier: NullifierWitness::Init { npk: account_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, @@ -210,7 +212,9 @@ fn timestamp_validity_window_works_in_privacy_preserving_transactions( vpk: account_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(account_keys.ask), + }, nullifier: NullifierWitness::Init { npk: account_keys.npk(), commitment_root: DUMMY_COMMITMENT_HASH, diff --git a/lee/state_machine/src/validated_state_diff/tests.rs b/lee/state_machine/src/validated_state_diff/tests.rs index 04aa3b71..003b2780 100644 --- a/lee/state_machine/src/validated_state_diff/tests.rs +++ b/lee/state_machine/src/validated_state_diff/tests.rs @@ -168,7 +168,9 @@ fn privacy_malicious_programs_cannot_drain_public_victim() { vpk: attacker_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(attacker_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: attacker_keys.nsk(), @@ -330,7 +332,9 @@ fn privacy_malicious_programs_cannot_drain_private_victim() { vpk: attacker_keys.vpk(), random_seed: [0; 32], identifier: 0, - kind: WitnessKind::Regular, + kind: WitnessKind::Regular { + ask: Some(attacker_keys.ask), + }, nullifier: NullifierWitness::Update { view_tag: 0, nsk: attacker_keys.nsk(), diff --git a/lez/wallet-ffi/src/keys.rs b/lez/wallet-ffi/src/keys.rs index 6a2c4d0b..b3f52d22 100644 --- a/lez/wallet-ffi/src/keys.rs +++ b/lez/wallet-ffi/src/keys.rs @@ -361,6 +361,7 @@ pub unsafe extern "C" fn wallet_ffi_free_account_identity( kind: _, account_id: _, key_path, + authorization_secret_key: _, nullifier_secret_key: _, nullifier_public_key: _, viewing_public_key, diff --git a/lez/wallet-ffi/src/types.rs b/lez/wallet-ffi/src/types.rs index 3779ba01..286b79db 100644 --- a/lez/wallet-ffi/src/types.rs +++ b/lez/wallet-ffi/src/types.rs @@ -8,7 +8,10 @@ use std::{ }; use lee::{Data, ProgramId, SharedSecretKey}; -use lee_core::{encryption::MlKem768EncapsulationKey, program::PdaSeed, NullifierPublicKey}; +use lee_core::{ + encryption::MlKem768EncapsulationKey, program::PdaSeed, AuthorizationSecretKey, + NullifierPublicKey, +}; use wallet::{account::AccountIdWithPrivacy, AccountIdentity}; use crate::error::WalletFfiError; @@ -238,6 +241,7 @@ pub struct FfiAccountIdentity { pub account_id: FfiBytes32, /// C-compatible string. pub key_path: *mut c_char, + pub authorization_secret_key: FfiBytes32, pub nullifier_secret_key: FfiBytes32, pub nullifier_public_key: FfiBytes32, pub viewing_public_key: *const u8, @@ -251,6 +255,7 @@ impl Default for FfiAccountIdentity { kind: FfiAccountIdentityKind::Public, account_id: FfiBytes32::default(), key_path: std::ptr::null_mut(), + authorization_secret_key: FfiBytes32::default(), nullifier_secret_key: FfiBytes32::default(), nullifier_public_key: FfiBytes32::default(), viewing_public_key: std::ptr::null(), @@ -444,8 +449,7 @@ impl From for FfiAccountIdentity { } } AccountIdentity::PrivateShared { - nsk, - npk, + ask, vpk, identifier, } => { @@ -460,8 +464,7 @@ impl From for FfiAccountIdentity { Self { kind: FfiAccountIdentityKind::PrivateShared, - nullifier_secret_key: nsk.into(), - nullifier_public_key: npk.0.into(), + authorization_secret_key: ask.0.into(), viewing_public_key: vpk_data, viewing_public_key_len: vpk_len, identifier: identifier.into(), @@ -579,8 +582,7 @@ impl TryFrom<&FfiAccountIdentity> for AccountIdentity { }?; Ok(Self::PrivateShared { - nsk: value.nullifier_secret_key.data, - npk: NullifierPublicKey(value.nullifier_public_key.data), + ask: AuthorizationSecretKey(value.authorization_secret_key.data), vpk, identifier: value.identifier.into(), }) @@ -658,7 +660,10 @@ impl From for AccountIdWithPrivacy { #[cfg(test)] mod tests { use lee::{AccountId, PrivateKey, PublicKey}; - use lee_core::{encryption::ViewingPublicKey, program::PdaSeed, PrivateAccountKind}; + use lee_core::{ + encryption::ViewingPublicKey, program::PdaSeed, AuthorizationSecretKey, NullifierSecretKey, + PrivateAccountKind, + }; use wallet::AccountIdentity; use crate::{FfiAccountIdentity, FfiAccountIdentityKind}; @@ -669,7 +674,8 @@ mod tests { let public_key = PublicKey::new_from_private_key(&private_key); let pub_acc_id = (&public_key).into(); - let nsk = [43; 32]; + let ask = AuthorizationSecretKey([43; 32]); + let nsk = NullifierSecretKey::from(&ask); let vpk = ViewingPublicKey::from_seed(&[44; 32], &[54; 32]); let npk = (&nsk).into(); let identifier = u128::from_le_bytes([45; 16]); @@ -708,8 +714,7 @@ mod tests { identifier, }; let acc_identity_7 = AccountIdentity::PrivateShared { - nsk, - npk, + ask, vpk: vpk.clone(), identifier, }; diff --git a/lez/wallet-ffi/wallet_ffi.h b/lez/wallet-ffi/wallet_ffi.h index bbd7da1f..e12f4655 100644 --- a/lez/wallet-ffi/wallet_ffi.h +++ b/lez/wallet-ffi/wallet_ffi.h @@ -249,6 +249,7 @@ typedef struct FfiAccountIdentity { * C-compatible string. */ char *key_path; + struct FfiBytes32 authorization_secret_key; struct FfiBytes32 nullifier_secret_key; struct FfiBytes32 nullifier_public_key; const uint8_t *viewing_public_key; diff --git a/lez/wallet/src/account_manager.rs b/lez/wallet/src/account_manager.rs index 882cd1f6..1e19f68a 100644 --- a/lez/wallet/src/account_manager.rs +++ b/lez/wallet/src/account_manager.rs @@ -4,9 +4,9 @@ use anyhow::Result; use keycard_wallet::KeycardWallet; use lee::{AccountId, PrivateKey, PublicKey, Signature}; use lee_core::{ - Commitment, CommitmentSetDigest, DummyInput, Identifier, InputAccountIdentity, MembershipProof, - NullifierPublicKey, NullifierSecretKey, NullifierWitness, PrivateAccountKind, PrivateWitness, - SharedSecretKey, WitnessKind, + AuthorizationSecretKey, Commitment, CommitmentSetDigest, DummyInput, Identifier, + InputAccountIdentity, MembershipProof, NullifierPublicKey, NullifierSecretKey, + NullifierWitness, PrivateAccountKind, PrivateWitness, SharedSecretKey, WitnessKind, account::{Account, AccountWithMetadata, Nonce}, compute_digest_for_path, encryption::{ @@ -45,11 +45,11 @@ pub enum AccountIdentity { identifier: Identifier, }, /// A shared regular private account with externally-provided keys (e.g. from GMS). - /// Uses standard `AccountId = from((&npk, identifier))` with authorized/unauthorized private - /// paths. Works with `authenticated_transfer` and all existing programs out of the box. + /// Carries the authorization secret key: the `nsk` and `npk` behind + /// `AccountId = from((&npk, &vpk, identifier))` are derived from it. + /// Works with `authenticated_transfer` and all existing programs out of the box. PrivateShared { - nsk: NullifierSecretKey, - npk: NullifierPublicKey, + ask: AuthorizationSecretKey, vpk: ViewingPublicKey, identifier: Identifier, }, @@ -102,14 +102,10 @@ impl fmt::Debug for AccountIdentity { .field("identifier", identifier) .finish(), Self::PrivateShared { - npk, - vpk, - identifier, - .. + vpk, identifier, .. } => f .debug_struct("PrivateShared") - .field("nsk", &"") - .field("npk", npk) + .field("ask", &"") .field("vpk", vpk) .field("identifier", identifier) .finish(), @@ -266,21 +262,10 @@ impl AccountManager { vpk, identifier, } => { - let acc = lee_core::account::Account::default(); - let auth_acc = AccountWithMetadata::new(acc, true, (&npk, &vpk, identifier)); - let random_seed = random_bytes(); - let pre = AccountPreparedData { - nsk: None, - npk, - identifier, - vpk, - pre_state: auth_acc, - proof: None, - random_seed, - is_pda: false, - }; - - State::Private(pre) + let account_id = lee::AccountId::from((&npk, &vpk, identifier)); + State::Private(private_foreign_acc_preparation( + account_id, npk, vpk, identifier, false, + )) } AccountIdentity::PrivatePdaOwned(account_id) => { let pre = private_key_tree_acc_preparation(wallet, account_id, true)?; @@ -291,31 +276,26 @@ impl AccountManager { npk, vpk, identifier, - } => { - let acc = lee_core::account::Account::default(); - let auth_acc = AccountWithMetadata::new(acc, false, account_id); - let random_seed = random_bytes(); - let pre = AccountPreparedData { - nsk: None, - npk, - identifier, - vpk, - pre_state: auth_acc, - proof: None, - random_seed, - is_pda: true, - }; - State::Private(pre) - } + } => State::Private(private_foreign_acc_preparation( + account_id, npk, vpk, identifier, true, + )), AccountIdentity::PrivateShared { - nsk, - npk, + ask, vpk, identifier, } => { + let nsk = NullifierSecretKey::from(&ask); + let npk = NullifierPublicKey::from(&nsk); let account_id = lee::AccountId::from((&npk, &vpk, identifier)); let pre = private_shared_acc_preparation( - wallet, account_id, nsk, npk, vpk, identifier, false, + wallet, + account_id, + nsk, + npk, + vpk, + identifier, + Some(ask), + false, ); State::Private(pre) @@ -328,7 +308,7 @@ impl AccountManager { identifier, } => { let pre = private_shared_acc_preparation( - wallet, account_id, nsk, npk, vpk, identifier, true, + wallet, account_id, nsk, npk, vpk, identifier, None, true, ); State::Private(pre) @@ -448,7 +428,7 @@ impl AccountManager { kind: if pre.is_pda { WitnessKind::Pda { binding: None } } else { - WitnessKind::Regular + WitnessKind::Regular { ask: pre.ask } }, nullifier: match (pre.nsk, pre.proof.clone()) { (Some(nsk), Some(membership_proof)) => NullifierWitness::Update { @@ -527,6 +507,7 @@ impl AccountManager { } struct AccountPreparedData { + ask: Option, nsk: Option, npk: NullifierPublicKey, identifier: Identifier, @@ -550,6 +531,7 @@ fn private_key_tree_acc_preparation( let from_identifier = from_acc.kind.identifier(); let from_keys = &from_acc.key_chain; + let ask = from_keys.private_key_holder.authorization_secret_key; let nsk = from_keys.private_key_holder.nullifier_secret_key(); let from_npk = from_keys.nullifier_public_key; let from_vpk = from_keys.viewing_public_key.clone(); @@ -561,6 +543,8 @@ fn private_key_tree_acc_preparation( let random_seed = random_bytes(); Ok(AccountPreparedData { + // A PDA is program-authorized and carries no credential of its own. + ask: (!is_pda).then_some(ask), nsk: Some(nsk), npk: from_npk, identifier: from_identifier, @@ -572,6 +556,31 @@ fn private_key_tree_acc_preparation( }) } +/// Prepare a private account with no secret key knowledge, i.e. for inits. +fn private_foreign_acc_preparation( + account_id: AccountId, + npk: NullifierPublicKey, + vpk: ViewingPublicKey, + identifier: Identifier, + is_pda: bool, +) -> AccountPreparedData { + AccountPreparedData { + // The wallet holds no key for a recipient, so it can neither spend the account nor + // consent on its behalf. The program still claims it: a private claim never requires + // authorization. + ask: None, + nsk: None, + npk, + identifier, + vpk, + pre_state: AccountWithMetadata::new(Account::default(), false, account_id), + proof: None, + random_seed: random_bytes(), + is_pda, + } +} + +#[expect(clippy::too_many_arguments, reason = "All keys need to be supplied")] fn private_shared_acc_preparation( wallet: &WalletCore, account_id: AccountId, @@ -579,6 +588,7 @@ fn private_shared_acc_preparation( npk: NullifierPublicKey, vpk: ViewingPublicKey, identifier: Identifier, + ask: Option, is_pda: bool, ) -> AccountPreparedData { let acc = wallet @@ -593,6 +603,7 @@ fn private_shared_acc_preparation( let random_seed = random_bytes(); AccountPreparedData { + ask, nsk: Some(nsk), npk, identifier, @@ -701,8 +712,7 @@ mod tests { #[test] fn private_shared_is_private() { let acc = AccountIdentity::PrivateShared { - nsk: [0; 32], - npk: NullifierPublicKey([1; 32]), + ask: AuthorizationSecretKey([0; 32]), vpk: ViewingPublicKey::from_seed(&[2_u8; 32], &[3_u8; 32]), identifier: 42, }; @@ -715,6 +725,7 @@ mod tests { let vpk = ViewingPublicKey::from_seed(&[0; 32], &[0; 32]); let pre_state = AccountWithMetadata::new(Account::default(), false, (&npk, &vpk, 0)); State::Private(AccountPreparedData { + ask: None, nsk: None, npk, identifier: 0, @@ -741,6 +752,23 @@ mod tests { } } + #[test] + fn foreign_private_init_is_unauthorized() { + let npk = NullifierPublicKey([7; 32]); + let vpk = ViewingPublicKey::from_seed(&[8; 32], &[9; 32]); + let account_id = lee::AccountId::from((&npk, &vpk, 0)); + let pre = private_foreign_acc_preparation(account_id, npk, vpk, 0, false); + + assert!(pre.ask.is_none()); + assert!(!pre.pre_state.is_authorized); + + let identities = manager(vec![State::Private(pre)]).account_identities(); + let InputAccountIdentity::Private(witness) = &identities[0] else { + panic!("expected a private witness"); + }; + assert!(matches!(witness.kind, WitnessKind::Regular { ask: None })); + } + #[test] fn dummy_inputs_default_pads_private_count_to_max() { let max = AccountManager::MAX_PRIVATE_ACCOUNTS; diff --git a/lez/wallet/src/lib.rs b/lez/wallet/src/lib.rs index 541abd9b..d2a00133 100644 --- a/lez/wallet/src/lib.rs +++ b/lez/wallet/src/lib.rs @@ -373,23 +373,20 @@ impl WalletCore { .key_chain() .shared_private_account(account_id)?; let keys = self.storage.key_chain().derive_shared_account_keys(entry)?; - let nsk = keys.nullifier_secret_key(); - let npk = keys.generate_nullifier_public_key(); let vpk = keys.generate_viewing_public_key(); let identifier = entry.identifier; if entry.pda_seed.is_some() { Some(AccountIdentity::PrivatePdaShared { account_id, - nsk, - npk, + nsk: keys.nullifier_secret_key(), + npk: keys.generate_nullifier_public_key(), vpk, identifier, }) } else { Some(AccountIdentity::PrivateShared { - nsk, - npk, + ask: keys.authorization_secret_key, vpk, identifier, }) diff --git a/tools/crypto_primitives_bench/README.md b/tools/crypto_primitives_bench/README.md index eb2da149..8834d176 100644 --- a/tools/crypto_primitives_bench/README.md +++ b/tools/crypto_primitives_bench/README.md @@ -12,7 +12,7 @@ cargo bench -p crypto_primitives_bench --bench primitives Criterion's per-operation report (point estimate, 95% CI, outlier counts) for: -- `keychain/new_os_random`: full mnemonic → SSK → NSK/VSK + public-key derivation (HMAC-SHA512 PBKDF dominates). +- `keychain/new_os_random`: full mnemonic → SSK → ASK → NSK, plus SSK → VSK, and public-key derivation (HMAC-SHA512 PBKDF dominates). - `keychain/new_mnemonic`: same pipeline, mnemonic exposed. - `shared_secret_key/sender_dh`: secp256k1 ECDH per recipient (includes ephemeral key gen). - `encryption/encrypt` / `decrypt`: ChaCha20 over an Account note.