diff --git a/integration_tests/tests/auth_transfer/private.rs b/integration_tests/tests/auth_transfer/private.rs index e3f9f8c6..a1e1118c 100644 --- a/integration_tests/tests/auth_transfer/private.rs +++ b/integration_tests/tests/auth_transfer/private.rs @@ -65,7 +65,7 @@ async fn private_transfer_to_foreign_account() -> Result<()> { let from: AccountId = ctx.existing_private_accounts()[0]; let to_npk = NullifierPublicKey([42; 32]); let to_npk_string = hex::encode(to_npk.0); - let to_vpk = ViewingPublicKey::from_seed(&to_npk.0, &[0u8; 32]); + let to_vpk = ViewingPublicKey::from_seed(&to_npk.0, &[0_u8; 32]); let command = Command::AuthTransfer(AuthTransferSubcommand::Send { from: private_mention(from), @@ -268,7 +268,7 @@ async fn shielded_transfer_to_foreign_account() -> Result<()> { let to_npk = NullifierPublicKey([42; 32]); let to_npk_string = hex::encode(to_npk.0); - let to_vpk = ViewingPublicKey::from_seed(&to_npk.0, &[0u8; 32]); + let to_vpk = ViewingPublicKey::from_seed(&to_npk.0, &[0_u8; 32]); let from: AccountId = ctx.existing_public_accounts()[0]; let command = Command::AuthTransfer(AuthTransferSubcommand::Send { @@ -654,9 +654,9 @@ async fn ppt_that_chain_calls_faucet_is_dropped() -> Result<()> { let auth_transfer_program_id = Program::authenticated_transfer_program().id(); let nsk: nssa_core::NullifierSecretKey = [3; 32]; let npk = NullifierPublicKey::from(&nsk); - let vpk = Secp256k1Point::from_scalar([4; 32]); - let ssk = SharedSecretKey::new([55; 32], &vpk); - let epk = EphemeralPublicKey::from_scalar([55; 32]); + let vpk = ViewingPublicKey(vec![4_u8; 1184]); + let ssk = SharedSecretKey([55_u8; 32]); + let epk = EphemeralPublicKey(vec![55_u8; 1088]); let attacker_vault_id = { let seed = vault_core::compute_vault_seed(attacker_id); AccountId::for_private_pda(&vault_program_id, &seed, &npk, 1337) diff --git a/integration_tests/tests/private_pda.rs b/integration_tests/tests/private_pda.rs index ce1868ee..9e15fe89 100644 --- a/integration_tests/tests/private_pda.rs +++ b/integration_tests/tests/private_pda.rs @@ -227,10 +227,10 @@ async fn private_pda_family_members_receive_and_spend() -> Result<()> { // Fresh recipients — hardcoded npks not in any wallet. let recipient_npk_0 = NullifierPublicKey([0xAA; 32]); - let recipient_vpk_0 = ViewingPublicKey::from_seed(&recipient_npk_0.0, &[0u8; 32]); + let recipient_vpk_0 = ViewingPublicKey::from_seed(&recipient_npk_0.0, &[0_u8; 32]); let recipient_npk_1 = NullifierPublicKey([0xBB; 32]); - let recipient_vpk_1 = ViewingPublicKey::from_seed(&recipient_npk_1.0, &[0u8; 32]); + let recipient_vpk_1 = ViewingPublicKey::from_seed(&recipient_npk_1.0, &[0_u8; 32]); let amount_spend_0: u128 = 13; let amount_spend_1: u128 = 37; diff --git a/integration_tests/tests/tps.rs b/integration_tests/tests/tps.rs index 4292bd53..7c956e67 100644 --- a/integration_tests/tests/tps.rs +++ b/integration_tests/tests/tps.rs @@ -193,7 +193,7 @@ pub async fn tps_test() -> Result<()> { fn build_privacy_transaction() -> PrivacyPreservingTransaction { let program = Program::authenticated_transfer_program(); let sender_nsk = [1; 32]; - let sender_vpk = ViewingPublicKey::from_seed(&[99u8; 32], &[100u8; 32]); + let sender_vpk = ViewingPublicKey::from_seed(&[99_u8; 32], &[100_u8; 32]); let sender_npk = NullifierPublicKey::from(&sender_nsk); let sender_pre = AccountWithMetadata::new( Account { @@ -206,7 +206,7 @@ fn build_privacy_transaction() -> PrivacyPreservingTransaction { AccountId::for_regular_private_account(&sender_npk, 0), ); let recipient_nsk = [2; 32]; - let recipient_vpk = ViewingPublicKey::from_seed(&[99u8; 32], &[100u8; 32]); + let recipient_vpk = ViewingPublicKey::from_seed(&[99_u8; 32], &[100_u8; 32]); let recipient_npk = NullifierPublicKey::from(&recipient_nsk); let recipient_pre = AccountWithMetadata::new( Account::default(), diff --git a/key_protocol/src/key_management/ephemeral_key_holder.rs b/key_protocol/src/key_management/ephemeral_key_holder.rs index dc223390..f491f0b3 100644 --- a/key_protocol/src/key_management/ephemeral_key_holder.rs +++ b/key_protocol/src/key_management/ephemeral_key_holder.rs @@ -6,7 +6,7 @@ use nssa_core::{ /// Ephemeral key holder for the sender side of a KEM-based shared-secret exchange. /// /// Non-clonable as intended for one-time use: construction encapsulates once and -/// stores both the shared secret and the ciphertext (EphemeralPublicKey) that must +/// stores both the shared secret and the ciphertext (`EphemeralPublicKey`) that must /// be sent to the receiver. pub struct EphemeralKeyHolder { shared_secret: SharedSecretKey, @@ -36,15 +36,15 @@ impl EphemeralKeyHolder { } } - /// Returns the KEM ciphertext to be transmitted to the receiver as the EphemeralPublicKey. + /// Returns the KEM ciphertext to be transmitted to the receiver as the `EphemeralPublicKey`. #[must_use] - pub fn ephemeral_public_key(&self) -> &EphemeralPublicKey { + pub const fn ephemeral_public_key(&self) -> &EphemeralPublicKey { &self.ephemeral_public_key } /// Returns the sender-side shared secret (established at construction time). #[must_use] - pub fn calculate_shared_secret_sender(&self) -> SharedSecretKey { + pub const fn calculate_shared_secret_sender(&self) -> SharedSecretKey { self.shared_secret } } diff --git a/key_protocol/src/key_management/group_key_holder.rs b/key_protocol/src/key_management/group_key_holder.rs index 89415bae..63c76f64 100644 --- a/key_protocol/src/key_management/group_key_holder.rs +++ b/key_protocol/src/key_management/group_key_holder.rs @@ -399,10 +399,10 @@ mod tests { let recipient_ssk = SecretSpendingKey([7_u8; 32]); let recipient_keys = recipient_ssk.produce_private_key_holder(None); let recipient_vpk = recipient_keys.generate_viewing_public_key(); - let recipient_vsk = recipient_keys.viewing_secret_key.clone(); + let recipient_vsk = recipient_keys.viewing_secret_key; let sealed = holder.seal_for(&SealingPublicKey::from_bytes(recipient_vpk.0)); - let restored = GroupKeyHolder::unseal(&sealed, recipient_vsk).expect("unseal"); + let restored = GroupKeyHolder::unseal(&sealed, &recipient_vsk).expect("unseal"); assert_eq!(restored.dangerous_raw_gms(), holder.dangerous_raw_gms()); @@ -429,11 +429,10 @@ mod tests { let wrong_vsk = SecretSpendingKey([99_u8; 32]) .produce_private_key_holder(None) - .viewing_secret_key - .clone(); + .viewing_secret_key; let sealed = holder.seal_for(&SealingPublicKey::from_bytes(recipient_vpk.0)); - let result = GroupKeyHolder::unseal(&sealed, wrong_vsk); + let result = GroupKeyHolder::unseal(&sealed, &wrong_vsk); assert!(matches!(result, Err(super::SealError::DecryptionFailed))); } @@ -445,14 +444,14 @@ mod tests { let recipient_ssk = SecretSpendingKey([7_u8; 32]); let recipient_keys = recipient_ssk.produce_private_key_holder(None); let recipient_vpk = recipient_keys.generate_viewing_public_key(); - let recipient_vsk = recipient_keys.viewing_secret_key.clone(); + let recipient_vsk = recipient_keys.viewing_secret_key; let mut sealed = holder.seal_for(&SealingPublicKey::from_bytes(recipient_vpk.0)); // Flip a byte in the AES-GCM ciphertext portion (after KEM ciphertext + nonce). let last = sealed.len() - 1; sealed[last] ^= 0xFF; - let result = GroupKeyHolder::unseal(&sealed, recipient_vsk); + let result = GroupKeyHolder::unseal(&sealed, &recipient_vsk); assert!(matches!(result, Err(super::SealError::DecryptionFailed))); } @@ -527,11 +526,11 @@ mod tests { let bob_ssk = SecretSpendingKey([77_u8; 32]); let bob_keys = bob_ssk.produce_private_key_holder(None); let bob_vpk = bob_keys.generate_viewing_public_key(); - let bob_vsk = bob_keys.viewing_secret_key.clone(); + let bob_vsk = bob_keys.viewing_secret_key; let sealed = alice_holder.seal_for(&SealingPublicKey::from_bytes(bob_vpk.0)); let bob_holder = - GroupKeyHolder::unseal(&sealed, bob_vsk).expect("Bob should unseal the GMS"); + GroupKeyHolder::unseal(&sealed, &bob_vsk).expect("Bob should unseal the GMS"); let bob_npk = bob_holder .derive_keys_for_pda(&TEST_PROGRAM_ID, &pda_seed) diff --git a/key_protocol/src/key_management/key_tree/keys_private.rs b/key_protocol/src/key_management/key_tree/keys_private.rs index 27b75f22..7401aa92 100644 --- a/key_protocol/src/key_management/key_tree/keys_private.rs +++ b/key_protocol/src/key_management/key_tree/keys_private.rs @@ -1,3 +1,5 @@ +use std::collections::BTreeMap; + use nssa_core::{NullifierPublicKey, PrivateAccountKind, encryption::ViewingPublicKey}; use serde::{Deserialize, Serialize}; use sha2::Digest as _; @@ -59,7 +61,7 @@ impl ChildKeysPrivate { pub fn nth_child(&self, cci: u32) -> Self { let mut parent_hash = sha2::Sha256::new(); parent_hash.update(b"LEE/keys"); - parent_hash.update([0u8; 16]); + parent_hash.update([0_u8; 16]); parent_hash.update([9_u8]); parent_hash.update(self.value.0.private_key_holder.nullifier_secret_key); parent_hash.update(self.value.0.private_key_holder.viewing_secret_key.d); @@ -128,7 +130,7 @@ impl KeyTreeNode for ChildKeysPrivate { #[cfg(test)] mod tests { - use nssa_core::{NullifierPublicKey, NullifierSecretKey}; + use nssa_core::NullifierSecretKey; use super::*; use crate::key_management::{self, secret_holders::ViewingSecretKey}; @@ -144,7 +146,7 @@ mod tests { let keys = ChildKeysPrivate::root(seed); - let expected_ssk: SecretSpendingKey = key_management::secret_holders::SecretSpendingKey([ + let expected_ssk = key_management::secret_holders::SecretSpendingKey([ 246, 79, 26, 124, 135, 95, 52, 51, 201, 27, 48, 194, 2, 144, 51, 219, 245, 128, 139, 222, 42, 195, 105, 33, 115, 97, 186, 0, 97, 14, 218, 191, ]); @@ -159,7 +161,7 @@ mod tests { 34, 234, 19, 222, 2, 22, 12, 163, 252, 88, 11, 0, 163, ]; - let expected_npk: NullifierPublicKey = nssa_core::NullifierPublicKey([ + let expected_npk = nssa_core::NullifierPublicKey([ 7, 123, 125, 191, 233, 183, 201, 4, 20, 214, 155, 210, 45, 234, 27, 240, 194, 111, 97, 247, 155, 113, 122, 246, 192, 0, 70, 61, 76, 71, 70, 2, ]); @@ -257,11 +259,10 @@ mod tests { 114, 39, 38, 118, 197, 205, 225, ]; - // Marvin-pq this test currently fails let root_node = ChildKeysPrivate::root(seed); let child_node = ChildKeysPrivate::nth_child(&root_node, 42_u32); - let expected_ssk: SecretSpendingKey = key_management::secret_holders::SecretSpendingKey([ + let expected_ssk = key_management::secret_holders::SecretSpendingKey([ 215, 207, 70, 52, 161, 220, 88, 88, 241, 149, 81, 130, 217, 214, 252, 170, 51, 232, 230, 158, 195, 173, 174, 37, 27, 101, 49, 35, 79, 13, 44, 225, ]); diff --git a/key_protocol/src/key_management/secret_holders.rs b/key_protocol/src/key_management/secret_holders.rs index 40d18d80..f8b54d24 100644 --- a/key_protocol/src/key_management/secret_holders.rs +++ b/key_protocol/src/key_management/secret_holders.rs @@ -19,7 +19,7 @@ pub struct SeedHolder { pub struct SecretSpendingKey(pub [u8; 32]); /// Viewing secret key: the KEM seed split into its two 32-byte halves `d` and `r` (= z in /// FIPS 203), from which the ML-KEM 768 decapsulation key is derived deterministically. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct ViewingSecretKey { pub d: [u8; 32], pub r: [u8; 32], @@ -27,7 +27,7 @@ pub struct ViewingSecretKey { /// Private key holder. Produces public keys. Can produce `account_id`. Can produce shared secret /// for recepient. -#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq)] +#[derive(Serialize, Deserialize, Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] pub struct PrivateKeyHolder { pub nullifier_secret_key: NullifierSecretKey, pub viewing_secret_key: ViewingSecretKey, @@ -150,7 +150,7 @@ impl SecretSpendingKey { } #[must_use] - pub fn generate_viewing_secret_key(seed: [u8; 64]) -> ViewingSecretKey { + pub const fn generate_viewing_secret_key(seed: [u8; 64]) -> ViewingSecretKey { ViewingSecretKey { d: *seed.first_chunk::<32>().expect("seed is 64 bytes"), r: *seed.last_chunk::<32>().expect("seed is 64 bytes"), @@ -169,11 +169,11 @@ impl SecretSpendingKey { impl From<&ViewingSecretKey> for ViewingPublicKey { fn from(sk: &ViewingSecretKey) -> Self { use ml_kem::{Kem, KeyExport as _, MlKem768, Seed}; - let mut seed_bytes = [0u8; 64]; + let mut seed_bytes = [0_u8; 64]; seed_bytes[..32].copy_from_slice(&sk.d); seed_bytes[32..].copy_from_slice(&sk.r); let dk = ::DecapsulationKey::from_seed(Seed::from(seed_bytes)); - ViewingPublicKey(dk.encapsulation_key().to_bytes().to_vec()) + Self(dk.encapsulation_key().to_bytes().to_vec()) } } diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs deleted file mode 100644 index d4747d39..00000000 --- a/key_protocol/src/key_protocol_core/mod.rs +++ /dev/null @@ -1,421 +0,0 @@ -use std::collections::BTreeMap; - -use anyhow::Result; -use k256::AffinePoint; -use nssa::{Account, AccountId}; -use nssa_core::{Identifier, PrivateAccountKind}; -use serde::{Deserialize, Serialize}; - -use crate::key_management::{ - KeyChain, - group_key_holder::GroupKeyHolder, - key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex}, - secret_holders::SeedHolder, -}; - -pub type PublicKey = AffinePoint; - -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct UserPrivateAccountData { - pub key_chain: KeyChain, - pub accounts: Vec<(PrivateAccountKind, Account)>, -} - -/// Metadata for a shared account (GMS-derived), stored alongside the cached plaintext state. -/// The group label and identifier (or PDA seed) are needed to re-derive keys during sync. -#[derive(Clone, Debug, Serialize, Deserialize)] -pub struct SharedAccountEntry { - pub group_label: String, - pub identifier: Identifier, - /// For PDA accounts, the seed and program ID used to derive keys via `derive_keys_for_pda`. - /// `None` for regular shared accounts (keys derived from identifier via derivation seed). - #[serde(default)] - pub pda_seed: Option, - #[serde(default)] - pub pda_program_id: Option, - pub account: Account, -} - -#[derive(Clone, Debug)] -pub struct NSSAUserData { - /// Default public accounts. - pub default_pub_account_signing_keys: BTreeMap, - /// Default private accounts. - pub default_user_private_accounts: BTreeMap, - /// Tree of public keys. - pub public_key_tree: KeyTreePublic, - /// Tree of private keys. - pub private_key_tree: KeyTreePrivate, - /// Group key holders for shared account management, keyed by a human-readable label. - pub group_key_holders: BTreeMap, - /// Cached plaintext state of shared private accounts (PDAs and regular shared accounts), - /// keyed by `AccountId`. Each entry stores the group label and identifier needed - /// to re-derive keys during sync. - pub shared_private_accounts: BTreeMap, - /// Dedicated sealing secret key for GMS distribution. Generated once via - /// `wallet group new-sealing-key`. The corresponding public key is shared with - /// group members so they can seal GMS for this wallet. - pub sealing_secret_key: Option, -} - -impl NSSAUserData { - fn valid_public_key_transaction_pairing_check( - accounts_keys_map: &BTreeMap, - ) -> bool { - let mut check_res = true; - for (account_id, key) in accounts_keys_map { - let expected_account_id = - nssa::AccountId::from(&nssa::PublicKey::new_from_private_key(key)); - if &expected_account_id != account_id { - println!("{expected_account_id}, {account_id}"); - check_res = false; - } - } - check_res - } - - fn valid_private_key_transaction_pairing_check( - accounts_keys_map: &BTreeMap, - ) -> bool { - let mut check_res = true; - for (account_id, entry) in accounts_keys_map { - let npk = &entry.key_chain.nullifier_public_key; - let any_match = entry - .accounts - .iter() - .any(|(kind, _)| nssa::AccountId::for_private_account(npk, kind) == *account_id); - if !any_match { - println!("No matching entry found for account_id {account_id}"); - check_res = false; - } - } - check_res - } - - pub fn new_with_accounts( - default_accounts_keys: BTreeMap, - default_accounts_key_chains: BTreeMap, - public_key_tree: KeyTreePublic, - private_key_tree: KeyTreePrivate, - ) -> Result { - if !Self::valid_public_key_transaction_pairing_check(&default_accounts_keys) { - anyhow::bail!( - "Key transaction pairing check not satisfied, there are public account_ids, which are not derived from keys" - ); - } - - if !Self::valid_private_key_transaction_pairing_check(&default_accounts_key_chains) { - anyhow::bail!( - "Key transaction pairing check not satisfied, there are private account_ids, which are not derived from keys" - ); - } - - Ok(Self { - default_pub_account_signing_keys: default_accounts_keys, - default_user_private_accounts: default_accounts_key_chains, - public_key_tree, - private_key_tree, - group_key_holders: BTreeMap::new(), - shared_private_accounts: BTreeMap::new(), - sealing_secret_key: None, - }) - } - - /// Generated new private key for public transaction signatures. - /// - /// Returns the `account_id` of new account. - pub fn generate_new_public_transaction_private_key( - &mut self, - parent_cci: Option, - ) -> (nssa::AccountId, ChainIndex) { - match parent_cci { - Some(parent_cci) => self - .public_key_tree - .generate_new_public_node(&parent_cci) - .expect("Parent must be present in a tree"), - None => self - .public_key_tree - .generate_new_public_node_layered() - .expect("Search for new node slot failed"), - } - } - - /// Returns the signing key for public transaction signatures. - #[must_use] - pub fn get_pub_account_signing_key( - &self, - account_id: nssa::AccountId, - ) -> Option<&nssa::PrivateKey> { - self.default_pub_account_signing_keys - .get(&account_id) - .or_else(|| self.public_key_tree.get_node(account_id).map(Into::into)) - } - - /// Creates a new receiving key node and returns its `ChainIndex`. - pub fn create_private_accounts_key(&mut self, parent_cci: Option) -> ChainIndex { - match parent_cci { - Some(parent_cci) => self - .private_key_tree - .create_private_accounts_key_node(&parent_cci) - .expect("Parent must be present in a tree"), - None => self - .private_key_tree - .create_private_accounts_key_node_layered() - .expect("Search for new node slot failed"), - } - } - - /// Registers an additional identifier on an existing private key node, deriving and recording - /// the corresponding `AccountId`. Returns `None` if the node does not exist or the identifier - /// is already registered. - pub fn register_identifier_on_private_key_chain( - &mut self, - cci: &ChainIndex, - identifier: Identifier, - ) -> Option { - self.private_key_tree - .register_identifier_on_node(cci, identifier) - } - - /// Returns the key chain and account data for the given private account ID. - #[must_use] - pub fn get_private_account( - &self, - account_id: nssa::AccountId, - ) -> Option<(KeyChain, nssa_core::account::Account, Identifier)> { - // Check default accounts - if let Some(entry) = self.default_user_private_accounts.get(&account_id) { - let npk = &entry.key_chain.nullifier_public_key; - if let Some((kind, account)) = entry - .accounts - .iter() - .find(|(kind, _)| nssa::AccountId::for_private_account(npk, kind) == account_id) - { - return Some((entry.key_chain.clone(), account.clone(), kind.identifier())); - } - return None; - } - // Check tree - if let Some(node) = self.private_key_tree.get_node(account_id) { - let key_chain = &node.value.0; - let npk = &key_chain.nullifier_public_key; - if let Some((kind, account)) = node - .value - .1 - .iter() - .find(|(kind, _)| nssa::AccountId::for_private_account(npk, kind) == account_id) - { - return Some((key_chain.clone(), account.clone(), kind.identifier())); - } - } - None - } - - pub fn account_ids(&self) -> impl Iterator { - self.public_account_ids().chain(self.private_account_ids()) - } - - pub fn public_account_ids(&self) -> impl Iterator { - self.default_pub_account_signing_keys - .keys() - .copied() - .chain(self.public_key_tree.account_id_map.keys().copied()) - } - - pub fn private_account_ids(&self) -> impl Iterator { - self.default_user_private_accounts - .keys() - .copied() - .chain(self.private_key_tree.account_id_map.keys().copied()) - } - - /// Returns the `GroupKeyHolder` for the given label, if it exists. - #[must_use] - pub fn group_key_holder(&self, label: &str) -> Option<&GroupKeyHolder> { - self.group_key_holders.get(label) - } - - /// Inserts or replaces a `GroupKeyHolder` under the given label. - /// - /// If a holder already exists under this label, it is silently replaced and the old - /// GMS is lost. Callers must ensure label uniqueness across groups. - pub fn insert_group_key_holder(&mut self, label: String, holder: GroupKeyHolder) { - self.group_key_holders.insert(label, holder); - } - - /// Returns the cached account for a shared private account, if it exists. - #[must_use] - pub fn shared_private_account( - &self, - account_id: &nssa::AccountId, - ) -> Option<&SharedAccountEntry> { - self.shared_private_accounts.get(account_id) - } - - /// Inserts or replaces a shared private account entry. - pub fn insert_shared_private_account( - &mut self, - account_id: nssa::AccountId, - entry: SharedAccountEntry, - ) { - self.shared_private_accounts.insert(account_id, entry); - } - - /// Updates the cached account state for a shared private account. - pub fn update_shared_private_account_state( - &mut self, - account_id: &nssa::AccountId, - account: nssa_core::account::Account, - ) { - if let Some(entry) = self.shared_private_accounts.get_mut(account_id) { - entry.account = account; - } - } - - /// Iterates over all shared private accounts. - pub fn shared_private_accounts_iter( - &self, - ) -> impl Iterator { - self.shared_private_accounts.iter() - } -} - -impl Default for NSSAUserData { - fn default() -> Self { - let (seed_holder, _mnemonic) = SeedHolder::new_mnemonic(""); - Self::new_with_accounts( - BTreeMap::new(), - BTreeMap::new(), - KeyTreePublic::new(&seed_holder), - KeyTreePrivate::new(&seed_holder), - ) - .unwrap() - } -} - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn group_key_holder_storage_round_trip() { - let mut user_data = NSSAUserData::default(); - assert!(user_data.group_key_holder("test-group").is_none()); - - let holder = GroupKeyHolder::from_gms([42_u8; 32]); - user_data.insert_group_key_holder(String::from("test-group"), holder.clone()); - - let retrieved = user_data - .group_key_holder("test-group") - .expect("should exist"); - assert_eq!(retrieved.dangerous_raw_gms(), holder.dangerous_raw_gms()); - } - - #[test] - fn group_key_holders_default_empty() { - let user_data = NSSAUserData::default(); - assert!(user_data.group_key_holders.is_empty()); - assert!(user_data.shared_private_accounts.is_empty()); - } - - #[test] - fn shared_account_entry_serde_round_trip() { - use nssa_core::program::PdaSeed; - - let entry = SharedAccountEntry { - group_label: String::from("test-group"), - identifier: 42, - pda_seed: None, - pda_program_id: None, - account: nssa_core::account::Account::default(), - }; - let encoded = bincode::serialize(&entry).expect("serialize"); - let decoded: SharedAccountEntry = bincode::deserialize(&encoded).expect("deserialize"); - assert_eq!(decoded.group_label, "test-group"); - assert_eq!(decoded.identifier, 42); - assert!(decoded.pda_seed.is_none()); - - let pda_entry = SharedAccountEntry { - group_label: String::from("pda-group"), - identifier: u128::MAX, - pda_seed: Some(PdaSeed::new([7_u8; 32])), - pda_program_id: Some([9; 8]), - account: nssa_core::account::Account::default(), - }; - let pda_encoded = bincode::serialize(&pda_entry).expect("serialize pda"); - let pda_decoded: SharedAccountEntry = - bincode::deserialize(&pda_encoded).expect("deserialize pda"); - assert_eq!(pda_decoded.group_label, "pda-group"); - assert_eq!(pda_decoded.identifier, u128::MAX); - assert_eq!(pda_decoded.pda_seed.unwrap(), PdaSeed::new([7_u8; 32])); - } - - #[test] - fn shared_account_entry_none_pda_seed_round_trips() { - // Verify that an entry with pda_seed=None serializes and deserializes correctly, - // confirming the #[serde(default)] attribute works for backward compatibility. - let entry = SharedAccountEntry { - group_label: String::from("old"), - identifier: 1, - pda_seed: None, - pda_program_id: None, - account: nssa_core::account::Account::default(), - }; - let encoded = bincode::serialize(&entry).expect("serialize"); - let decoded: SharedAccountEntry = bincode::deserialize(&encoded).expect("deserialize"); - assert_eq!(decoded.group_label, "old"); - assert_eq!(decoded.identifier, 1); - assert!(decoded.pda_seed.is_none()); - } - - #[test] - fn shared_account_derives_consistent_keys_from_group() { - use nssa_core::program::PdaSeed; - - let mut user_data = NSSAUserData::default(); - let gms_holder = GroupKeyHolder::from_gms([42_u8; 32]); - user_data.insert_group_key_holder(String::from("my-group"), gms_holder); - - let holder = user_data.group_key_holder("my-group").unwrap(); - - // Regular shared account: derive via tag - let tag = [1_u8; 32]; - let keys_a = holder.derive_keys_for_shared_account(&tag); - let keys_b = holder.derive_keys_for_shared_account(&tag); - assert_eq!( - keys_a.generate_nullifier_public_key(), - keys_b.generate_nullifier_public_key(), - ); - - // PDA shared account: derive via seed - let seed = PdaSeed::new([2_u8; 32]); - let pda_keys_a = holder.derive_keys_for_pda(&[9; 8], &seed); - let pda_keys_b = holder.derive_keys_for_pda(&[9; 8], &seed); - assert_eq!( - pda_keys_a.generate_nullifier_public_key(), - pda_keys_b.generate_nullifier_public_key(), - ); - - // PDA and shared derivations don't collide - assert_ne!( - keys_a.generate_nullifier_public_key(), - pda_keys_a.generate_nullifier_public_key(), - ); - } - - #[test] - fn new_account() { - let mut user_data = NSSAUserData::default(); - - let chain_index = user_data.create_private_accounts_key(Some(ChainIndex::root())); - - let is_key_chain_generated = user_data - .private_key_tree - .key_map - .contains_key(&chain_index); - assert!(is_key_chain_generated); - - let key_chain = &user_data.private_key_tree.key_map[&chain_index].value.0; - println!("{key_chain:#?}"); - } -} diff --git a/nssa/core/src/encoding.rs b/nssa/core/src/encoding.rs index 354169a8..4e6481ef 100644 --- a/nssa/core/src/encoding.rs +++ b/nssa/core/src/encoding.rs @@ -7,7 +7,7 @@ use std::io::Read as _; #[cfg(feature = "host")] use crate::Nullifier; #[cfg(feature = "host")] -use crate::encryption::{EphemeralPublicKey, shared_key_derivation::Secp256k1Point}; +use crate::encryption::EphemeralPublicKey; #[cfg(feature = "host")] use crate::error::NssaCoreError; use crate::{ @@ -157,25 +157,6 @@ impl Ciphertext { } } -#[cfg(feature = "host")] -impl Secp256k1Point { - /// Converts the point to bytes. - #[must_use] - pub fn to_bytes(&self) -> [u8; 33] { - self.0.clone().try_into().unwrap() - } - - /// Deserializes a secp256k1 point from a cursor. - pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { - let mut value = vec![0; 33]; - cursor.read_exact(&mut value)?; - Ok(Self(value)) - } -} - -// Marvin-pq: EphemeralPublicKey is now the ML-KEM-768 ciphertext (1088 bytes) produced by -// SharedSecretKey::encapsulate. It replaces the old Secp256k1Point (33 bytes) on the wire. -// Fixed size: 1088 bytes for ML-KEM-768 (EncodedUSize + EncodedVSize per FIPS 203 §7.2). #[cfg(feature = "host")] impl EphemeralPublicKey { /// Serializes the ML-KEM-768 ciphertext to bytes (always 1088 bytes). @@ -187,7 +168,7 @@ impl EphemeralPublicKey { /// Deserializes an ML-KEM-768 ciphertext from a cursor. /// Reads exactly 1088 bytes — the fixed ciphertext size for ML-KEM-768. pub fn from_cursor(cursor: &mut Cursor<&[u8]>) -> Result { - let mut value = vec![0u8; 1088]; + let mut value = vec![0_u8; 1088]; cursor.read_exact(&mut value)?; Ok(Self(value)) } diff --git a/nssa/core/src/encryption/mod.rs b/nssa/core/src/encryption/mod.rs index 0c44fef6..bfef74a0 100644 --- a/nssa/core/src/encryption/mod.rs +++ b/nssa/core/src/encryption/mod.rs @@ -154,4 +154,41 @@ mod tests { assert_eq!(account_ct.0.len(), pda_ct.0.len()); } + + /// Verifies the full account-note pipeline: ML-KEM-768 encapsulation/decapsulation + /// feeds the correct shared secret into the SHA-256 KDF and ChaCha20 round-trip. + #[cfg(feature = "host")] + #[test] + fn kem_to_chacha20_round_trip() { + let d = [1_u8; 32]; + let r = [2_u8; 32]; + let vpk = shared_key_derivation::ViewingPublicKey::from_seed(&d, &r); + + let (sender_ss, epk) = SharedSecretKey::encapsulate(&vpk); + let receiver_ss = SharedSecretKey::decapsulate(&epk, &d, &r); + + let account = Account { + program_owner: [12_u32; 8], + balance: 999, + ..Account::default() + }; + let kind = PrivateAccountKind::Regular(0); + let commitment = crate::Commitment::new(&AccountId::new([7_u8; 32]), &account); + + let ct = EncryptionScheme::encrypt(&account, &kind, &sender_ss, &commitment, 0); + let (decoded_kind, decoded_account) = + EncryptionScheme::decrypt(&ct, &receiver_ss, &commitment, 0) + .expect("decryption must succeed with correct shared secret"); + + assert_eq!(decoded_account, account); + assert_eq!(decoded_kind, kind); + + // Wrong shared secret must not decrypt correctly. + let wrong_ss = SharedSecretKey([0_u8; 32]); + let bad = EncryptionScheme::decrypt(&ct, &wrong_ss, &commitment, 0); + assert!( + bad.is_none() || bad.is_some_and(|(_, a)| a.balance != 999), + "wrong shared secret must not produce the correct plaintext" + ); + } } diff --git a/nssa/core/src/encryption/shared_key_derivation.rs b/nssa/core/src/encryption/shared_key_derivation.rs index 3cd5396c..72b5d950 100644 --- a/nssa/core/src/encryption/shared_key_derivation.rs +++ b/nssa/core/src/encryption/shared_key_derivation.rs @@ -1,48 +1,8 @@ -#![expect( - clippy::arithmetic_side_effects, - reason = "Multiplication of finite field elements can't overflow" -)] - -use std::fmt::Write as _; - use borsh::{BorshDeserialize, BorshSerialize}; -use k256::{ - AffinePoint, FieldBytes, ProjectivePoint, - elliptic_curve::{PrimeField as _, sec1::ToEncodedPoint as _}, -}; use ml_kem::{Decapsulate as _, Encapsulate as _, KeyExport as _, Seed}; use serde::{Deserialize, Serialize}; -use crate::{SharedSecretKey, encryption::Scalar}; -/// Marvin-pq check this -/// A compressed secp256k1 point (33 bytes). -/// Kept for backward compatibility with Phase-2+ callers; no longer used as `EphemeralPublicKey`. -#[derive(Serialize, Deserialize, Clone, PartialEq, Eq, BorshSerialize, BorshDeserialize)] -pub struct Secp256k1Point(pub Vec); - -impl std::fmt::Debug for Secp256k1Point { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let hex: String = self.0.iter().fold(String::new(), |mut acc, b| { - write!(acc, "{b:02x}").expect("writing to string should not fail"); - acc - }); - write!(f, "Secp256k1Point({hex})") - } -} - -impl Secp256k1Point { - #[must_use] - pub fn from_scalar(value: Scalar) -> Self { - let x_bytes: FieldBytes = value.into(); - let x = k256::Scalar::from_repr(x_bytes).unwrap(); - - let p = ProjectivePoint::GENERATOR * x; - let q = AffinePoint::from(p); - let enc = q.to_encoded_point(true); - - Self(enc.as_bytes().to_vec()) - } -} +use crate::SharedSecretKey; /// The ML-KEM-768 ciphertext produced during encapsulation; transmitted on-wire in place of the /// former ECDH ephemeral public key. Always 1088 bytes for ML-KEM-768. @@ -50,7 +10,7 @@ impl Secp256k1Point { pub struct EphemeralPublicKey(pub Vec); /// ML-KEM-768 encapsulation key bytes (1184 bytes, opaque to this crate). -#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, BorshSerialize, BorshDeserialize)] +#[derive(Serialize, Deserialize, Clone, Debug, PartialEq, Eq, PartialOrd, Ord, BorshSerialize, BorshDeserialize)] pub struct ViewingPublicKey(pub Vec); impl ViewingPublicKey { @@ -113,7 +73,7 @@ impl SharedSecretKey { input.extend_from_slice(message_hash); input.extend_from_slice(&output_index.to_le_bytes()); let hash = Impl::hash_bytes(&input); - let m: ml_kem::B32 = ml_kem::array::Array::try_from(hash.as_bytes() as &[u8]) + let m: ml_kem::B32 = ml_kem::array::Array::try_from(hash.as_bytes()) .expect("SHA-256 output is 32 bytes"); let ek_bytes: ml_kem::kem::Key = vpk @@ -159,8 +119,8 @@ mod tests { #[test] fn encapsulate_decapsulate_round_trip() { - let d = [1u8; 32]; - let r = [2u8; 32]; + let d = [1_u8; 32]; + let r = [2_u8; 32]; let mut seed = Seed::default(); seed[..32].copy_from_slice(&d); @@ -184,8 +144,8 @@ mod tests { #[test] fn different_vpks_produce_different_shared_secrets() { - let (d1, r1) = ([1u8; 32], [2u8; 32]); - let (d2, r2) = ([3u8; 32], [4u8; 32]); + let (d1, r1) = ([1_u8; 32], [2_u8; 32]); + let (d2, r2) = ([3_u8; 32], [4_u8; 32]); let vpk1 = { let mut seed = Seed::default(); diff --git a/nssa/src/privacy_preserving_transaction/circuit.rs b/nssa/src/privacy_preserving_transaction/circuit.rs index 483407fe..503afe75 100644 --- a/nssa/src/privacy_preserving_transaction/circuit.rs +++ b/nssa/src/privacy_preserving_transaction/circuit.rs @@ -244,7 +244,7 @@ mod tests { let expected_sender_pre = sender.clone(); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 0).0; let (output, proof) = execute_and_prove( vec![sender, recipient], @@ -341,10 +341,10 @@ mod tests { ]; let shared_secret_1 = - SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0).0; let shared_secret_2 = - SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0u8; 32], 1).0; + SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 1).0; let (output, proof) = execute_and_prove( vec![sender_pre, recipient], @@ -419,7 +419,7 @@ mod tests { .unwrap(); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0).0; let program_with_deps = ProgramWithDependencies::new( validity_window_chain_caller, @@ -450,7 +450,7 @@ mod tests { let seed = PdaSeed::new([42; 32]); let identifier: u128 = 99; let shared_secret = - SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, identifier); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); @@ -488,7 +488,7 @@ mod tests { let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); let shared_secret_pda = - SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; // PDA (new, mask 3) let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 0); @@ -527,7 +527,7 @@ mod tests { let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); let shared_secret_pda = - SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; // PDA (new, private PDA) let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 0); @@ -582,7 +582,7 @@ mod tests { let shared_npk = shared_keys.npk(); let shared_identifier: u128 = 42; let shared_secret = - SharedSecretKey::encapsulate_deterministic(&shared_keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&shared_keys.vpk(), &[0_u8; 32], 0).0; // Sender: public account with balance, owned by auth-transfer let sender_id = AccountId::new([99; 32]); @@ -633,7 +633,7 @@ mod tests { let program = Program::authenticated_transfer_program(); let keys = test_private_account_keys_1(); let identifier: u128 = 99; - let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_regular_private_account(&keys.npk(), identifier); let pre = AccountWithMetadata::new(Account::default(), true, account_id); @@ -663,7 +663,7 @@ mod tests { let program = Program::authenticated_transfer_program(); let keys = test_private_account_keys_1(); let identifier: u128 = 99; - let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let sender = AccountWithMetadata::new( Account { @@ -708,7 +708,7 @@ mod tests { let program = Program::authenticated_transfer_program(); let keys = test_private_account_keys_1(); let identifier: u128 = 99; - let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_regular_private_account(&keys.npk(), identifier); let account = Account { program_owner: program.id(), @@ -757,7 +757,7 @@ mod tests { let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); let identifier: u128 = 99; - let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let auth_transfer_id = auth_transfer.id(); let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, identifier); @@ -812,7 +812,7 @@ mod tests { let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 5); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); @@ -838,7 +838,7 @@ mod tests { let keys = test_private_account_keys_1(); let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); - let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + let ssk = SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let auth_transfer_id = auth_transfer.id(); let pda_id = AccountId::for_private_pda(&program.id(), &seed, &npk, 5); diff --git a/nssa/src/privacy_preserving_transaction/message.rs b/nssa/src/privacy_preserving_transaction/message.rs index 97643cd1..2dc66485 100644 --- a/nssa/src/privacy_preserving_transaction/message.rs +++ b/nssa/src/privacy_preserving_transaction/message.rs @@ -208,7 +208,7 @@ pub mod tests { let nonces_bytes: &[u8] = &[1, 0, 0, 0, 5, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]; // all remaining vec fields are empty: u32 len=0 let empty_vec_bytes: &[u8] = &[0_u8; 4]; - // validity windows: unbounded = {from: None (0u8), to: None (0u8)} + // validity windows: unbounded = {from: None (0_u8), to: None (0_u8)} let unbounded_window_bytes: &[u8] = &[0_u8; 2]; let expected_borsh_vec: Vec = [ @@ -246,11 +246,11 @@ pub mod tests { #[test] fn encrypted_account_data_constructor() { let npk = NullifierPublicKey::from(&[1; 32]); - let vpk = ViewingPublicKey::from_seed(&[2u8; 32], &[3u8; 32]); + let vpk = ViewingPublicKey::from_seed(&[2_u8; 32], &[3_u8; 32]); let account = Account::default(); let account_id = nssa_core::account::AccountId::for_regular_private_account(&npk, 0); let commitment = Commitment::new(&account_id, &account); - let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(&vpk, &[0u8; 32], 0); + let (shared_secret, epk) = SharedSecretKey::encapsulate_deterministic(&vpk, &[0_u8; 32], 0); let ciphertext = EncryptionScheme::encrypt( &account, &PrivateAccountKind::Regular(0), diff --git a/nssa/src/state.rs b/nssa/src/state.rs index c79d26ea..386a6428 100644 --- a/nssa/src/state.rs +++ b/nssa/src/state.rs @@ -1345,7 +1345,7 @@ pub mod tests { AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); let (shared_secret, epk) = - SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 0); let (output, proof) = circuit::execute_and_prove( vec![sender, recipient], @@ -1396,10 +1396,10 @@ pub mod tests { AccountWithMetadata::new(Account::default(), false, (&recipient_keys.npk(), 0)); let (shared_secret_1, epk_1) = - SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0); let (shared_secret_2, epk_2) = - SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0u8; 32], 1); + SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 1); let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], @@ -1464,7 +1464,7 @@ pub mod tests { ); let (shared_secret, epk) = - SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0); let (output, proof) = circuit::execute_and_prove( vec![sender_pre, recipient_pre], @@ -1974,7 +1974,7 @@ pub mod tests { InputAccountIdentity::PrivateAuthorizedUpdate { ssk: SharedSecretKey::encapsulate_deterministic( &sender_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -1986,7 +1986,7 @@ pub mod tests { npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2030,7 +2030,7 @@ pub mod tests { InputAccountIdentity::PrivateAuthorizedUpdate { ssk: SharedSecretKey::encapsulate_deterministic( &sender_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2042,7 +2042,7 @@ pub mod tests { npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2086,7 +2086,7 @@ pub mod tests { InputAccountIdentity::PrivateAuthorizedUpdate { ssk: SharedSecretKey::encapsulate_deterministic( &sender_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2098,7 +2098,7 @@ pub mod tests { npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2142,7 +2142,7 @@ pub mod tests { InputAccountIdentity::PrivateAuthorizedUpdate { ssk: SharedSecretKey::encapsulate_deterministic( &sender_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2154,7 +2154,7 @@ pub mod tests { npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2198,7 +2198,7 @@ pub mod tests { InputAccountIdentity::PrivateAuthorizedUpdate { ssk: SharedSecretKey::encapsulate_deterministic( &sender_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2210,7 +2210,7 @@ pub mod tests { npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2252,7 +2252,7 @@ pub mod tests { InputAccountIdentity::PrivateAuthorizedUpdate { ssk: SharedSecretKey::encapsulate_deterministic( &sender_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2264,7 +2264,7 @@ pub mod tests { npk: recipient_keys.npk(), ssk: SharedSecretKey::encapsulate_deterministic( &recipient_keys.vpk(), - &[0u8; 32], + &[0_u8; 32], 0, ) .0, @@ -2287,7 +2287,7 @@ pub mod tests { let keys = test_private_account_keys_1(); let npk = keys.npk(); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let public_account_1 = AccountWithMetadata::new( Account { program_owner: program.id(), @@ -2329,7 +2329,7 @@ pub mod tests { let npk = keys.npk(); let seed = PdaSeed::new([42; 32]); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_private_pda(&program.id(), &seed, &npk, u128::MAX); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); @@ -2366,7 +2366,7 @@ pub mod tests { let npk_b = keys_b.npk(); let seed = PdaSeed::new([42; 32]); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&keys_b.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys_b.vpk(), &[0_u8; 32], 0).0; // `account_id` is derived from `npk_a`, but `npk_b` is supplied for this pre_state. // `AccountId::for_private_pda(program, seed, npk_b) != account_id`, so the claim check in @@ -2401,7 +2401,7 @@ pub mod tests { let npk = keys.npk(); let seed = PdaSeed::new([77; 32]); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_private_pda(&delegator.id(), &seed, &npk, u128::MAX); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); @@ -2440,7 +2440,7 @@ pub mod tests { let claim_seed = PdaSeed::new([77; 32]); let wrong_delegated_seed = PdaSeed::new([88; 32]); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let account_id = AccountId::for_private_pda(&delegator.id(), &claim_seed, &npk, u128::MAX); let pre_state = AccountWithMetadata::new(Account::default(), false, account_id); @@ -2477,8 +2477,8 @@ pub mod tests { let keys_a = test_private_account_keys_1(); let keys_b = test_private_account_keys_2(); let seed = PdaSeed::new([55; 32]); - let shared_a = SharedSecretKey::encapsulate_deterministic(&keys_a.vpk(), &[0u8; 32], 0).0; - let shared_b = SharedSecretKey::encapsulate_deterministic(&keys_b.vpk(), &[0u8; 32], 0).0; + let shared_a = SharedSecretKey::encapsulate_deterministic(&keys_a.vpk(), &[0_u8; 32], 0).0; + let shared_b = SharedSecretKey::encapsulate_deterministic(&keys_b.vpk(), &[0_u8; 32], 0).0; let account_a = AccountId::for_private_pda(&program.id(), &seed, &keys_a.npk(), u128::MAX); let account_b = AccountId::for_private_pda(&program.id(), &seed, &keys_b.npk(), u128::MAX); @@ -2524,7 +2524,7 @@ pub mod tests { let keys = test_private_account_keys_1(); let npk = keys.npk(); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&keys.vpk(), &[0_u8; 32], 0).0; let seed = PdaSeed::new([99; 32]); // Simulate a previously-claimed private PDA: program_owner != DEFAULT, is_authorized = @@ -2624,7 +2624,7 @@ pub mod tests { ); let shared_secret = - SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0).0; let result = execute_and_prove( vec![private_account_1.clone(), private_account_1], Program::serialize_instruction(100_u128).unwrap(), @@ -2969,7 +2969,7 @@ pub mod tests { let recipient_pre = AccountWithMetadata::new(Account::default(), true, recipient_account_id); let (shared_secret, epk) = - SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0); let balance = 37; @@ -3074,10 +3074,10 @@ pub mod tests { ); let (from_ss, from_epk) = - SharedSecretKey::encapsulate_deterministic(&from_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&from_keys.vpk(), &[0_u8; 32], 0); let (to_ss, to_epk) = - SharedSecretKey::encapsulate_deterministic(&to_keys.vpk(), &[0u8; 32], 1); + SharedSecretKey::encapsulate_deterministic(&to_keys.vpk(), &[0_u8; 32], 1); let mut dependencies = HashMap::new(); @@ -3375,7 +3375,7 @@ pub mod tests { // Set up parameters for the new account let (shared_secret, epk) = - SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0); let instruction = authenticated_transfer_core::Instruction::Initialize; @@ -3426,7 +3426,7 @@ pub mod tests { let program = Program::claimer(); let (shared_secret, epk) = - SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0); let (output, proof) = execute_and_prove( vec![unauthorized_account], @@ -3475,7 +3475,7 @@ pub mod tests { // Set up parameters for claiming the new account let (shared_secret, epk) = - SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0); let instruction = authenticated_transfer_core::Instruction::Initialize; @@ -3524,7 +3524,7 @@ pub mod tests { let noop_program = Program::noop(); let shared_secret2 = - SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&private_keys.vpk(), &[0_u8; 32], 0).0; // Step 3: Try to execute noop program with authentication but without initialization let res = execute_and_prove( @@ -3608,7 +3608,7 @@ pub mod tests { vec![private_account], Program::serialize_instruction(instruction).unwrap(), vec![InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0u8; 32], 0) + ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0) .0, nsk: sender_keys.nsk, membership_proof: (0, vec![]), @@ -3635,7 +3635,7 @@ pub mod tests { vec![private_account], Program::serialize_instruction(instruction).unwrap(), vec![InputAccountIdentity::PrivateAuthorizedUpdate { - ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0u8; 32], 0) + ssk: SharedSecretKey::encapsulate_deterministic(&sender_keys.vpk(), &[0_u8; 32], 0) .0, nsk: sender_keys.nsk, membership_proof: (0, vec![]), @@ -3683,7 +3683,7 @@ pub mod tests { let instruction = (balance_to_transfer, auth_transfers.id()); let recipient = - SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0u8; 32], 0).0; + SharedSecretKey::encapsulate_deterministic(&recipient_keys.vpk(), &[0_u8; 32], 0).0; let mut dependencies = HashMap::new(); dependencies.insert(auth_transfers.id(), auth_transfers); @@ -3840,7 +3840,7 @@ pub mod tests { let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); let tx = { let (shared_secret, epk) = - SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0); let instruction = ( block_validity_window, @@ -3909,7 +3909,7 @@ pub mod tests { let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0).with_test_programs(); let tx = { let (shared_secret, epk) = - SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&account_keys.vpk(), &[0_u8; 32], 0); let instruction = ( BlockValidityWindow::new_unbounded(), @@ -4464,9 +4464,9 @@ pub mod tests { }; let (alice_shared_0, alice_epk_0) = - SharedSecretKey::encapsulate_deterministic(&alice_keys.vpk(), &[0u8; 32], 0); + SharedSecretKey::encapsulate_deterministic(&alice_keys.vpk(), &[0_u8; 32], 0); let (alice_shared_1, alice_epk_1) = - SharedSecretKey::encapsulate_deterministic(&alice_keys.vpk(), &[0u8; 32], 1); + SharedSecretKey::encapsulate_deterministic(&alice_keys.vpk(), &[0_u8; 32], 1); // Fund alice_pda_0 { @@ -4576,7 +4576,7 @@ pub mod tests { let message = Message::try_from_circuit_output( vec![recipient_id], vec![Nonce(0)], - vec![(alice_npk, alice_keys.vpk(), alice_epk_0.clone())], + vec![(alice_npk, alice_keys.vpk(), alice_epk_0)], output, ) .unwrap(); @@ -4616,7 +4616,7 @@ pub mod tests { let message = Message::try_from_circuit_output( vec![recipient_id], vec![], - vec![(alice_npk, alice_keys.vpk(), alice_epk_1.clone())], + vec![(alice_npk, alice_keys.vpk(), alice_epk_1)], output, ) .unwrap(); diff --git a/testnet_initial_state/src/lib.rs b/testnet_initial_state/src/lib.rs index 80e3dc8b..e7000d5a 100644 --- a/testnet_initial_state/src/lib.rs +++ b/testnet_initial_state/src/lib.rs @@ -129,20 +129,20 @@ pub fn initial_priv_accounts_private_keys() -> Vec Result<()> { let (_kc, _mnemonic) = KeyChain::new_mnemonic(""); })); - // SharedSecretKey: caller has ephemeral secret, recipient has VSK→VPK. - // We bench the SENDER side: derive ephemeral pubkey, then SharedSecretKey::new(scalar, point). + // SharedSecretKey: caller has recipient VPK; we bench the SENDER side — + // ML-KEM-768 encapsulation (replaces the old ECDH scalar multiplication). let recipient_kc = KeyChain::new_os_random(); let vpk = recipient_kc.viewing_public_key; - results.push(time("SharedSecretKey::new (sender DH)", ITERS, || { - let mut bytes = [0_u8; 32]; - OsRng.fill_bytes(&mut bytes); - let esk: EphemeralSecretKey = bytes; - let _epk = EphemeralPublicKey::from(&esk); - let _ssk = SharedSecretKey::new(esk, &vpk); + results.push(time("SharedSecretKey::encapsulate (sender KEM)", ITERS, || { + let (_ssk, _epk) = SharedSecretKey::encapsulate(&vpk); })); // EncryptionScheme::encrypt / decrypt over a small Account note. let account = Account::default(); let account_id = AccountId::new([7; 32]); let commitment = Commitment::new(&account_id, &account); - let shared = { - let mut bytes = [0_u8; 32]; - OsRng.fill_bytes(&mut bytes); - let esk: EphemeralSecretKey = bytes; - SharedSecretKey::new(esk, &vpk) - }; + let (shared, _epk) = SharedSecretKey::encapsulate(&vpk); let kind = PrivateAccountKind::Regular(0_u128); let output_index: u32 = 0; diff --git a/wallet-ffi/src/keys.rs b/wallet-ffi/src/keys.rs index b676ffab..68f952cb 100644 --- a/wallet-ffi/src/keys.rs +++ b/wallet-ffi/src/keys.rs @@ -125,7 +125,7 @@ pub unsafe extern "C" fn wallet_ffi_get_private_account_keys( // NPK is a 32-byte array let npk_bytes = key_chain.nullifier_public_key.0; - // VPK is a compressed secp256k1 point (33 bytes) + // VPK is an ML-KEM-768 encapsulation key (1184 bytes) let vpk_bytes = key_chain.viewing_public_key.to_bytes(); let vpk_len = vpk_bytes.len(); let vpk_vec = vpk_bytes.to_vec(); diff --git a/wallet-ffi/src/types.rs b/wallet-ffi/src/types.rs index 1c7d261d..45b746f1 100644 --- a/wallet-ffi/src/types.rs +++ b/wallet-ffi/src/types.rs @@ -71,9 +71,9 @@ impl Default for FfiAccount { pub struct FfiPrivateAccountKeys { /// Nullifier public key (32 bytes). pub nullifier_public_key: FfiBytes32, - /// viewing public key (compressed secp256k1 point). + /// Viewing public key (ML-KEM-768 encapsulation key, 1184 bytes). pub viewing_public_key: *const u8, - /// Length of viewing public key (typically 33 bytes). + /// Length of viewing public key (always 1184 bytes for ML-KEM-768). pub viewing_public_key_len: usize, } diff --git a/wallet-ffi/wallet_ffi.h b/wallet-ffi/wallet_ffi.h index adbb7b50..8994fca0 100644 --- a/wallet-ffi/wallet_ffi.h +++ b/wallet-ffi/wallet_ffi.h @@ -135,11 +135,11 @@ typedef struct FfiPrivateAccountKeys { */ struct FfiBytes32 nullifier_public_key; /** - * viewing public key (compressed secp256k1 point). + * Viewing public key (ML-KEM-768 encapsulation key, 1184 bytes). */ const uint8_t *viewing_public_key; /** - * Length of viewing public key (typically 33 bytes). + * Length of viewing public key (always 1184 bytes for ML-KEM-768). */ uintptr_t viewing_public_key_len; } FfiPrivateAccountKeys; diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index e9c5aaad..dcf2b6eb 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -266,8 +266,8 @@ impl WalletCore { } /// Set the wallet's dedicated sealing secret key. - pub fn set_sealing_secret_key(&mut self, key: key_protocol::key_management::secret_holders::ViewingSecretKey) { - self.storage.user_data.sealing_secret_key = Some(key); + pub const fn set_sealing_secret_key(&mut self, key: key_protocol::key_management::secret_holders::ViewingSecretKey) { + self.storage.key_chain_mut().set_sealing_secret_key(key); } /// Resolve an `AccountId` to the appropriate `PrivacyPreservingAccount` variant. diff --git a/wallet/src/privacy_preserving_tx.rs b/wallet/src/privacy_preserving_tx.rs index f87a10ac..cd703146 100644 --- a/wallet/src/privacy_preserving_tx.rs +++ b/wallet/src/privacy_preserving_tx.rs @@ -414,7 +414,7 @@ mod tests { let acc = PrivacyPreservingAccount::PrivateShared { nsk: [0; 32], npk: NullifierPublicKey([1; 32]), - vpk: ViewingPublicKey::from_seed(&[2u8; 32], &[3u8; 32]), + vpk: ViewingPublicKey::from_seed(&[2_u8; 32], &[3_u8; 32]), identifier: 42, }; assert!(acc.is_private()); diff --git a/wallet/src/storage/key_chain.rs b/wallet/src/storage/key_chain.rs index e00dee8d..65143e0b 100644 --- a/wallet/src/storage/key_chain.rs +++ b/wallet/src/storage/key_chain.rs @@ -6,7 +6,7 @@ use key_protocol::key_management::{ KeyChain, group_key_holder::GroupKeyHolder, key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex, traits::KeyTreeNode as _}, - secret_holders::SeedHolder, + secret_holders::{SeedHolder, ViewingSecretKey}, }; use log::{debug, warn}; use nssa::{Account, AccountId}; @@ -79,7 +79,7 @@ pub struct UserKeyChain { /// Dedicated sealing secret key for GMS distribution. Generated once via /// `wallet group new-sealing-key`. The corresponding public key is shared with /// group members so they can seal GMS for this wallet. - sealing_secret_key: Option, + sealing_secret_key: Option, } impl UserKeyChain { @@ -509,12 +509,12 @@ impl UserKeyChain { /// Returns the sealing secret key for GMS distribution, if it exists. #[must_use] - pub const fn sealing_secret_key(&self) -> Option { - self.sealing_secret_key + pub const fn sealing_secret_key(&self) -> Option<&ViewingSecretKey> { + self.sealing_secret_key.as_ref() } /// Sets the sealing secret key for GMS distribution. - pub const fn set_sealing_secret_key(&mut self, key: nssa_core::encryption::Scalar) { + pub const fn set_sealing_secret_key(&mut self, key: ViewingSecretKey) { self.sealing_secret_key = Some(key); } @@ -584,7 +584,7 @@ impl UserKeyChain { KeyChainPersistentData { accounts, - sealing_secret_key: *sealing_secret_key, + sealing_secret_key: sealing_secret_key.clone(), group_key_holders: group_key_holders.clone(), shared_private_accounts: shared_private_accounts.clone(), } diff --git a/wallet/src/storage/persistent.rs b/wallet/src/storage/persistent.rs index 0c82ceaa..a233e644 100644 --- a/wallet/src/storage/persistent.rs +++ b/wallet/src/storage/persistent.rs @@ -5,6 +5,7 @@ use key_protocol::key_management::{ key_tree::{ chain_index::ChainIndex, keys_private::ChildKeysPrivate, keys_public::ChildKeysPublic, }, + secret_holders::ViewingSecretKey, }; use serde::{Deserialize, Serialize}; use testnet_initial_state::{PrivateAccountPrivateInitialData, PublicAccountPrivateInitialData}; @@ -26,7 +27,7 @@ pub struct PersistentStorage { pub struct KeyChainPersistentData { pub accounts: Vec, #[serde(default)] - pub sealing_secret_key: Option, + pub sealing_secret_key: Option, #[serde(default)] pub group_key_holders: BTreeMap, #[serde(default)]