The way it usually goes, your online identity is your private key. If the key is compromised, there goes your identity. Inkan fixes that. You keep a master key in cold storage and a signing key for everyday use. If the signing key ever leaks or gets lost, the master revokes it and delegates to a new one. Same identity, same followers, fresh signing key. If you'd like to take a look at the prototype: Log in with your NIP-07 extension and say hi to the test identities already walking around. Or make one of your own.

Replies (13)

There is no nip, but I wrote up this explanation of how it works ๐Ÿ‘‡
inkan's avatar inkan
A short explainer of the Inkan identity system. โ–Œ What is Inkan? Inkan enables 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 itself can nonetheless be used easily for everyday online authentication, like posting, replying, messaging, or signing in to services. โ–Œ How does it work? Inkan builds on the Nostr protocol which facilitates the distribution of digitally signed content. The idea behind Inkan is that the identity-securing key never has to sign that content itself. It can delegate signing authority to delegatee keys, which handle the everyday signing on its behalf. If a delegatee key is ever lost or stolen, it can be replaced. โ–Œ Delegations between keys Delegations are time-indexed two-place relations. Between any two key pairs, at any moment in time, the first key pair either delegates signing authority to the second or it doesn't. When it does, the second key is authorized to sign on the first's behalf. When it doesn't, the second key 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, on Inkan's concept of delegation, the relation is transitively closed: if X delegates to Y and Y delegates to Z, then X also indirectly delegates to Z. โ–Œ How is a direct delegation created? A direct delegation from key pair X to key pair Y is created when both key pairs digitally 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 must then be recorded on-chain (Inkan 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: the above holds only if neither X nor Y has previously permanently invalidated itself. If either has, no delegation relationship is created, even though the declaration sits on-chain. (See "What is permanent key invalidation?" below.) As part of the delegation 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. This matters most if Y's private key is ever lost: with unilateral revocation, the user can still revoke using X alone; if Y's consent is required, they cannot (since they no longer have access to Y, and thus can no longer use Y to sign the revocation declaration). For most users, unilateral revocation is recommended for this reason. โ–Œ How is a direct delegation terminated? A direct delegation from key pair X to key pair Y is terminated when X digitally signs a "Declaration of Revocation of Signing Authority," by which it revokes the signing authority it had delegated to Y. If the original delegation declaration specified that Y's consent is required for revocation, Y must also sign; otherwise, X's signature alone is enough. The signed revocation declaration must then be recorded on-chain. If it carries all required signatures, the delegation relationship terminates at the time of the block in which the declaration was recorded. One final wrinkle: when a delegation declaration and a revocation declaration between the same X and Y land in the same Ethereum block, the revocation is by convention treated as occurring first, and the delegation as occurring thereafter. So from that block on, the delegation is in effect (assuming neither X nor Y has been permanently invalidated). The convention is just a tiebreaker for cases that block timestamps can't order. โ–Œ What is permanent key invalidation? The third type of declaration in Inkan is the "Declaration of Permanent Invalidation of a Key Pair." It is signed by the key pair invalidating itself, declaring that, from then on, this key pair can no longer participate in any direct delegation relationship, either as delegator or as delegatee. Once signed, the invalidation declaration must be recorded on-chain. It takes effect at the time of the block in which it was recorded. From that point on, all existing direct delegations in which the invalidated key pair was delegator or delegatee are terminated, and no new direct delegations involving it can come into existence, even if a delegation declaration naming it is later recorded on-chain. The main use is invalidating a compromised master key. Such a key has no delegator, so ordinary revocation isn't available. Permanent invalidation lets the user withdraw it from the Inkan ecosystem entirely. โ–Œ Delegation timelines An Inkan-enabled client can read the three types of declarations on the blockchain and, for any given pubkey and any past moment up to the present, compute the direct delegation relationships that the pubkey was involved in at that moment. The indirect ones follow by transitive closure. So for any pubkey, the client can compile a complete timeline showing every direct and indirect delegation relationship that pubkey has been involved in at any time, up to the present moment. At www.inkan.cc, registered users can view such timelines for any pubkey that has been involved in delegations and trace them back to the underlying declarations recorded on-chain. โ–Œ Bitcoin timestamping of Nostr events Inkan needs an objective, auditable rule for when the act of signing each Nostr event is deemed to have occurred. To achieve this, Inkan uses OpenTimestamps (OTS) to anchor Nostr events to Bitcoin blocks. An OTS proof for an event establishes that the event and the cryptographic artifact that constitutes its "signature" existed by the time of a specific Bitcoin block. Inkan then treats an event's 'created_at' field (the signer's self-declared timestamp) as its objective signing time if an OTS proof shows the event and its signature existed shortly after that time. The "shortly after" window is user-configurable (default: four hours). Events without such a proof are treated as if they had no valid digital signature at all, are not displayed by the client, and are not attributed to any delegator. โ–Œ Attribution of Nostr events Under this framework, we know, for any past time T and any two keys X and Y, whether X delegated (directly or indirectly) signing authority to Y at T. And for any event that Y has signed, with both a valid cryptographic signature and a valid OTS anchor, we have an objective signing time, i.e. the event's 'created_at'. Putting this together gives us the necessary ingredients for a well-defined attribution rule: an event E is attributed to key X if and only if some key Y validly signed E at time T and, as of T, X delegated signing authority to Y. So, for example, 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. โ–Œ Setting up and managing an Inkan identity Inkan provides a tool, the "Inkan Management Utility," that automates the creation of the key pairs and the delegations running through them that constitute an identity. The utility can also create key replacements and permanent invalidations. The utility runs without network access and is intended to be used on an amnesic, air-gapped system such as Tails (with networking disabled), so that nothing persists after shutdown and the master key is never exposed to a networked host. The utility is available as a Linux AppImage: https://www.inkan.cc/settings/inkan-management-utility Users should set up their identity by building a chain of delegations (master โ†’ intermediate(s) โ†’ signer) rather than a single master-to-signer delegation. By transitive closure, events signed by the bottom-of-chain signer are attributed to the master. Revocations only require the immediate delegator above the affected signer, so it is not necessary to keep private keys above that immediate delegator close at hand. In particular, the master, once it has signed the chain's initial top-level delegation during the identity's creation ceremony, can stay in cold storage indefinitely. Generated key pairs can be backed up in encrypted form on USB sticks; multiple copies in separate locations are easy and recommended. Only the current signer key and the delegator immediately above that signer need to be reachable. The rest can be stored in relatively inaccessible locations. The Ethereum gas fees required for on-chain recording of delegation, revocation and invalidation declarations can be paid by the user directly or by a sponsor. Sponsored payment lets a user create and manage an Inkan identity without holding ETH, and without revealing any private key information to the sponsor. Setting up an Inkan identity takes some up-front ceremony, but ongoing use does not. The initial work is to create key pairs, sign the declarations that wire the keys into delegation relationships, and record these declarations on-chain. This is a one-time effort and is in large part automated through the management utility. After that, most of the keys can be put away and not be touched again, with only the signing key sitting "hot" on internet-connected devices for everyday signing, and the immediate delegator of the signing key kept close at hand so that it can perform key rotations if the signer is lost or compromised, or preemptively on a periodic basis. โ–Œ Using the client โ€” two profiles, one identity When using the Inkan client, you log in with your signer key and sign events with it, just as you would with any other Nostr client. These events appear on the signer's profile page (as in any other client), but in addition they also appear on the profile pages of each of its delegators, including, especially, your top-level identity. You can set the signer's profile (avatar, banner, display name, etc.) by publishing a kind-0 event as usual. In addition, Inkan includes a mechanism by which the signer can set profiles for its current delegators. This allows your top-level identity to maintain a profile without having to sign any Nostr event itself. Your identity's profile is what you present yourself with 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 sooner or later. The identity is expected to be permanent and is the appropriate object to which followers should attach.
View quoted note →
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.
Huh, it looks like NostrHub isn't actually surfacing the draft NIP or the link to Inkan that I published there. That would make that site unsuitable for discussing NIPs. Maybe I should republish the NIP here as a long-form note, with markdown for better formatting ...
The event isn't on the relays or is encrypted. You need to make sure that it is a proper kind 30817 and broadcast. On NostrHub, you can set your own relay list as a publishing target. I just use my own client to publish them, tho.
COLDIRON OS's avatar
COLDIRON OS 3 days ago
NIP-26 already covers delegated signing โ€” your delta is revocable, on-chain-ordered delegation + OTS attribution. Say that; it's the strongest case for a new NIP. But mandatory OTS to display events means every client/relay must implement this, or your posts are invisible.
Thanks, posting NIPs through NostrHub seems to be broken. I was able to publish a 30817 through your imwald client, here it is:
inkan's avatar inkan
`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. | Tag | Required | Meaning | | ----- | -------- | ------- | | `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. ```json { "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. | Tag | Required | Meaning | | ----- | -------- | --------------------------------------------------- | | `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. ```json { "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.
View quoted note →
I had dabbled with making something similar to ColdIron as an airgapped place to create Nostr identities and sign key delegation and revocation transactions. I didn't go about it as thoroughly as you seem to have done. I basically started with Tails OS, asked an AI to strip out network functionality, and added the Inkan Management Utility, resulting in this: I wonder whether the Management Utility would run inside ColdIron. I may take a closer look.
โ†‘