v0.0.69 - Adopt n_signer role-based API: add --nsigner-role flag, Role entry in login dialog, use nostr_signer_nsigner_set_role_path() and nostr_signer_last_error(), sync nostr_core_lib to v0.6.13
This commit is contained in:
@@ -281,6 +281,7 @@ credentials.
|
|||||||
| `--nsigner-transport <t>` | (nsigner) `serial\|unix\|tcp\|qrexec` |
|
| `--nsigner-transport <t>` | (nsigner) `serial\|unix\|tcp\|qrexec` |
|
||||||
| `--nsigner-device <path>` | (nsigner) device path / socket / host:port / qube |
|
| `--nsigner-device <path>` | (nsigner) device path / socket / host:port / qube |
|
||||||
| `--nsigner-service <name>` | (nsigner) qrexec service name |
|
| `--nsigner-service <name>` | (nsigner) qrexec service name |
|
||||||
|
| `--nsigner-role <name>` | (nsigner) role name (default: nostr_range) |
|
||||||
| `--nsigner-index <n>` | (nsigner) key index (default 0) |
|
| `--nsigner-index <n>` | (nsigner) key index (default 0) |
|
||||||
| `--no-save-identity` | Do not persist identity to disk |
|
| `--no-save-identity` | Do not persist identity to disk |
|
||||||
| `--login-timeout <ms>` | Override agent login timeout (GTK dialog path) |
|
| `--login-timeout <ms>` | Override agent login timeout (GTK dialog path) |
|
||||||
|
|||||||
@@ -0,0 +1,283 @@
|
|||||||
|
# n_signer / nostr_core_lib Adoption Plan
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Adopt the major changes from n_signer (v0.0.57 → v0.1.21) and nostr_core_lib (v0.6.10 → v0.6.13) into sovereign_browser. The key change is migrating from the deprecated `nostr_index` selector to the new `role` + `role_path` selector model.
|
||||||
|
|
||||||
|
## Background
|
||||||
|
|
||||||
|
### n_signer API Changes
|
||||||
|
|
||||||
|
The n_signer project redesigned its API:
|
||||||
|
|
||||||
|
1. **Unified verb naming**: All Nostr-specific verbs now use `nostr_` prefix
|
||||||
|
- `get_public_key` → `nostr_get_public_key` (for Nostr keys)
|
||||||
|
- `sign_event` → `nostr_sign_event`
|
||||||
|
- `nip04_encrypt` → `nostr_nip04_encrypt`
|
||||||
|
- etc.
|
||||||
|
|
||||||
|
2. **Algorithm-based API**: New generic verbs for non-Nostr crypto
|
||||||
|
- `get_public_key`, `sign`, `verify`, `encapsulate`, `decapsulate`, `derive_shared_secret`, `derive`, `encrypt`, `decrypt`
|
||||||
|
- Support for secp256k1, ed25519, x25519, ml-dsa-65, slh-dsa-128s, ml-kem-768, otp
|
||||||
|
|
||||||
|
3. **Role-based selectors**: `nostr_index` is **deprecated**
|
||||||
|
- Old: `{"nostr_index": N}` → automatically expanded to `m/44'/1237'/N'/0/0`
|
||||||
|
- New: `{"role": "<name>", "role_path": "<full-bip44-path>"}` — both required together
|
||||||
|
- The role must be pre-registered in the signer's wizard
|
||||||
|
- The role_path must match the role's registered template
|
||||||
|
|
||||||
|
4. **New error codes**:
|
||||||
|
- `2006 nostr_index_deprecated` — `nostr_index` is removed
|
||||||
|
- `2007 index_deprecated` — `index` removed for nostr verbs
|
||||||
|
- `2008 role_required` — `role` required when using `role_path`
|
||||||
|
- `2009 path_required` — `role_path` required for roles with variable templates
|
||||||
|
|
||||||
|
### nostr_core_lib Changes
|
||||||
|
|
||||||
|
The upstream library added:
|
||||||
|
|
||||||
|
1. **New public API** in `nostr_signer.h`:
|
||||||
|
- `nostr_signer_last_error()` — human-readable error from last failed call
|
||||||
|
- `nostr_signer_get_info()` — signer metadata (name, version, verbs, algorithms)
|
||||||
|
- `nostr_signer_get_public_key_alg()` — algorithm-based public key
|
||||||
|
- `nostr_signer_sign()` / `nostr_signer_verify()` — generic sign/verify
|
||||||
|
- `nostr_signer_encapsulate()` / `nostr_signer_decapsulate()` — ML-KEM-768
|
||||||
|
- `nostr_signer_derive_shared_secret()` — X25519
|
||||||
|
- `nostr_signer_otp_encrypt()` / `nostr_signer_otp_decrypt()` — OTP
|
||||||
|
- `nostr_signer_mine_event()` — NIP-13 proof-of-work
|
||||||
|
- `nostr_signer_nsigner_from_transport()` / `from_client()` — flexible constructors
|
||||||
|
- `nostr_signer_nsigner_set_role_path()` — set full BIP-44 path
|
||||||
|
|
||||||
|
2. **Changed behavior**:
|
||||||
|
- `nostr_signer_nsigner_set_nostr_index()` is now a **compatibility shim** that expands to `role="main"` + `role_path="m/44'/1237'/N'/0/0"`
|
||||||
|
- Remote backend sends `role` + `role_path` instead of `nostr_index`
|
||||||
|
- New internal fields: `role_path[128]`, `has_role_path`, `derive_index`, `has_derive_index`
|
||||||
|
|
||||||
|
3. **New files**: `nip034.c/h` (NIP-34 git stuff)
|
||||||
|
|
||||||
|
## Current sovereign_browser Usage
|
||||||
|
|
||||||
|
The browser currently uses these deprecated APIs:
|
||||||
|
|
||||||
|
| File | Line | Current Usage |
|
||||||
|
|------|------|---------------|
|
||||||
|
| `src/key_store.c` | 84-86 | `nostr_signer_nsigner_set_nostr_index(signer, identity->nsigner_index)` |
|
||||||
|
| `src/key_store.h` | 53 | `int nsigner_index;` in `key_store_identity_t` |
|
||||||
|
| `src/login_dialog.c` | 524 | `nostr_signer_nsigner_set_nostr_index(signer, nostr_index)` |
|
||||||
|
| `src/login_dialog.c` | 1043-1089 | Key index spinner UI with `m/44'/1237'/N'/0/0` label |
|
||||||
|
| `src/agent_login.c` | 394 | `nostr_signer_nsigner_set_nostr_index(signer, index)` |
|
||||||
|
| `src/cli.c` | 63-66 | `--nsigner-index` CLI flag |
|
||||||
|
| `src/cli.h` | 64 | `int nsigner_index;` in `cli_args_t` |
|
||||||
|
|
||||||
|
## Adoption Plan
|
||||||
|
|
||||||
|
### Phase 1: Update Vendored nostr_core_lib
|
||||||
|
|
||||||
|
Update the vendored library from v0.6.10 to v0.6.13.
|
||||||
|
|
||||||
|
**Tasks:**
|
||||||
|
1. Sync `nostr_core_lib/` directory with upstream v0.6.13
|
||||||
|
2. Verify build succeeds with `make`
|
||||||
|
3. Test basic login flows still work (local, seed, readonly)
|
||||||
|
|
||||||
|
**New APIs available after update:**
|
||||||
|
- `nostr_signer_last_error()` — better error messages
|
||||||
|
- `nostr_signer_get_info()` — signer capability detection
|
||||||
|
- `nostr_signer_nsigner_set_role_path()` — new selector API
|
||||||
|
- Algorithm-based verbs (for future use)
|
||||||
|
|
||||||
|
### Phase 2: Migrate to role + role_path Selector
|
||||||
|
|
||||||
|
Replace the deprecated `nostr_index` with explicit `role` + `role_path`.
|
||||||
|
|
||||||
|
#### 2.1 Update Data Structures
|
||||||
|
|
||||||
|
**`src/key_store.h`** — Change `key_store_identity_t`:
|
||||||
|
```c
|
||||||
|
/* BEFORE */
|
||||||
|
int nsigner_index; /* nostr_index (NIP-06 m/44'/1237'/N'/0/0) */
|
||||||
|
|
||||||
|
/* AFTER */
|
||||||
|
int nsigner_index; /* NIP-06 index (0, 1, 2, ...) */
|
||||||
|
char nsigner_role[64]; /* role name (default: "nostr_range") */
|
||||||
|
```
|
||||||
|
|
||||||
|
**`src/cli.h`** — Change `cli_args_t`:
|
||||||
|
```c
|
||||||
|
/* BEFORE */
|
||||||
|
int nsigner_index; /* --nsigner-index (-1 = unset) */
|
||||||
|
|
||||||
|
/* AFTER */
|
||||||
|
int nsigner_index; /* --nsigner-index (-1 = unset) */
|
||||||
|
char *nsigner_role; /* --nsigner-role (default: "nostr_range") */
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2 Update CLI Flags
|
||||||
|
|
||||||
|
**`src/cli.c`**:
|
||||||
|
- Keep `--nsigner-index <n>` (unchanged)
|
||||||
|
- Add `--nsigner-role <name>` (default: "nostr_range")
|
||||||
|
- The role + index are combined client-side to form the full role_path
|
||||||
|
- Update help text
|
||||||
|
|
||||||
|
#### 2.3 Update Login Dialog UI
|
||||||
|
|
||||||
|
**`src/login_dialog.c`** — Add a Role entry field after the Service entry. The Key Index spinner stays the same.
|
||||||
|
|
||||||
|
**BEFORE:**
|
||||||
|
```
|
||||||
|
Connect to n_signer hardware signer:
|
||||||
|
|
||||||
|
Transport: [USB Serial / UNIX Socket / TCP / Other Qube ▼]
|
||||||
|
Target Qube: [nostr_signer________________________]
|
||||||
|
Service: [qubes.NsignerRpc________]
|
||||||
|
Key Index (m/44'/1237'/0'/0/0): [0]
|
||||||
|
|
||||||
|
[n_signer is a foreground, RAM-only hardware signer...]
|
||||||
|
```
|
||||||
|
|
||||||
|
**AFTER:**
|
||||||
|
```
|
||||||
|
Connect to n_signer hardware signer:
|
||||||
|
|
||||||
|
Transport: [USB Serial / UNIX Socket / TCP / Other Qube ▼]
|
||||||
|
Target Qube: [nostr_signer________________________]
|
||||||
|
Service: [qubes.NsignerRpc________]
|
||||||
|
Role: [nostr_range_________________]
|
||||||
|
Key Index (m/44'/1237'/0'/0/0): [0]
|
||||||
|
|
||||||
|
[n_signer is a foreground, RAM-only hardware signer...]
|
||||||
|
```
|
||||||
|
|
||||||
|
The Role field is a simple text entry with default value "nostr_range". The Key Index spinner remains unchanged — it still selects the NIP-06 index. The role + index are combined client-side to form the full `role_path` (e.g. role="nostr_range" + index=0 → role_path="m/44'/1237'/0'/0/0").
|
||||||
|
|
||||||
|
#### 2.4 Update Signer Creation
|
||||||
|
|
||||||
|
**`src/key_store.c`** — `key_store_create_signer()`:
|
||||||
|
```c
|
||||||
|
/* BEFORE */
|
||||||
|
if (signer && identity->nsigner_index >= 0) {
|
||||||
|
nostr_signer_nsigner_set_nostr_index(signer, identity->nsigner_index);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* AFTER */
|
||||||
|
if (signer) {
|
||||||
|
/* Build role_path from role + index: m/44'/1237'/N'/0/0 */
|
||||||
|
char role_path[128];
|
||||||
|
snprintf(role_path, sizeof(role_path), "m/44'/1237'/%d'/0/0", identity->nsigner_index);
|
||||||
|
nostr_signer_nsigner_set_role_path(signer, role_path);
|
||||||
|
/* Note: role is passed to the constructor */
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**`src/login_dialog.c`** — `on_sign_in_clicked()`:
|
||||||
|
```c
|
||||||
|
/* BEFORE */
|
||||||
|
int rc_idx = nostr_signer_nsigner_set_nostr_index(signer, nostr_index);
|
||||||
|
|
||||||
|
/* AFTER */
|
||||||
|
/* Build role_path from role + index */
|
||||||
|
char role_path[128];
|
||||||
|
snprintf(role_path, sizeof(role_path), "m/44'/1237'/%d'/0/0", nostr_index);
|
||||||
|
int rc_idx = nostr_signer_nsigner_set_role_path(signer, role_path);
|
||||||
|
```
|
||||||
|
|
||||||
|
**`src/agent_login.c`** — `login_nsigner()`:
|
||||||
|
```c
|
||||||
|
/* BEFORE */
|
||||||
|
int rc = nostr_signer_nsigner_set_nostr_index(signer, index);
|
||||||
|
|
||||||
|
/* AFTER */
|
||||||
|
/* Get role from params (default: "nostr_range") */
|
||||||
|
const char *role = cJSON_GetStringValue(cJSON_GetObjectItem(params, "role"));
|
||||||
|
if (!role || !role[0]) role = "nostr_range";
|
||||||
|
/* Build role_path from index */
|
||||||
|
char role_path[128];
|
||||||
|
snprintf(role_path, sizeof(role_path), "m/44'/1237'/%d'/0/0", index);
|
||||||
|
int rc = nostr_signer_nsigner_set_role_path(signer, role_path);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.5 Update MCP/Agent Tools
|
||||||
|
|
||||||
|
**`src/agent_mcp.c`** — Update login tool schema:
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"method": "nsigner",
|
||||||
|
"transport": "qrexec",
|
||||||
|
"device": "nostr_signer",
|
||||||
|
"service": "qubes.NsignerRpc",
|
||||||
|
"role": "nostr_range",
|
||||||
|
"index": 0
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Error Handling Improvements
|
||||||
|
|
||||||
|
Use the new `nostr_signer_last_error()` for better error messages.
|
||||||
|
|
||||||
|
**`src/login_dialog.c`** and **`src/agent_login.c`**:
|
||||||
|
```c
|
||||||
|
/* BEFORE */
|
||||||
|
const char *err_str = nostr_strerror(rc);
|
||||||
|
|
||||||
|
/* AFTER */
|
||||||
|
const char *err_str = nostr_signer_last_error(signer);
|
||||||
|
if (!err_str || err_str[0] == '\0') {
|
||||||
|
err_str = nostr_strerror(rc);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 4: Testing
|
||||||
|
|
||||||
|
1. **Unit tests**: Update `tests/test_bookmarks_tree.c` if needed
|
||||||
|
2. **Integration tests**:
|
||||||
|
- Test n_signer login with each transport (serial, unix, tcp, qrexec)
|
||||||
|
- Test with different roles (main, nostr_range, custom)
|
||||||
|
- Test error cases (unknown role, path mismatch, etc.)
|
||||||
|
3. **MCP tests**: Test agent login with new role/path parameters
|
||||||
|
|
||||||
|
## Migration Notes
|
||||||
|
|
||||||
|
### Backward Compatibility
|
||||||
|
|
||||||
|
- **Saved identities**: The browser does not persist n_signer identities to disk (in-memory only), so no migration of saved data is needed.
|
||||||
|
- **CLI scripts**: `--nsigner-index` still works; `--nsigner-role` is optional (defaults to "nostr_range").
|
||||||
|
- **MCP/API clients**: The `login` tool's `index` parameter still works; `role` is optional.
|
||||||
|
|
||||||
|
### Default Role
|
||||||
|
|
||||||
|
The default role is **"nostr_range"**:
|
||||||
|
- This matches the n_signer wizard preset #2
|
||||||
|
- The client combines role + index to form the full path: `m/44'/1237'/N'/0/0`
|
||||||
|
- The n_signer server verifies the path matches the role's registered template
|
||||||
|
|
||||||
|
### How It Works
|
||||||
|
|
||||||
|
1. User selects a role (default: "nostr_range") and key index (default: 0)
|
||||||
|
2. Client builds the full derivation path: `m/44'/1237'/<index>'/0/0`
|
||||||
|
3. Client sends `{"role": "<role>", "role_path": "<path>"}` to the signer
|
||||||
|
4. Signer verifies the path matches the role's template and derives the key
|
||||||
|
|
||||||
|
## Files to Modify
|
||||||
|
|
||||||
|
| File | Changes |
|
||||||
|
|------|---------|
|
||||||
|
| `nostr_core_lib/` | Sync to v0.6.13 |
|
||||||
|
| `src/key_store.h` | Replace `nsigner_index` with `nsigner_role` + `nsigner_role_path` |
|
||||||
|
| `src/key_store.c` | Update `key_store_create_signer()` to use new API |
|
||||||
|
| `src/login_dialog.c` | Replace index spinner with role/path UI, update sign-in logic |
|
||||||
|
| `src/agent_login.c` | Update `login_nsigner()` to accept role/path params |
|
||||||
|
| `src/cli.c` | Replace `--nsigner-index` with `--nsigner-role` + `--nsigner-role-path` |
|
||||||
|
| `src/cli.h` | Update `cli_args_t` structure |
|
||||||
|
| `src/agent_mcp.c` | Update login tool schema and handler |
|
||||||
|
| `README.md` | Update CLI flags documentation |
|
||||||
|
| `.roo/agents.md` | Update login tool documentation |
|
||||||
|
|
||||||
|
## Success Criteria
|
||||||
|
|
||||||
|
1. Browser builds successfully with updated nostr_core_lib
|
||||||
|
2. n_signer login works with all transports (serial, unix, tcp, qrexec)
|
||||||
|
3. Role + role_path selector works correctly
|
||||||
|
4. No use of deprecated `nostr_index` API
|
||||||
|
5. Error messages use `nostr_signer_last_error()` for clarity
|
||||||
|
6. MCP login tool accepts role/path parameters
|
||||||
+17
-7
@@ -330,6 +330,7 @@ static cJSON *login_nsigner(cJSON *params) {
|
|||||||
const char *transport = cJSON_GetStringValue(cJSON_GetObjectItem(params, "transport"));
|
const char *transport = cJSON_GetStringValue(cJSON_GetObjectItem(params, "transport"));
|
||||||
const char *device = cJSON_GetStringValue(cJSON_GetObjectItem(params, "device"));
|
const char *device = cJSON_GetStringValue(cJSON_GetObjectItem(params, "device"));
|
||||||
const char *service = cJSON_GetStringValue(cJSON_GetObjectItem(params, "service"));
|
const char *service = cJSON_GetStringValue(cJSON_GetObjectItem(params, "service"));
|
||||||
|
const char *role = cJSON_GetStringValue(cJSON_GetObjectItem(params, "role"));
|
||||||
cJSON *index_json = cJSON_GetObjectItem(params, "index");
|
cJSON *index_json = cJSON_GetObjectItem(params, "index");
|
||||||
int index = (index_json && cJSON_IsNumber(index_json)) ? index_json->valueint : 0;
|
int index = (index_json && cJSON_IsNumber(index_json)) ? index_json->valueint : 0;
|
||||||
|
|
||||||
@@ -340,6 +341,9 @@ static cJSON *login_nsigner(cJSON *params) {
|
|||||||
return make_error("MISSING_PARAM", "Provide 'device' (path, socket name, host:port, or qube name)");
|
return make_error("MISSING_PARAM", "Provide 'device' (path, socket name, host:port, or qube name)");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Use default role if not provided */
|
||||||
|
if (!role || !role[0]) role = "nostr_range";
|
||||||
|
|
||||||
/* Block SIGCHLD during n_signer calls (qrexec spawns children). */
|
/* Block SIGCHLD during n_signer calls (qrexec spawns children). */
|
||||||
sigset_t block_set, old_set;
|
sigset_t block_set, old_set;
|
||||||
sigemptyset(&block_set);
|
sigemptyset(&block_set);
|
||||||
@@ -350,10 +354,10 @@ static cJSON *login_nsigner(cJSON *params) {
|
|||||||
const char *transport_name = "unknown";
|
const char *transport_name = "unknown";
|
||||||
|
|
||||||
if (strcmp(transport, "serial") == 0) {
|
if (strcmp(transport, "serial") == 0) {
|
||||||
signer = nostr_signer_nsigner_serial(device, NULL, 15000);
|
signer = nostr_signer_nsigner_serial(device, role, 15000);
|
||||||
transport_name = "serial";
|
transport_name = "serial";
|
||||||
} else if (strcmp(transport, "unix") == 0) {
|
} else if (strcmp(transport, "unix") == 0) {
|
||||||
signer = nostr_signer_nsigner_unix(device, NULL, 15000);
|
signer = nostr_signer_nsigner_unix(device, role, 15000);
|
||||||
transport_name = "unix";
|
transport_name = "unix";
|
||||||
} else if (strcmp(transport, "tcp") == 0) {
|
} else if (strcmp(transport, "tcp") == 0) {
|
||||||
char host[256] = {0};
|
char host[256] = {0};
|
||||||
@@ -369,11 +373,11 @@ static cJSON *login_nsigner(cJSON *params) {
|
|||||||
} else {
|
} else {
|
||||||
strncpy(host, device, sizeof(host) - 1);
|
strncpy(host, device, sizeof(host) - 1);
|
||||||
}
|
}
|
||||||
signer = nostr_signer_nsigner_tcp(host, port, NULL, 15000);
|
signer = nostr_signer_nsigner_tcp(host, port, role, 15000);
|
||||||
transport_name = "tcp";
|
transport_name = "tcp";
|
||||||
} else if (strcmp(transport, "qrexec") == 0) {
|
} else if (strcmp(transport, "qrexec") == 0) {
|
||||||
const char *svc = (service && service[0]) ? service : "qubes.NsignerRpc";
|
const char *svc = (service && service[0]) ? service : "qubes.NsignerRpc";
|
||||||
signer = nostr_signer_nsigner_qrexec(device, svc, NULL, 30000);
|
signer = nostr_signer_nsigner_qrexec(device, svc, role, 30000);
|
||||||
transport_name = "qrexec";
|
transport_name = "qrexec";
|
||||||
} else {
|
} else {
|
||||||
sigprocmask(SIG_SETMASK, &old_set, NULL);
|
sigprocmask(SIG_SETMASK, &old_set, NULL);
|
||||||
@@ -391,17 +395,23 @@ static cJSON *login_nsigner(cJSON *params) {
|
|||||||
* rejects the call with an auth error that surfaces as "code -310". */
|
* rejects the call with an auth error that surfaces as "code -310". */
|
||||||
key_store_nsigner_set_default_auth(signer);
|
key_store_nsigner_set_default_auth(signer);
|
||||||
|
|
||||||
int rc = nostr_signer_nsigner_set_nostr_index(signer, index);
|
/* Build role_path from index: m/44'/1237'/N'/0/0 */
|
||||||
|
char role_path[128];
|
||||||
|
snprintf(role_path, sizeof(role_path), "m/44'/1237'/%d'/0/0", index);
|
||||||
|
int rc = nostr_signer_nsigner_set_role_path(signer, role_path);
|
||||||
if (rc != NOSTR_SUCCESS) {
|
if (rc != NOSTR_SUCCESS) {
|
||||||
nostr_signer_free(signer);
|
nostr_signer_free(signer);
|
||||||
sigprocmask(SIG_SETMASK, &old_set, NULL);
|
sigprocmask(SIG_SETMASK, &old_set, NULL);
|
||||||
return make_error("NSIGNER_INDEX", "Failed to set nostr_index on n_signer.");
|
return make_error("NSIGNER_INDEX", "Failed to set role_path on n_signer.");
|
||||||
}
|
}
|
||||||
|
|
||||||
char pubkey_hex[65];
|
char pubkey_hex[65];
|
||||||
rc = nostr_signer_get_public_key(signer, pubkey_hex);
|
rc = nostr_signer_get_public_key(signer, pubkey_hex);
|
||||||
if (rc != NOSTR_SUCCESS) {
|
if (rc != NOSTR_SUCCESS) {
|
||||||
const char *err_str = nostr_strerror(rc);
|
const char *err_str = nostr_signer_last_error(signer);
|
||||||
|
if (!err_str || err_str[0] == '\0' || strcmp(err_str, "no error details available") == 0) {
|
||||||
|
err_str = nostr_strerror(rc);
|
||||||
|
}
|
||||||
g_printerr("[agent-login] n_signer get_public_key failed: rc=%d (%s)\n",
|
g_printerr("[agent-login] n_signer get_public_key failed: rc=%d (%s)\n",
|
||||||
rc, err_str ? err_str : "?");
|
rc, err_str ? err_str : "?");
|
||||||
nostr_signer_free(signer);
|
nostr_signer_free(signer);
|
||||||
|
|||||||
+2
-2
@@ -132,7 +132,7 @@ const mcp_tool_def_t tool_defs[] = {
|
|||||||
|
|
||||||
{"login",
|
{"login",
|
||||||
"Log in to the browser with a Nostr identity. Methods: 'random' (generate a fresh random key — quickest for testing), 'local' (nsec or hex privkey), 'seed' (BIP-39 mnemonic), 'readonly' (npub), 'nip46' (bunker:// URL), 'nsigner' (hardware signer). Must be called before browser tools work.",
|
"Log in to the browser with a Nostr identity. Methods: 'random' (generate a fresh random key — quickest for testing), 'local' (nsec or hex privkey), 'seed' (BIP-39 mnemonic), 'readonly' (npub), 'nip46' (bunker:// URL), 'nsigner' (hardware signer). Must be called before browser tools work.",
|
||||||
"{\"type\":\"object\",\"properties\":{\"method\":{\"type\":\"string\",\"enum\":[\"random\",\"local\",\"seed\",\"readonly\",\"nip46\",\"nsigner\"]},\"nsec\":{\"type\":\"string\",\"description\":\"nsec1... string (method: local)\"},\"privkey_hex\":{\"type\":\"string\",\"description\":\"64-char hex private key (method: local)\"},\"mnemonic\":{\"type\":\"string\",\"description\":\"12 or 24 BIP-39 words (method: seed)\"},\"account\":{\"type\":\"integer\",\"default\":0,\"description\":\"Account index (method: seed)\"},\"npub\":{\"type\":\"string\",\"description\":\"npub1... string (method: readonly)\"},\"pubkey_hex\":{\"type\":\"string\",\"description\":\"64-char hex pubkey (method: readonly)\"},\"bunker_url\":{\"type\":\"string\",\"description\":\"bunker:// URL (method: nip46)\"},\"transport\":{\"type\":\"string\",\"enum\":[\"serial\",\"unix\",\"tcp\",\"qrexec\"],\"description\":\"Transport type (method: nsigner)\"},\"device\":{\"type\":\"string\",\"description\":\"Device path, socket, host:port, or qube name (method: nsigner)\"},\"service\":{\"type\":\"string\",\"default\":\"qubes.NsignerRpc\",\"description\":\"Qrexec service name (method: nsigner)\"},\"index\":{\"type\":\"integer\",\"default\":0,\"description\":\"Key index (method: nsigner)\"}},\"required\":[\"method\"]}"},
|
"{\"type\":\"object\",\"properties\":{\"method\":{\"type\":\"string\",\"enum\":[\"random\",\"local\",\"seed\",\"readonly\",\"nip46\",\"nsigner\"]},\"nsec\":{\"type\":\"string\",\"description\":\"nsec1... string (method: local)\"},\"privkey_hex\":{\"type\":\"string\",\"description\":\"64-char hex private key (method: local)\"},\"mnemonic\":{\"type\":\"string\",\"description\":\"12 or 24 BIP-39 words (method: seed)\"},\"account\":{\"type\":\"integer\",\"default\":0,\"description\":\"Account index (method: seed)\"},\"npub\":{\"type\":\"string\",\"description\":\"npub1... string (method: readonly)\"},\"pubkey_hex\":{\"type\":\"string\",\"description\":\"64-char hex pubkey (method: readonly)\"},\"bunker_url\":{\"type\":\"string\",\"description\":\"bunker:// URL (method: nip46)\"},\"transport\":{\"type\":\"string\",\"enum\":[\"serial\",\"unix\",\"tcp\",\"qrexec\"],\"description\":\"Transport type (method: nsigner)\"},\"device\":{\"type\":\"string\",\"description\":\"Device path, socket, host:port, or qube name (method: nsigner)\"},\"service\":{\"type\":\"string\",\"default\":\"qubes.NsignerRpc\",\"description\":\"Qrexec service name (method: nsigner)\"},\"role\":{\"type\":\"string\",\"default\":\"nostr_range\",\"description\":\"Role name (method: nsigner)\"},\"index\":{\"type\":\"integer\",\"default\":0,\"description\":\"Key index (method: nsigner)\"}},\"required\":[\"method\"]}"},
|
||||||
|
|
||||||
{"logout",
|
{"logout",
|
||||||
"Log out and clear the current Nostr identity.",
|
"Log out and clear the current Nostr identity.",
|
||||||
@@ -140,7 +140,7 @@ const mcp_tool_def_t tool_defs[] = {
|
|||||||
|
|
||||||
{"switch_identity",
|
{"switch_identity",
|
||||||
"Switch to a new Nostr identity. Same parameters as login (including 'generate'). Frees the old signer first.",
|
"Switch to a new Nostr identity. Same parameters as login (including 'generate'). Frees the old signer first.",
|
||||||
"{\"type\":\"object\",\"properties\":{\"method\":{\"type\":\"string\",\"enum\":[\"generate\",\"local\",\"seed\",\"readonly\",\"nip46\",\"nsigner\"]},\"nsec\":{\"type\":\"string\"},\"privkey_hex\":{\"type\":\"string\"},\"mnemonic\":{\"type\":\"string\"},\"npub\":{\"type\":\"string\"},\"bunker_url\":{\"type\":\"string\"},\"transport\":{\"type\":\"string\"},\"device\":{\"type\":\"string\"},\"index\":{\"type\":\"integer\"}},\"required\":[\"method\"]}"},
|
"{\"type\":\"object\",\"properties\":{\"method\":{\"type\":\"string\",\"enum\":[\"generate\",\"local\",\"seed\",\"readonly\",\"nip46\",\"nsigner\"]},\"nsec\":{\"type\":\"string\"},\"privkey_hex\":{\"type\":\"string\"},\"mnemonic\":{\"type\":\"string\"},\"npub\":{\"type\":\"string\"},\"bunker_url\":{\"type\":\"string\"},\"transport\":{\"type\":\"string\"},\"device\":{\"type\":\"string\"},\"role\":{\"type\":\"string\"},\"index\":{\"type\":\"integer\"}},\"required\":[\"method\"]}"},
|
||||||
|
|
||||||
/* Navigation tools */
|
/* Navigation tools */
|
||||||
{"open",
|
{"open",
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ void cli_print_usage(FILE *fp) {
|
|||||||
" --nsigner-transport <t> (nsigner) serial|unix|tcp|qrexec\n"
|
" --nsigner-transport <t> (nsigner) serial|unix|tcp|qrexec\n"
|
||||||
" --nsigner-device <path> (nsigner) device path / socket / host:port / qube\n"
|
" --nsigner-device <path> (nsigner) device path / socket / host:port / qube\n"
|
||||||
" --nsigner-service <name> (nsigner) qrexec service name\n"
|
" --nsigner-service <name> (nsigner) qrexec service name\n"
|
||||||
|
" --nsigner-role <name> (nsigner) role name (default: nostr_range)\n"
|
||||||
" --nsigner-index <n> (nsigner) key index (default 0)\n"
|
" --nsigner-index <n> (nsigner) key index (default 0)\n"
|
||||||
" --login-timeout <ms> Override agent login timeout (GTK dialog path)\n"
|
" --login-timeout <ms> Override agent login timeout (GTK dialog path)\n"
|
||||||
"\n"
|
"\n"
|
||||||
@@ -103,6 +104,7 @@ enum {
|
|||||||
OPT_NSIGNER_TRANSPORT,
|
OPT_NSIGNER_TRANSPORT,
|
||||||
OPT_NSIGNER_DEVICE,
|
OPT_NSIGNER_DEVICE,
|
||||||
OPT_NSIGNER_SERVICE,
|
OPT_NSIGNER_SERVICE,
|
||||||
|
OPT_NSIGNER_ROLE,
|
||||||
OPT_NSIGNER_INDEX,
|
OPT_NSIGNER_INDEX,
|
||||||
OPT_NO_LOGIN,
|
OPT_NO_LOGIN,
|
||||||
OPT_LOGIN_TIMEOUT,
|
OPT_LOGIN_TIMEOUT,
|
||||||
@@ -137,6 +139,7 @@ static struct option long_opts[] = {
|
|||||||
{"nsigner-transport", required_argument, 0, OPT_NSIGNER_TRANSPORT},
|
{"nsigner-transport", required_argument, 0, OPT_NSIGNER_TRANSPORT},
|
||||||
{"nsigner-device", required_argument, 0, OPT_NSIGNER_DEVICE},
|
{"nsigner-device", required_argument, 0, OPT_NSIGNER_DEVICE},
|
||||||
{"nsigner-service", required_argument, 0, OPT_NSIGNER_SERVICE},
|
{"nsigner-service", required_argument, 0, OPT_NSIGNER_SERVICE},
|
||||||
|
{"nsigner-role", required_argument, 0, OPT_NSIGNER_ROLE},
|
||||||
{"nsigner-index", required_argument, 0, OPT_NSIGNER_INDEX},
|
{"nsigner-index", required_argument, 0, OPT_NSIGNER_INDEX},
|
||||||
{"no-login", no_argument, 0, OPT_NO_LOGIN},
|
{"no-login", no_argument, 0, OPT_NO_LOGIN},
|
||||||
{"login-timeout", required_argument, 0, OPT_LOGIN_TIMEOUT},
|
{"login-timeout", required_argument, 0, OPT_LOGIN_TIMEOUT},
|
||||||
@@ -200,6 +203,7 @@ void cli_args_free(cli_args_t *args) {
|
|||||||
g_free(args->nsigner_transport);
|
g_free(args->nsigner_transport);
|
||||||
g_free(args->nsigner_device);
|
g_free(args->nsigner_device);
|
||||||
g_free(args->nsigner_service);
|
g_free(args->nsigner_service);
|
||||||
|
g_free(args->nsigner_role);
|
||||||
g_free(args->download_url);
|
g_free(args->download_url);
|
||||||
memset(args, 0, sizeof(*args));
|
memset(args, 0, sizeof(*args));
|
||||||
}
|
}
|
||||||
@@ -330,6 +334,10 @@ int cli_parse(int *argc, char ***argv, cli_args_t *out) {
|
|||||||
g_free(out->nsigner_service);
|
g_free(out->nsigner_service);
|
||||||
out->nsigner_service = xstrdup(optarg);
|
out->nsigner_service = xstrdup(optarg);
|
||||||
break;
|
break;
|
||||||
|
case OPT_NSIGNER_ROLE:
|
||||||
|
g_free(out->nsigner_role);
|
||||||
|
out->nsigner_role = xstrdup(optarg);
|
||||||
|
break;
|
||||||
case OPT_NSIGNER_INDEX:
|
case OPT_NSIGNER_INDEX:
|
||||||
out->nsigner_index = atoi(optarg);
|
out->nsigner_index = atoi(optarg);
|
||||||
if (out->nsigner_index < 0) {
|
if (out->nsigner_index < 0) {
|
||||||
@@ -441,6 +449,7 @@ int cli_login(const cli_args_t *args) {
|
|||||||
if (args->nsigner_transport) cJSON_AddStringToObject(params, "transport", args->nsigner_transport);
|
if (args->nsigner_transport) cJSON_AddStringToObject(params, "transport", args->nsigner_transport);
|
||||||
if (args->nsigner_device) cJSON_AddStringToObject(params, "device", args->nsigner_device);
|
if (args->nsigner_device) cJSON_AddStringToObject(params, "device", args->nsigner_device);
|
||||||
if (args->nsigner_service) cJSON_AddStringToObject(params, "service", args->nsigner_service);
|
if (args->nsigner_service) cJSON_AddStringToObject(params, "service", args->nsigner_service);
|
||||||
|
if (args->nsigner_role) cJSON_AddStringToObject(params, "role", args->nsigner_role);
|
||||||
if (args->nsigner_index >= 0) cJSON_AddNumberToObject(params, "index", args->nsigner_index);
|
if (args->nsigner_index >= 0) cJSON_AddNumberToObject(params, "index", args->nsigner_index);
|
||||||
|
|
||||||
/* agent_login() calls app_set_signer() on success, which sets the
|
/* agent_login() calls app_set_signer() on success, which sets the
|
||||||
|
|||||||
@@ -61,6 +61,7 @@ typedef struct {
|
|||||||
char *nsigner_transport; /* --nsigner-transport */
|
char *nsigner_transport; /* --nsigner-transport */
|
||||||
char *nsigner_device; /* --nsigner-device */
|
char *nsigner_device; /* --nsigner-device */
|
||||||
char *nsigner_service; /* --nsigner-service */
|
char *nsigner_service; /* --nsigner-service */
|
||||||
|
char *nsigner_role; /* --nsigner-role (default: "nostr_range") */
|
||||||
int nsigner_index; /* --nsigner-index (-1 = unset) */
|
int nsigner_index; /* --nsigner-index (-1 = unset) */
|
||||||
int login_timeout_ms; /* --login-timeout (0 = unset) */
|
int login_timeout_ms; /* --login-timeout (0 = unset) */
|
||||||
|
|
||||||
|
|||||||
+9
-5
@@ -60,10 +60,11 @@ nostr_signer_t *key_store_create_signer(const key_store_identity_t *identity) {
|
|||||||
case KEY_STORE_METHOD_NSIGNER: {
|
case KEY_STORE_METHOD_NSIGNER: {
|
||||||
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
#if defined(NOSTR_ENABLE_NSIGNER_CLIENT)
|
||||||
nostr_signer_t *signer = NULL;
|
nostr_signer_t *signer = NULL;
|
||||||
|
const char *role = identity->nsigner_role[0] ? identity->nsigner_role : "nostr_range";
|
||||||
if (strcmp(identity->nsigner_transport, "serial") == 0) {
|
if (strcmp(identity->nsigner_transport, "serial") == 0) {
|
||||||
signer = nostr_signer_nsigner_serial(identity->nsigner_device, NULL, 15000);
|
signer = nostr_signer_nsigner_serial(identity->nsigner_device, role, 15000);
|
||||||
} else if (strcmp(identity->nsigner_transport, "unix") == 0) {
|
} else if (strcmp(identity->nsigner_transport, "unix") == 0) {
|
||||||
signer = nostr_signer_nsigner_unix(identity->nsigner_device, NULL, 15000);
|
signer = nostr_signer_nsigner_unix(identity->nsigner_device, role, 15000);
|
||||||
} else if (strcmp(identity->nsigner_transport, "tcp") == 0) {
|
} else if (strcmp(identity->nsigner_transport, "tcp") == 0) {
|
||||||
/* nsigner_device is "host:port" */
|
/* nsigner_device is "host:port" */
|
||||||
char host[256];
|
char host[256];
|
||||||
@@ -75,14 +76,17 @@ nostr_signer_t *key_store_create_signer(const key_store_identity_t *identity) {
|
|||||||
memcpy(host, identity->nsigner_device, hlen);
|
memcpy(host, identity->nsigner_device, hlen);
|
||||||
host[hlen] = '\0';
|
host[hlen] = '\0';
|
||||||
port = atoi(sep + 1);
|
port = atoi(sep + 1);
|
||||||
signer = nostr_signer_nsigner_tcp(host, port, NULL, 15000);
|
signer = nostr_signer_nsigner_tcp(host, port, role, 15000);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else if (strcmp(identity->nsigner_transport, "qrexec") == 0) {
|
} else if (strcmp(identity->nsigner_transport, "qrexec") == 0) {
|
||||||
signer = nostr_signer_nsigner_qrexec(identity->nsigner_device, "qubes.NsignerRpc", NULL, 30000);
|
signer = nostr_signer_nsigner_qrexec(identity->nsigner_device, "qubes.NsignerRpc", role, 30000);
|
||||||
}
|
}
|
||||||
if (signer && identity->nsigner_index >= 0) {
|
if (signer && identity->nsigner_index >= 0) {
|
||||||
nostr_signer_nsigner_set_nostr_index(signer, identity->nsigner_index);
|
/* Build role_path from index: m/44'/1237'/N'/0/0 */
|
||||||
|
char role_path[128];
|
||||||
|
snprintf(role_path, sizeof(role_path), "m/44'/1237'/%d'/0/0", identity->nsigner_index);
|
||||||
|
nostr_signer_nsigner_set_role_path(signer, role_path);
|
||||||
}
|
}
|
||||||
/* n_signer's serial and TCP transports require a kind-27235 auth
|
/* n_signer's serial and TCP transports require a kind-27235 auth
|
||||||
* envelope on every request. Install the default caller identity
|
* envelope on every request. Install the default caller identity
|
||||||
|
|||||||
+2
-1
@@ -50,7 +50,8 @@ typedef struct {
|
|||||||
/* n_signer transport (nsigner method only). */
|
/* n_signer transport (nsigner method only). */
|
||||||
char nsigner_transport[16]; /* "serial" | "unix" | "tcp" | "qrexec" */
|
char nsigner_transport[16]; /* "serial" | "unix" | "tcp" | "qrexec" */
|
||||||
char nsigner_device[256]; /* device path / socket name / host:port / qube */
|
char nsigner_device[256]; /* device path / socket name / host:port / qube */
|
||||||
int nsigner_index; /* nostr_index (NIP-06 m/44'/1237'/N'/0/0) */
|
int nsigner_index; /* NIP-06 index (0, 1, 2, ...) */
|
||||||
|
char nsigner_role[64]; /* role name (default: "nostr_range") */
|
||||||
} key_store_identity_t;
|
} key_store_identity_t;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
|
|||||||
+52
-29
@@ -454,12 +454,17 @@ static void on_login_clicked(GtkWidget *btn, gpointer user_data) {
|
|||||||
GtkWidget *screen = get_method_page(ctx, "nsigner");
|
GtkWidget *screen = get_method_page(ctx, "nsigner");
|
||||||
GtkWidget *transport_combo = g_object_get_data(G_OBJECT(screen), "transport-combo");
|
GtkWidget *transport_combo = g_object_get_data(G_OBJECT(screen), "transport-combo");
|
||||||
GtkWidget *device_entry = g_object_get_data(G_OBJECT(screen), "device-entry");
|
GtkWidget *device_entry = g_object_get_data(G_OBJECT(screen), "device-entry");
|
||||||
|
GtkWidget *role_entry = g_object_get_data(G_OBJECT(screen), "role-entry");
|
||||||
GtkWidget *index_spin = g_object_get_data(G_OBJECT(screen), "index-spin");
|
GtkWidget *index_spin = g_object_get_data(G_OBJECT(screen), "index-spin");
|
||||||
|
|
||||||
const char *device = gtk_entry_get_text(GTK_ENTRY(device_entry));
|
const char *device = gtk_entry_get_text(GTK_ENTRY(device_entry));
|
||||||
|
const char *role = gtk_entry_get_text(GTK_ENTRY(role_entry));
|
||||||
int nostr_index = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(index_spin));
|
int nostr_index = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(index_spin));
|
||||||
gint transport_idx = gtk_combo_box_get_active(GTK_COMBO_BOX(transport_combo));
|
gint transport_idx = gtk_combo_box_get_active(GTK_COMBO_BOX(transport_combo));
|
||||||
|
|
||||||
|
/* Use default role if empty */
|
||||||
|
if (role[0] == '\0') role = "nostr_range";
|
||||||
|
|
||||||
/* Block SIGCHLD during n_signer calls — the qrexec transport spawns
|
/* Block SIGCHLD during n_signer calls — the qrexec transport spawns
|
||||||
* child processes, and SIGCHLD can interrupt gtk_dialog_run's
|
* child processes, and SIGCHLD can interrupt gtk_dialog_run's
|
||||||
* internal main loop, causing the dialog to close prematurely. */
|
* internal main loop, causing the dialog to close prematurely. */
|
||||||
@@ -479,10 +484,10 @@ static void on_login_clicked(GtkWidget *btn, gpointer user_data) {
|
|||||||
const char *transport_name = "serial";
|
const char *transport_name = "serial";
|
||||||
|
|
||||||
if (transport_idx == 0) {
|
if (transport_idx == 0) {
|
||||||
signer = nostr_signer_nsigner_serial(device, NULL, 15000);
|
signer = nostr_signer_nsigner_serial(device, role, 15000);
|
||||||
transport_name = "serial";
|
transport_name = "serial";
|
||||||
} else if (transport_idx == 1) {
|
} else if (transport_idx == 1) {
|
||||||
signer = nostr_signer_nsigner_unix(device, NULL, 15000);
|
signer = nostr_signer_nsigner_unix(device, role, 15000);
|
||||||
transport_name = "unix";
|
transport_name = "unix";
|
||||||
} else if (transport_idx == 2) {
|
} else if (transport_idx == 2) {
|
||||||
char host[256];
|
char host[256];
|
||||||
@@ -494,7 +499,7 @@ static void on_login_clicked(GtkWidget *btn, gpointer user_data) {
|
|||||||
memcpy(host, device, hlen);
|
memcpy(host, device, hlen);
|
||||||
host[hlen] = '\0';
|
host[hlen] = '\0';
|
||||||
port = atoi(sep + 1);
|
port = atoi(sep + 1);
|
||||||
signer = nostr_signer_nsigner_tcp(host, port, NULL, 15000);
|
signer = nostr_signer_nsigner_tcp(host, port, role, 15000);
|
||||||
transport_name = "tcp";
|
transport_name = "tcp";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -502,7 +507,7 @@ static void on_login_clicked(GtkWidget *btn, gpointer user_data) {
|
|||||||
GtkWidget *service_entry = g_object_get_data(G_OBJECT(screen), "service-entry");
|
GtkWidget *service_entry = g_object_get_data(G_OBJECT(screen), "service-entry");
|
||||||
const char *service = service_entry ? gtk_entry_get_text(GTK_ENTRY(service_entry)) : "qubes.NsignerRpc";
|
const char *service = service_entry ? gtk_entry_get_text(GTK_ENTRY(service_entry)) : "qubes.NsignerRpc";
|
||||||
if (service[0] == '\0') service = "qubes.NsignerRpc";
|
if (service[0] == '\0') service = "qubes.NsignerRpc";
|
||||||
signer = nostr_signer_nsigner_qrexec(device, service, NULL, 30000);
|
signer = nostr_signer_nsigner_qrexec(device, service, role, 30000);
|
||||||
transport_name = "qrexec";
|
transport_name = "qrexec";
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -521,10 +526,14 @@ static void on_login_clicked(GtkWidget *btn, gpointer user_data) {
|
|||||||
* "code -310". */
|
* "code -310". */
|
||||||
key_store_nsigner_set_default_auth(signer);
|
key_store_nsigner_set_default_auth(signer);
|
||||||
|
|
||||||
int rc_idx = nostr_signer_nsigner_set_nostr_index(signer, nostr_index);
|
/* Build role_path from index: m/44'/1237'/N'/0/0 */
|
||||||
|
char role_path[128];
|
||||||
|
snprintf(role_path, sizeof(role_path), "m/44'/1237'/%d'/0/0", nostr_index);
|
||||||
|
g_print("[login] n_signer: setting role=%s role_path=%s\n", role, role_path);
|
||||||
|
int rc_idx = nostr_signer_nsigner_set_role_path(signer, role_path);
|
||||||
if (rc_idx != NOSTR_SUCCESS) {
|
if (rc_idx != NOSTR_SUCCESS) {
|
||||||
gtk_label_set_text(GTK_LABEL(ctx->status_label),
|
gtk_label_set_text(GTK_LABEL(ctx->status_label),
|
||||||
"Failed to set nostr_index on n_signer.");
|
"Failed to set role_path on n_signer.");
|
||||||
nostr_signer_free(signer);
|
nostr_signer_free(signer);
|
||||||
sigprocmask(SIG_SETMASK, &old_set, NULL);
|
sigprocmask(SIG_SETMASK, &old_set, NULL);
|
||||||
return;
|
return;
|
||||||
@@ -532,29 +541,26 @@ static void on_login_clicked(GtkWidget *btn, gpointer user_data) {
|
|||||||
|
|
||||||
char pubkey_hex[65];
|
char pubkey_hex[65];
|
||||||
int rc_pk = nostr_signer_get_public_key(signer, pubkey_hex);
|
int rc_pk = nostr_signer_get_public_key(signer, pubkey_hex);
|
||||||
|
g_print("[login] n_signer: get_public_key returned rc=%d pubkey=%s\n", rc_pk, pubkey_hex);
|
||||||
if (rc_pk != NOSTR_SUCCESS) {
|
if (rc_pk != NOSTR_SUCCESS) {
|
||||||
char errmsg[320];
|
char errmsg[320];
|
||||||
const char *desc = "unknown error";
|
const char *desc = nostr_signer_last_error(signer);
|
||||||
switch (rc_pk) {
|
if (!desc || desc[0] == '\0' || strcmp(desc, "no error details available") == 0) {
|
||||||
case -5: desc = "I/O failed — signer may have denied the request or disconnected"; break;
|
/* Fall back to error code mapping */
|
||||||
case -2001: desc = "policy denied — caller not approved at signer terminal"; break;
|
switch (rc_pk) {
|
||||||
case -2002: desc = "index not in signer's whitelist"; break;
|
case -5: desc = "I/O failed — signer may have denied the request or disconnected"; break;
|
||||||
case -3: desc = "crypto operation failed"; break;
|
case -2001: desc = "policy denied — caller not approved at signer terminal"; break;
|
||||||
case NOSTR_ERROR_NIP46_AUTH_CHALLENGE:
|
case -2002: desc = "index not in signer's whitelist"; break;
|
||||||
/* Device RPC codes 2010-2017: auth-related rejection.
|
case -3: desc = "crypto operation failed"; break;
|
||||||
* Most commonly this means the kind-27235 caller auth
|
case NOSTR_ERROR_NIP46_AUTH_CHALLENGE:
|
||||||
* envelope was missing/rejected (the default caller
|
desc = "auth rejected by n_signer (code -310). The caller "
|
||||||
* identity is installed automatically, but the device's
|
"auth envelope was missing or denied, or the device "
|
||||||
* policy may still deny it). It can also mean the device
|
"is requesting on-device approval. Confirm any "
|
||||||
* is requesting on-device approval (button press / TUI
|
"prompt on the n_signer hardware, then click Sign "
|
||||||
* confirm) for this operation. */
|
"In again";
|
||||||
desc = "auth rejected by n_signer (code -310). The caller "
|
break;
|
||||||
"auth envelope was missing or denied, or the device "
|
default: desc = "unknown error"; break;
|
||||||
"is requesting on-device approval. Confirm any "
|
}
|
||||||
"prompt on the n_signer hardware, then click Sign "
|
|
||||||
"In again";
|
|
||||||
break;
|
|
||||||
default: break;
|
|
||||||
}
|
}
|
||||||
snprintf(errmsg, sizeof(errmsg),
|
snprintf(errmsg, sizeof(errmsg),
|
||||||
"n_signer error: %s (code %d). Try a different key index.",
|
"n_signer error: %s (code %d). Try a different key index.",
|
||||||
@@ -570,8 +576,8 @@ static void on_login_clicked(GtkWidget *btn, gpointer user_data) {
|
|||||||
|
|
||||||
char npub[128];
|
char npub[128];
|
||||||
pubkey_to_npub(pubkey_hex, npub);
|
pubkey_to_npub(pubkey_hex, npub);
|
||||||
g_print("[login] n_signer: transport=%s index=%d pubkey=%s npub=%s\n",
|
g_print("[login] n_signer: transport=%s role=%s index=%d pubkey=%s npub=%s\n",
|
||||||
transport_name, nostr_index, pubkey_hex,
|
transport_name, role, nostr_index, pubkey_hex,
|
||||||
npub[0] ? npub : "(conversion failed)");
|
npub[0] ? npub : "(conversion failed)");
|
||||||
|
|
||||||
ctx->result->method = KEY_STORE_METHOD_NSIGNER;
|
ctx->result->method = KEY_STORE_METHOD_NSIGNER;
|
||||||
@@ -589,6 +595,9 @@ static void on_login_clicked(GtkWidget *btn, gpointer user_data) {
|
|||||||
sizeof(ctx->result->identity.nsigner_device) - 1);
|
sizeof(ctx->result->identity.nsigner_device) - 1);
|
||||||
ctx->result->identity.nsigner_device[sizeof(ctx->result->identity.nsigner_device) - 1] = '\0';
|
ctx->result->identity.nsigner_device[sizeof(ctx->result->identity.nsigner_device) - 1] = '\0';
|
||||||
ctx->result->identity.nsigner_index = nostr_index;
|
ctx->result->identity.nsigner_index = nostr_index;
|
||||||
|
strncpy(ctx->result->identity.nsigner_role, role,
|
||||||
|
sizeof(ctx->result->identity.nsigner_role) - 1);
|
||||||
|
ctx->result->identity.nsigner_role[sizeof(ctx->result->identity.nsigner_role) - 1] = '\0';
|
||||||
|
|
||||||
ctx->done = TRUE;
|
ctx->done = TRUE;
|
||||||
gtk_dialog_response(GTK_DIALOG(ctx->dialog), GTK_RESPONSE_ACCEPT);
|
gtk_dialog_response(GTK_DIALOG(ctx->dialog), GTK_RESPONSE_ACCEPT);
|
||||||
@@ -1036,6 +1045,19 @@ static GtkWidget *create_nsigner_screen(login_ctx_t *ctx) {
|
|||||||
/* Show the service field by default (qrexec is the default transport). */
|
/* Show the service field by default (qrexec is the default transport). */
|
||||||
gtk_widget_show_all(service_box);
|
gtk_widget_show_all(service_box);
|
||||||
|
|
||||||
|
/* Role entry field. */
|
||||||
|
GtkWidget *role_box = gtk_box_new(GTK_ORIENTATION_HORIZONTAL, 8);
|
||||||
|
gtk_box_pack_start(GTK_BOX(box), role_box, FALSE, FALSE, 0);
|
||||||
|
|
||||||
|
GtkWidget *role_label = gtk_label_new("Role:");
|
||||||
|
gtk_box_pack_start(GTK_BOX(role_box), role_label, FALSE, FALSE, 0);
|
||||||
|
|
||||||
|
GtkWidget *role_entry = gtk_entry_new();
|
||||||
|
gtk_entry_set_text(GTK_ENTRY(role_entry), "nostr_range");
|
||||||
|
gtk_entry_set_placeholder_text(GTK_ENTRY(role_entry), "nostr_range");
|
||||||
|
gtk_entry_set_width_chars(GTK_ENTRY(role_entry), 30);
|
||||||
|
gtk_box_pack_start(GTK_BOX(role_box), role_entry, FALSE, FALSE, 0);
|
||||||
|
|
||||||
/* Connect transport change callback to update the UI dynamically. */
|
/* Connect transport change callback to update the UI dynamically. */
|
||||||
g_signal_connect(transport_combo, "changed",
|
g_signal_connect(transport_combo, "changed",
|
||||||
G_CALLBACK(on_transport_changed), box);
|
G_CALLBACK(on_transport_changed), box);
|
||||||
@@ -1070,6 +1092,7 @@ static GtkWidget *create_nsigner_screen(login_ctx_t *ctx) {
|
|||||||
g_object_set_data(G_OBJECT(box), "enum-btn", enum_btn);
|
g_object_set_data(G_OBJECT(box), "enum-btn", enum_btn);
|
||||||
g_object_set_data(G_OBJECT(box), "service-box", service_box);
|
g_object_set_data(G_OBJECT(box), "service-box", service_box);
|
||||||
g_object_set_data(G_OBJECT(box), "service-entry", service_entry);
|
g_object_set_data(G_OBJECT(box), "service-entry", service_entry);
|
||||||
|
g_object_set_data(G_OBJECT(box), "role-entry", role_entry);
|
||||||
g_object_set_data(G_OBJECT(box), "index-spin", index_spin);
|
g_object_set_data(G_OBJECT(box), "index-spin", index_spin);
|
||||||
g_object_set_data(G_OBJECT(box), "index-label", index_label);
|
g_object_set_data(G_OBJECT(box), "index-label", index_label);
|
||||||
return box;
|
return box;
|
||||||
|
|||||||
+2
-2
@@ -11,9 +11,9 @@
|
|||||||
#ifndef SOVEREIGN_BROWSER_VERSION_H
|
#ifndef SOVEREIGN_BROWSER_VERSION_H
|
||||||
#define SOVEREIGN_BROWSER_VERSION_H
|
#define SOVEREIGN_BROWSER_VERSION_H
|
||||||
|
|
||||||
#define SB_VERSION "v0.0.68"
|
#define SB_VERSION "v0.0.69"
|
||||||
#define SB_VERSION_MAJOR 0
|
#define SB_VERSION_MAJOR 0
|
||||||
#define SB_VERSION_MINOR 0
|
#define SB_VERSION_MINOR 0
|
||||||
#define SB_VERSION_PATCH 68
|
#define SB_VERSION_PATCH 69
|
||||||
|
|
||||||
#endif /* SOVEREIGN_BROWSER_VERSION_H */
|
#endif /* SOVEREIGN_BROWSER_VERSION_H */
|
||||||
|
|||||||
@@ -0,0 +1,53 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<title>SharedWorker Absolute URL Test</title>
|
||||||
|
<style>
|
||||||
|
body { font-family: sans-serif; background: #1a1a2e; color: #eee; padding: 40px; }
|
||||||
|
h1 { margin-bottom: 10px; }
|
||||||
|
.pass { color: #0f0; }
|
||||||
|
.fail { color: #f00; }
|
||||||
|
.info { color: #ff0; }
|
||||||
|
#output { background: #16213e; padding: 16px; border-radius: 8px; margin-top: 20px; font-family: monospace; white-space: pre-wrap; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<h1>🔄 SharedWorker Absolute local:// importScripts Test</h1>
|
||||||
|
<p>Tests whether <code>importScripts('local:///absolute/path')</code> works inside a SharedWorker.</p>
|
||||||
|
|
||||||
|
<div id="output">Running tests...</div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
var output = document.getElementById('output');
|
||||||
|
function log(msg, cls) {
|
||||||
|
output.innerHTML += '<span class="' + (cls || '') + '">' + msg + '</span>\n';
|
||||||
|
}
|
||||||
|
|
||||||
|
log('=== Absolute local:// importScripts Test ===\n', 'info');
|
||||||
|
|
||||||
|
try {
|
||||||
|
var worker = new SharedWorker('shared-worker-absolute-test.js');
|
||||||
|
log('✓ SharedWorker constructor succeeded', 'pass');
|
||||||
|
|
||||||
|
worker.port.onmessage = function(e) {
|
||||||
|
log('✓ Received: ' + e.data, 'pass');
|
||||||
|
};
|
||||||
|
|
||||||
|
worker.port.onerror = function(e) {
|
||||||
|
log('✗ Worker error: ' + (e.message || 'unknown'), 'fail');
|
||||||
|
};
|
||||||
|
|
||||||
|
worker.port.postMessage('ping');
|
||||||
|
|
||||||
|
setTimeout(function() {
|
||||||
|
log('\n--- Done ---', 'info');
|
||||||
|
}, 3000);
|
||||||
|
|
||||||
|
} catch (e) {
|
||||||
|
log('✗ SharedWorker constructor threw: ' + e.message, 'fail');
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,21 @@
|
|||||||
|
/**
|
||||||
|
* SharedWorker test for importScripts() with absolute local:// URLs.
|
||||||
|
*/
|
||||||
|
console.log('[Abs-Import-Test] Worker script executing');
|
||||||
|
console.log('[Abs-Import-Test] self.location.href:', self.location.href);
|
||||||
|
|
||||||
|
// Try importScripts with an absolute local:// URL
|
||||||
|
try {
|
||||||
|
importScripts('local:///home/user/lt/sovereign_browser/tests/local-site/shared-worker-test.js');
|
||||||
|
console.log('[Abs-Import-Test] Absolute local:// importScripts succeeded');
|
||||||
|
} catch (e) {
|
||||||
|
console.log('[Abs-Import-Test] Absolute local:// importScripts FAILED:', e.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.onconnect = function(e) {
|
||||||
|
var port = e.ports[0];
|
||||||
|
port.postMessage('worker_ready');
|
||||||
|
port.onmessage = function(e) {
|
||||||
|
if (e.data === 'ping') port.postMessage('pong');
|
||||||
|
};
|
||||||
|
};
|
||||||
Reference in New Issue
Block a user