mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-05-12 12:49:36 +00:00
feat: add dedicated sealing key for GMS distribution
This commit is contained in:
parent
4ace6e1570
commit
4e7963c655
Binary file not shown.
@ -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);
|
||||
|
||||
@ -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<nssa::AccountId, SharedAccountEntry>,
|
||||
/// 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<nssa_core::encryption::Scalar>,
|
||||
}
|
||||
|
||||
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,
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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<nssa_core::encryption::Scalar>,
|
||||
}
|
||||
|
||||
impl PersistentStorage {
|
||||
|
||||
@ -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,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user