v0.0.18 - Fix sign-event JSON escaping and variable-path role key caching (per-path re-derivation)

This commit is contained in:
Laan Tungir
2026-08-20 18:34:14 -04:00
parent b5b58ecb87
commit 9c762edbd2
6 changed files with 24 additions and 7 deletions
Generated
+1 -1
View File
@@ -2207,7 +2207,7 @@ dependencies = [
[[package]]
name = "signer"
version = "0.0.17"
version = "0.0.18"
dependencies = [
"base64",
"chacha20poly1305",
+1 -1
View File
@@ -1,6 +1,6 @@
[package]
name = "signer"
version = "0.0.17"
version = "0.0.18"
edition = "2021"
license = "MIT"
description = "Attended Nostr signing daemon — Rust port of n_signer"
+16 -4
View File
@@ -359,9 +359,16 @@ fn handle_nostr_verb(
// 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 {
let has_variable = role.has_variable_path();
// template. Variable-path roles are re-derived whenever the requested
// path differs from the currently-derived one (the cache is per-path,
// not per-role).
let has_variable = role.has_variable_path();
let needs_derive = if has_variable && sel.has_role_path {
role.derived_path.as_deref() != Some(sel.role_path.as_str())
} else {
!role.derived
};
if needs_derive {
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)
@@ -414,7 +421,12 @@ fn handle_nostr_verb(
None => return make_error_response(id, RpcError::INVALID_PARAMS),
};
match ctx.key_store.sign_event(role_index, event_json) {
Ok(signed) => make_success_response(id, &format!("\"{}\"", signed)),
// The signed event is itself JSON; serialize it as a proper
// JSON string value so embedded quotes are escaped.
Ok(signed) => {
let wrapped = serde_json::to_string(&signed).unwrap_or_else(|_| "\"\"".into());
make_success_response(id, &wrapped)
}
Err(_) => make_error_response(id, RpcError::INVALID_PARAMS),
}
}
+2
View File
@@ -133,10 +133,12 @@ impl KeyStore {
role.derived = false;
role.pubkey_hex.clear();
role.derived_path = None;
let dk = derive_for_role(concrete_path, role, phrase)?;
role.pubkey_hex = dk.pubkey_hex.clone();
role.derived = true;
role.derived_path = Some(concrete_path.to_string());
if self.keys.len() <= role_index {
self.keys.resize_with(role_index + 1, || None);
+1 -1
View File
@@ -31,4 +31,4 @@ pub mod error;
pub use error::SignerError;
/// Version string (matches C NSIGNER_VERSION).
pub const VERSION: &str = "v0.0.17";
pub const VERSION: &str = "v0.0.18";
+3
View File
@@ -137,6 +137,8 @@ pub struct RoleEntry {
pub pubkey_hex: String,
/// 1 if pubkey_hex has been populated.
pub derived: bool,
/// The concrete path the key was last derived for (variable-path roles).
pub derived_path: Option<String>,
/// Inclusive lower bound for %d; -1 = fixed path (no variable).
pub path_range_lo: i32,
/// Inclusive upper bound; == path_range_lo for single.
@@ -162,6 +164,7 @@ impl Default for RoleEntry {
role_path: String::new(),
pubkey_hex: String::new(),
derived: false,
derived_path: None,
path_range_lo: -1,
path_range_hi: -1,
path_default_index: -1,