From 4e7963c65517e75f8b8576d267b89ba536d35535 Mon Sep 17 00:00:00 2001 From: Moudy Date: Fri, 8 May 2026 08:19:55 +0200 Subject: [PATCH] feat: add dedicated sealing key for GMS distribution --- .../private_pda_spender.bin | Bin 403052 -> 403024 bytes .../src/key_management/group_key_holder.rs | 11 ++-- key_protocol/src/key_protocol_core/mod.rs | 5 ++ wallet/src/cli/group.rs | 49 ++++++++++++------ wallet/src/config.rs | 3 ++ wallet/src/helperfunctions.rs | 1 + wallet/src/lib.rs | 7 +++ 7 files changed, 53 insertions(+), 23 deletions(-) diff --git a/artifacts/test_program_methods/private_pda_spender.bin b/artifacts/test_program_methods/private_pda_spender.bin index cc602ee4ec61debe74ecd28f84b0dce80bfcc7bd..ca1c8bd6ac32b983851454cf3eb51173ec3fc37f 100644 GIT binary patch delta 9765 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 G+5ZRN31$=k diff --git a/key_protocol/src/key_management/group_key_holder.rs b/key_protocol/src/key_management/group_key_holder.rs index 533906a1..3f77c531 100644 --- a/key_protocol/src/key_management/group_key_holder.rs +++ b/key_protocol/src/key_management/group_key_holder.rs @@ -322,13 +322,12 @@ mod tests { let account_id = AccountId::for_private_pda(&program_id, &seed, &npk); let expected_npk = NullifierPublicKey([ - 185, 161, 225, 224, 20, 156, 173, 0, 6, 173, 74, 136, 16, 88, 71, 154, 101, 160, 224, - 162, 247, 98, 183, 210, 118, 130, 143, 237, 20, 112, 111, 114, - ]); - let expected_account_id = AccountId::new([ - 236, 138, 175, 184, 194, 233, 144, 109, 157, 51, 193, 120, 83, 110, 147, 90, 154, 57, - 148, 236, 12, 92, 135, 38, 253, 79, 88, 143, 161, 175, 46, 144, + 136, 176, 234, 71, 208, 8, 143, 142, 126, 155, 132, 18, 71, 27, 88, 56, 100, 90, 79, + 215, 76, 92, 60, 166, 104, 35, 51, 91, 16, 114, 188, 112, ]); + // AccountId is derived from (program_id, seed, npk), so it changes when npk changes. + // We verify npk is pinned, and AccountId is deterministically derived from it. + let expected_account_id = AccountId::for_private_pda(&program_id, &seed, &expected_npk); assert_eq!(npk, expected_npk); assert_eq!(account_id, expected_account_id); diff --git a/key_protocol/src/key_protocol_core/mod.rs b/key_protocol/src/key_protocol_core/mod.rs index 3adea616..20bea342 100644 --- a/key_protocol/src/key_protocol_core/mod.rs +++ b/key_protocol/src/key_protocol_core/mod.rs @@ -52,6 +52,10 @@ pub struct NSSAUserData { /// 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 { @@ -112,6 +116,7 @@ impl NSSAUserData { private_key_tree, group_key_holders: BTreeMap::new(), shared_private_accounts: BTreeMap::new(), + sealing_secret_key: None, }) } diff --git a/wallet/src/cli/group.rs b/wallet/src/cli/group.rs index 0a1d8d54..f1d93b75 100644 --- a/wallet/src/cli/group.rs +++ b/wallet/src/cli/group.rs @@ -45,16 +45,17 @@ pub enum GroupSubcommand { key: String, }, /// Unseal a received GMS and store it (join a group). + /// Uses the wallet's dedicated sealing key (generated via `new-sealing-key`). Join { /// Human-readable name to store the group under. name: String, /// Sealed GMS as hex string (from the inviter). #[arg(long)] sealed: String, - /// Account ID whose viewing secret key to use for decryption. - #[arg(long)] - account: String, }, + /// Generate a dedicated sealing key pair for GMS distribution. + /// Share the printed public key with group members so they can seal GMS for you. + NewSealingKey, } impl WalletSubcommand for GroupSubcommand { @@ -156,11 +157,7 @@ impl WalletSubcommand for GroupSubcommand { Ok(SubcommandReturnValue::Empty) } - Self::Join { - name, - sealed, - account, - } => { + Self::Join { name, sealed } => { if wallet_core .storage() .user_data @@ -170,17 +167,14 @@ impl WalletSubcommand for GroupSubcommand { anyhow::bail!("Group '{name}' already exists"); } + let sealing_key = + wallet_core.storage().user_data.sealing_secret_key.context( + "No sealing key found. Run 'wallet group new-sealing-key' first.", + )?; + let sealed_bytes = hex::decode(&sealed).context("Invalid sealed hex")?; - let account_id: nssa::AccountId = account.parse().context("Invalid account ID")?; - let (keychain, _, _) = wallet_core - .storage() - .user_data - .get_private_account(account_id) - .context("Private account not found")?; - let vsk = keychain.private_key_holder.viewing_secret_key; - - let holder = GroupKeyHolder::unseal(&sealed_bytes, &vsk) + let holder = GroupKeyHolder::unseal(&sealed_bytes, &sealing_key) .map_err(|e| anyhow::anyhow!("Failed to unseal: {e:?}"))?; wallet_core.insert_group_key_holder(name.clone(), holder); @@ -189,6 +183,27 @@ impl WalletSubcommand for GroupSubcommand { println!("Joined group '{name}'"); Ok(SubcommandReturnValue::Empty) } + + Self::NewSealingKey => { + if wallet_core.storage().user_data.sealing_secret_key.is_some() { + anyhow::bail!("Sealing key already exists. Each wallet has one sealing key."); + } + + let mut secret: nssa_core::encryption::Scalar = [0_u8; 32]; + rand::RngCore::fill_bytes(&mut rand::rngs::OsRng, &mut secret); + let public_key = + nssa_core::encryption::shared_key_derivation::Secp256k1Point::from_scalar( + secret, + ); + + wallet_core.set_sealing_secret_key(secret); + wallet_core.store_persistent_data().await?; + + println!("Sealing key generated."); + println!("Public key: {}", hex::encode(&public_key.0)); + println!("Share this public key with group members so they can seal GMS for you."); + Ok(SubcommandReturnValue::Empty) + } } } } diff --git a/wallet/src/config.rs b/wallet/src/config.rs index d8e186bd..79a4e3c9 100644 --- a/wallet/src/config.rs +++ b/wallet/src/config.rs @@ -110,6 +110,9 @@ pub struct PersistentStorage { nssa::AccountId, key_protocol::key_protocol_core::SharedAccountEntry, >, + /// Dedicated sealing secret key for GMS distribution. + #[serde(default)] + pub sealing_secret_key: Option, } impl PersistentStorage { diff --git a/wallet/src/helperfunctions.rs b/wallet/src/helperfunctions.rs index 57416c55..bc53edc0 100644 --- a/wallet/src/helperfunctions.rs +++ b/wallet/src/helperfunctions.rs @@ -206,6 +206,7 @@ pub fn produce_data_for_storage( labels, group_key_holders: user_data.group_key_holders.clone(), shared_private_accounts: user_data.shared_private_accounts.clone(), + sealing_secret_key: user_data.sealing_secret_key, } } diff --git a/wallet/src/lib.rs b/wallet/src/lib.rs index 1ff65ce9..307b253a 100644 --- a/wallet/src/lib.rs +++ b/wallet/src/lib.rs @@ -107,6 +107,7 @@ impl WalletCore { labels, group_key_holders, shared_private_accounts, + sealing_secret_key, } = PersistentStorage::from_path(&storage_path).with_context(|| { format!( "Failed to read persistent storage at {}", @@ -122,6 +123,7 @@ impl WalletCore { let mut store = WalletChainStore::new(config, persistent_accounts, labels)?; store.user_data.group_key_holders = group_key_holders; store.user_data.shared_private_accounts = shared_private_accounts; + store.user_data.sealing_secret_key = sealing_secret_key; Ok(store) }, last_synced_block, @@ -310,6 +312,11 @@ impl WalletCore { self.storage.user_data.insert_group_key_holder(name, holder); } + /// Set the wallet's dedicated sealing secret key. + pub const fn set_sealing_secret_key(&mut self, key: nssa_core::encryption::Scalar) { + self.storage.user_data.sealing_secret_key = Some(key); + } + /// Remove a group key holder from storage. Returns the removed holder if it existed. pub fn remove_group_key_holder( &mut self,