You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
feat(cli): --signing-key-file flag overrides signing identity per command (#282)
* feat(cli): --signing-key-file flag overrides signing identity per command
Adds a top-level `--signing-key-file <PATH>` flag (with
`RIVER_SIGNING_KEY_FILE` env var fallback) that reads a raw 32-byte
Ed25519 secret key and uses it in place of the room's stored
`signing_key_bytes` for the current command. The override is
in-memory only; `rooms.json` on disk is never modified.
# Why
`rooms.json` only stores ONE `signing_key_bytes` per room, but this
machine often has multiple identities for the same room (room owner,
invite bot, alt accounts). The UI's chat-delegate sync periodically
overwrites `rooms.json[room].signing_key_bytes` with whatever the
delegate has stored — silently leaving owner ops broken without a
manual swap of the on-disk key.
Pattern as documented in `river-official-room` skill: swap key → run
command → optionally swap back. Fragile and recurring. The flag
formalizes the "I have multiple identities, pick at command time"
model: nominate the right identity per command, no rooms.json
mutation, the existing identity stays loaded.
Distinct from `message send --signing-key` which takes a base64-
encoded inline key — the global file-based flag is preferred for
non-test use because the key doesn't appear in shell history.
# Tests
- `signing_key_override_is_returned_and_not_persisted` in
`cli/src/storage.rs` pins the contract: with override, `get_room`
returns the override; without override (fresh Storage), the
ORIGINAL stored bytes come back — proving the override is not
written back to rooms.json.
# Manual verification
Sent the river#275/#276/#278 release announcement to the official
Freenet River room as Room Owner via `--signing-key-file
~/.config/freenet-river-official/room_owner_signing_key.bin`, no
rooms.json swap. Confirmed via `riverctl message list` that the
message landed signed by Room Owner.
# Follow-up
#281 tracks the "perfect world" decentralized-version-pointer
follow-up (a `freenet-updates` crate that uses a Freenet contract
for tooling version checks). Separate PR.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
* fix(cli): address PR #282 review findings
Skeptical HIGH-1: identity export under `--signing-key-file` could
produce a wire-format-corrupted token. When the override resolves to
a different identity than the cached `self_authorized_member` in
rooms.json, the bundled IdentityExport had a signing_key whose
verifying_key() did NOT match authorized_member.member.member_vk.
Importing such a token would silently fail every subsequent contract
operation.
Fix: validate `signing_key.verifying_key() == authorized_member.member.member_vk`
just before constructing IdentityExport; error out cleanly with a
message pointing at the override as the likely cause if they
disagree. The owner-self-signed path (line 74-81) and the
network-lookup path (line 82-113) already produce coherent pairs;
only the cached-from-disk path could mismatch.
Code-first/skeptical Important: doc-comment drift on
`Storage::signing_key_override` field said `--signing-key` /
`RIVER_SIGNING_KEY` but the actual flag/env are `--signing-key-file`
/ `RIVER_SIGNING_KEY_FILE`. Fixed both the field doc and the test
docstring so a future grep for the right names actually hits this
code.
Code-first Important: the file-loader error message referenced
`riverctl identity export` as if it produced raw 32-byte output. It
doesn't — it produces an armored multi-field token. Clarified the
error to point at the room-key .bin backup format (which IS raw 32
bytes) and explicitly contrast with `identity export`'s armored
shape.
Skeptical Important #5: added unit tests for the file loader's
length validation. Extracted `parse_signing_key_bytes` as a pure
helper so the tests don't need a tempfile. Tests cover: 32-byte
accept, short reject (with byte-count in message), long reject
(with base64 hint in message), empty reject.
Other Important items (skeptical #2 info-log on override, #3
file-permissions warning, #5 env-vs-flag precedence test) are
documented for follow-up but not blocking — the override-mismatch
fix is the load-bearing correctness item from this review round.
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
0 commit comments