From 336ff25710af1f3e8d73591acbc1704d19eea7a3 Mon Sep 17 00:00:00 2001 From: BCR Date: Tue, 8 Sep 2026 14:57:15 -0400 Subject: [PATCH] nip13: let minePow keep a caller-chosen created_at (NIP-59 wraps) (#552) * nip13: let minePow keep a caller-chosen created_at minePow refreshes created_at to the current second while it mines, which is right for a plain note but wrong for anything that has already chosen its timestamp: NIP-59 seals and gift wraps carry a randomized created_at up to two days in the past, and callers mining those lost it. This adds an opt-in keepCreatedAt option (default behaviour unchanged) with a test. * Delete bun.lock --------- Co-authored-by: Ronish Bhatt <16808243+sunmoonron@users.noreply.github.com> --- nip13.test.ts | 20 ++++++++++++++++++++ nip13.ts | 23 ++++++++++++++++++----- 2 files changed, 38 insertions(+), 5 deletions(-) diff --git a/nip13.test.ts b/nip13.test.ts index 7c4a805..56ff087 100644 --- a/nip13.test.ts +++ b/nip13.test.ts @@ -28,3 +28,23 @@ test('mines POW for an event', async () => { expect(getPow(event.id)).toBeGreaterThanOrEqual(difficulty) }) + +test('keeps a caller-chosen created_at when asked', async () => { + const difficulty = 10 + const createdAt = 1700000000 // a fixed, past timestamp, as NIP-59 wraps use + + const event = minePow( + { + kind: 1, + tags: [], + content: 'Hello, world!', + created_at: createdAt, + pubkey: '79c2cae114ea28a981e7559b4fe7854a473521a8d22a66bbab9fa248eb820ff6', + }, + difficulty, + { keepCreatedAt: true }, + ) + + expect(event.created_at).toEqual(createdAt) + expect(getPow(event.id)).toBeGreaterThanOrEqual(difficulty) +}) diff --git a/nip13.ts b/nip13.ts index 8b078a4..57b7363 100644 --- a/nip13.ts +++ b/nip13.ts @@ -38,11 +38,22 @@ function getPowFromBytes(hash: Uint8Array): number { return count } +export type MinePowOptions = { + /** + * By default the miner refreshes `created_at` to the current second while it works and restarts the + * nonce whenever the second changes, so a long mining run still yields a fresh timestamp. Set this + * to `true` to leave `created_at` exactly as given. NIP-59 gift wraps and seals must carry a + * randomized timestamp (up to two days in the past), and any other caller that has already chosen a + * `created_at` needs it to survive mining. + */ + keepCreatedAt?: boolean +} + /** * Mine an event with the desired POW. This function mutates the event. * Note that this operation is synchronous and should be run in a worker context to avoid blocking the main thread. */ -export function minePow(unsigned: UnsignedEvent, difficulty: number): Omit { +export function minePow(unsigned: UnsignedEvent, difficulty: number, options: MinePowOptions = {}): Omit { let count = 0 const event = unsigned as Omit @@ -51,11 +62,13 @@ export function minePow(unsigned: UnsignedEvent, difficulty: number): Omit