Skip to content

feat: wallet owner_id on the wire (B1.4 + B1.5)#41

Merged
frahlg merged 5 commits into
mainfrom
b1.4-b1.5-wallet-wire
Jun 13, 2026
Merged

feat: wallet owner_id on the wire (B1.4 + B1.5)#41
frahlg merged 5 commits into
mainfrom
b1.4-b1.5-wallet-wire

Conversation

@frahlg

@frahlg frahlg commented Jun 13, 2026

Copy link
Copy Markdown
Member

Implements docs/superpowers/specs/2026-06-12-b1.4-wallet-owner-id-wiring.md
(decisions locked 2026-06-13) and the plan in
docs/superpowers/plans/2026-06-13-b1.4-b1.5-wallet-wire-plan.md.

Summary

owner_id is now the base58 Solana wallet address end-to-end. The agent
recovers the X25519 Noise-KK pin from a wallet-signed binding carried on the
offer, and pairing pins the wallet — proven by an auth signature over the Noise
channel binding. No legacy path (decision 2): wallet + binding are always
required; an identity without a wallet re-pairs via mir keygen --wallet.

The Noise data plane is byte-identical — the X25519 transport and the
wallet-derivation / wallet-binding / Noise-KK vectors are untouched. Only the
source of the pinned key changed, plus a new pairing auth scheme.

What changed (per slice)

  • B1.4.0 signal — opaque Binding field on SignalMsg; the relay forwards it
    verbatim and never reads it (still blind). Backward-compatible, independently
    deployable.
  • B1.4.1 agent — at attach, parse+verify the offer's binding
    (wallet == owner_id + signature), pin binding.x25519. Replaces hex.Decode(owner_id).
  • B1.4.2 clientowner.json gains a stable device_id and a cached signed
    binding (signed at SetFromSecret/Rekey); mir attach dials owner_id=<wallet>
    and carries the binding on the offer. KK initiator still uses the X25519 key.
  • B1.4.3 pairing — msg1 carries PairClaim{wallet}; a new msg3 carries
    Ed25519.sign(wallet, "miranda/auth/v1" || channelBinding). The agent pins the
    base58 wallet only if it verifies. Adds identity.SignAuth/VerifyAuth.
  • B1.5 web — sign-in derives the wallet alongside the X25519 key (one prf root);
    attach sends owner_id=<wallet> + a signed binding; pairing mirrors PairClaim +
    msg3 auth byte-for-byte with Go.

device semantics (clarified vs the draft spec)

binding.device is the owner's stable device id, not the agent's machine_id, so
one cached binding works across all the owner's machines (decision 3). The agent checks
wallet == owner_id + signature, not device == machine_id — KK already
authenticates the X25519 holder, so the device check would only break caching.

Tests

  • Go: go test ./... green (incl. agent/client/pairing e2e updated to wallet+binding);
    go build ./..., gofmt, go vet clean.
  • Web: npm test 93/93 (new auth.test.js; rewritten pairing-interop asserts
    msg1/msg2/msg3 byte-match Go via the new wallet_prf in the vector).
  • Byte gate: only testdata/pair-interop.json changed; wallet-derivation /
    wallet-binding / Noise-KK vectors untouched.

Deploy sequencing (your hand)

The new mir-signal (one-line Binding passthrough) is backward-compatible, so it
can be redeployed first. Because there is no legacy path, agents + clients are a
coordinated cut with the wallet wire; existing X25519-only identities re-pair via
mir keygen --wallet.

🤖 Generated with Claude Code

frahlg and others added 5 commits June 13, 2026 08:05
Carry an opaque wallet-binding record on the offer browser->agent. The
relay copies it verbatim and never interprets it (still blind: sees only
ciphertext + routing metadata). Backward-compatible; no behavior change
when absent.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The agent recovers the Noise-KK X25519 pin from the offer's wallet-signed
binding instead of hex-decoding owner_id. Checks binding.wallet==owner_id
and a valid signature, then pins binding.x25519. No legacy hex path. device
is the owner's device id (not machine_id), so it is not checked here.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
owner.json gains a stable device_id and a cached wallet->x25519 binding,
signed at SetFromSecret/Rekey. mir attach now dials owner_id=<wallet
address> and carries the signed binding on the offer; the KK initiator
still uses the X25519 transport key. Errors if the identity has no wallet.

The two client e2e tests pin the wallet address (not the hex transport
key) to match the agent's binding.wallet==owner_id check (B1.4.1).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pairing now pins the base58 wallet, proven by an Ed25519 auth signature
over the Noise channel binding (domain miranda/auth/v1). msg1 carries
PairClaim{wallet}; a new msg3 carries the signature. The agent pins the
wallet only if auth verifies. Adds identity.SignAuth/VerifyAuth.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Sign-in derives the Ed25519 wallet alongside the X25519 transport key
(one prf root, mirroring Go's owner.json). Attach dials owner_id=<wallet
address> and carries a signed binding on the offer; the Noise-KK initiator
still uses the X25519 transport key. Pairing sends PairClaim{wallet} + a
msg3 auth signature over the channel binding, byte-identical to Go. Adds
identity/auth.js and a stable per-browser device id; precache auth.js.

The pairing interop vector now carries wallet_prf so the JS side derives
the same wallet and reproduces msg1/msg3 exactly. wallet-derivation and
wallet-binding vectors are untouched (Noise data plane unchanged).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@chatgpt-codex-connector

Copy link
Copy Markdown

You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard.

@frahlg frahlg merged commit 700ce3d into main Jun 13, 2026
2 checks passed
@frahlg frahlg deleted the b1.4-b1.5-wallet-wire branch June 13, 2026 12:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant