Skip to content

feat: let mission 17 sign a real event and show what one is - #94

Merged
Jolah1 merged 1 commit into
mainfrom
feat/sign-event-mission
Jul 18, 2026
Merged

feat: let mission 17 sign a real event and show what one is#94
Jolah1 merged 1 commit into
mainfrom
feat/sign-event-mission

Conversation

@Jolah1

@Jolah1 Jolah1 commented Jul 18, 2026

Copy link
Copy Markdown
Owner

Closes #85 in part.

Mission 17 listed the five fields of a Nostr event and then asked which kind number is a text note. Now the learner writes something, the browser signs it with their key, and the app shows the actual event:

kind      1 (short text note)
content   learning what an event really is
pubkey    989c0b76cb563971fdc9bef31ec06c3560f3249...
id        (hash of this event)
sig       (that id, signed)

…with a nudge to run it again with one character changed and watch the id and sig move completely.

Nothing is published. Mission 26 is where broadcasting happens, and the outcome copy is careful not to imply otherwise. The lesson gained a short paragraph on the two computed fields, because "the id is a hash of the contents, and the sig is that id signed by your key" is the idea that makes the rest of Nostr make sense.

The server checks both halves of that claim

Event::verify recomputes the canonical hash, confirms the id really is that hash, and checks the BIP340 signature against the embedded pubkey. Then we compare that pubkey to the npub registered in mission 14, so the event has to be theirs. Verification is offline by construction: the signing check was split out of broadcast_signed_event, so this mission can never reach a relay.

Verified by trying to break it

Against a running backend, one fresh learner per case:

proof result
garbage, not JSON rejected
content edited after signing rejected
validly signed by someone else rejected
id swapped for a fake hash rejected
sig swapped for zeros rejected
the learner's own signed event accepted

Plus a new browser test (15 assertions) that drives the real flow, asserts all five fields reach the learner, re-derives the id to show it is content-derived, and confirms no relay call is made and the nsec is never sent.

Full suite green: sign-event 15, badge-share 19, passphrase-fork 10, secret-reveal 7/6, publish-confirm 5, mission-prose 3, solo-rank 5. Plus 30 unit, 30 integration, tsc and build clean.

Notes

  • needsNsec no longer piggybacks on needsPublishConfirm: mission 17 needs the key but must not warn that something is "public and permanent" when nobody will ever see it.
  • The e2e harness now registers a real deterministic npub at mission 14 instead of a placeholder, because mission 17's verifier requires the signing key to match the registered identity.
  • No mission added and no path membership changed, so no badge reverts.

🤖 Generated with Claude Code

Mission 17 listed the five fields of a Nostr event and asked the learner
which kind number is a text note. They now write something, the browser
signs it with their key, and the app shows the actual event: kind,
content, pubkey, id, sig, with a nudge to run it again with one
character changed and watch the id and sig move completely.

Nothing is published. Mission 26 is where broadcasting happens, and the
outcome copy is careful not to imply otherwise. The lesson gained a
paragraph on the two computed fields, because "the id is a hash of the
contents and the sig is that id signed by your key" is the idea that
makes the rest of Nostr make sense.

The server checks both halves of that claim. `Event::verify` recomputes
the canonical hash, confirms the id really is that hash, and checks the
BIP340 signature against the embedded pubkey; then we compare that
pubkey against the npub registered in mission 14, so the event has to be
theirs. Verification is offline by construction: the signing path was
split out of `broadcast_signed_event` so this mission never reaches a
relay.

`needsNsec` no longer piggybacks on `needsPublishConfirm`. Mission 17
needs the key but must not warn that something is public and permanent
when nobody will ever see it.

Verified against a running backend by trying to break it. A tampered
content field, an event signed by a different key, a swapped id, a
zeroed signature and plain garbage are all rejected; only the learner's
own signed event is accepted. A new browser test (15 assertions) drives
the real flow, asserts all five fields reach the learner, re-derives the
id to show it is content-derived, and confirms no relay call is made and
the nsec is never sent.

Note on the e2e harness: seeding mission 14 now registers a real
deterministic npub instead of a placeholder, because mission 17's
verifier requires the signing key to match the registered identity.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@vercel

vercel Bot commented Jul 18, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
bitpilot Ready Ready Preview, Comment Jul 18, 2026 4:29pm

@Jolah1
Jolah1 merged commit 2c3b46b into main Jul 18, 2026
5 checks passed
@Jolah1
Jolah1 deleted the feat/sign-event-mission branch July 18, 2026 16:35
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.

Nostr: 5 of 16 missions have a hands-on step

1 participant