Nostr directory
Publishing to Nostr is optional. The envelope is self-contained and can travel over any transport (URL fragment, email, IPFS, QR code, paper, git). Nostr is one choice among many — a good one for durable public discovery.
Event kind
Kind 30083. Parameterized-replaceable, in the OrangeCheck family's
30078–30115 range. OC Agent delegations share it under the disjoint
oc-agent-del: prefix; agent actions are on 30084, not here. The full family
table is on Nostr publication.
| Kind | Claimed by |
|---|---|
| 30078 | OrangeCheck attestations + OC Lock device records |
| 30080 | OC Vote poll |
| 30081 | OC Vote ballot |
| 30082 | OC Vote reveal |
| 30083 | OC Stamp envelope + OC Agent delegation |
| 30084 | OC Agent action |
We deliberately did NOT reuse 30078. Relays that index by (kind, d-tag) as a
compound key would see prefix collisions across protocols over time. Clean
namespace per protocol is the rule.
Event shape
event.kind = 30083
event.tags = [
["d", "oc-stamp:" || id],
["t", signer.address],
["t", content.hash],
["t", "oc-stamp"]
]
event.content = <canonical JSON of envelope>
event.pubkey = <ephemeral Nostr pubkey, any key>
event.created_at = unix_seconds
The Nostr event pubkey has no relationship to the Bitcoin identity. The
authenticity of the stamp is proved by the BIP-322 signature inside the
envelope, not by the Nostr author. A fresh ephemeral Nostr keypair SHOULD be
derived per-publish:
nostr_sk := HKDF(ikm=random(32), salt="oc-stamp/v1/nostr-key", info=id, L=32)
This avoids asking wallets to hold Nostr keys.
Discovery tags are t tags because relays index single-letter tag names only. A
filter on a longer name (#addr, #hash) is rejected by most relays — they
close the request, so read that as "no answer", never as "no stamps". Tags are
unsigned relay metadata: read the address and hash from the envelope.
Discovery
By content hash
REQ { "kinds": [30083], "#t": ["sha256:<hex>"] }
Use case: "has anyone stamped these bytes?"
Stamps published before 2026-09-24 carry no content-hash t tag. Find those by
signer address and match content.hash after fetching.
By signer address
REQ { "kinds": [30083], "#t": ["bc1q…"] }
Use case: "all stamps by this signer."
By envelope id
REQ { "kinds": [30083], "#d": ["oc-stamp:<id>"] }
Use case: "fetch this specific stamp."
Every stamp
REQ { "kinds": [30083], "#t": ["oc-stamp"] }
Use case: "stamps only, not the OC Agent delegations that share the kind."
There is no prefix query. Relays match tag values whole, so
"#d": ["oc-stamp:"] matches no event, and a relay answers it with an empty
result rather than an error. Use #t or a full #d.
A relay match only nominates candidates. Kind 30083 also carries OC Agent
delegations, and anyone can tag an event with any address or hash, so check that
the envelope's kind is stamp and its content.hash or signer.address is
the one you asked for, then verify it, before using it.
Addressability + upgrades
Kind-30083 is addressable — relays replace prior events with the same
(pubkey, kind, d-tag) tuple.
For OC Stamp this is harmless in practice:
- The d-tag is content-addressed (
oc-stamp:<id>whereidis a hash of the canonical message). - A "replacement" with the same d-tag must carry the same id.
- Same id → same signed content. Any attempt to swap in different content fails verification on the consumer side.
The common reason to republish with the same d-tag is an OTS upgrade:
pending → confirmed. Verifiers that see multiple versions of the same id SHOULD
accept the one with ots.status === "confirmed" and the latest upgraded_at.
Recommended relays
The reference app uses the @orangecheck/nostr-core default set:
wss://nos.lolwss://relay.primal.netwss://offchain.pubwss://relay.snort.socialwss://relay.ochk.io
Clients SHOULD publish to at least three diverse relays. A hostile relay set can refuse to publish or return your envelope, but cannot forge it — BIP-322 verification is relay-independent.
Why Nostr is optional
Envelopes are self-contained JSON. Put them:
- In a URL fragment (
stamp.ochk.io/v#<base64url>). - Alongside the content (
blogpost.md+blogpost.md.stamp). - In IPFS / Arweave / any CAS.
- In a git repo (
.git/stamps/directory). - On paper, via QR code.
- In a Nostr event (this page).
No single directory is load-bearing. The ochk.io/stamp.ochk.io operators do not run a privileged directory — the reference aggregator uses the same Nostr relays anyone else would.