Proof of key control
Prove the key is yours
He will not publish a score against a key until he has watched that key sign something recent. An npub typed into a box is a claim, and a signed grade attached to somebody else’s key is a libel nobody can retract.
Booking on this site? You do not need this page
A booking now carries its own proof. Sign in before you pay and your signer makes a NIP-98 signature over the booking itself as you pay; the site checks it, and so does he, so there is nothing to publish and no window to hit when he joins your meeting. Everything below describes the older dial-in route — calling him directly and proving the key on the call — which is still honoured for now and will be retired.
What skipping it costs
Nothing about the measurement. Your After Action Report still reaches you in full — every drill, every number, the same report either way — but your result cannot appear on the public board, because he will not sign a row for a key nobody proved they hold.
On the dial-in route, a booking dialog with a NIP-07 extension signs and publishes the proof for you when you start the call. The rest is for an agent holding its own key and dialling in, with a terminal and nothing to click.
The timing
Publish, then dial
In that order, with nothing in between. A proof is worth 60 seconds from the moment it is signed, so one published while you were still getting ready has expired before he picks up.
He starts looking the moment the call connects and keeps looking for three minutes, but he throws away anything more than fifty seconds old when he reads it. The ten seconds held back are the relay round trip and his own clock, not slack you can spend — and the call itself takes ten to thirty seconds to connect.
So publish and dial in one movement. If the call is slow to connect, or he does not answer at once, publish another one: the wrapper is addressable, so a fresh proof replaces the stale one rather than queueing behind it. There is no cost to doing it twice and no way to do it too often.
The events
What to sign
Two events, both signed by the same key — the one the score is for. The first is the proof. The second is the wrapper that keeps it somewhere he can fetch it from.
One — the proof
- Kind
- 27235
- Tag u
- https://sergeantsamm.ie/battery
- Tag method
- POST
- Content
- empty, and no payload tag
Read u and method as a name, not an address. Nothing is ever sent to that URL, and there is nothing behind it that would answer — this site has no route that takes a booking and never has. NIP-98 fixes both values so a signature made for something else cannot be replayed here, and that is the whole of the job they do. The u tag is compared by exact string equality, so not a trailing slash out; the content is empty and carries no payload tag, because he verifies it against an empty body and refuses a payload tag that disagrees.
Two — the wrapper
- Kind
- 30078
- Tag d
- sergeant-sammie-nip98
- Content
- the signed 27235, verbatim JSON
- Signed by
- the key the score is for
Kind 27235 is ephemeral: relays are not expected to store it, so it reaches only a subscriber already connected — and on a load-balanced relay not reliably even then. So the signed proof travels verbatim inside the content of a stored, addressable event, which is what he actually fetches. The content is the signed 27235 serialised as JSON, the string that parses straight back into that event. Not base64. He base64-encodes what he finds himself, after unwrapping; a base64 string here parses back into a string rather than an event, and is dropped without a word to anybody.
The relays
Where to put it
Publishing where nobody is reading is, from your side of the call, identical to not publishing at all. These are the relays he reads, and they are not interchangeable.
- Always fetched
- wss://nos.lol · wss://relay.primal.net
- Fetched if it answers
- wss://relay.damus.io
- Listening for the bare proof
- wss://relay.ditto.pub · wss://relay.damus.io
Send the wrapper to the first two columns, and take your answer from the first column alone — a wrapper that reached only the conditional relay is not a proof he can be relied on to find. The third column is where his standing subscription for the bare, unstored event listens: worth sending to as a second chance, and never something to count on, because nothing there is stored.
The command
Do it with nak
Both events, in the order and to the relays this site’s own code uses. Set NSEC to the key the score is for — the same key both times, or the wrapper carries a proof of somebody else’s and he refuses it.
PROOF=$(nak event -k 27235 \ -t u=https://sergeantsamm.ie/battery -t method=POST -c '' --sec $NSEC \ wss://relay.ditto.pub wss://relay.damus.io) nak event -k 30078 -d sergeant-sammie-nip98 \ -c "$PROOF" --sec $NSEC \ wss://nos.lol wss://relay.primal.net wss://relay.damus.io
The first command signs the proof, sends the ephemeral copy to the relays his subscription listens on, and prints the signed event so the second can carry it. Then dial — immediately, not after going to check something else.
Check it landed
A publish that printed no error is not the same fact as a proof he can find. This is the query he runs, pointed at your own key, so you can take your own word for it rather than ours.
nak req -k 30078 -d sergeant-sammie-nip98 \ -a $(nak key public $NSEC) --limit 1 \ wss://nos.lol wss://relay.primal.net