Chapter 26 - Key Management
26
Nostr Implementation Possibility - Hex № 26

Delegated Event Signing

A retired scheme that let one keypair authorize another keypair to sign events on its behalf.

Deprecated

1 Overview

Deprecated

The NIP repository marks this NIP unrecommended: it adds unnecessary burden for little gain. Every relay and client must carry extra validation logic for a feature few users need. Do not build new software on it. For the main use case — keeping the root key off everyday devices — use NIP-46 remote signing instead. This chapter documents the scheme for readers who meet it in old events.

Every event carries the pubkey of the key that signed it (NIP-01). Normally that key is the user's one identity key. This NIP lets a delegator authorize a second keypair, the delegatee, to sign events for it.

The delegator signs a short authorization string once. The signature is called the delegation token. The delegatee attaches the token to each event it signs. Clients then display those events as if the delegator published them.

The intended use was root-key hygiene. A user keeps the root keypair in cold storage. The user generates a fresh keypair for each client and delegates to it. If one client leaks its key, the root key stays safe and the delegation expires.

Delegator root key, cold Delegatee client key, hot Relay Client shows delegator token EVENT REQ
Fig. 26-1 — Delegation flow. The delegator signs one token offline. The delegatee signs and publishes events with that token attached.

2 The delegation Tag

This NIP introduces one new tag: delegation. The delegatee adds it to each delegated event. The tag has exactly four elements:

Wire format - delegation tag
[
  "delegation",
  <pubkey of the delegator>,
  <conditions query string>,
  <delegation token: 64-byte Schnorr signature of the
   sha256 hash of the delegation string>
]
Tag element reference
IndexElementRule
0tag nameThe literal string delegation.
1delegator pubkeyThe public key of the account that granted the delegation.
2conditions query stringLimits on what the delegatee may sign. See §4.
3delegation token64-byte Schnorr signature, hex. Made by the delegator. See §3.

The event itself stays a normal NIP-01 event. Its pubkey is the delegatee's key. Its sig is the delegatee's signature. Only the tag links it to the delegator.

3 The Delegation Token

The delegation token proves that the delegator approved this delegatee and these conditions. To create it, the delegator builds the delegation string:

Delegation string
nostr:delegation:<pubkey of publisher (delegatee)>:<conditions query string>

Hash the delegation string with sha256. Sign the hash with the delegator's private key. The signature is a 64-byte Schnorr signature, the same scheme as event signatures. That signature is the delegation token. Place it at index 3 of the delegation tag.

Delegation string nostr:delegation:pk:cond sha256 32-byte hash Sign delegator key Token 64 bytes
Fig. 26-2 — Token pipeline. Build the string, hash it, sign the hash with the delegator's key.
Warning!

The delegation string names the delegatee. The tag names the delegator. Do not swap them. A token signed over the wrong pubkey verifies for nobody.

4 The Conditions Query String

The conditions query string limits the delegation. A condition is one supported field plus one supported operator. Combine multiple conditions with &. You may repeat the same field. The supported fields and operators are:

Fields and operators
FieldOperatorMeaning
kind=${KIND_NUMBER}The delegatee may only sign events of this kind.
created_at<${TIMESTAMP}The delegatee may only sign events created before this timestamp.
created_at>${TIMESTAMP}The delegatee may only sign events created after this timestamp.

These condition strings are all valid:

Valid condition strings
kind=1&created_at<1675721813
kind=0&kind=1&created_at>1675721813
kind=1&created_at>1674777689&created_at<1675721813
Hint!

For almost every use case, follow both rules. One: include a created_at after condition set to the current time. This stops the delegatee from publishing historic notes in the delegator's name. Two: include a created_at before condition that is not empty and not in the far future. An unbounded delegation carries the same risk as handing out the root key.

Tech Note

The spec says conditions "must be combined with &" but never defines the logic between them. Read as AND, the valid example kind=0&kind=1 can never match: no event has two kinds. The intended reading is OR between repeated kind conditions, but the text does not say so. A fix (PR #208) was merged in 2023-02 and reverted five days later. The ambiguity remains.

5 Validation and Display

Treat an event as a valid delegation only when both checks pass:

  1. The event satisfies every condition in the tag's query string. In the worked example: kind=1, created_at>1674834236, and created_at<1677426236.
  2. The delegation token verifies against the delegator's pubkey. The verified conditions must be unchanged from the original delegation string.

Clients should display a valid delegated note as if the delegator published it directly. The delegatee's key does not appear in the UI.

Tech Note

The spec does not say who performs these checks — relay, client, or both. It only says the event "should be considered" valid. In practice both sides must verify, or a forged token slips through. This vagueness is part of why the NIP is deprecated.

6 Relay and Client Support

Two relay rules make delegation usable:

Relay behavior
RuleBehavior
queryAnswer a request such as ["REQ", "", {"authors": ["A"]}] by matching both the event pubkey and the delegator pubkey at index 1 of delegation tags.
deleteRelays SHOULD allow the delegator to delete events that the delegatee published. See NIP-09 for deletion requests.

Without the query rule, a follower of the delegator never sees delegated notes. The follower's authors filter names the delegator, but the events carry the delegatee's pubkey.

7 Worked Example

Two keypairs take part. The delegator holds the root identity. The delegatee is a fresh client key.

Keypairs
# Delegator:
privkey: ee35e8bb71131c02c1d7e73231daa48e9953d329a4b701f7133c8f46dd21139c
pubkey:  8e0d3d3eb2881ec137a11debe736a9086715a8c8beeeda615780064d68bc25dd

# Delegatee:
privkey: 777e4f60b4aa87937e13acc84f7abcc3c93cc035cb4c1e9f7a9086dd78fffce1
pubkey:  477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396

The delegator grants note-publishing rights to the delegatee (477318cf) for the next 30 days. The current timestamp is 1674834236. The delegation string is:

Delegation string
nostr:delegation:477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396:kind=1&created_at>1674834236&created_at<1677426236

The delegator (8e0d3d3e) signs the sha256 hash of that string. The result is the delegation token:

Delegation token
6f44d7fe4f1c09f3954640fb58bd12bae8bb8ff4120853c4693106c82e920e2b
898f1f9ba9bd65449a987c39c0423426ab7b53910c0c6abfb41b30bc16e5f524

The delegatee now constructs an event on behalf of the delegator. The delegatee signs it with its own private key and publishes it:

Delegated event - kind 1
{
  "id": "e93c6095c3db1c31d15ac771f8fc5fb672f6e52cd25505099f62cd055523224f",
  "pubkey": "477318cfb5427b9cfc66a9fa376150c1ddbc62115ae27cef72417eb959691396",
  "created_at": 1677426298,
  "kind": 1,
  "tags": [
    [
      "delegation",
      "8e0d3d3eb2881ec137a11debe736a9086715a8c8beeeda615780064d68bc25dd",
      "kind=1&created_at>1674834236&created_at<1677426236",
      "6f44d7fe4f1c09f3954640fb58bd12bae8bb8ff4120853c4693106c82e920e2b898f1f9ba9bd65449a987c39c0423426ab7b53910c0c6abfb41b30bc16e5f524"
    ]
  ],
  "content": "Hello, world!",
  "sig": "633db60e2e7082c13a47a6b19d663d45b2a2ebdeaf0b4c35ef83be2738030c54fc7fd56d139652937cdca875ee61b51904a1d0d0588a6acd6168d7be2909d693"
}

The event is a valid delegation. It is kind 1. Its created_at of 1677426298 sits inside the allowed window. The token verifies and the conditions are unchanged. A client shows this note as posted by 8e0d3d3e, the delegator.

Tech Note

The example's created_at of 1677426298 is 62 seconds after the created_at<1677426236 limit in its own delegation tag. Read strictly, the spec's own example event fails its own conditions. The surrounding text still calls it valid. The mismatch was never fixed.

8 Version History

NIP-26 lived for under three years as an active proposal. Adoption stayed low, and remote signing (NIP-46) covered the key-hygiene use case with less protocol burden. Notable changes, newest first:

  • 20262026-05The "unrecommended: adds unnecessary burden for little gain" warning was stamped into the NIP body.
  • 20252025-12Added the relay tag to the status line (#2173).
  • 20252025-04Formally deprecated: marked unrecommended (#1051, Vitor Pamplona).
  • 20232023-06Added advice to bound the created_at before-condition; unbounded delegations equal root-key risk.
  • 20232023-04Relays SHOULD allow the delegator to delete events published by the delegatee.
  • 20232023-02Naming of token and string made consistent. A multiple-kind semantics fix (#208) was merged, then reverted.
  • 20232023-01Documented the valid fields and operators for the conditions string (#194). Example changed to expire at a future date (#157).
  • 2023+2023-2025Typo, header-style, and author-list cleanups.
  • 20222022-08Revised after community feedback, three weeks in.
  • Origin2022-08-04Mark Harding published the first draft of Delegated Event Signing.

Original Proposal - Credits

AuthorMark Harding
First commit2022-08-04 — “First draft of Delegated Event Signing”
Statusdraft - unrecommended - optional - relay — deprecated, do not implement
Source26.md on GitHub - full change history

9 See Also

  • NIP-01 — the event format, pubkey, sig, and the Schnorr signature scheme this NIP reuses.
  • NIP-46 — remote signing; the living replacement for the key-protection use case.
  • NIP-09 — deletion requests; the mechanism behind the delegator-delete rule in §6.
  • NIP-07 — browser signer extensions; another way to keep private keys out of clients.