Skip to content

Commit 31c2816

Browse files
docs: define migration event format (kind 30078, NIP-32 labels)
1 parent be5bd75 commit 31c2816

1 file changed

Lines changed: 47 additions & 2 deletions

File tree

docs/specs/2026-03-31-heartwood-design.md

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -244,14 +244,59 @@ A faithful port of the TypeScript nsec-tree library to Rust:
244244
Existing Nostr users create a fresh Heartwood identity and prove the link to their old npub:
245245

246246
1. Heartwood generates new mnemonic (new master, clean chain of custody)
247-
2. Old signer (Amber, nsec.app, whatever) signs: "I'm migrating to [new npub]"
248-
3. Heartwood signs: "I accept migration from [old npub]"
247+
2. Old signer (Amber, nsec.app, whatever) signs the migration intent event
248+
3. Heartwood signs the migration acceptance event
249249
4. Both events published to relays
250250
5. Old key retired. New key was born in hardware.
251251
6. Clients that understand migration events transfer follower/reputation data.
252252

253253
The old nsec never touches Heartwood. It stays in whatever signer it was in, signs one migration event, and is retired.
254254

255+
#### Migration event format
256+
257+
Two replaceable kind 30078 (NIP-78 application-specific data) events, one from each party. Replaceable semantics mean re-migration overwrites cleanly.
258+
259+
**Event 1: Migration Intent** (signed by old key)
260+
261+
```json
262+
{
263+
"kind": 30078,
264+
"tags": [
265+
["d", "heartwood-migration:<new-npub>"],
266+
["p", "<new-pubkey-hex>"],
267+
["L", "heartwood"],
268+
["l", "migration-intent", "heartwood"]
269+
],
270+
"content": "{\"type\":\"migration-intent\",\"to\":\"<new-npub>\"}"
271+
}
272+
```
273+
274+
**Event 2: Migration Acceptance** (signed by new key, on Heartwood)
275+
276+
```json
277+
{
278+
"kind": 30078,
279+
"tags": [
280+
["d", "heartwood-migration-accept:<old-pubkey-hex>"],
281+
["p", "<old-pubkey-hex>"],
282+
["e", "<migration-intent-event-id>"],
283+
["L", "heartwood"],
284+
["l", "migration-accept", "heartwood"]
285+
],
286+
"content": "{\"type\":\"migration-acceptance\",\"from\":\"<old-npub>\",\"to\":\"<new-npub>\"}"
287+
}
288+
```
289+
290+
| Field | Purpose |
291+
|-------|---------|
292+
| `d` tag | Scopes within kind 30078 namespace. Prefix `heartwood-migration:` / `heartwood-migration-accept:` |
293+
| `p` tag | Points to the other party, enabling relay routing |
294+
| `e` tag | On acceptance only. Links to the intent event, creating a verifiable chain |
295+
| `L`/`l` tags | NIP-32 labels. Makes migrations discoverable via relay label queries |
296+
| `content` | JSON with `type` field for forward compatibility |
297+
298+
**Verification:** A client verifying a migration checks that both events exist, the `p` tags cross-reference correctly, the `e` tag on the acceptance matches the intent event ID, and both signatures are valid.
299+
255300
## Ecosystem Integration
256301

257302
Heartwood is a **signing appliance**. It does one thing: keep keys safe and sign when asked. Everything else talks to it over NIP-46.

0 commit comments

Comments
 (0)