NIP-XX
Cold-Storage Identities for Nostr
draft optional
This NIP specifies online identities whose private key can be created in cold storage and stay there permanently. The key never has to touch an internet-connected device, and does not have to be kept close at hand. The identity can nonetheless be used easily for everyday online authentication, such as posting, replying, messaging, or signing in to services.
▌ 1. How it works
The identity-securing key never signs Nostr events itself. It delegates signing authority to delegatee keys, which handle the everyday signing on its behalf. If a delegatee key is lost or stolen, it is replaced, and the identity is untouched.
▌ 2. Delegations between keys
Delegations are time-indexed two-place relations. Between any two key pairs, at any moment, the first either delegates signing authority to the second or it does not. When it does, the second key is authorized to sign on the first's behalf. When it does not, the second has no such authority.
Delegations are either direct or indirect. A direct delegation arises when one key has expressly declared that it delegates signing authority to another. An indirect delegation arises because the relation is transitively closed: if X delegates to Y and Y delegates to Z, then X indirectly delegates to Z. This is what lets an identity be built as a chain (master → intermediate → signer), so that events signed by the bottom key are attributed all the way up to the master, while the master, having signed the top link once, retires to cold storage.
▌ 3. How a direct delegation is created
A direct delegation from key pair X to key pair Y is created when both key pairs sign a Declaration of Delegation of Signing Authority, by which X declares that it delegates signing authority to Y and Y declares that it accepts. The signed declaration is then recorded on-chain (the reference implementation uses Ethereum). The delegation begins at the time of the block in which the declaration was recorded, and remains in effect until it is revoked.
One special case: this holds only if neither X nor Y has previously permanently invalidated itself (§5). If either has, no delegation is created, even though the declaration sits on-chain.
As part of the declaration, X and Y also specify whether X can revoke the delegation unilaterally or whether Y's consent is required. The default is unilateral revocation by X, and it is the right default for most users: if Y's key is ever lost, X can still revoke it alone. Were Y's consent required, a lost Y could never be revoked, because Y would no longer be available to sign.
▌ 4. How a direct delegation is terminated
A direct delegation from X to Y is terminated when X signs a Declaration of Revocation of Signing Authority and records it on-chain, with Y's signature too if the original delegation required it. The delegation terminates at the time of the block in which the revocation was recorded. Everything Y signed while the delegation was in effect stays attributed to X; nothing it signs afterward does. A stolen signer therefore damages only the window between key compromise and revocation, and that window is the user's to close.
When a delegation and a revocation between the same X and Y land in the same block, the revocation is by convention treated as occurring first and the delegation thereafter. The convention is a tiebreaker for cases that block timestamps cannot order.
▌ 5. Permanent key invalidation
The third declaration is the Declaration of Permanent Invalidation of a Key Pair, signed by a key pair against itself, declaring that from then on it can no longer participate in any direct delegation, as delegator or delegatee. Once recorded on-chain, it takes effect at the time of its block. From that point, all existing direct delegations involving the key are terminated, and no new one involving it can come into existence, even if a declaration naming it is later recorded.
Its main use is invalidating a compromised master key. Such a key has no delegator above it, so ordinary revocation is unavailable; permanent invalidation lets its owner withdraw it from the system entirely.
▌ 6. Delegation timelines
A client can read the three kinds of declaration from the chain and, for any pubkey and any past moment up to the present, compute the direct delegations that pubkey was involved in at that moment. The indirect ones follow by transitive closure. So for any pubkey a client can compile a complete timeline of every direct and indirect delegation it has ever been involved in, and trace each back to the declaration that established it. Because the rules are fixed and the data is public, every client computes the same timeline, and no server has to be trusted to report it.
▌ 7. Bitcoin timestamping of events
Attribution needs an objective, auditable rule for when the signing of each event is deemed to have occurred. A self-declared created_at proves nothing on its own.
Events are anchored to Bitcoin using OpenTimestamps (OTS). An OTS proof for an event establishes that the event and its signature existed by the time of a specific Bitcoin block. An event's created_at is then treated as its objective signing time when a proof shows the event and its signature existed shortly after that time. The length of the "shortly after" window is configurable; the reference implementation defaults to four hours. Events without such a proof are treated as if they carried no valid signature at all: they are not displayed, and are attributed to no one.
A proof over the event id alone would show only that the event's contents existed by the attested block, not that anyone had yet signed them. Binding the signature into the proof as well shows that the signature itself existed by then, which is what an objective signing time requires. These proofs are published as kind 31045 events (§10).
▌ 8. Attribution of events
We now know, for any past time T and any two keys X and Y, whether X delegated (directly or indirectly) to Y at T; and for any event Y signed with both a valid signature and a valid OTS anchor, we have an objective signing time. Together these give a well-defined attribution rule:
An event E is attributed to a key X if and only if some key Y validly signed E at a time T and, as of T, X delegated signing authority to Y.
So a text note signed by a delegatee Y during a valid delegation period from X appears on X's timeline and is displayed there under X's profile, as if posted by X, even though X's own key never signed it.
▌ 9. Two profiles, one identity
You log in with your signer key and sign events with it, exactly as in any other Nostr client. Those events appear on the signer's own profile, and in addition on the profile of each of its delegators, up to your top-level identity.
You set the signer's profile by publishing a kind-0 event as usual. Separately, a signer can also set a profile for each of its current delegators, so that a top-level identity can present a face to the world without signing any event itself. This delegator-profile mechanism is kind 31065 (§10).
Your identity's profile is what you present to the world. Others should follow and interact with the identity, not with the signer beneath it. The signer is ephemeral and expected to be replaced; the identity is permanent, and is the thing followers should attach to.
▌ 10. Event kinds
This NIP defines two addressable event kinds (kinds 30000 to 39999 in NIP-01, where the latest event for a given (pubkey, kind, d-tag) replaces any earlier one).
Kind | Name | d tag | Signed by
31045 | Bitcoin timestamp | id of the referenced event | any party
31065 | Attributed profile | the delegator's pubkey | a delegatee
▌ 10.1 Kind 31045: Bitcoin timestamp
A 31045 event carries an OpenTimestamps proof for another event (§7). It is addressable by the referenced event's id, so a consumer finds the proof for an event E by querying kind 31045 with d = [E.id], whoever published it. Any valid proof for E is as good as any other, because the proof rests on Bitcoin and not on its publisher.
31045 event tags, marked (yes) if required, (no) if not:
d (yes): Id of the referenced event. The addressable key.
s (yes): Signature of the referenced event. With d, binds the proof to a signed event, not just an id.
p (no): Author pubkey of the referenced event.
b (no): Bitcoin block height of the attestation, or not_yet_available while pending.
t (no): Bitcoin block time (unix seconds), or not_yet_available while pending.
c (no): created_at of the referenced event, when known.
alt (no): NIP-31 summary, e.g. Complete Bitcoin Timestamp.
content is the base64-encoded OpenTimestamps (.ots) proof. Its leaf must commit to sha256(referenced_id || referenced_signature), the concatenation of the referenced event's 32-byte id and 64-byte signature. A consumer should verify the proof against its own view of the Bitcoin chain rather than rely on a relay having accepted it.
Only d and s, together with a verifiable content proof, are required to verify a timestamp. Every single-letter tag (d, p, s, b, t, c) is relay-indexed, so it can also serve as a query key, for example finding proofs by referenced author (p) or by anchoring block (b). b and t additionally duplicate what the proof itself attests; alt is not indexed and serves only as a NIP-31 display label.
{
"kind": 31045,
"tags": [
["d", "<referenced-event-id>"],
["p", "<referenced-event-author>"],
["s", "<referenced-event-signature>"],
["b", "<bitcoin-block-height>"],
["t", "<bitcoin-block-time>"],
["alt", "Complete Bitcoin Timestamp"],
["c", "<referenced-event-created-at>"]
],
"content": "<base64 .ots proof committing to sha256(id||sig)>"
}
▌ 10.2 Kind 31065: Attributed profile
A 31065 event is a profile for one key, published by another: how an identity that never signs anything nonetheless presents a face to the world (§9). Its d tag is the delegator's pubkey, the identity the profile describes, while the event is signed by a delegatee authorized to sign for it. The defining property is that event.pubkey, the signer, is not the subject d. A consumer must confirm, against the delegation timeline of §6, that the signer was a current delegatee of the subject before honoring the profile; one signed by a non-delegatee is ignored.
31065 event tags, marked (yes) if required:
d (yes): The subject: the delegator's pubkey.
alt (yes): The exact string Attributed profile.
content is a JSON object carrying the subject's presentation. Its kind-0-style fields (name, display_name, about, picture, banner, website, nip05, lud16) mean what they mean in NIP-01 and NIP-05. It may also carry the subject's follow list, mute list, and a map giving relay lists for the members of the identity's delegation network (the keys tied to it by delegation), so a client can present and route for the identity without a further fetch.
{
"kind": 31065,
"pubkey": "<delegatee-that-signs>",
"tags": [
["d", "<delegator-pubkey-the-profile-is-for>"],
["alt", "Attributed profile"]
],
"content": "{\"name\":\"…\",\"display_name\":\"…\",\"about\":\"…\",\"website\":\"…\",\"nip05\":\"…\",\"picture\":\"…\",\"relay_lists_by_pubkey\":{\"<pubkey>\":{\"readWriteRelays\":[[\"r\",\"<relay-url>\",\"<scope>\"]],\"favoriteRelays\":[\"<relay-url>\"],\"relaySets\":[{\"id\":\"…\",\"name\":\"…\",\"relayUrls\":[\"<relay-url>\"]}]}},\"publicly_muted_pubkey_hex_ids\":[\"<muted-pubkey>\"],\"current_snapshot_of_dir_indir_followed_pubkey\":[[\"p\",\"<followed-pubkey>\"]]}"
}
Because 31065 is addressable, the current profile for an identity is simply the latest one whose d is that identity, signed by a key that is currently its delegatee. When the signer is rotated, the new signer republishes, and the identity's face carries across the change unbroken.
▌ 11. Reference implementation
Inkan (https://www.inkan.cc) implements this specification as a working Nostr client and a cooperating relay at wss://relay.inkan.cc. Delegation, revocation, and permanent invalidation are recorded by an Ethereum contract deployed at 0x385C0A272b1f44687cb360557b25827bD7CdB7eb, which is the normative reference for the exact on-chain declaration encoding. A separate, network-disconnected "Management Utility" generates keys and signs declarations on an air-gapped machine, so a master key can be created and used to bootstrap an identity without ever touching a networked host.