From 1d89038375a667d26e933c375bf88a290b342f6e Mon Sep 17 00:00:00 2001 From: ciegovolador Date: Thu, 17 Oct 2024 09:38:04 -0300 Subject: [PATCH] add nip59 (#438) * feat(nip59) add nip59 based on https://nips.nostr.com/59 * fix(nip59) export the code as nip59 * Update nip59.ts Co-authored-by: Asai Toshiya * fix(nip59) change GiftWrap kind and using kinds from kinds.ts --------- Co-authored-by: Asai Toshiya --- index.ts | 1 + kinds.ts | 2 +- nip59.test.ts | 49 +++++++++++++++++++++++++++++++++ nip59.ts | 76 +++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 127 insertions(+), 1 deletion(-) create mode 100644 nip59.test.ts create mode 100644 nip59.ts diff --git a/index.ts b/index.ts index 2a19672..706fa45 100644 --- a/index.ts +++ b/index.ts @@ -21,6 +21,7 @@ export * as nip42 from './nip42.ts' export * as nip44 from './nip44.ts' export * as nip47 from './nip47.ts' export * as nip57 from './nip57.ts' +export * as nip59 from './nip59.ts' export * as nip98 from './nip98.ts' export * as kinds from './kinds.ts' diff --git a/kinds.ts b/kinds.ts index dc49b26..ad5ab63 100644 --- a/kinds.ts +++ b/kinds.ts @@ -48,6 +48,7 @@ export const ChannelMessage = 42 export const ChannelHideMessage = 43 export const ChannelMuteUser = 44 export const OpenTimestamps = 1040 +export const GiftWrap = 1059 export const FileMetadata = 1063 export const LiveChatMessage = 1311 export const ProblemTracker = 1971 @@ -73,7 +74,6 @@ export const SearchRelaysList = 10007 export const InterestsList = 10015 export const UserEmojiList = 10030 export const DirectMessageRelaysList = 10050 -export const GiftWrap = 10059 export const FileServerPreference = 10096 export const NWCWalletInfo = 13194 export const LightningPubRPC = 21000 diff --git a/nip59.test.ts b/nip59.test.ts new file mode 100644 index 0000000..8553599 --- /dev/null +++ b/nip59.test.ts @@ -0,0 +1,49 @@ +import { test, expect } from 'bun:test' +import { wrapEvent, unwrapEvent } from './nip59.ts' +import { decode } from './nip19.ts' +import { getPublicKey } from './pure.ts' + + +const senderPrivateKey = decode(`nsec1p0ht6p3wepe47sjrgesyn4m50m6avk2waqudu9rl324cg2c4ufesyp6rdg`).data +const recipientPrivateKey = decode(`nsec1uyyrnx7cgfp40fcskcr2urqnzekc20fj0er6de0q8qvhx34ahazsvs9p36`).data +const recipientPublicKey = getPublicKey(recipientPrivateKey) +const event = { + kind: 1, + content: "Are you going to the party tonight?", +} + +const wrapedEvent = wrapEvent(event, senderPrivateKey, recipientPublicKey) + +test('wrapEvent', () => { + const expected = { + content: '', id: '', created_at: 1728537932, kind: 1059, pubkey: '', sig: '', tags: [ + [ + "p", + "166bf3765ebd1fc55decfe395beff2ea3b2a4e0a8946e7eb578512b555737c99" + ] + ], + [Symbol('verified')]: true, + } + const result = wrapEvent(event, senderPrivateKey, recipientPublicKey) + + expect(result.kind).toEqual(expected.kind) + expect(result.tags).toEqual(expected.tags) + +}) + +test('unwrapEvent', () => { + const expected = { + kind: 1, + content: "Are you going to the party tonight?", + pubkey: "611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9", + tags: [], + } + const result = unwrapEvent(wrapedEvent, recipientPrivateKey) + + expect(result.kind).toEqual(expected.kind) + expect(result.content).toEqual(expected.content) + expect(result.pubkey).toEqual(expected.pubkey) + expect(result.tags).toEqual(expected.tags) + +}) + diff --git a/nip59.ts b/nip59.ts new file mode 100644 index 0000000..6dba716 --- /dev/null +++ b/nip59.ts @@ -0,0 +1,76 @@ + +import { EventTemplate, UnsignedEvent, Event } from './core.ts' +import { getConversationKey, decrypt, encrypt } from './nip44.ts' +import { getEventHash, generateSecretKey, finalizeEvent, getPublicKey } from './pure.ts' +import { Seal, GiftWrap } from './kinds.ts' + +type Rumor = UnsignedEvent & { id: string } + +const TWO_DAYS = 2 * 24 * 60 * 60 + +const now = () => Math.round(Date.now() / 1000) +const randomNow = () => Math.round(now() - (Math.random() * TWO_DAYS)) + + +const nip44ConversationKey = (privateKey: Uint8Array, publicKey: string) => + getConversationKey(privateKey, publicKey) + +const nip44Encrypt = (data: EventTemplate, privateKey: Uint8Array, publicKey: string) => + encrypt(JSON.stringify(data), nip44ConversationKey(privateKey, publicKey)) + +const nip44Decrypt = (data: Event, privateKey: Uint8Array) => + JSON.parse(decrypt(data.content, nip44ConversationKey(privateKey, data.pubkey))) + +export function createRumor(event: Partial, privateKey: Uint8Array) { + const rumor = { + created_at: now(), + content: "", + tags: [], + ...event, + pubkey: getPublicKey(privateKey), + } as any + + rumor.id = getEventHash(rumor) + + return rumor as Rumor +} + +export function createSeal(rumor: Rumor, privateKey: Uint8Array, recipientPublicKey: string) { + return finalizeEvent( + { + kind: Seal, + content: nip44Encrypt(rumor, privateKey, recipientPublicKey), + created_at: randomNow(), + tags: [], + }, + privateKey + ) as Event +} + +export function createWrap(seal: Event, recipientPublicKey: string) { + const randomKey = generateSecretKey() + + return finalizeEvent( + { + kind: GiftWrap, + content: nip44Encrypt(seal, randomKey, recipientPublicKey), + created_at: randomNow(), + tags: [["p", recipientPublicKey]], + }, + randomKey + ) as Event +} + +export function wrapEvent(event: Partial, senderPrivateKey: Uint8Array, recipientPublicKey: string) { + + const rumor = createRumor(event, senderPrivateKey) + + const seal = createSeal(rumor, senderPrivateKey, recipientPublicKey) + return createWrap(seal, recipientPublicKey) +} + +export function unwrapEvent(wrap: Event, recipientPrivateKey: Uint8Array) { + + const unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey) + return nip44Decrypt(unwrappedSeal, recipientPrivateKey) +}