mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-05-17 05:29:32 +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 account_id = AccountId::for_private_pda(&program_id, &seed, &npk);
|
||||||
|
|
||||||
let expected_npk = NullifierPublicKey([
|
let expected_npk = NullifierPublicKey([
|
||||||
185, 161, 225, 224, 20, 156, 173, 0, 6, 173, 74, 136, 16, 88, 71, 154, 101, 160, 224,
|
136, 176, 234, 71, 208, 8, 143, 142, 126, 155, 132, 18, 71, 27, 88, 56, 100, 90, 79,
|
||||||
162, 247, 98, 183, 210, 118, 130, 143, 237, 20, 112, 111, 114,
|
215, 76, 92, 60, 166, 104, 35, 51, 91, 16, 114, 188, 112,
|
||||||
]);
|
|
||||||
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,
|
|
||||||
]);
|
]);
|
||||||
|
// 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!(npk, expected_npk);
|
||||||
assert_eq!(account_id, expected_account_id);
|
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
|
/// keyed by `AccountId`. Each entry stores the group label and identifier needed
|
||||||
/// to re-derive keys during sync.
|
/// to re-derive keys during sync.
|
||||||
pub shared_private_accounts: BTreeMap<nssa::AccountId, SharedAccountEntry>,
|
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 {
|
impl NSSAUserData {
|
||||||
@ -112,6 +116,7 @@ impl NSSAUserData {
|
|||||||
private_key_tree,
|
private_key_tree,
|
||||||
group_key_holders: BTreeMap::new(),
|
group_key_holders: BTreeMap::new(),
|
||||||
shared_private_accounts: BTreeMap::new(),
|
shared_private_accounts: BTreeMap::new(),
|
||||||
|
sealing_secret_key: None,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -45,16 +45,17 @@ pub enum GroupSubcommand {
|
|||||||
key: String,
|
key: String,
|
||||||
},
|
},
|
||||||
/// Unseal a received GMS and store it (join a group).
|
/// Unseal a received GMS and store it (join a group).
|
||||||
|
/// Uses the wallet's dedicated sealing key (generated via `new-sealing-key`).
|
||||||
Join {
|
Join {
|
||||||
/// Human-readable name to store the group under.
|
/// Human-readable name to store the group under.
|
||||||
name: String,
|
name: String,
|
||||||
/// Sealed GMS as hex string (from the inviter).
|
/// Sealed GMS as hex string (from the inviter).
|
||||||
#[arg(long)]
|
#[arg(long)]
|
||||||
sealed: String,
|
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 {
|
impl WalletSubcommand for GroupSubcommand {
|
||||||
@ -156,11 +157,7 @@ impl WalletSubcommand for GroupSubcommand {
|
|||||||
Ok(SubcommandReturnValue::Empty)
|
Ok(SubcommandReturnValue::Empty)
|
||||||
}
|
}
|
||||||
|
|
||||||
Self::Join {
|
Self::Join { name, sealed } => {
|
||||||
name,
|
|
||||||
sealed,
|
|
||||||
account,
|
|
||||||
} => {
|
|
||||||
if wallet_core
|
if wallet_core
|
||||||
.storage()
|
.storage()
|
||||||
.user_data
|
.user_data
|
||||||
@ -170,17 +167,14 @@ impl WalletSubcommand for GroupSubcommand {
|
|||||||
anyhow::bail!("Group '{name}' already exists");
|
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 sealed_bytes = hex::decode(&sealed).context("Invalid sealed hex")?;
|
||||||
|
|
||||||
let account_id: nssa::AccountId = account.parse().context("Invalid account ID")?;
|
let holder = GroupKeyHolder::unseal(&sealed_bytes, &sealing_key)
|
||||||
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)
|
|
||||||
.map_err(|e| anyhow::anyhow!("Failed to unseal: {e:?}"))?;
|
.map_err(|e| anyhow::anyhow!("Failed to unseal: {e:?}"))?;
|
||||||
|
|
||||||
wallet_core.insert_group_key_holder(name.clone(), holder);
|
wallet_core.insert_group_key_holder(name.clone(), holder);
|
||||||
@ -189,6 +183,27 @@ impl WalletSubcommand for GroupSubcommand {
|
|||||||
println!("Joined group '{name}'");
|
println!("Joined group '{name}'");
|
||||||
Ok(SubcommandReturnValue::Empty)
|
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,
|
nssa::AccountId,
|
||||||
key_protocol::key_protocol_core::SharedAccountEntry,
|
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 {
|
impl PersistentStorage {
|
||||||
|
|||||||
@ -206,6 +206,7 @@ pub fn produce_data_for_storage(
|
|||||||
labels,
|
labels,
|
||||||
group_key_holders: user_data.group_key_holders.clone(),
|
group_key_holders: user_data.group_key_holders.clone(),
|
||||||
shared_private_accounts: user_data.shared_private_accounts.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,
|
labels,
|
||||||
group_key_holders,
|
group_key_holders,
|
||||||
shared_private_accounts,
|
shared_private_accounts,
|
||||||
|
sealing_secret_key,
|
||||||
} = PersistentStorage::from_path(&storage_path).with_context(|| {
|
} = PersistentStorage::from_path(&storage_path).with_context(|| {
|
||||||
format!(
|
format!(
|
||||||
"Failed to read persistent storage at {}",
|
"Failed to read persistent storage at {}",
|
||||||
@ -122,6 +123,7 @@ impl WalletCore {
|
|||||||
let mut store = WalletChainStore::new(config, persistent_accounts, labels)?;
|
let mut store = WalletChainStore::new(config, persistent_accounts, labels)?;
|
||||||
store.user_data.group_key_holders = group_key_holders;
|
store.user_data.group_key_holders = group_key_holders;
|
||||||
store.user_data.shared_private_accounts = shared_private_accounts;
|
store.user_data.shared_private_accounts = shared_private_accounts;
|
||||||
|
store.user_data.sealing_secret_key = sealing_secret_key;
|
||||||
Ok(store)
|
Ok(store)
|
||||||
},
|
},
|
||||||
last_synced_block,
|
last_synced_block,
|
||||||
@ -310,6 +312,11 @@ impl WalletCore {
|
|||||||
self.storage.user_data.insert_group_key_holder(name, holder);
|
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.
|
/// Remove a group key holder from storage. Returns the removed holder if it existed.
|
||||||
pub fn remove_group_key_holder(
|
pub fn remove_group_key_holder(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user