v0.0.13 - Renamed nsigner to signer throughout project; release build with signer + signer-client binaries

This commit is contained in:
Laan Tungir
2026-08-20 11:28:57 -04:00
parent a472e9785b
commit 567985434d
34 changed files with 401 additions and 383 deletions
Generated
+31 -31
View File
@@ -1488,37 +1488,6 @@ dependencies = [
"tracing",
]
[[package]]
name = "nsigner"
version = "0.0.12"
dependencies = [
"base64",
"chacha20poly1305",
"clap",
"crossterm 0.27.0",
"ed25519-dalek",
"hex",
"hmac 0.12.1",
"libc",
"ml-dsa",
"ml-kem",
"nostr-core",
"nostr-nips",
"rand",
"rand_core 0.6.4",
"ratatui",
"secp256k1",
"serde",
"serde_json",
"sha2 0.10.9",
"sha3 0.10.9",
"slh-dsa",
"tempfile",
"thiserror",
"x25519-dalek",
"zeroize",
]
[[package]]
name = "num-conv"
version = "0.2.2"
@@ -2236,6 +2205,37 @@ dependencies = [
"rand_core 0.10.1",
]
[[package]]
name = "signer"
version = "0.0.13"
dependencies = [
"base64",
"chacha20poly1305",
"clap",
"crossterm 0.27.0",
"ed25519-dalek",
"hex",
"hmac 0.12.1",
"libc",
"ml-dsa",
"ml-kem",
"nostr-core",
"nostr-nips",
"rand",
"rand_core 0.6.4",
"ratatui",
"secp256k1",
"serde",
"serde_json",
"sha2 0.10.9",
"sha3 0.10.9",
"slh-dsa",
"tempfile",
"thiserror",
"x25519-dalek",
"zeroize",
]
[[package]]
name = "slab"
version = "0.4.12"
+4 -4
View File
@@ -1,12 +1,12 @@
[package]
name = "nsigner"
version = "0.0.12"
name = "signer"
version = "0.0.13"
edition = "2021"
license = "MIT"
description = "Attended Nostr signing daemon — Rust port of n_signer"
[[bin]]
name = "nsigner"
name = "signer"
path = "src/main.rs"
[[bin]]
@@ -14,7 +14,7 @@ name = "signer-client"
path = "src/client/main.rs"
[lib]
name = "nsigner"
name = "signer"
path = "src/lib.rs"
[dependencies]
+24 -6
View File
@@ -1,7 +1,7 @@
#!/bin/bash
set -e
# nsigner (Rust) — Increment and Push Script
# signer (Rust) — Increment and Push Script
#
# Increments the version (patch/minor/major), updates Cargo.toml and
# src/lib.rs, commits, tags, and pushes. Optionally creates a release
@@ -34,7 +34,7 @@ RELEASE_MODE=false
VERSION_INCREMENT_TYPE="patch"
show_usage() {
echo "nsigner (Rust) Increment and Push Script"
echo "signer (Rust) Increment and Push Script"
echo ""
echo "USAGE:"
echo " $0 [OPTIONS] \"commit message\""
@@ -169,18 +169,23 @@ verify_binary_version() {
}
build_release_binary() {
print_status "Building release binary (cargo build --release)..."
print_status "Building release binaries (cargo build --release)..."
cargo build --release 2>&1 | tail -5 || return 1
local bin_path="target/release/nsigner"
local bin_path="target/release/signer"
verify_binary_version "$bin_path" "$NEW_VERSION" || return 1
local client_path="target/release/signer-client"
if [[ -f "$client_path" ]]; then
print_success "Release binary built: $client_path"
fi
print_success "Release binary built: $bin_path"
return 0
}
create_source_tarball() {
local tarball_name="nsigner-${NEW_VERSION#v}.tar.gz"
local tarball_name="signer-${NEW_VERSION#v}.tar.gz"
if tar -czf "$tarball_name" \
--exclude='target/*' \
@@ -279,7 +284,8 @@ main() {
git_commit_and_push
local binary_path="target/release/nsigner"
local binary_path="target/release/signer"
local client_path="target/release/signer-client"
local tarball_path=""
tarball_path=$(create_source_tarball || true)
@@ -288,6 +294,18 @@ main() {
if [[ -n "$release_id" ]]; then
upload_release_assets "$release_id" "$binary_path" "$tarball_path"
# Also upload the signer-client binary if it exists
if [[ -f "$client_path" ]]; then
local token
token=$(cat "$HOME/.gitea_token" | tr -d '\n\r')
local api_url="https://git.laantungir.net/api/v1/repos/laantungir/signer"
local assets_url="$api_url/releases/$release_id/assets"
print_status "Uploading signer-client..."
curl -s -X POST "$assets_url" \
-H "Authorization: token $token" \
-F "attachment=@$client_path;filename=signer-client" \
-F "name=signer-client" > /dev/null
fi
fi
print_success "Release flow completed: $NEW_VERSION"
+2 -2
View File
@@ -1,7 +1,7 @@
# Menu Gap Analysis: C `main.c` vs Rust `signer`
**Source of truth:** the C code in [`src/main.c`](../n_signer/src/main.c:1), NOT
[`documents/nsigner_menus.md`](../n_signer/documents/nsigner_menus.md:1) (which is
[`documents/signer_menus.md`](../n_signer/documents/signer_menus.md:1) (which is
stale — e.g. it claims the wizard prompts `Require interactive approval? [Y/n]`, but
the actual C code hardcodes `requires_approval = 0` and never prompts).
@@ -31,7 +31,7 @@ Legend: ✅ matches, ⚠️ partial, ❌ missing/divergent.
- Success: `Seed phrase is valid and accepted.`
**Rust** ([`load_mnemonic_tui`](../signer/src/main.rs:549)):
- Frame: `nsigner v<ver> > Unlock`, title `"Enter mnemonic phrase"`
- Frame: `signer v<ver> > Unlock`, title `"Enter mnemonic phrase"`
- Prompt: `Enter your BIP-39 mnemonic phrase, or 'g' to generate a new one.`
- `g`/`G` → generate, numbered, warning ✅
- Otherwise → load as mnemonic (paste works implicitly) ⚠️
+3 -3
View File
@@ -2,7 +2,7 @@
## Problem
The Rust `nsigner` library modules are complete and tested (92 tests pass), but the server loop in [`server.rs`](src/server.rs:117) accepts connections, reads requests, dispatches them, and sends responses **without any policy enforcement**. The `policy: &mut PolicyTable` parameter is accepted but never used. This means the daemon would sign anything for anyone without prompting — it is not an "attended signer."
The Rust `signer` library modules are complete and tested (92 tests pass), but the server loop in [`server.rs`](src/server.rs:117) accepts connections, reads requests, dispatches them, and sends responses **without any policy enforcement**. The `policy: &mut PolicyTable` parameter is accepted but never used. This means the daemon would sign anything for anyone without prompting — it is not an "attended signer."
## What the C version does (server.c)
@@ -148,10 +148,10 @@ Add methods to insert session grants:
```rust
impl PolicyTable {
/// Insert a session grant for caller+role+verb.
pub fn insert_session_grant(&mut self, caller: &str, verb: &str, role: &str) -> Result<(), NsignerError>;
pub fn insert_session_grant(&mut self, caller: &str, verb: &str, role: &str) -> Result<(), SignerError>;
/// Insert a session grant for caller+role (all verbs).
pub fn insert_session_grant_all(&mut self, caller: &str, role: &str) -> Result<(), NsignerError>;
pub fn insert_session_grant_all(&mut self, caller: &str, role: &str) -> Result<(), SignerError>;
}
```
+4 -4
View File
@@ -101,7 +101,7 @@ graph TB
### 4. Error Handling
- **C**: Integer error codes + string messages
- **Rust**: `thiserror`-based `NsignerError` enum. The JSON-RPC error codes are preserved exactly for wire compatibility.
- **Rust**: `thiserror`-based `SignerError` enum. The JSON-RPC error codes are preserved exactly for wire compatibility.
### 5. Transport
- **C**: Raw syscalls (`socket`, `bind`, `accept`, `SO_PEERCRED`)
@@ -323,7 +323,7 @@ signer/
│ ├── otp_pad.rs # One-time pad encryption
│ ├── socket_name.rs # Abstract socket naming
│ ├── tui.rs # Terminal UI
│ └── error.rs # NsignerError enum
│ └── error.rs # SignerError enum
├── tests/
│ ├── integration_test.rs
│ ├── algorithm_test.rs
@@ -337,7 +337,7 @@ signer/
```toml
[package]
name = "nsigner"
name = "signer"
version = "0.1.0"
edition = "2021"
@@ -372,7 +372,7 @@ tempfile = "3"
3. **Derivation paths**: BIP-32/SLIP-0010 paths must produce identical keys from the same mnemonic
4. **Socket protocol**: Length-prefixed framing (4-byte BE) must be compatible
5. **HTTP**: Same minimal HTTP/1.1 POST-only parser behavior
6. **Abstract socket names**: `@nsigner_<word1>_<word2>` format preserved
6. **Abstract socket names**: `@signer_<word1>_<word2>` format preserved
## Open Questions
+3 -3
View File
@@ -33,8 +33,8 @@ full width at the bottom.
├──────────────────────────────────────┬───────────────────────────────┤
│ Information │ Activity (latest first) ▲ │
│ session=unlocked (12 words) │ 2026-08-18 08:00:15 req… │
│ signer=nsigner01 derived=2 │ 2026-08-18 08:00:01 start │
│ socket=@nsigner01 transport=unix │ │
│ signer=signer01 derived=2 │ 2026-08-18 08:00:01 start │
│ socket=@signer01 transport=unix │ │
│ OTP pad: chksum=abc… offset=128 │ │
├──────────────────────────────────────┤ │
│ Roles │ │
@@ -273,7 +273,7 @@ Each setup screen has its own `draw` function and event handler. The
`tui_continuous.rs` entirely.
9. **Test** — `cargo test` (unit tests don't touch the TUI). Manual test:
start signer, verify setup screens work with InputField, verify 4-section
main screen renders, press `d`/`l`/`r`/`q`, connect with `nsigner_client`.
main screen renders, press `d`/`l`/`r`/`q`, connect with `signer_client`.
## What stays the same
+21 -21
View File
@@ -1,14 +1,14 @@
# Plan: `signer-client` — Rust CLI for the nsigner daemon
# Plan: `signer-client` — Rust CLI for the signer daemon
## Goal
A standalone Rust command-line client `signer-client` that connects to a running
`nsigner` process over its framed transports (Unix abstract socket, TCP, serial,
`signer` process over its framed transports (Unix abstract socket, TCP, serial,
qrexec) and exposes the full JSON-RPC verb surface over stdin/stdout so that
signed events can be piped directly into `nak publish`.
This is a **Rust port** of the C [`n_signer_client.c`](../n_signer/client/n_signer_client.c:1)
(~855 lines). It reuses the existing `nsigner` library crate for transport
(~855 lines). It reuses the existing `signer` library crate for transport
framing, socket discovery, and verb/error constants — the new code is the
typed-verb client layer + CLI parsing + non-Unix transports.
@@ -24,8 +24,8 @@ typed-verb client layer + CLI parsing + non-Unix transports.
- [`src/client/main.rs`](src/client/main.rs:1) — entry point, CLI parse, dispatch
- [`src/client/cli.rs`](src/client/cli.rs:1) — clap `Cli` / `Verb` structs + usage text
- [`src/client/transport.rs`](src/client/transport.rs:1) — `ClientTransport` enum (Unix/Tcp/Serial/Qrexec) + open/connect helpers
- [`src/client/rpc.rs`](src/client/rpc.rs:1) — low-level `NsignerClient` (send framed JSON-RPC, recv, parse result/error)
- [`src/client/signer.rs`](src/client/signer.rs:1) — high-level `NsignerSigner` typed-verb wrappers (mirrors C `nostr_signer_t`)
- [`src/client/rpc.rs`](src/client/rpc.rs:1) — low-level `SignerClient` (send framed JSON-RPC, recv, parse result/error)
- [`src/client/signer.rs`](src/client/signer.rs:1) — high-level `SignerSigner` typed-verb wrappers (mirrors C `nostr_signer_t`)
- [`src/client/auth.rs`](src/client/auth.rs:1) — client-side auth envelope builder for TCP/qrexec
- New doc: [`src/client/README.md`](src/client/README.md:1) — usage, verbs, pipe-to-nak recipes (port of `n_signer_client_README.md`).
- The existing `client` subcommand in [`src/main.rs`](src/main.rs:276) stays as a thin raw-passthrough convenience; it is **not** removed.
@@ -48,23 +48,23 @@ typed-verb client layer + CLI parsing + non-Unix transports.
options, selector options, algorithm options, mine-event options, and a
`Verb` enum.
2. **`ClientTransport`** — enum wrapping the four connection types behind a
unified `send`/`recv` interface (the C `nsigner_transport_t` vtable).
unified `send`/`recv` interface (the C `signer_transport_t` vtable).
- Unix: `connect_abstract_unix` (already in crate).
- TCP: `std::net::TcpStream` + framed I/O (server already speaks framed
JSON over TCP per [`server.rs`](src/server.rs:108)).
- Serial: `std::fs::OpenOptions` on `/dev/ttyACM*` + framed I/O over the
file handle (matches C `nsigner_transport_open_serial`).
file handle (matches C `signer_transport_open_serial`).
- Qrexec: spawn `qrexec-client-vm <qube> <service>` via `std::process`,
pipe framed JSON over its stdin/stdout (matches C
`nsigner_transport_open_qrexec`).
3. **`NsignerClient`** — low-level RPC caller: builds `{"id","method","params"}`
`signer_transport_open_qrexec`).
3. **`SignerClient`** — low-level RPC caller: builds `{"id","method","params"}`
JSON, sends framed, receives framed, splits `result` vs `error`, holds
`last_error`. Mirrors C `nsigner_client_t` / `nsigner_client_call`.
4. **`NsignerSigner`** — high-level typed-verb layer. Holds a `NsignerClient`
`last_error`. Mirrors C `signer_client_t` / `signer_client_call`.
4. **`SignerSigner`** — high-level typed-verb layer. Holds a `SignerClient`
plus the resolved selector (`role` + `role_path`) and auth state. One
method per verb, each building the correct `params` array + options object
and parsing the typed result. Mirrors C `nostr_signer_t` /
`nostr_signer_nsigner_from_client`.
`nostr_signer_signer_from_client`.
5. **Client-side auth envelope builder** — for TCP/qrexec: construct a
NIP-42 kind-22242 auth event from the `--auth-privkey`, sign it, and
prepend it to the request frame. The server-side verifier in
@@ -125,7 +125,7 @@ and the verb table in [`enforcement.rs`](src/enforcement.rs:22).
| Verb | stdout |
|---|---|
| `list` | Running nsigner abstract sockets (one per line) |
| `list` | Running signer abstract sockets (one per line) |
### Metadata
@@ -229,9 +229,9 @@ flowchart TD
Tr -->|tcp| Tcp[TcpStream + auth.rs]
Tr -->|serial| Serial[OpenOptions /dev/ttyACM]
Tr -->|qrexec| Qrexec[spawn qrexec-client-vm]
Tr --> Rpc[rpc.rs<br/>NsignerClient]
Rpc --> Signer[signer.rs<br/>NsignerSigner typed verbs]
Signer -->|build params| Disp[nsigner daemon<br/>dispatcher.rs]
Tr --> Rpc[rpc.rs<br/>SignerClient]
Rpc --> Signer[signer.rs<br/>SignerSigner typed verbs]
Signer -->|build params| Disp[signer daemon<br/>dispatcher.rs]
Disp -->|result/error| Signer
Signer -->|one line| Stdout[stdout]
```
@@ -246,10 +246,10 @@ flowchart TD
`open_tcp`, `open_serial`, `open_qrexec`; unified `send`/`recv` via the
existing `send_framed`/`recv_framed`. Include `parse_host_port` and
`parse_qube_service` helpers.
4. Implement `rpc.rs`: `NsignerClient` struct holding the transport,
4. Implement `rpc.rs`: `SignerClient` struct holding the transport,
`call(method, params) -> Result<Value, String>`, `last_error`, and
framed send/recv using `serde_json`.
5. Implement `signer.rs`: `NsignerSigner` with selector state + one method
5. Implement `signer.rs`: `SignerSigner` with selector state + one method
per verb (get_info, get_public_key, sign_event, mine_event, nip04/44
encrypt/decrypt, sign, verify, derive, encapsulate, decapsulate,
derive_shared_secret, otp encrypt/decrypt). Each builds the `params`
@@ -269,8 +269,8 @@ flowchart TD
- **Unit tests** in `rpc.rs` / `signer.rs`: build-params correctness using
`serde_json::json!` assertions (no socket needed).
- **Integration test** `tests/client_smoke.rs`: spawn `nsigner
--mnemonic-stdin --listen unix --socket-name nsigner_test` with a fixed
- **Integration test** `tests/client_smoke.rs`: spawn `signer
--mnemonic-stdin --listen unix --socket-name signer_test` with a fixed
test mnemonic in a thread, then run the client verbs against it and
assert stdout shape + exit codes. Tear down the server.
- Manual pipe-to-`nak` check for `sign-event`.
@@ -278,7 +278,7 @@ flowchart TD
## Out of scope
- No TUI, no approval UI — the human attendant lives in the running
`nsigner` process; the client is a thin wire caller.
`signer` process; the client is a thin wire caller.
- No key storage, no mnemonic handling.
- No HTTP-listener client (HTTP is a server-side listener mode; the client
uses the framed transports).
+5 -5
View File
@@ -34,7 +34,7 @@ underlined (e.g. "Quit" with Q underlined, not "Q quit").
The user asked whether "client name" should be renamed since the
signer is more a server than a client. **Decision: rename to "signer
name".** The field shows the socket name (e.g. `nsigner01`), which is
name".** The field shows the socket name (e.g. `signer01`), which is
the name clients use to connect. Calling it "signer name" is clearer
than "client name" and consistent with the program name.
@@ -124,9 +124,9 @@ the C-format log entries (newest first).
├──────────────────────────────────────────┬──────────────────────────────────────────┤
│ Information │ Activity │
│ │ │
│ signer name: nsigner01 │ 2026-08-18 15:05:42 unix:1000 │
│ signer name: signer01 │ 2026-08-18 15:05:42 unix:1000 │
│ Unix address: │ secp256k1 m/44'/1237'/0'/0/0 │
│ nsigner01 │ 2026-08-18 15:05:30 unix:1000 │
│ signer01 │ 2026-08-18 15:05:30 unix:1000 │
│ Qube address: │ - - │
│ (inactive) │ 2026-08-18 15:04:55 unix:1000 │
│ FIPS address: │ secp256k1 m/44'/1237'/0'/0/0 │
@@ -142,7 +142,7 @@ the C-format log entries (newest first).
│ [ ] Qube b̲ridge │ secp256k1 m/44'/1237'/0'/0/0 │
│ [ ] F̲IPS │ 2026-08-18 15:00:22 unix:1000 │
│ [ ] H̲TTP │ secp256k1 m/44'/1237'/0'/0/0 │
│ │ 2026-08-18 15:00:10 nsigner started │
│ │ 2026-08-18 15:00:10 signer started │
├──────────────────────────────────────────┤ │
│ Roles │ │
│ │ │
@@ -575,7 +575,7 @@ and deleted directly in the Roles section.
verify AddRole popup preset menu flow, verify Transport toggle
(4 lines, tab navigation), verify Help screen is scrollable and
shows app description + key commands, verify activity log shows
detailed request info, connect with `nsigner_client`.
detailed request info, connect with `signer_client`.
## What stays the same
+12 -12
View File
@@ -6,7 +6,7 @@
use crate::mnemonic::MnemonicState;
use crate::pq_crypto::CryptoAlg;
use crate::secure_mem::SecureBuf;
use crate::NsignerError;
use crate::SignerError;
pub const ALG_KEY_CACHE_MAX: usize = 32;
@@ -47,9 +47,9 @@ impl AlgorithmKeyCache {
mnemonic: &MnemonicState,
alg: CryptoAlg,
index: i32,
) -> Result<(), NsignerError> {
) -> Result<(), SignerError> {
if !mnemonic.is_loaded() {
return Err(NsignerError::MnemonicNotLoaded);
return Err(SignerError::MnemonicNotLoaded);
}
// Already cached?
@@ -62,7 +62,7 @@ impl AlgorithmKeyCache {
self.entries.remove(0);
}
let phrase = mnemonic.phrase().ok_or(NsignerError::MnemonicNotLoaded)?;
let phrase = mnemonic.phrase().ok_or(SignerError::MnemonicNotLoaded)?;
// Build the standard derivation path for this algorithm
let path = match alg {
@@ -72,7 +72,7 @@ impl AlgorithmKeyCache {
CryptoAlg::MlDsa65 => format!("m/44'/102003'/{}'/0'/0'", index),
CryptoAlg::SlhDsa128s => format!("m/44'/102004'/{}'/0'/0'", index),
CryptoAlg::MlKem768 => format!("m/44'/102005'/{}'/0'/0'", index),
CryptoAlg::Unknown => return Err(NsignerError::InvalidInput),
CryptoAlg::Unknown => return Err(SignerError::InvalidInput),
};
let entry = derive_alg_key(phrase, &path, alg, index)?;
@@ -98,10 +98,10 @@ fn derive_alg_key(
path: &str,
alg: CryptoAlg,
index: i32,
) -> Result<AlgKeyEntry, NsignerError> {
) -> Result<AlgKeyEntry, SignerError> {
let sizes = alg
.sizes()
.ok_or(NsignerError::KeyDerivationFailed)?;
.ok_or(SignerError::KeyDerivationFailed)?;
let seed = crate::pq_crypto::derive_seed_from_mnemonic(mnemonic_phrase, path)?;
@@ -111,19 +111,19 @@ fn derive_alg_key(
let bip39_seed = nips::nip006::mnemonic_to_seed(mnemonic_phrase, "");
let (master_key, master_chain_code) = nips::nip006::bip32_master_key(&bip39_seed);
let path_indices = nips::nip006::parse_bip44_path(path)
.map_err(|_| NsignerError::KeyDerivationFailed)?;
.map_err(|_| SignerError::KeyDerivationFailed)?;
let (derived_key, _) = nips::nip006::bip32_derive_path(
&master_key,
&master_chain_code,
&path_indices,
)
.map_err(|_| NsignerError::KeyDerivationFailed)?;
.map_err(|_| SignerError::KeyDerivationFailed)?;
let mut priv_arr = [0u8; 32];
priv_arr.copy_from_slice(&derived_key);
let sk = nostr_core::types::SecretKey::from_bytes(priv_arr);
let pk = nostr_core::crypto::keys::public_key_from_secret_key(&sk)
.map_err(|_| NsignerError::CryptoFailed)?;
.map_err(|_| SignerError::CryptoFailed)?;
let pubkey_hex = hex::encode(pk.as_bytes());
let key_id = if pubkey_hex.len() >= 16 {
@@ -200,8 +200,8 @@ fn derive_alg_key(
}
CryptoAlg::MlDsa65 | CryptoAlg::SlhDsa128s | CryptoAlg::MlKem768 => {
// PQ algorithms — TODO: Phase 13
Err(NsignerError::NotYetImplemented)
Err(SignerError::NotYetImplemented)
}
CryptoAlg::Unknown => Err(NsignerError::InvalidInput),
CryptoAlg::Unknown => Err(SignerError::InvalidInput),
}
}
+4 -4
View File
@@ -132,17 +132,17 @@ pub fn verify_request(
return Err((AUTH_ERR_KIND_INVALID, "auth_kind_invalid"));
}
// Extract tags: nsigner_rpc, nsigner_method, nsigner_body_hash
// Extract tags: signer_rpc, signer_method, signer_body_hash
let mut tag_rpc: Option<String> = None;
let mut tag_method: Option<String> = None;
let mut tag_body_hash: Option<String> = None;
for tag in &auth_event.tags {
if tag.kind() == "nsigner_rpc" {
if tag.kind() == "signer_rpc" {
tag_rpc = tag.get(1).map(|s| s.to_string());
} else if tag.kind() == "nsigner_method" {
} else if tag.kind() == "signer_method" {
tag_method = tag.get(1).map(|s| s.to_string());
} else if tag.kind() == "nsigner_body_hash" {
} else if tag.kind() == "signer_body_hash" {
tag_body_hash = tag.get(1).map(|s| s.to_string());
}
}
+8 -8
View File
@@ -1,6 +1,6 @@
# `signer-client` — Rust CLI for nsigner
# `signer-client` — Rust CLI for signer
A standalone command-line client that connects to a running [`nsigner`](../..)
A standalone command-line client that connects to a running [`signer`](../..)
process and calls its JSON-RPC verbs over stdin/stdout. Designed for
pipe-to-`nak` workflows. Rust port of the C
[`n_signer_client.c`](../../n_signer/client/n_signer_client.c).
@@ -62,7 +62,7 @@ signer-client [global options] <verb> [verb args...]
| Verb | stdout |
|------|--------|
| `list` | Lists running nsigner abstract sockets (one per line) |
| `list` | Lists running signer abstract sockets (one per line) |
### Metadata
@@ -140,10 +140,10 @@ signer-client get-info
| Transport | Flag | Notes |
|-----------|------|-------|
| UNIX abstract socket | `--socket-name <name>` or auto-discover | Default. Auto-discovers if exactly one `nsigner*` socket exists. |
| UNIX abstract socket | `--socket-name <name>` or auto-discover | Default. Auto-discovers if exactly one `signer*` socket exists. |
| TCP | `--tcp <host:port>` | Requires `--auth-privkey` for auth envelope. |
| Serial (USB CDC-ACM) | `--serial <device>` | e.g. `--serial /dev/ttyACM0` |
| Qubes qrexec | `--qrexec <qube:service>` | e.g. `--qrexec sys-signer:qubes.NsignerRpc` |
| Qubes qrexec | `--qrexec <qube:service>` | e.g. `--qrexec sys-signer:qubes.SignerRpc` |
## Exit codes
@@ -170,11 +170,11 @@ signer-client get-info
| [`main.rs`](main.rs:1) | Entry point, CLI parse, verb dispatch, stdin/stdout |
| [`cli.rs`](cli.rs:1) | clap `Cli` / `Verb` structs + usage text |
| [`transport.rs`](transport.rs:1) | `ClientTransport` enum (Unix/Tcp/Serial/Qrexec) |
| [`rpc.rs`](rpc.rs:1) | Low-level `NsignerClient` (framed JSON-RPC send/recv) |
| [`signer.rs`](signer.rs:1) | High-level `NsignerSigner` typed-verb wrappers |
| [`rpc.rs`](rpc.rs:1) | Low-level `SignerClient` (framed JSON-RPC send/recv) |
| [`signer.rs`](signer.rs:1) | High-level `SignerSigner` typed-verb wrappers |
| [`auth.rs`](auth.rs:1) | Client-side auth envelope builder (NIP-42 kind 22242) |
## See also
- [`plans/signer_client_plan.md`](../../plans/signer_client_plan.md:1) — implementation plan
- [`README.md`](../../README.md:1) — nsigner main documentation
- [`README.md`](../../README.md:1) — signer main documentation
+7 -7
View File
@@ -1,13 +1,13 @@
//! Client-side auth envelope builder for TCP/qrexec transports.
//!
//! Builds a NIP-42 kind-22242 auth event matching the wire shape verified by
//! [`nsigner::auth_envelope::verify_request`]. The event is attached as a
//! [`signer::auth_envelope::verify_request`]. The event is attached as a
//! top-level `"auth"` field on the JSON-RPC request.
//!
//! Tag contract (must match the server verifier):
//! - `["nsigner_rpc", <request id>]`
//! - `["nsigner_method", <method>]`
//! - `["nsigner_body_hash", <hex sha256 of compact params JSON>]`
//! - `["signer_rpc", <request id>]`
//! - `["signer_method", <method>]`
//! - `["signer_body_hash", <hex sha256 of compact params JSON>]`
//! - `content` = auth label
use nostr_core::types::{Event, Kind, SecretKey, Tag};
@@ -46,9 +46,9 @@ pub fn build_auth_event(
// Tags (two-element: kind + value)
let tags = vec![
Tag::with_value("nsigner_rpc", request_id),
Tag::with_value("nsigner_method", method),
Tag::with_value("nsigner_body_hash", &body_hash_hex),
Tag::with_value("signer_rpc", request_id),
Tag::with_value("signer_method", method),
Tag::with_value("signer_body_hash", &body_hash_hex),
];
let created_at = std::time::SystemTime::now()
+4 -4
View File
@@ -6,8 +6,8 @@ use clap::{Parser, Subcommand};
#[derive(Parser, Debug)]
#[command(
name = "signer-client",
version = nsigner::VERSION,
about = "Standalone CLI for the nsigner JSON-RPC API"
version = ::signer::VERSION,
about = "Standalone CLI for the signer JSON-RPC API"
)]
pub struct Cli {
/// Abstract socket name (without @ prefix). Default: auto-discover.
@@ -89,7 +89,7 @@ pub struct Cli {
/// Verb subcommands.
#[derive(Subcommand, Debug)]
pub enum Verb {
/// List running nsigner abstract sockets.
/// List running signer abstract sockets.
List,
/// Get signer metadata.
@@ -227,7 +227,7 @@ pub fn print_usage(prog: &str) {
\x20 --timeout-sec <N> Mining timeout in seconds\n\
\n\
Verbs:\n\
\x20 list List running nsigner sockets\n\
\x20 list List running signer sockets\n\
\x20 get-info\n\
\x20 get-public-key\n\
\x20 sign-event\n\
+6 -6
View File
@@ -1,7 +1,7 @@
//! signer-client — standalone CLI for the nsigner JSON-RPC API.
//! signer-client — standalone CLI for the signer JSON-RPC API.
//!
//! Port of the C [`n_signer_client.c`](../../n_signer/client/n_signer_client.c).
//! Connects to a running `nsigner` process over its framed transports
//! Connects to a running `signer` process over its framed transports
//! (Unix / TCP / serial / qrexec) and exposes the full verb surface over
//! stdin/stdout so that signed events can be piped directly into `nak publish`.
@@ -13,7 +13,7 @@ mod transport;
use clap::Parser;
use cli::{Cli, Verb};
use signer::{result_to_line, AlgOptions, NsignerSigner};
use crate::signer::{result_to_line, AlgOptions, SignerSigner};
fn main() {
let cli = match Cli::try_parse() {
@@ -31,9 +31,9 @@ fn main() {
fn run(cli: Cli) -> i32 {
// ── list verb (no connection needed) ──────────────────────────────
if matches!(cli.verb, Verb::List) {
let sockets = nsigner::socket_name::list_sockets();
let sockets = ::signer::socket_name::list_sockets();
if sockets.is_empty() {
println!("no nsigner sockets found");
println!("no signer sockets found");
} else {
for name in &sockets {
println!("{}", name);
@@ -92,7 +92,7 @@ fn run(cli: Cli) -> i32 {
return 2;
}
let mut signer = NsignerSigner::new(rpc::NsignerClient::new(transport));
let mut signer = SignerSigner::new(rpc::SignerClient::new(transport));
// ── set selector for nostr verbs ──────────────────────────────────
if is_nostr_verb && !is_algorithm_verb {
+7 -7
View File
@@ -1,6 +1,6 @@
//! Low-level JSON-RPC 2.0 client — framed send/recv over a transport.
//!
//! Port of the C `nsigner_client_t` / `nsigner_client_call`. Builds a
//! Port of the C `signer_client_t` / `signer_client_call`. Builds a
//! `{"id","method","params"}` request, sends it framed, receives the framed
//! response, and splits `result` vs `error`.
@@ -8,17 +8,17 @@ use serde_json::{json, Value};
use super::transport::ClientTransport;
/// Low-level nsigner RPC client. Owns the transport.
pub struct NsignerClient {
/// Low-level signer RPC client. Owns the transport.
pub struct SignerClient {
transport: ClientTransport,
last_error: String,
next_id: u64,
}
impl NsignerClient {
impl SignerClient {
/// Wrap an open transport.
pub fn new(transport: ClientTransport) -> Self {
NsignerClient {
SignerClient {
transport,
last_error: String::new(),
next_id: 1,
@@ -115,7 +115,7 @@ mod tests {
#[test]
fn test_extract_result_success() {
let mut client = NsignerClient {
let mut client = SignerClient {
transport: ClientTransport::Unix(
std::os::unix::net::UnixStream::pair().unwrap().0,
),
@@ -129,7 +129,7 @@ mod tests {
#[test]
fn test_extract_result_error() {
let mut client = NsignerClient {
let mut client = SignerClient {
transport: ClientTransport::Unix(
std::os::unix::net::UnixStream::pair().unwrap().0,
),
+8 -8
View File
@@ -1,12 +1,12 @@
//! High-level typed-verb layer — one method per JSON-RPC verb.
//!
//! Port of the C `nostr_signer_t` wrappers. Each method builds the correct
//! `params` array + options object, calls the low-level [`NsignerClient`],
//! `params` array + options object, calls the low-level [`SignerClient`],
//! and parses the typed result.
use serde_json::{json, Value};
use super::rpc::NsignerClient;
use super::rpc::SignerClient;
/// Selector state for nostr verbs.
#[derive(Debug, Clone, Default)]
@@ -58,18 +58,18 @@ impl AlgOptions {
}
}
/// High-level nsigner signer. Wraps a low-level client and holds selector +
/// High-level signer signer. Wraps a low-level client and holds selector +
/// auth state used across typed verbs.
pub struct NsignerSigner {
pub client: NsignerClient,
pub struct SignerSigner {
pub client: SignerClient,
pub selector: Selector,
pub auth_privkey: Option<String>,
pub auth_label: String,
}
impl NsignerSigner {
pub fn new(client: NsignerClient) -> Self {
NsignerSigner {
impl SignerSigner {
pub fn new(client: SignerClient) -> Self {
SignerSigner {
client,
selector: Selector::default(),
auth_privkey: None,
+14 -14
View File
@@ -1,8 +1,8 @@
//! Client transport — unified framed I/O over Unix / TCP / Serial / Qrexec.
//!
//! Port of the C `nsigner_transport_t` vtable. All four transports share the
//! Port of the C `signer_transport_t` vtable. All four transports share the
//! same `send_framed` / `recv_framed` path after construction (defined in
//! [`nsigner::transport`]).
//! [`::signer::transport`]).
use std::io;
use std::process::{Child, ChildStdin, ChildStdout};
@@ -27,7 +27,7 @@ pub enum ClientTransport {
impl ClientTransport {
/// Open a Unix abstract-socket transport by name (without `@`).
pub fn open_unix(name: &str, _timeout_ms: u64) -> io::Result<Self> {
let stream = nsigner::transport::connect_abstract_unix(name)?;
let stream = ::signer::transport::connect_abstract_unix(name)?;
Ok(ClientTransport::Unix(stream))
}
@@ -76,11 +76,11 @@ impl ClientTransport {
/// Send a framed JSON message.
pub fn send(&mut self, payload: &str) -> io::Result<()> {
match self {
ClientTransport::Unix(s) => nsigner::transport::send_framed(s, payload),
ClientTransport::Tcp(s) => nsigner::transport::send_framed(s, payload),
ClientTransport::Serial(f) => nsigner::transport::send_framed(f, payload),
ClientTransport::Unix(s) => ::signer::transport::send_framed(s, payload),
ClientTransport::Tcp(s) => ::signer::transport::send_framed(s, payload),
ClientTransport::Serial(f) => ::signer::transport::send_framed(f, payload),
ClientTransport::Qrexec { stdin, .. } => {
nsigner::transport::send_framed(stdin, payload)
::signer::transport::send_framed(stdin, payload)
}
}
}
@@ -88,11 +88,11 @@ impl ClientTransport {
/// Receive a framed JSON message.
pub fn recv(&mut self) -> io::Result<String> {
match self {
ClientTransport::Unix(s) => nsigner::transport::recv_framed(s),
ClientTransport::Tcp(s) => nsigner::transport::recv_framed(s),
ClientTransport::Serial(f) => nsigner::transport::recv_framed(f),
ClientTransport::Unix(s) => ::signer::transport::recv_framed(s),
ClientTransport::Tcp(s) => ::signer::transport::recv_framed(s),
ClientTransport::Serial(f) => ::signer::transport::recv_framed(f),
ClientTransport::Qrexec { stdout, .. } => {
nsigner::transport::recv_framed(stdout)
::signer::transport::recv_framed(stdout)
}
}
}
@@ -179,13 +179,13 @@ pub fn open_from_cli(
}
// Auto-discover: enumerate abstract UNIX sockets.
let sockets = nsigner::socket_name::list_sockets();
let sockets = ::signer::socket_name::list_sockets();
if sockets.is_empty() {
return Err("no nsigner sockets found. Is nsigner running?".into());
return Err("no signer sockets found. Is signer running?".into());
}
if sockets.len() > 1 {
let mut msg = String::from(
"multiple nsigner sockets found. Use --socket-name to select one:\n",
"multiple signer sockets found. Use --socket-name to select one:\n",
);
for n in &sockets {
msg.push_str(&format!(" {}\n", n));
+3 -3
View File
@@ -11,7 +11,7 @@ use crate::mnemonic::MnemonicState;
use crate::pq_crypto::CryptoAlg;
use crate::role_table::RoleTable;
use crate::selector::{selector_resolve, SelectorRequest};
use crate::NsignerError;
use crate::SignerError;
use serde_json::{json, Value};
@@ -551,7 +551,7 @@ fn is_nostr_verb(verb: &str) -> bool {
)
}
fn sign_with_alg(alg: CryptoAlg, priv_key: &[u8; 32], msg: &[u8]) -> Result<Vec<u8>, NsignerError> {
fn sign_with_alg(alg: CryptoAlg, priv_key: &[u8; 32], msg: &[u8]) -> Result<Vec<u8>, SignerError> {
match alg {
CryptoAlg::Secp256k1 => {
// Check for scheme option (schnorr default, ecdsa alternative)
@@ -567,7 +567,7 @@ fn sign_with_alg(alg: CryptoAlg, priv_key: &[u8; 32], msg: &[u8]) -> Result<Vec<
}
CryptoAlg::MlDsa65 => crate::pq_crypto::ml_dsa_65_sign(priv_key, msg),
CryptoAlg::SlhDsa128s => crate::pq_crypto::slh_dsa_128s_sign(priv_key, msg),
_ => Err(NsignerError::CryptoFailed),
_ => Err(SignerError::CryptoFailed),
}
}
+9 -9
View File
@@ -1,13 +1,13 @@
//! Error types for nsigner.
//! Error types for signer.
//!
//! JSON-RPC error codes are preserved exactly for wire compatibility
//! with the C n_signer.
use thiserror::Error;
/// nsigner-specific errors (internal operations).
/// signer-specific errors (internal operations).
#[derive(Error, Debug, Clone)]
pub enum NsignerError {
pub enum SignerError {
#[error("invalid input")]
InvalidInput,
#[error("memory allocation failed (mlock)")]
@@ -32,13 +32,13 @@ pub enum NsignerError {
Internal(String),
}
impl From<nostr_core::error::NostrError> for NsignerError {
impl From<nostr_core::error::NostrError> for SignerError {
fn from(e: nostr_core::error::NostrError) -> Self {
// Map NostrError to NsignerError
// Map NostrError to SignerError
match e {
nostr_core::error::NostrError::InvalidInput => NsignerError::InvalidInput,
nostr_core::error::NostrError::CryptoFailed => NsignerError::CryptoFailed,
_ => NsignerError::CryptoFailed,
nostr_core::error::NostrError::InvalidInput => SignerError::InvalidInput,
nostr_core::error::NostrError::CryptoFailed => SignerError::CryptoFailed,
_ => SignerError::CryptoFailed,
}
}
}
@@ -60,7 +60,7 @@ impl RpcError {
pub const INVALID_PARAMS: Self = RpcError { code: -32602, message: "invalid_params" };
pub const INTERNAL_ERROR: Self = RpcError { code: -32603, message: "internal_error" };
// ── nsigner-specific errors ──────────────────────────────────────
// ── signer-specific errors ──────────────────────────────────────
pub const AMBIGUOUS_ROLE_SELECTOR: Self = RpcError { code: 1001, message: "ambiguous_role_selector" };
pub const UNKNOWN_ROLE: Self = RpcError { code: 1002, message: "unknown_role" };
pub const NO_DEFAULT_ROLE: Self = RpcError { code: 1003, message: "no_default_role" };
+1 -1
View File
@@ -1,4 +1,4 @@
//! Minimal HTTP/1.1 parser for nsigner's HTTP listener mode.
//! Minimal HTTP/1.1 parser for signer's HTTP listener mode.
//!
//! Port of `http_listener.c`. Only supports POST with a JSON body.
//! No chunked encoding, no keep-alive, one request per connection.
+41 -41
View File
@@ -7,7 +7,7 @@ use crate::mnemonic::MnemonicState;
use crate::pq_crypto::{self, CryptoAlg};
use crate::role_table::{self, RoleCurve, RoleEntry, RolePurpose, RoleTable};
use crate::secure_mem::SecureBuf;
use crate::NsignerError;
use crate::SignerError;
/// Per-role derived key material (stored in secure memory).
pub struct DerivedKey {
@@ -35,12 +35,12 @@ impl KeyStore {
&mut self,
table: &mut RoleTable,
mnemonic: &MnemonicState,
) -> Result<usize, NsignerError> {
) -> Result<usize, SignerError> {
if !mnemonic.is_loaded() {
return Err(NsignerError::MnemonicNotLoaded);
return Err(SignerError::MnemonicNotLoaded);
}
let phrase = mnemonic.phrase().ok_or(NsignerError::MnemonicNotLoaded)?;
let phrase = mnemonic.phrase().ok_or(SignerError::MnemonicNotLoaded)?;
self.keys.clear();
self.keys.resize_with(table.entries.len(), || None);
@@ -86,16 +86,16 @@ impl KeyStore {
table: &mut RoleTable,
mnemonic: &MnemonicState,
role_index: usize,
) -> Result<(), NsignerError> {
) -> Result<(), SignerError> {
if !mnemonic.is_loaded() {
return Err(NsignerError::MnemonicNotLoaded);
return Err(SignerError::MnemonicNotLoaded);
}
let phrase = mnemonic.phrase().ok_or(NsignerError::MnemonicNotLoaded)?;
let phrase = mnemonic.phrase().ok_or(SignerError::MnemonicNotLoaded)?;
let role = table
.entries
.get_mut(role_index)
.ok_or(NsignerError::InvalidInput)?;
.ok_or(SignerError::InvalidInput)?;
role.derived = false;
role.pubkey_hex.clear();
@@ -120,16 +120,16 @@ impl KeyStore {
mnemonic: &MnemonicState,
role_index: usize,
concrete_path: &str,
) -> Result<(), NsignerError> {
) -> Result<(), SignerError> {
if !mnemonic.is_loaded() {
return Err(NsignerError::MnemonicNotLoaded);
return Err(SignerError::MnemonicNotLoaded);
}
let phrase = mnemonic.phrase().ok_or(NsignerError::MnemonicNotLoaded)?;
let phrase = mnemonic.phrase().ok_or(SignerError::MnemonicNotLoaded)?;
let role = table
.entries
.get_mut(role_index)
.ok_or(NsignerError::InvalidInput)?;
.ok_or(SignerError::InvalidInput)?;
role.derived = false;
role.pubkey_hex.clear();
@@ -161,14 +161,14 @@ impl KeyStore {
&self,
role_index: usize,
event_json: &str,
) -> Result<String, NsignerError> {
) -> Result<String, SignerError> {
let priv_bytes = self
.get_private_key(role_index)
.ok_or(NsignerError::KeyDerivationFailed)?;
.ok_or(SignerError::KeyDerivationFailed)?;
// Parse the unsigned event from JSON
let event: nostr_core::types::Event =
serde_json::from_str(event_json).map_err(|_| NsignerError::InvalidInput)?;
serde_json::from_str(event_json).map_err(|_| SignerError::InvalidInput)?;
// Use NIP-01 to create and sign the event
let mut priv_arr = [0u8; 32];
@@ -182,9 +182,9 @@ impl KeyStore {
&sk,
event.created_at,
)
.map_err(|_| NsignerError::CryptoFailed)?;
.map_err(|_| SignerError::CryptoFailed)?;
serde_json::to_string(&signed).map_err(|_| NsignerError::InvalidInput)
serde_json::to_string(&signed).map_err(|_| SignerError::InvalidInput)
}
/// NIP-44 encrypt.
@@ -193,20 +193,20 @@ impl KeyStore {
role_index: usize,
recipient_pubkey_hex: &str,
plaintext: &str,
) -> Result<Vec<u8>, NsignerError> {
) -> Result<Vec<u8>, SignerError> {
let priv_bytes = self
.get_private_key(role_index)
.ok_or(NsignerError::KeyDerivationFailed)?;
.ok_or(SignerError::KeyDerivationFailed)?;
let recipient_pk: nostr_core::types::PublicKey = recipient_pubkey_hex
.parse()
.map_err(|_| NsignerError::InvalidInput)?;
.map_err(|_| SignerError::InvalidInput)?;
let mut priv_arr = [0u8; 32];
priv_arr.copy_from_slice(&priv_bytes[..32]);
let sk = nostr_core::types::SecretKey::from_bytes(priv_arr);
nostr_core::crypto::nip44::nip44_encrypt(&sk, &recipient_pk, plaintext.as_bytes())
.map_err(|_| NsignerError::CryptoFailed)
.map_err(|_| SignerError::CryptoFailed)
}
/// NIP-44 decrypt.
@@ -215,20 +215,20 @@ impl KeyStore {
role_index: usize,
sender_pubkey_hex: &str,
ciphertext: &[u8],
) -> Result<Vec<u8>, NsignerError> {
) -> Result<Vec<u8>, SignerError> {
let priv_bytes = self
.get_private_key(role_index)
.ok_or(NsignerError::KeyDerivationFailed)?;
.ok_or(SignerError::KeyDerivationFailed)?;
let sender_pk: nostr_core::types::PublicKey = sender_pubkey_hex
.parse()
.map_err(|_| NsignerError::InvalidInput)?;
.map_err(|_| SignerError::InvalidInput)?;
let mut priv_arr = [0u8; 32];
priv_arr.copy_from_slice(&priv_bytes[..32]);
let sk = nostr_core::types::SecretKey::from_bytes(priv_arr);
nostr_core::crypto::nip44::nip44_decrypt(&sk, &sender_pk, ciphertext)
.map_err(|_| NsignerError::CryptoFailed)
.map_err(|_| SignerError::CryptoFailed)
}
/// NIP-04 encrypt.
@@ -237,20 +237,20 @@ impl KeyStore {
role_index: usize,
recipient_pubkey_hex: &str,
plaintext: &str,
) -> Result<String, NsignerError> {
) -> Result<String, SignerError> {
let priv_bytes = self
.get_private_key(role_index)
.ok_or(NsignerError::KeyDerivationFailed)?;
.ok_or(SignerError::KeyDerivationFailed)?;
let recipient_pk: nostr_core::types::PublicKey = recipient_pubkey_hex
.parse()
.map_err(|_| NsignerError::InvalidInput)?;
.map_err(|_| SignerError::InvalidInput)?;
let mut priv_arr = [0u8; 32];
priv_arr.copy_from_slice(&priv_bytes[..32]);
let sk = nostr_core::types::SecretKey::from_bytes(priv_arr);
nips::nip004::nip04_encrypt(&sk, &recipient_pk, plaintext)
.map_err(|_| NsignerError::CryptoFailed)
.map_err(|_| SignerError::CryptoFailed)
}
/// NIP-04 decrypt.
@@ -259,20 +259,20 @@ impl KeyStore {
role_index: usize,
sender_pubkey_hex: &str,
ciphertext: &str,
) -> Result<String, NsignerError> {
) -> Result<String, SignerError> {
let priv_bytes = self
.get_private_key(role_index)
.ok_or(NsignerError::KeyDerivationFailed)?;
.ok_or(SignerError::KeyDerivationFailed)?;
let sender_pk: nostr_core::types::PublicKey = sender_pubkey_hex
.parse()
.map_err(|_| NsignerError::InvalidInput)?;
.map_err(|_| SignerError::InvalidInput)?;
let mut priv_arr = [0u8; 32];
priv_arr.copy_from_slice(&priv_bytes[..32]);
let sk = nostr_core::types::SecretKey::from_bytes(priv_arr);
nips::nip004::nip04_decrypt(&sk, &sender_pk, ciphertext)
.map_err(|_| NsignerError::CryptoFailed)
.map_err(|_| SignerError::CryptoFailed)
}
/// Zeroize all derived keys.
@@ -288,18 +288,18 @@ fn derive_for_role(
path: &str,
role: &RoleEntry,
mnemonic_phrase: &str,
) -> Result<DerivedKey, NsignerError> {
) -> Result<DerivedKey, SignerError> {
let alg = role_table::crypto_alg_from_role(role.curve, role.purpose);
// crypto_alg_from_role returns Unknown for OTP, but we skip OTP earlier
let alg = if alg == CryptoAlg::Unknown {
return Err(NsignerError::KeyDerivationFailed);
return Err(SignerError::KeyDerivationFailed);
} else {
alg
};
let sizes = alg
.sizes()
.ok_or(NsignerError::KeyDerivationFailed)?;
.ok_or(SignerError::KeyDerivationFailed)?;
// Derive the 32-byte seed from the mnemonic using the path
let seed = pq_crypto::derive_seed_from_mnemonic(mnemonic_phrase, path)?;
@@ -310,19 +310,19 @@ fn derive_for_role(
let bip39_seed = nips::nip006::mnemonic_to_seed(mnemonic_phrase, "");
let (master_key, master_chain_code) = nips::nip006::bip32_master_key(&bip39_seed);
let path_indices = nips::nip006::parse_bip44_path(path)
.map_err(|_| NsignerError::KeyDerivationFailed)?;
.map_err(|_| SignerError::KeyDerivationFailed)?;
let (derived_key, _) = nips::nip006::bip32_derive_path(
&master_key,
&master_chain_code,
&path_indices,
)
.map_err(|_| NsignerError::KeyDerivationFailed)?;
.map_err(|_| SignerError::KeyDerivationFailed)?;
let mut priv_arr = [0u8; 32];
priv_arr.copy_from_slice(&derived_key);
let sk = nostr_core::types::SecretKey::from_bytes(priv_arr);
let pk = nostr_core::crypto::keys::public_key_from_secret_key(&sk)
.map_err(|_| NsignerError::CryptoFailed)?;
.map_err(|_| SignerError::CryptoFailed)?;
let mut priv_buf = SecureBuf::alloc(sizes.priv_key_len)?;
priv_buf.copy_from(&priv_arr);
@@ -384,9 +384,9 @@ fn derive_for_role(
}
CryptoAlg::MlDsa65 | CryptoAlg::SlhDsa128s | CryptoAlg::MlKem768 => {
// PQ algorithms — TODO: Phase 13
Err(NsignerError::NotYetImplemented)
Err(SignerError::NotYetImplemented)
}
CryptoAlg::Unknown => Err(NsignerError::KeyDerivationFailed),
CryptoAlg::Unknown => Err(SignerError::KeyDerivationFailed),
}
}
+3 -3
View File
@@ -1,4 +1,4 @@
//! # nsigner — Attended Nostr signing daemon
//! # signer — Attended Nostr signing daemon
//!
//! Rust port of the C-based `n_signer`. Holds signing key material in
//! locked memory and signs on request via a JSON-RPC 2.0 API over
@@ -28,7 +28,7 @@ pub mod socket_name;
pub mod tui;
pub mod error;
pub use error::NsignerError;
pub use error::SignerError;
/// Version string (matches C NSIGNER_VERSION).
pub const VERSION: &str = "v0.0.12";
pub const VERSION: &str = "v0.0.13";
+40 -40
View File
@@ -1,22 +1,22 @@
//! nsigner — attended Nostr signing daemon.
//! signer — attended Nostr signing daemon.
//!
//! Port of `main.c`. Single binary that holds signing key material in
//! locked memory and signs on request via JSON-RPC 2.0.
use clap::{Parser, Subcommand};
use nsigner::{
use signer::{
alg_cache::AlgorithmKeyCache,
dispatcher::DispatcherContext,
key_store::KeyStore,
mnemonic::MnemonicState,
role_table::{RoleCurve, RolePurpose, RoleTable},
server::{AuthMode, ListenMode, ServerContext},
NsignerError,
SignerError,
};
/// Command-line arguments.
#[derive(Parser, Debug)]
#[command(name = "nsigner", version = nsigner::VERSION, about = "Attended Nostr signing daemon")]
#[command(name = "signer", version = signer::VERSION, about = "Attended Nostr signing daemon")]
struct Cli {
/// Socket name (abstract namespace, without @ prefix)
#[arg(long, short = 'n', alias = "name")]
@@ -85,7 +85,7 @@ enum Commands {
to: Option<String>,
},
/// List running nsigner abstract sockets
/// List running signer abstract sockets
List,
}
@@ -111,7 +111,7 @@ fn main() {
match server_main(&cli) {
Ok(()) => {}
Err(e) => {
eprintln!("nsigner: {}", e);
eprintln!("signer: {}", e);
std::process::exit(1);
}
}
@@ -126,11 +126,11 @@ fn main() {
/// `--register-role` / `--listen`): mnemonic and roles are set up here,
/// then the App runs with `listen_override` so it goes straight to the
/// main screen. Headless modes (stdio/qrexec/tcp/http) never show a TUI.
fn server_main(cli: &Cli) -> Result<(), NsignerError> {
println!("nsigner {}", nsigner::VERSION);
fn server_main(cli: &Cli) -> Result<(), SignerError> {
println!("signer {}", signer::VERSION);
if cli.allow_unlocked_memory {
nsigner::secure_mem::allow_unlocked();
signer::secure_mem::allow_unlocked();
}
let interactive = !cli.mnemonic_stdin && cli.mnemonic_fd.is_none();
@@ -153,7 +153,7 @@ fn server_main(cli: &Cli) -> Result<(), NsignerError> {
.socket_name
.clone()
.unwrap_or_else(|| {
nsigner::socket_name::socket_name_random().unwrap_or_default()
signer::socket_name::socket_name_random().unwrap_or_default()
});
let auth_mode = parse_auth_mode(&cli.auth);
@@ -163,7 +163,7 @@ fn server_main(cli: &Cli) -> Result<(), NsignerError> {
// (Unix only — non-Unix modes are headless above), the transport
// is pre-selected on the main screen. Pass the raw --listen string
// so the App can adopt an explicit tcp:/http: bind address.
let mut app = nsigner::tui::App::new(
let mut app = signer::tui::App::new(
RoleTable::new(),
MnemonicState::new(),
KeyStore::new(),
@@ -176,7 +176,7 @@ fn server_main(cli: &Cli) -> Result<(), NsignerError> {
let mut terminal = ratatui::init();
let result = app.run(&mut terminal);
ratatui::restore();
result.map_err(|e| NsignerError::IoFailed(e.to_string()))
result.map_err(|e| SignerError::IoFailed(e.to_string()))
} else {
// Unreachable: non-interactive modes return headless above.
Ok(())
@@ -184,14 +184,14 @@ fn server_main(cli: &Cli) -> Result<(), NsignerError> {
}
/// Run a headless server (stdio, qrexec, tcp, http) — no TUI.
fn run_headless(cli: &Cli, listen_mode: ListenMode) -> Result<(), NsignerError> {
fn run_headless(cli: &Cli, listen_mode: ListenMode) -> Result<(), SignerError> {
let mut mnemonic = MnemonicState::new();
if cli.mnemonic_stdin {
let mut input = String::new();
std::io::stdin()
.read_line(&mut input)
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
let phrase = input.trim().to_string();
mnemonic.load(&phrase)?;
} else if let Some(fd) = cli.mnemonic_fd {
@@ -200,7 +200,7 @@ fn run_headless(cli: &Cli, listen_mode: ListenMode) -> Result<(), NsignerError>
let mut file = unsafe { std::fs::File::from_raw_fd(fd) };
let mut input = String::new();
file.read_to_string(&mut input)
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
let phrase = input.trim().to_string();
mnemonic.load(&phrase)?;
}
@@ -219,7 +219,7 @@ fn run_headless(cli: &Cli, listen_mode: ListenMode) -> Result<(), NsignerError>
RoleCurve::Secp256k1,
-1, -1, -1, &[],
)
.map_err(|e| NsignerError::Internal(e.to_string()))?;
.map_err(|e| SignerError::Internal(e.to_string()))?;
}
let mut key_store = KeyStore::new();
@@ -276,16 +276,16 @@ fn run_headless(cli: &Cli, listen_mode: ListenMode) -> Result<(), NsignerError>
fn client_main(request: &str, cli: &Cli) -> i32 {
use std::io::Read;
let socket_name = cli.socket_name.as_deref().unwrap_or("nsigner01");
let socket_name = cli.socket_name.as_deref().unwrap_or("signer01");
// Discover single socket if not explicit
let socket_name = if cli.socket_name.is_some() {
socket_name.to_string()
} else {
nsigner::socket_name::discover_single_socket().unwrap_or_else(|_| socket_name.to_string())
signer::socket_name::discover_single_socket().unwrap_or_else(|_| socket_name.to_string())
};
let mut stream = match nsigner::transport::connect_abstract_unix(&socket_name) {
let mut stream = match signer::transport::connect_abstract_unix(&socket_name) {
Ok(s) => s,
Err(e) => {
eprintln!("Failed to connect to {}: {}", socket_name, e);
@@ -306,13 +306,13 @@ fn client_main(request: &str, cli: &Cli) -> i32 {
};
// Send framed request
if nsigner::transport::send_framed(&mut stream, &request).is_err() {
if signer::transport::send_framed(&mut stream, &request).is_err() {
eprintln!("Failed to send request");
return 1;
}
// Receive framed response
match nsigner::transport::recv_framed(&mut stream) {
match signer::transport::recv_framed(&mut stream) {
Ok(response) => {
println!("{}", response);
0
@@ -328,18 +328,18 @@ fn client_main(request: &str, cli: &Cli) -> i32 {
fn bridge_main(to: Option<&str>, cli: &Cli) -> i32 {
let target = to.unwrap_or("nsigner01");
let target = to.unwrap_or("signer01");
let target = if cli.socket_name.is_some() {
target.to_string()
} else {
nsigner::socket_name::discover_single_socket().unwrap_or_else(|_| target.to_string())
signer::socket_name::discover_single_socket().unwrap_or_else(|_| target.to_string())
};
// Read source qube from qrexec environment
let source_qube = std::env::var("QREXEC_REMOTE_DOMAIN").unwrap_or_default();
// Connect to persistent signer via abstract socket
let mut stream = match nsigner::transport::connect_abstract_unix(&target) {
let mut stream = match signer::transport::connect_abstract_unix(&target) {
Ok(s) => s,
Err(e) => {
eprintln!("bridge: cannot connect to {}: {}", target, e);
@@ -349,14 +349,14 @@ fn bridge_main(to: Option<&str>, cli: &Cli) -> i32 {
// Send source-qube preamble
let preamble = format!(r#"{{"qrexec_source":"{}"}}"#, source_qube);
if nsigner::transport::send_framed(&mut stream, &preamble).is_err() {
if signer::transport::send_framed(&mut stream, &preamble).is_err() {
eprintln!("bridge: failed to send preamble");
return 1;
}
// Read one framed request from stdin and forward
let mut stdin = std::io::stdin();
let request = match nsigner::transport::recv_framed(&mut stdin) {
let request = match signer::transport::recv_framed(&mut stdin) {
Ok(r) => r,
Err(e) => {
eprintln!("bridge: failed to read request from stdin: {}", e);
@@ -364,17 +364,17 @@ fn bridge_main(to: Option<&str>, cli: &Cli) -> i32 {
}
};
if nsigner::transport::send_framed(&mut stream, &request).is_err() {
if signer::transport::send_framed(&mut stream, &request).is_err() {
eprintln!("bridge: failed to forward request");
return 1;
}
// Relay response to stdout
match nsigner::transport::recv_framed(&mut stream) {
match signer::transport::recv_framed(&mut stream) {
Ok(response) => {
let mut stdout = std::io::stdout();
if nsigner::transport::send_framed(&mut stdout, &response).is_err() {
if signer::transport::send_framed(&mut stdout, &response).is_err() {
eprintln!("bridge: failed to relay response");
return 1;
}
@@ -387,9 +387,9 @@ fn bridge_main(to: Option<&str>, cli: &Cli) -> i32 {
}
}
/// List subcommand: list running nsigner sockets.
/// List subcommand: list running signer sockets.
fn list_main() -> i32 {
let sockets = nsigner::socket_name::list_sockets();
let sockets = signer::socket_name::list_sockets();
if sockets.is_empty() {
println!("(none)");
} else {
@@ -404,10 +404,10 @@ fn list_main() -> i32 {
fn register_role_from_spec(
role_table: &mut RoleTable,
spec: &str,
) -> Result<(), NsignerError> {
) -> Result<(), SignerError> {
let parts: Vec<&str> = spec.splitn(3, ':').collect();
if parts.len() != 3 {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
let name = parts[0];
@@ -415,13 +415,13 @@ fn register_role_from_spec(
let path_token = parts[2];
if name.is_empty() || path_token.is_empty() {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
// Resolve curve
let curve = if curve_str.is_empty() {
// Auto-detect from path
match nsigner::role_table::purpose_from_path(path_token) {
match signer::role_table::purpose_from_path(path_token) {
RolePurpose::Ssh => RoleCurve::Ed25519,
RolePurpose::Age => RoleCurve::X25519,
RolePurpose::PqSig => {
@@ -439,15 +439,15 @@ fn register_role_from_spec(
};
if curve == RoleCurve::Unknown {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
let purpose = nsigner::role_table::purpose_from_path(path_token);
let purpose = signer::role_table::purpose_from_path(path_token);
// Parse path template
let (template, range_lo, range_hi, allowed_indices) =
nsigner::role_table::parse_path_template(path_token)
.map_err(|_| NsignerError::InvalidInput)?;
signer::role_table::parse_path_template(path_token)
.map_err(|_| SignerError::InvalidInput)?;
role_table
.register_role_path(
@@ -460,7 +460,7 @@ fn register_role_from_spec(
-1, // no default index
&allowed_indices,
)
.map_err(|e| NsignerError::Internal(e.to_string()))?;
.map_err(|e| SignerError::Internal(e.to_string()))?;
Ok(())
}
+6 -6
View File
@@ -44,14 +44,14 @@ pub fn miner_run(
target_difficulty: i32,
thread_count: i32,
timeout_sec: u64,
) -> Result<MineResult, crate::NsignerError> {
) -> Result<MineResult, crate::SignerError> {
let threads = thread_count.clamp(1, 32) as usize;
let timeout = if timeout_sec == 0 { 600 } else { timeout_sec };
let deadline = Instant::now() + Duration::from_secs(timeout);
// Parse the unsigned event
let mut event: Event =
serde_json::from_str(event_json).map_err(|_| crate::NsignerError::InvalidInput)?;
serde_json::from_str(event_json).map_err(|_| crate::SignerError::InvalidInput)?;
// Ensure there's a nonce tag (will be updated by workers)
let has_nonce = event.tags.iter().any(|t| t.kind() == "nonce");
@@ -96,9 +96,9 @@ pub fn miner_run(
}
let shared = Arc::try_unwrap(shared)
.map_err(|_| crate::NsignerError::Internal("mining thread still holds shared state".into()))?
.map_err(|_| crate::SignerError::Internal("mining thread still holds shared state".into()))?
.into_inner()
.map_err(|_| crate::NsignerError::Internal("mining shared state poisoned".into()))?;
.map_err(|_| crate::SignerError::Internal("mining shared state poisoned".into()))?;
let elapsed = start.elapsed().as_secs();
@@ -117,8 +117,8 @@ pub fn miner_run(
&sk,
event.created_at,
)
.map_err(|_| crate::NsignerError::CryptoFailed)?;
serde_json::to_string(&signed).map_err(|_| crate::NsignerError::InvalidInput)?
.map_err(|_| crate::SignerError::CryptoFailed)?;
serde_json::to_string(&signed).map_err(|_| crate::SignerError::InvalidInput)?
} else {
// No event mined — return the original unsigned event
event_json.to_string()
+7 -7
View File
@@ -4,7 +4,7 @@
//! generation, and seed conversion.
use crate::secure_mem::SecureBuf;
use crate::NsignerError;
use crate::SignerError;
/// Maximum mnemonic length: 24 words * ~10 chars + spaces + null.
pub const MNEMONIC_MAX_LEN: usize = 256;
@@ -32,18 +32,18 @@ impl MnemonicState {
///
/// Validates word count (12/15/18/21/24) and BIP-39 checksum.
/// Returns `InvalidInput` on invalid mnemonic, `MemoryFailed` on alloc error.
pub fn load(&mut self, phrase: &str) -> Result<(), NsignerError> {
pub fn load(&mut self, phrase: &str) -> Result<(), SignerError> {
let words: Vec<&str> = phrase.split_whitespace().collect();
let count = words.len();
// Validate word count
if ![12, 15, 18, 21, 24].contains(&count) {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
// Validate via nostr_core_lib_rust
if !nips::nip006::mnemonic_validate(phrase) {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
// Store in secure memory
@@ -62,14 +62,14 @@ impl MnemonicState {
/// Generate a new BIP-39 mnemonic phrase.
///
/// `word_count` must be 12, 15, 18, 21, or 24.
pub fn generate(&mut self, word_count: u8) -> Result<String, NsignerError> {
pub fn generate(&mut self, word_count: u8) -> Result<String, SignerError> {
let entropy_bytes = match word_count {
12 => 16,
15 => 20,
18 => 24,
21 => 28,
24 => 32,
_ => return Err(NsignerError::InvalidInput),
_ => return Err(SignerError::InvalidInput),
};
let mut entropy = vec![0u8; entropy_bytes];
@@ -77,7 +77,7 @@ impl MnemonicState {
rand::thread_rng().fill_bytes(&mut entropy);
let phrase = nips::nip006::mnemonic_from_bytes(&entropy)
.map_err(|_| NsignerError::CryptoFailed)?;
.map_err(|_| SignerError::CryptoFailed)?;
// Zeroize entropy
use zeroize::Zeroize;
+23 -23
View File
@@ -4,7 +4,7 @@
//! startup. Pad offset advances monotonically across requests.
use crate::secure_mem::SecureBuf;
use crate::NsignerError;
use crate::SignerError;
use std::fs::File;
use std::io::{Read, Seek, SeekFrom};
@@ -63,14 +63,14 @@ impl OtpPadState {
/// `dir` — directory containing .pad and .state files
/// `spec` — pad checksum (64 hex) or unique prefix
/// `allow_blkback` — allow pads on qvm-block devices (not for production)
pub fn bind(&mut self, dir: &str, spec: &str, _allow_blkback: bool) -> Result<(), NsignerError> {
pub fn bind(&mut self, dir: &str, spec: &str, _allow_blkback: bool) -> Result<(), SignerError> {
// Find the pad file matching the spec
let pad_filename = if spec.len() == 64 {
format!("{}/{}.pad", dir, spec)
} else {
// Prefix match — find a .pad file starting with spec
let entries = std::fs::read_dir(dir)
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
let mut found = None;
for entry in entries {
@@ -85,15 +85,15 @@ impl OtpPadState {
}
found
.map(|p| p.to_string_lossy().to_string())
.ok_or(NsignerError::InvalidInput)?
.ok_or(SignerError::InvalidInput)?
};
let file = File::open(&pad_filename)
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
let metadata = file
.metadata()
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
let size = metadata.len();
// Extract checksum from filename
@@ -113,7 +113,7 @@ impl OtpPadState {
// Allocate scratch buffer for XOR
let scratch = SecureBuf::alloc(4 * 1024 * 1024) // 4 MB max chunk
.map_err(|_| NsignerError::MemoryFailed)?;
.map_err(|_| SignerError::MemoryFailed)?;
self.bound = true;
self.pads_dir = dir.to_string();
@@ -144,9 +144,9 @@ impl OtpPadState {
&mut self,
plaintext: &[u8],
_encoding: Option<&str>,
) -> Result<(Vec<u8>, u64, u64), NsignerError> {
) -> Result<(Vec<u8>, u64, u64), SignerError> {
if !self.bound {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
let off_before = self.offset;
@@ -164,32 +164,32 @@ impl OtpPadState {
&mut self,
ciphertext: &[u8],
_encoding: Option<&str>,
) -> Result<Vec<u8>, NsignerError> {
) -> Result<Vec<u8>, SignerError> {
if !self.bound {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
self.xor_with_pad(ciphertext)
}
/// XOR data with pad bytes at the current offset, advancing the offset.
fn xor_with_pad(&mut self, data: &[u8]) -> Result<Vec<u8>, NsignerError> {
let file = self.pad_file.as_mut().ok_or(NsignerError::InvalidInput)?;
let scratch = self.scratch.as_mut().ok_or(NsignerError::InvalidInput)?;
fn xor_with_pad(&mut self, data: &[u8]) -> Result<Vec<u8>, SignerError> {
let file = self.pad_file.as_mut().ok_or(SignerError::InvalidInput)?;
let scratch = self.scratch.as_mut().ok_or(SignerError::InvalidInput)?;
let data_len = data.len();
if data_len > scratch.size() {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
// Seek to current offset
file.seek(SeekFrom::Start(self.offset))
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
// Read pad bytes
let pad_slice = &mut scratch.as_mut_slice()[..data_len];
file.read_exact(pad_slice)
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
// XOR
let result: Vec<u8> = data
@@ -231,11 +231,11 @@ static GLOBAL_OTP_PAD: Mutex<Option<OtpPadState>> = Mutex::new(None);
/// Bind the global OTP pad. Called from the role wizard (OTP preset) or from
/// `--otp-pad-dir` CLI handling. Replaces any previously bound pad.
pub fn bind_global(dir: &str, spec: &str, allow_blkback: bool) -> Result<(), NsignerError> {
pub fn bind_global(dir: &str, spec: &str, allow_blkback: bool) -> Result<(), SignerError> {
let mut pad = OtpPadState::new();
pad.bind(dir, spec, allow_blkback)?;
let mut guard = GLOBAL_OTP_PAD.lock().map_err(|e| {
NsignerError::Internal(format!("global otp pad lock poisoned: {}", e))
SignerError::Internal(format!("global otp pad lock poisoned: {}", e))
})?;
*guard = Some(pad);
Ok(())
@@ -287,7 +287,7 @@ mod tests {
#[test]
fn test_bind_and_encrypt() {
let dir = std::env::temp_dir().join("nsigner_otp_test_1");
let dir = std::env::temp_dir().join("signer_otp_test_1");
let _ = std::fs::remove_dir_all(&dir);
let chksum = make_test_pad(&dir, 1024);
@@ -313,7 +313,7 @@ mod tests {
#[test]
fn test_encrypt_decrypt_roundtrip() {
let dir = std::env::temp_dir().join("nsigner_otp_test_2");
let dir = std::env::temp_dir().join("signer_otp_test_2");
let _ = std::fs::remove_dir_all(&dir);
let chksum = make_test_pad(&dir, 1024);
@@ -346,7 +346,7 @@ mod tests {
#[test]
fn test_unbind() {
let dir = std::env::temp_dir().join("nsigner_otp_test_3");
let dir = std::env::temp_dir().join("signer_otp_test_3");
let _ = std::fs::remove_dir_all(&dir);
let chksum = make_test_pad(&dir, 1024);
@@ -363,7 +363,7 @@ mod tests {
#[test]
fn test_offset_advances() {
let dir = std::env::temp_dir().join("nsigner_otp_test_4");
let dir = std::env::temp_dir().join("signer_otp_test_4");
let _ = std::fs::remove_dir_all(&dir);
let chksum = make_test_pad(&dir, 1024);
+21 -21
View File
@@ -90,12 +90,12 @@ impl CryptoAlg {
pub fn derive_seed_from_mnemonic(
mnemonic: &str,
path: &str,
) -> Result<[u8; 32], crate::NsignerError> {
) -> Result<[u8; 32], crate::SignerError> {
let seed = nips::nip006::mnemonic_to_seed(mnemonic, "");
// Parse the path
let path_indices = nips::nip006::parse_bip44_path(path)
.map_err(|_| crate::NsignerError::KeyDerivationFailed)?;
.map_err(|_| crate::SignerError::KeyDerivationFailed)?;
// Determine if this is a secp256k1 path (BIP-32) or ed25519/x25519 path (SLIP-0010)
// by checking the purpose prefix.
@@ -103,13 +103,13 @@ pub fn derive_seed_from_mnemonic(
// BIP-32 derivation for secp256k1
let (master_key, master_chain_code) = nips::nip006::bip32_master_key(&seed);
let (derived_key, _) = nips::nip006::bip32_derive_path(&master_key, &master_chain_code, &path_indices)
.map_err(|_| crate::NsignerError::KeyDerivationFailed)?;
.map_err(|_| crate::SignerError::KeyDerivationFailed)?;
Ok(derived_key)
} else {
// SLIP-0010 derivation for ed25519/x25519/PQ
let (master_key, master_chain_code) = nips::nip006::slip10_master_key(&seed);
let (derived_key, _) = nips::nip006::slip10_derive_path(&master_key, &master_chain_code, &path_indices)
.map_err(|_| crate::NsignerError::KeyDerivationFailed)?;
.map_err(|_| crate::SignerError::KeyDerivationFailed)?;
Ok(derived_key)
}
}
@@ -162,12 +162,12 @@ pub fn x25519_ecdh(our_priv: &[u8; 32], peer_pub: &[u8; 32]) -> [u8; 32] {
/// secp256k1 ECDSA sign arbitrary bytes.
/// Hashes the message with SHA-256 before signing.
/// Returns 64-byte compact signature (r || s).
pub fn secp256k1_ecdsa_sign(priv_key: &[u8; 32], msg: &[u8]) -> Result<[u8; 64], crate::NsignerError> {
pub fn secp256k1_ecdsa_sign(priv_key: &[u8; 32], msg: &[u8]) -> Result<[u8; 64], crate::SignerError> {
use secp256k1::{Message, Secp256k1, SecretKey};
let secp = Secp256k1::new();
let sk = SecretKey::from_slice(priv_key).map_err(|_| crate::NsignerError::CryptoFailed)?;
let sk = SecretKey::from_slice(priv_key).map_err(|_| crate::SignerError::CryptoFailed)?;
let hash = sha256(msg);
let msg = Message::from_digest_slice(&hash).map_err(|_| crate::NsignerError::CryptoFailed)?;
let msg = Message::from_digest_slice(&hash).map_err(|_| crate::SignerError::CryptoFailed)?;
let sig = secp.sign_ecdsa(&msg, &sk);
Ok(sig.serialize_compact())
}
@@ -211,14 +211,14 @@ pub fn secp256k1_ecdsa_verify(pub_key: &[u8; 32], msg: &[u8], sig: &[u8; 64]) ->
/// ML-DSA-65: generate keypair from a 32-byte seed (deterministic).
/// TODO: Wire up ml-dsa crate API.
pub fn ml_dsa_65_keygen_from_seed(_seed: &[u8; 32]) -> Result<(Vec<u8>, Vec<u8>), crate::NsignerError> {
Err(crate::NsignerError::NotYetImplemented)
pub fn ml_dsa_65_keygen_from_seed(_seed: &[u8; 32]) -> Result<(Vec<u8>, Vec<u8>), crate::SignerError> {
Err(crate::SignerError::NotYetImplemented)
}
/// ML-DSA-65: sign a message.
/// TODO: Wire up ml-dsa crate API.
pub fn ml_dsa_65_sign(_priv: &[u8], _msg: &[u8]) -> Result<Vec<u8>, crate::NsignerError> {
Err(crate::NsignerError::NotYetImplemented)
pub fn ml_dsa_65_sign(_priv: &[u8], _msg: &[u8]) -> Result<Vec<u8>, crate::SignerError> {
Err(crate::SignerError::NotYetImplemented)
}
/// ML-DSA-65: verify a signature.
@@ -229,14 +229,14 @@ pub fn ml_dsa_65_verify(_pub: &[u8], _msg: &[u8], _sig: &[u8]) -> bool {
/// SLH-DSA-128s: generate keypair from a 32-byte seed (deterministic).
/// TODO: Wire up slh-dsa crate API.
pub fn slh_dsa_128s_keygen_from_seed(_seed: &[u8; 32]) -> Result<(Vec<u8>, Vec<u8>), crate::NsignerError> {
Err(crate::NsignerError::NotYetImplemented)
pub fn slh_dsa_128s_keygen_from_seed(_seed: &[u8; 32]) -> Result<(Vec<u8>, Vec<u8>), crate::SignerError> {
Err(crate::SignerError::NotYetImplemented)
}
/// SLH-DSA-128s: sign a message.
/// TODO: Wire up slh-dsa crate API.
pub fn slh_dsa_128s_sign(_priv: &[u8], _msg: &[u8]) -> Result<Vec<u8>, crate::NsignerError> {
Err(crate::NsignerError::NotYetImplemented)
pub fn slh_dsa_128s_sign(_priv: &[u8], _msg: &[u8]) -> Result<Vec<u8>, crate::SignerError> {
Err(crate::SignerError::NotYetImplemented)
}
/// SLH-DSA-128s: verify a signature.
@@ -247,22 +247,22 @@ pub fn slh_dsa_128s_verify(_pub: &[u8], _msg: &[u8], _sig: &[u8]) -> bool {
/// ML-KEM-768: generate keypair from a 32-byte seed (deterministic).
/// TODO: Wire up ml-kem crate API.
pub fn ml_kem_768_keygen_from_seed(_seed: &[u8; 32]) -> Result<(Vec<u8>, Vec<u8>), crate::NsignerError> {
Err(crate::NsignerError::NotYetImplemented)
pub fn ml_kem_768_keygen_from_seed(_seed: &[u8; 32]) -> Result<(Vec<u8>, Vec<u8>), crate::SignerError> {
Err(crate::SignerError::NotYetImplemented)
}
/// ML-KEM-768: encapsulate. pub is 1184-byte public key.
/// Returns (ciphertext[1088], shared_secret[32]).
/// TODO: Wire up ml-kem crate API.
pub fn ml_kem_768_encaps(_pub: &[u8]) -> Result<(Vec<u8>, [u8; 32]), crate::NsignerError> {
Err(crate::NsignerError::NotYetImplemented)
pub fn ml_kem_768_encaps(_pub: &[u8]) -> Result<(Vec<u8>, [u8; 32]), crate::SignerError> {
Err(crate::SignerError::NotYetImplemented)
}
/// ML-KEM-768: decapsulate. priv is 2400-byte secret key, ct is 1088-byte ciphertext.
/// Returns shared_secret[32].
/// TODO: Wire up ml-kem crate API.
pub fn ml_kem_768_decaps(_priv: &[u8], _ct: &[u8]) -> Result<[u8; 32], crate::NsignerError> {
Err(crate::NsignerError::NotYetImplemented)
pub fn ml_kem_768_decaps(_priv: &[u8], _ct: &[u8]) -> Result<[u8; 32], crate::SignerError> {
Err(crate::SignerError::NotYetImplemented)
}
// ── Helpers ─────────────────────────────────────────────────────────────────
+15 -15
View File
@@ -4,7 +4,7 @@
//! (acting as an access token) to a BIP-44 derivation path template.
//! The template may contain a `%d` placeholder for a variable index.
use crate::NsignerError;
use crate::SignerError;
use std::collections::HashSet;
// ── Limits ───────────────────────────────────────────────────────────────────
@@ -264,12 +264,12 @@ impl RoleTable {
}
/// Add a role entry. Returns error if table full or name duplicate.
pub fn add(&mut self, entry: RoleEntry) -> Result<(), NsignerError> {
pub fn add(&mut self, entry: RoleEntry) -> Result<(), SignerError> {
if self.entries.len() >= ROLE_TABLE_MAX_ENTRIES {
return Err(NsignerError::Internal("role table full".into()));
return Err(SignerError::Internal("role table full".into()));
}
if self.find_by_name(&entry.name).is_some() {
return Err(NsignerError::Internal("duplicate role name".into()));
return Err(SignerError::Internal("duplicate role name".into()));
}
self.entries.push(entry);
Ok(())
@@ -303,7 +303,7 @@ impl RoleTable {
}
/// Register a nostr-index role if missing.
pub fn register_nostr_index(&mut self, nostr_index: i32) -> Result<(), NsignerError> {
pub fn register_nostr_index(&mut self, nostr_index: i32) -> Result<(), SignerError> {
if self.find_by_nostr_index(nostr_index).is_some() {
return Ok(());
}
@@ -336,7 +336,7 @@ impl RoleTable {
range_hi: i32,
default_index: i32,
allowed_indices: &[i32],
) -> Result<(), NsignerError> {
) -> Result<(), SignerError> {
let mut entry = RoleEntry::default();
entry.name = name.to_string();
entry.purpose = purpose;
@@ -431,10 +431,10 @@ pub fn role_path_extract_index(concrete: &str, template: &str) -> i32 {
#[allow(clippy::too_many_arguments)]
pub fn parse_path_template(
token: &str,
) -> Result<(String, i32, i32, Vec<i32>), NsignerError> {
) -> Result<(String, i32, i32, Vec<i32>), SignerError> {
let segs: Vec<&str> = token.split('/').collect();
if segs.is_empty() {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
let mut template_out = String::new();
@@ -490,18 +490,18 @@ pub fn parse_path_template(
let mut set = HashSet::new();
for tok in seg_clean.split('+') {
if let Some(dash) = tok.find('-') {
let lo: i32 = tok[..dash].parse().map_err(|_| NsignerError::InvalidInput)?;
let hi: i32 = tok[dash + 1..].parse().map_err(|_| NsignerError::InvalidInput)?;
let lo: i32 = tok[..dash].parse().map_err(|_| SignerError::InvalidInput)?;
let hi: i32 = tok[dash + 1..].parse().map_err(|_| SignerError::InvalidInput)?;
if lo < 0 || hi < 0 || lo > hi {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
for v in lo..=hi {
set.insert(v);
}
} else {
let val: i32 = tok.parse().map_err(|_| NsignerError::InvalidInput)?;
let val: i32 = tok.parse().map_err(|_| SignerError::InvalidInput)?;
if val < 0 {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
set.insert(val);
}
@@ -527,10 +527,10 @@ pub fn parse_path_template(
let dash_pos = seg_clean.find('-').unwrap();
let lo: i32 = seg_clean[..dash_pos]
.parse()
.map_err(|_| NsignerError::InvalidInput)?;
.map_err(|_| SignerError::InvalidInput)?;
let hi: i32 = seg_clean[dash_pos + 1..]
.parse()
.map_err(|_| NsignerError::InvalidInput)?;
.map_err(|_| SignerError::InvalidInput)?;
if lo < 0 || hi < 0 || lo > hi {
// Not a valid numeric range — treat as literal
template_out.push_str(seg);
+7 -7
View File
@@ -43,17 +43,17 @@ impl SecureBuf {
///
/// Returns `MemoryFailed` if allocation or mlock fails (unless
/// `allow_unlocked()` was called).
pub fn alloc(size: usize) -> Result<Self, crate::NsignerError> {
pub fn alloc(size: usize) -> Result<Self, crate::SignerError> {
if size == 0 {
return Err(crate::NsignerError::InvalidInput);
return Err(crate::SignerError::InvalidInput);
}
let layout = Layout::from_size_align(size, 1)
.map_err(|_| crate::NsignerError::MemoryFailed)?;
.map_err(|_| crate::SignerError::MemoryFailed)?;
let ptr = unsafe { alloc(layout) };
if ptr.is_null() {
return Err(crate::NsignerError::MemoryFailed);
return Err(crate::SignerError::MemoryFailed);
}
// Zero-initialize
@@ -64,7 +64,7 @@ impl SecureBuf {
if !locked && !is_unlocked_allowed() {
// mlock failed and unlocked mode not permitted — fail hard
unsafe { dealloc(ptr, layout) };
return Err(crate::NsignerError::MemoryFailed);
return Err(crate::SignerError::MemoryFailed);
}
Ok(SecureBuf { ptr, size, locked })
@@ -111,9 +111,9 @@ impl SecureBuf {
/// If `new_size` is 0, returns `InvalidInput`. If allocation of the
/// new buffer fails, the original buffer is left intact and an error
/// is returned.
pub fn resize(&mut self, new_size: usize) -> Result<(), crate::NsignerError> {
pub fn resize(&mut self, new_size: usize) -> Result<(), crate::SignerError> {
if new_size == 0 {
return Err(crate::NsignerError::InvalidInput);
return Err(crate::SignerError::InvalidInput);
}
if new_size == self.size {
return Ok(());
+15 -15
View File
@@ -10,7 +10,7 @@
use crate::auth_envelope::AuthNonceCache;
use crate::dispatcher::DispatcherContext;
use crate::selector::{selector_resolve, SelectorRequest};
use crate::NsignerError;
use crate::SignerError;
use std::net::TcpListener;
use std::os::unix::net::UnixListener;
@@ -93,14 +93,14 @@ impl ServerContext {
}
/// Start listening. Returns error on bind failure.
pub fn start(&mut self) -> Result<(), NsignerError> {
pub fn start(&mut self) -> Result<(), SignerError> {
match self.listen_mode {
ListenMode::Unix => {
// Abstract namespace: bind via libc (sun_path[0] = '\0')
let listener = bind_abstract_unix(&self.socket_name)?;
listener
.set_nonblocking(true)
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
self.listener = Some(listener);
self.running = true;
Ok(())
@@ -113,10 +113,10 @@ impl ServerContext {
.or_else(|| self.socket_name.strip_prefix("http:"))
.unwrap_or(&self.socket_name);
let listener = TcpListener::bind(addr)
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
listener
.set_nonblocking(true)
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
self.tcp_listener = Some(listener);
self.running = true;
Ok(())
@@ -142,7 +142,7 @@ impl ServerContext {
pub fn handle_one(
&mut self,
dispatcher: &mut DispatcherContext,
) -> Result<Option<String>, NsignerError> {
) -> Result<Option<String>, SignerError> {
if let Some(ref listener) = self.listener {
match listener.accept() {
Ok((stream, _)) => {
@@ -153,7 +153,7 @@ impl ServerContext {
let mut reader = stream
.try_clone()
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
let mut writer = stream;
// Read framed request. A connection with no data yet
@@ -182,7 +182,7 @@ impl ServerContext {
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
return Ok(None); // Nothing pending
}
Err(e) => return Err(NsignerError::IoFailed(e.to_string())),
Err(e) => return Err(SignerError::IoFailed(e.to_string())),
}
}
@@ -198,7 +198,7 @@ impl ServerContext {
let mut reader = stream
.try_clone()
.map_err(|e| NsignerError::IoFailed(e.to_string()))?;
.map_err(|e| SignerError::IoFailed(e.to_string()))?;
let mut writer = stream;
let request = if self.listen_mode == ListenMode::Http {
@@ -232,7 +232,7 @@ impl ServerContext {
Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => {
return Ok(None);
}
Err(e) => return Err(NsignerError::IoFailed(e.to_string())),
Err(e) => return Err(SignerError::IoFailed(e.to_string())),
}
}
@@ -358,16 +358,16 @@ impl ServerContext {
///
/// Rust's safe `UnixListener::bind` rejects paths containing null bytes,
/// so abstract sockets (sun_path[0] = '\0') must be bound via libc.
fn bind_abstract_unix(name: &str) -> Result<UnixListener, NsignerError> {
fn bind_abstract_unix(name: &str) -> Result<UnixListener, SignerError> {
use std::os::unix::io::FromRawFd;
if name.len() >= 107 {
return Err(NsignerError::InvalidInput);
return Err(SignerError::InvalidInput);
}
let fd = unsafe { libc::socket(libc::AF_UNIX, libc::SOCK_STREAM, 0) };
if fd < 0 {
return Err(NsignerError::IoFailed("socket() failed".into()));
return Err(SignerError::IoFailed("socket() failed".into()));
}
// Build sockaddr_un with abstract namespace (sun_path[0] = '\0')
@@ -391,14 +391,14 @@ fn bind_abstract_unix(name: &str) -> Result<UnixListener, NsignerError> {
if rc != 0 {
let err = std::io::Error::last_os_error();
unsafe { libc::close(fd) };
return Err(NsignerError::IoFailed(format!("bind: {}", err)));
return Err(SignerError::IoFailed(format!("bind: {}", err)));
}
let rc = unsafe { libc::listen(fd, 16) };
if rc != 0 {
let err = std::io::Error::last_os_error();
unsafe { libc::close(fd) };
return Err(NsignerError::IoFailed(format!("listen: {}", err)));
return Err(SignerError::IoFailed(format!("listen: {}", err)));
}
// Wrap the raw fd in a UnixListener
+22 -22
View File
@@ -1,23 +1,23 @@
//! Socket naming — sequential abstract socket name generation.
//!
//! Generates names in the format `nsigner01`, `nsigner02`, … incrementing
//! Generates names in the format `signer01`, `signer02`, … incrementing
//! until an unused name is found (by checking /proc/net/unix).
//!
//! The `nsigner` prefix is required for compatibility with the C
//! `nsigner_client` / `nsigner_transport_list_unix`, which scans
//! /proc/net/unix for the literal prefix `@nsigner`.
//! The `signer` prefix is required for compatibility with the C
//! `signer_client` / `signer_transport_list_unix`, which scans
//! /proc/net/unix for the literal prefix `@signer`.
/// Prefix for generated socket names.
pub const SOCKET_NAME_PREFIX: &str = "nsigner";
pub const SOCKET_NAME_PREFIX: &str = "signer";
/// Generate a socket name: `nsigner01`, `nsigner02`, …
/// Generate a socket name: `signer01`, `signer02`, …
///
/// Scans /proc/net/unix for already-running nsigner sockets and picks the
/// Scans /proc/net/unix for already-running signer sockets and picks the
/// lowest unused number (starting at 1, zero-padded to 2 digits).
pub fn socket_name_random() -> Result<String, crate::NsignerError> {
pub fn socket_name_random() -> Result<String, crate::SignerError> {
let in_use = list_sockets();
// Try nsigner01, nsigner02, … up to nsigner99
// Try signer01, signer02, … up to signer99
for n in 1..=99u32 {
let candidate = format!("{}{:02}", SOCKET_NAME_PREFIX, n);
if !in_use.contains(&candidate) {
@@ -25,7 +25,7 @@ pub fn socket_name_random() -> Result<String, crate::NsignerError> {
}
}
// Fallback: nsigner100, nsigner101, … (no zero-padding beyond 99)
// Fallback: signer100, signer101, … (no zero-padding beyond 99)
for n in 100..=9999u32 {
let candidate = format!("{}{}", SOCKET_NAME_PREFIX, n);
if !in_use.contains(&candidate) {
@@ -33,22 +33,22 @@ pub fn socket_name_random() -> Result<String, crate::NsignerError> {
}
}
Err(crate::NsignerError::Internal(
"no available socket name (nsigner01..nsigner9999 all in use)".into(),
Err(crate::SignerError::Internal(
"no available socket name (signer01..signer9999 all in use)".into(),
))
}
/// List running nsigner abstract sockets by reading /proc/net/unix.
/// List running signer abstract sockets by reading /proc/net/unix.
///
/// Matches the C `nsigner_transport_list_unix` scan: looks for the literal
/// prefix `@nsigner` in the path column.
/// Matches the C `signer_transport_list_unix` scan: looks for the literal
/// prefix `@signer` in the path column.
pub fn list_sockets() -> Vec<String> {
let mut found = Vec::new();
if let Ok(content) = std::fs::read_to_string("/proc/net/unix") {
for line in content.lines() {
// Look for @nsigner prefix in the path column (matches C client scan)
if let Some(pos) = line.find("@nsigner") {
// Look for @signer prefix in the path column (matches C client scan)
if let Some(pos) = line.find("@signer") {
let rest = &line[pos + 1..]; // skip @
// Extract the name (up to whitespace or end of line)
let name: String = rest
@@ -65,14 +65,14 @@ pub fn list_sockets() -> Vec<String> {
found
}
/// Discover a single running nsigner socket.
/// Discover a single running signer socket.
/// Returns Ok(name) if exactly one is found, Err if zero or multiple.
pub fn discover_single_socket() -> Result<String, crate::NsignerError> {
pub fn discover_single_socket() -> Result<String, crate::SignerError> {
let sockets = list_sockets();
if sockets.len() == 1 {
Ok(sockets[0].clone())
} else {
Err(crate::NsignerError::NotFound)
Err(crate::SignerError::NotFound)
}
}
@@ -83,8 +83,8 @@ mod tests {
#[test]
fn test_socket_name_random() {
let name = socket_name_random().unwrap();
assert!(name.starts_with("nsigner"));
// Should be nsigner01..nsigner99 (8 chars) or nsigner100+ (9+ chars)
assert!(name.starts_with("signer"));
// Should be signer01..signer99 (8 chars) or signer100+ (9+ chars)
assert!(name.len() >= 8);
}
}
+6 -6
View File
@@ -10,7 +10,7 @@ use crate::key_store::KeyStore;
use crate::mnemonic::MnemonicState;
use crate::role_table::{RoleCurve, RolePurpose, RoleTable};
use crate::server::{AuthMode, ListenMode, ServerContext};
use crate::NsignerError;
use crate::SignerError;
use ratatui::layout::{Constraint, Layout, Rect, Spacing};
use ratatui::style::{Modifier, Style};
@@ -36,7 +36,7 @@ pub const TRANSPORT_HTTP: u8 = 0x08;
/// Qrexec service name used for the bridge transport.
/// Matches NSIGNER_QREXEC_SERVICE_NAME in n_signer/src/main.c.
pub const QREXEC_SERVICE_NAME: &str = "qubes.NsignerRpc";
pub const QREXEC_SERVICE_NAME: &str = "qubes.SignerRpc";
/// Check if a word is in the BIP-39 English wordlist.
fn is_valid_bip39_word(word: &str) -> bool {
@@ -535,7 +535,7 @@ impl App {
}
/// Start the server for the currently selected transport.
fn start_server(&mut self) -> Result<(), NsignerError> {
fn start_server(&mut self) -> Result<(), SignerError> {
// Pick the first active transport by priority (Unix > Qrexec > TCP > HTTP).
let (listen_mode, server_name) = if self.transport_toggles[0] {
(ListenMode::Unix, self.socket_name.clone())
@@ -652,7 +652,7 @@ impl App {
self.transport_mask_apply();
match self.start_server() {
Ok(()) => {
self.activity_log.add("nsigner started");
self.activity_log.add("signer started");
}
Err(e) => {
self.activity_log.add(&format!("server start failed: {}", e));
@@ -781,7 +781,7 @@ impl App {
}
match self.start_server() {
Ok(()) => {
self.activity_log.add("nsigner started");
self.activity_log.add("signer started");
}
Err(e) => {
self.activity_log.add(&format!("server start failed: {}", e));
@@ -1484,7 +1484,7 @@ impl App {
// client command that callers use to reach the signer,
// matching the connection info in n_signer's main.c.
lines.push(Line::from(format!(" qrexec service: {}", QREXEC_SERVICE_NAME)));
lines.push(Line::from(format!(" nsigner_client --qrexec <qube>:{}", QREXEC_SERVICE_NAME)));
lines.push(Line::from(format!(" signer_client --qrexec <qube>:{}", QREXEC_SERVICE_NAME)));
} else {
lines.push(Line::from(" (inactive)"));
}
+15 -15
View File
@@ -4,7 +4,7 @@
//! caller sends a request with a role name → selector resolution → dispatch.
//! No policy table, no approval prompt. Knowing a valid role name is sufficient.
use nsigner::{
use signer::{
alg_cache::AlgorithmKeyCache,
dispatcher::DispatcherContext,
key_store::KeyStore,
@@ -85,15 +85,15 @@ fn spawn_server_loop(
/// Send a framed request to a Unix socket and return the response.
fn send_request(socket_name: &str, request: &str) -> String {
let mut stream = nsigner::transport::connect_abstract_unix(socket_name).unwrap();
nsigner::transport::send_framed(&mut stream, request).unwrap();
nsigner::transport::recv_framed(&mut stream).unwrap()
let mut stream = signer::transport::connect_abstract_unix(socket_name).unwrap();
signer::transport::send_framed(&mut stream, request).unwrap();
signer::transport::recv_framed(&mut stream).unwrap()
}
/// Wait for the server socket to be ready.
fn wait_for_server(socket_name: &str, attempts: u32) {
for _ in 0..attempts {
if nsigner::transport::connect_abstract_unix(socket_name).is_ok() {
if signer::transport::connect_abstract_unix(socket_name).is_ok() {
return;
}
std::thread::sleep(Duration::from_millis(20));
@@ -103,7 +103,7 @@ fn wait_for_server(socket_name: &str, attempts: u32) {
#[test]
fn test_get_info_works() {
let socket_name = format!("nsigner_test_info_{}", std::process::id());
let socket_name = format!("signer_test_info_{}", std::process::id());
let (server, role_table, mnemonic, key_store, alg_cache) = setup_server(&socket_name);
let (handle, stop) = spawn_server_loop(server, role_table, mnemonic, key_store, alg_cache);
@@ -113,14 +113,14 @@ fn test_get_info_works() {
let resp = send_request(&socket_name, r#"{"id":"1","method":"get_info","params":[]}"#);
assert!(resp.contains("\"result\""), "get_info failed: {}", resp);
let _ = nsigner::transport::connect_abstract_unix(&socket_name);
let _ = signer::transport::connect_abstract_unix(&socket_name);
stop.store(true, std::sync::atomic::Ordering::SeqCst);
handle.join().ok();
}
#[test]
fn test_role_as_password_allows_with_valid_role() {
let socket_name = format!("nsigner_test_allow_{}", std::process::id());
let socket_name = format!("signer_test_allow_{}", std::process::id());
let (server, role_table, mnemonic, key_store, alg_cache) = setup_server(&socket_name);
let (handle, stop) = spawn_server_loop(server, role_table, mnemonic, key_store, alg_cache);
@@ -136,14 +136,14 @@ fn test_role_as_password_allows_with_valid_role() {
assert!(resp.contains("e8bcf3823669444d0b49ad45d65088635d9fd8500a75b5f20b59abefa56a144f"),
"expected pubkey in result, got: {}", resp);
let _ = nsigner::transport::connect_abstract_unix(&socket_name);
let _ = signer::transport::connect_abstract_unix(&socket_name);
stop.store(true, std::sync::atomic::Ordering::SeqCst);
handle.join().ok();
}
#[test]
fn test_unknown_role_returns_selector_error() {
let socket_name = format!("nsigner_test_unknown_{}", std::process::id());
let socket_name = format!("signer_test_unknown_{}", std::process::id());
let (server, role_table, mnemonic, key_store, alg_cache) = setup_server(&socket_name);
let (handle, stop) = spawn_server_loop(server, role_table, mnemonic, key_store, alg_cache);
@@ -156,14 +156,14 @@ fn test_unknown_role_returns_selector_error() {
);
assert!(resp.contains("unknown_role"), "expected unknown_role, got: {}", resp);
let _ = nsigner::transport::connect_abstract_unix(&socket_name);
let _ = signer::transport::connect_abstract_unix(&socket_name);
stop.store(true, std::sync::atomic::Ordering::SeqCst);
handle.join().ok();
}
#[test]
fn test_ed25519_sign_allowed_no_authorization() {
let socket_name = format!("nsigner_test_alg_{}", std::process::id());
let socket_name = format!("signer_test_alg_{}", std::process::id());
let (server, role_table, mnemonic, key_store, alg_cache) = setup_server(&socket_name);
let (handle, stop) = spawn_server_loop(server, role_table, mnemonic, key_store, alg_cache);
@@ -179,14 +179,14 @@ fn test_ed25519_sign_allowed_no_authorization() {
assert!(resp.contains("\"result\""), "expected success, got: {}", resp);
assert!(resp.contains("signature"), "expected signature in result: {}", resp);
let _ = nsigner::transport::connect_abstract_unix(&socket_name);
let _ = signer::transport::connect_abstract_unix(&socket_name);
stop.store(true, std::sync::atomic::Ordering::SeqCst);
handle.join().ok();
}
#[test]
fn test_repeated_requests_all_allowed() {
let socket_name = format!("nsigner_test_repeat_{}", std::process::id());
let socket_name = format!("signer_test_repeat_{}", std::process::id());
let (server, role_table, mnemonic, key_store, alg_cache) = setup_server(&socket_name);
let (handle, stop) = spawn_server_loop(server, role_table, mnemonic, key_store, alg_cache);
@@ -202,7 +202,7 @@ fn test_repeated_requests_all_allowed() {
assert!(resp.contains("\"result\""), "request {} failed: {}", i, resp);
}
let _ = nsigner::transport::connect_abstract_unix(&socket_name);
let _ = signer::transport::connect_abstract_unix(&socket_name);
stop.store(true, std::sync::atomic::Ordering::SeqCst);
handle.join().ok();
}