v0.0.10 - Fix: derive variable-path roles using the concrete role_path from the request, not the template

This commit is contained in:
Laan Tungir
2026-08-19 20:19:00 -04:00
parent 5d3cc4e96d
commit b3cc4704c3
5 changed files with 51 additions and 10 deletions
Generated
+3 -3
View File
@@ -1446,7 +1446,7 @@ dependencies = [
[[package]]
name = "nostr-core"
version = "0.0.2"
version = "0.0.3"
dependencies = [
"aes",
"base64",
@@ -1468,7 +1468,7 @@ dependencies = [
[[package]]
name = "nostr-nips"
version = "0.0.2"
version = "0.0.3"
dependencies = [
"aes",
"block-modes",
@@ -1490,7 +1490,7 @@ dependencies = [
[[package]]
name = "nsigner"
version = "0.0.8"
version = "0.0.9"
dependencies = [
"base64",
"chacha20poly1305",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "nsigner"
version = "0.0.9"
version = "0.0.10"
edition = "2021"
license = "MIT"
description = "Attended Nostr signing daemon — Rust port of n_signer"
+12 -5
View File
@@ -357,12 +357,19 @@ fn handle_nostr_verb(
return make_error_response(id, RpcError::MNEMONIC_NOT_LOADED);
}
// Ensure key is derived
// Ensure key is derived. For variable-path roles, use the concrete
// path supplied by the client; for fixed-path roles, use the stored
// template.
if !role.derived {
if let Err(_) = ctx
.key_store
.derive_one(ctx.role_table, ctx.mnemonic, role_index)
{
let has_variable = role.has_variable_path();
let result = if has_variable && sel.has_role_path {
ctx.key_store
.derive_one_with_path(ctx.role_table, ctx.mnemonic, role_index, &sel.role_path)
} else {
ctx.key_store
.derive_one(ctx.role_table, ctx.mnemonic, role_index)
};
if let Err(_) = result {
return make_error_response(
id,
RpcError {
+34
View File
@@ -111,6 +111,40 @@ impl KeyStore {
Ok(())
}
/// Derive key for a role using a concrete path (substituting the
/// variable index into the template). Used for variable-path roles
/// where the client supplies the full concrete `role_path`.
pub fn derive_one_with_path(
&mut self,
table: &mut RoleTable,
mnemonic: &MnemonicState,
role_index: usize,
concrete_path: &str,
) -> Result<(), NsignerError> {
if !mnemonic.is_loaded() {
return Err(NsignerError::MnemonicNotLoaded);
}
let phrase = mnemonic.phrase().ok_or(NsignerError::MnemonicNotLoaded)?;
let role = table
.entries
.get_mut(role_index)
.ok_or(NsignerError::InvalidInput)?;
role.derived = false;
role.pubkey_hex.clear();
let dk = derive_for_role(concrete_path, role, phrase)?;
role.pubkey_hex = dk.pubkey_hex.clone();
role.derived = true;
if self.keys.len() <= role_index {
self.keys.resize_with(role_index + 1, || None);
}
self.keys[role_index] = Some(dk);
Ok(())
}
/// Get the derived private key for a role (by table index).
pub fn get_private_key(&self, role_index: usize) -> Option<&[u8]> {
self.keys.get(role_index)?.as_ref().map(|dk| dk.private_key.as_slice())
+1 -1
View File
@@ -31,4 +31,4 @@ pub mod error;
pub use error::NsignerError;
/// Version string (matches C NSIGNER_VERSION).
pub const VERSION: &str = "v0.0.9";
pub const VERSION: &str = "v0.0.10";