live · mainnetoc · docs
specs · api · guides
docs / nostr publication

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–30099 range.

The family's claims so far:

KindClaimed by
30078OrangeCheck attestations + OC Lock device records
30080OC Vote poll
30081OC Vote ballot
30082OC Vote reveal
30083OC Stamp envelope

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],
  ["addr",      signer.address],
  ["hash",      content.hash],
  ["signed_at", signed_at]
]
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

By content hash

REQ { "kinds": [30083], "#hash": ["sha256:<hex>"] }

Use case: "has anyone stamped these bytes?"

By signer address

REQ { "kinds": [30083], "#addr": ["bc1q…"] }

Use case: "all stamps by this signer."

By envelope id

REQ { "kinds": [30083], "#d": ["oc-stamp:<id>"] }

Use case: "fetch this specific stamp."

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> where id is 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.

The reference clients use this set:

  • wss://relay.damus.io
  • wss://relay.nostr.band
  • wss://nos.lol
  • wss://relay.snort.social

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.

Further reading

  • SPEC §7 — normative rules.
  • SPEC §13 — the 30078–30099 family kind registry.