diff --git a/Cargo.lock b/Cargo.lock index bbafdf5..6f40e27 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -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", diff --git a/Cargo.toml b/Cargo.toml index 72f7e0e..90b3cd9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -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" diff --git a/src/dispatcher.rs b/src/dispatcher.rs index fcb3fff..4540a1a 100644 --- a/src/dispatcher.rs +++ b/src/dispatcher.rs @@ -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 { diff --git a/src/key_store.rs b/src/key_store.rs index d2706f9..3d6a6c8 100644 --- a/src/key_store.rs +++ b/src/key_store.rs @@ -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()) diff --git a/src/lib.rs b/src/lib.rs index 3f644fc..632fa9c 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -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";