mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-08-11 09:23:20 +00:00
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.
This commit is contained in:
parent
fc8f7f2d42
commit
279a9b8313
@ -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
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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<AuthorizationSecretKey> },
|
||||
/// 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.
|
||||
|
||||
@ -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]
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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(),
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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<AccountIdentity> for FfiAccountIdentity {
|
||||
}
|
||||
}
|
||||
AccountIdentity::PrivateShared {
|
||||
nsk,
|
||||
npk,
|
||||
ask,
|
||||
vpk,
|
||||
identifier,
|
||||
} => {
|
||||
@ -460,8 +464,7 @@ impl From<AccountIdentity> 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<FfiAccountIdWithPrivacy> 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,
|
||||
};
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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", &"<redacted>")
|
||||
.field("npk", npk)
|
||||
.field("ask", &"<redacted>")
|
||||
.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<AuthorizationSecretKey>,
|
||||
nsk: Option<NullifierSecretKey>,
|
||||
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<AuthorizationSecretKey>,
|
||||
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;
|
||||
|
||||
@ -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,
|
||||
})
|
||||
|
||||
@ -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.
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user