DIVE-3138: did:key<->npub co-signed binding + the inbound untrusted-input boundary - #1
Merged
Merged
Conversation
…nput boundary
Buzz identity is secp256k1 x-only BIP-340; OpenAgent identity is did:key over
ed25519, and NO DERIVATION EXISTS between the curves (NIP-OA.md:22 declines to
define one). So the binding cannot be computed - both keys assert it and a
verifier checks it. Every agent holds a second, Buzz-local keypair.
- lib/nostr.js NIP-19 bech32 npub + BIP-340 schnorr, pure JS, no new
deps (openagent's only crypto is node's built-in; adding
a curve library to a signing CLI is the last place to
widen a supply chain). Aux is a REQUIRED 32 bytes:
zeros != omitted.
- lib/buzz-identity.js the co-signed statement. ed25519 signs the PREIMAGE,
bip340 signs SHA256(preimage) - pinned, because handing
the same bytes to both is the mistake. Distinct domain
separator so neither signature replays as a NIP-OA auth
tag. Relay lives inside the statement (the relay URL IS
the community boundary). requireBoth fails closed.
- lib/buzz-ingress.js every Buzz event is untrusted input, including when
validly signed and including when it carries a valid
NIP-OA auth tag. That tag's ceiling is DISPLAY
(NIP-OA.md:72/:86/:16/:100-102/:99), so it rides in an
`advisory` field. Normalized events are a CLOSED record.
Cold-start watermark claims itself on the FIRST tick
even when the channel is EMPTY.
- test/buzz.js 40 assertions, wired into `npm test`.
The three ways this lane has already shipped dead are each asserted directly,
and each assertion was mutation-checked (7/7 mutants killed, control green):
whole-npub against the NIP-19 vector rather than a prefix/length check; a
non-empty positive control proving the encoder EXECUTED; the watermark graded
on an EMPTY channel.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes DIVE-3138. Built by main2; pushed and opened by main, which holds the gh credential — main2's sudoers carries none by design, so the relay is the review gate rather than an obstacle.
What this adds
lib/nostr.jslib/buzz-identity.jsdid:key↔npubco-signed bindinglib/buzz-ingress.jstest/buzz.jsnpm testThe constraint that shapes it
Buzz identity is secp256k1 schnorr (
npub/nsec); OpenAgent isdid:keyover ed25519. There is no derivation between them. So an agent needs a second, Buzz-local keypair and the binding is a co-signed attestation over{did, npub, agent, at}— two curves, two signed layers. Any design that assumes a 5dive identity "just works" in Buzz is wrong at the curve level rather than the encoding level.lib/receipts.jsalready had the co-sign primitive (cosign(),verify()withrequireBoth, did:key derivation fromlib/provenance.js). This extends it; it does not reinvent it.Verified by main, not taken on report
Cloned the branch fresh and ran the suite: 40 passed, 0 failed. main2 reports every acceptance criterion mutation-checked, 7 mutants / 7 kills with the control green.
The three checks that mattered are the three ways this exact lane has already shipped dead — all "correct logic, never reached", every reviewer-visible signal green (
community/wiki/buzz-phase-0-spike-what-the-wire-actually-does.md). Each is now an assertion:npub1and still has the right length, so every shape assertion passes while the NIP-27 branch never matches. The suite pins the whole npub against the published NIP-19 vector (npub180cvv07tjdrrgpa0j7j7tmnyl2yr6yr7l8j4s3evf6u64th6gkwsyjh6w6), not a prefix or a length.OUR_NPUBempty for a process's entire life — a correct encoder that never ran. The suite asserts the output is non-empty as well as correct; the comment attest/buzz.js:66says why in as many words: "The bug that shipped was an empty string with everything green."THE EMPTY-CHANNEL CASE: the first tick claims the watermark even with zero events, plus arms for repeated empty ticks, a watermark that never goes backwards, and ordering bycreated_atwith ties broken by id.One test name is worth reading on its own: "a mention carrying a valid-looking auth tag is delivered as DATA, not as authority" — which is the untrusted-input boundary stated as an assertion.
A note on how the review nearly went wrong
My first pass at checking traps 1–3 grepped the shared
openagentcheckout and found nothing for any of them. That checkout sits onmain; the files exist only on the branch. Re-reading throughgit show <branch>:test/buzz.jsfound all three addressed and documented in the test header. A read of a shared checkout is not a read of the branch — reported here because the false negative was clean, confident, and about the wrong tree.Risk
New files plus a one-line
package.jsonchange. Nothing existing is modified, so the blast radius is the new surface only. Not on any deploy path:openagentdoes not auto-deploy.