feat: add GroupKeyHolder storage and PrivateGroupPda wallet variant

This commit is contained in:
Moudy
2026-04-27 02:43:51 +02:00
parent f3215606fb
commit 48f95b1b7a
3 changed files with 157 additions and 2 deletions
+49
View File
@@ -6,6 +6,7 @@ use serde::{Deserialize, Serialize};
use crate::key_management::{
KeyChain,
group_key_holder::GroupKeyHolder,
key_tree::{KeyTreePrivate, KeyTreePublic, chain_index::ChainIndex},
secret_holders::SeedHolder,
};
@@ -23,6 +24,17 @@ pub struct NSSAUserData {
pub public_key_tree: KeyTreePublic,
/// Tree of private keys.
pub private_key_tree: KeyTreePrivate,
/// Group key holders for private PDA groups, keyed by a human-readable label.
/// Defaults to empty for backward compatibility with wallets that predate group PDAs.
/// An older wallet binary that re-serializes this struct will drop the field.
#[serde(default)]
pub group_key_holders: BTreeMap<String, GroupKeyHolder>,
/// Cached plaintext state of group PDA accounts, keyed by `AccountId`.
/// Updated after each group PDA transaction by decrypting the circuit output.
/// The sequencer only stores encrypted commitments, so this local cache is the
/// only source of plaintext state for group PDAs.
#[serde(default)]
pub group_pda_accounts: BTreeMap<nssa::AccountId, nssa_core::account::Account>,
}
impl NSSAUserData {
@@ -81,6 +93,8 @@ impl NSSAUserData {
default_user_private_accounts: default_accounts_key_chains,
public_key_tree,
private_key_tree,
group_key_holders: BTreeMap::new(),
group_pda_accounts: BTreeMap::new(),
})
}
@@ -177,6 +191,20 @@ impl NSSAUserData {
.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 get_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);
}
}
impl Default for NSSAUserData {
@@ -196,6 +224,27 @@ impl Default for NSSAUserData {
mod tests {
use super::*;
#[test]
fn group_key_holder_storage_round_trip() {
let mut user_data = NSSAUserData::default();
assert!(user_data.get_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
.get_group_key_holder("test-group")
.expect("should exist");
assert_eq!(retrieved.dangerous_raw_gms(), holder.dangerous_raw_gms());
assert_eq!(retrieved.epoch(), holder.epoch());
}
#[test]
fn group_key_holders_default_empty() {
let user_data = NSSAUserData::default();
assert!(user_data.group_key_holders.is_empty());
}
#[test]
fn new_account() {
let mut user_data = NSSAUserData::default();