6.0 KiB
6.0 KiB
n_signer Remote-Qube Login Fix
Problem
Signing in via the n_signer tab with the "Other Qube" transport silently fails, while the equivalent CLI works:
signer-client --qrexec nostr_signer:qubes.SignerRpc --role nostr_range --path "m/44'/1237'/0'/0/0" get-public-key
Root Causes
- qrexec is rejected outright.
on_login_clicked()insrc/login_dialog.rsreturns "This transport is not supported in this build" for the Other Qube transport (which is the dialog default), even thoughQrexecTransportexists in the nostr-signer crate. The Service field is never read by the handler at all. - Wrong wire protocol in the nostr-signer crate.
NsignerClient::call()sendsparamsas a JSON object with no requestid. The signer server's dispatcher requiresparamsto be an array (options object as the last element) and rejects objects withINVALID_REQUEST. Trait methods also use wrong verb names:get_public_keyinstead ofnostr_get_public_key,nip04_encryptinstead ofnostr_nip04_encrypt, etc. Even UNIX/TCP logins can never work against this server. - Errors are swallowed. All io errors map to
NostrError::NetworkFaileddiscarding the real message;qrexec-client-vmstderr is sent toStdio::null()(the working client inherits it); error responses are parsed as strings when the server sends{"code":N,"message":"..."}objects.
Wire Protocol — verified against /home/user/lt/signer/src/dispatcher.rs
Request: {"id":"1","method":...,"params":[...]} — params MUST be an array; the options
object is the LAST array element.
Response: {"id":"...","result":...} or {"id":"...","error":{"code":N,"message":"..."}}
| Operation | Method | Params |
|---|---|---|
| Nostr pubkey | nostr_get_public_key |
[ {role, role_path} ] |
| Sign event | nostr_sign_event |
[ event_json, opts ] |
| NIP-04 enc/dec | nostr_nip04_encrypt / nostr_nip04_decrypt |
[ peer_hex, text, opts ] |
| NIP-44 enc/dec | nostr_nip44_encrypt / nostr_nip44_decrypt |
[ peer_hex, text, opts ] |
| Info | get_info |
[] |
| Alg pubkey | get_public_key |
[ {algorithm, index} ] |
| Alg sign | sign |
[ msg_hex, opts ] |
| Alg verify | verify |
[ msg_hex, sig_hex, opts ] |
| X25519 ECDH | derive_shared_secret |
[ peer_hex, opts ] |
| HMAC derive | derive |
[ data, opts with index ] |
| ML-KEM | encapsulate / decapsulate |
[ hex, opts ] |
| OTP | encrypt / decrypt |
[ text, opts with algorithm otp ] |
opts = {"role":"...","role_path":"..."} for nostr verbs;
{"algorithm":"...","index":N} for algorithm verbs.
Request Flow
sequenceDiagram
participant D as Login Dialog
participant S as NsignerSigner
participant Q as QrexecTransport
participant V as qrexec-client-vm
participant B as signer bridge on nostr_signer qube
participant U as signer unix socket
D->>S: from_transport + set_role_path
D->>S: get_public_key
S->>Q: call nostr_get_public_key
Q->>V: spawn qrexec-client-vm nostr_signer qubes.SignerRpc
V->>B: qrexec connection
B->>U: preamble qrexec_source
B->>U: framed JSON-RPC request
U-->>B: framed response
B-->>V: framed response
V-->>Q: stdout
Q-->>S: response JSON
S-->>D: pubkey hex
One request per qrexec connection — the crate's reconnect-per-call model is correct.
Changes
nostr_core_lib_rust/signer/src/nsigner.rs
NsignerClient::call(): takeVec<Value>params; add an id counter; parse error objects (message + code) intolast_error;eprintln!diagnostics on send/recv/parse failures.NostrSignerimpl onNsignerSigner: usenostr_*verbs with a selector-options helper that always includesroleand includesrole_pathwhen set.- Algorithm methods: correct verb names (
derive_shared_secret,encrypt/decryptwith algorithm otp), array params, structured result parsing (result.signature,result.shared_secret,result.valid,result.digest, ...). Adjustml_dsa_verifyto server semantics (server verifies against its own derived key). QrexecTransport: inherit stderr fromqrexec-client-vm;eprintln!the real spawn/io error messages.UnixTransport: abstract-socket support via libc (mirroring the reference client'sconnect_abstract_unix) so the UNIX Socket option works against this server.- Update unit tests to assert the corrected wire protocol.
sovereign_browser_rust/src/login_dialog.rs
- Service default
qubes.NsignerRpc→qubes.SignerRpc. - Read the service field; wire
QrexecTransportfor Other Qube (transport index 3) andSerialTransportfor USB Serial (index 0). eprintln!diagnostics: connection parameters, failures withlast_error, success with pubkey.
sovereign_browser_rust/src/key_store.rs
- Add
nsigner_servicefield toKeyStoreIdentity. - Implement the
Nsignerarm ofkey_store_create_signer().
sovereign_browser_rust/src/main.rs and src/menu.rs
- Print sign-in result (method + pubkey) to console in
do_login().
Verification
cargo testin nostr_core_lib_rust/signer.cargo buildin sovereign_browser_rust.- Manual: launch browser → n_signer tab → defaults → Sign In. Console shows the
connection parameters and any errors; pubkey matches the CLI output
8ff74724....
Known Limitations
- qrexec reads have no timeout (
ChildStdoutis blocking) — a hung service freezes the dialog; same limitation as the reference client. - The sign-in RPC runs on the GTK main thread (pre-existing design).
- The crate's auth-envelope format does not match the server's NIP-42 event envelope;
it is unused (browser never calls
set_auth, server auth is off) — out of scope.