mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-09 16:48:50 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
29ecdfc5ec | ||
|
|
d3fc4734b4 | ||
|
|
66d0b8a4e1 | ||
|
|
e2ec7a4b55 | ||
|
|
a72e47135a | ||
|
|
de7bbfc6a2 | ||
|
|
f2d421fa4f | ||
|
|
cae06fc4fe |
@@ -23,7 +23,7 @@ export type SubscribeManyParams = Omit<SubscriptionParams, 'onclose' | 'id'> & {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class AbstractSimplePool {
|
export class AbstractSimplePool {
|
||||||
protected relays = new Map<string, AbstractRelay>()
|
protected relays: Map<string, AbstractRelay> = new Map()
|
||||||
public seenOn: Map<string, Set<AbstractRelay>> = new Map()
|
public seenOn: Map<string, Set<AbstractRelay>> = new Map()
|
||||||
public trackRelays: boolean = false
|
public trackRelays: boolean = false
|
||||||
|
|
||||||
|
|||||||
7
jsr.json
7
jsr.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nostr/tools",
|
"name": "@nostr/tools",
|
||||||
"version": "2.3.2",
|
"version": "2.9.4",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./index.ts",
|
||||||
"./core": "./core.ts",
|
"./core": "./core.ts",
|
||||||
@@ -20,6 +20,7 @@
|
|||||||
"./nip10": "./nip10.ts",
|
"./nip10": "./nip10.ts",
|
||||||
"./nip11": "./nip11.ts",
|
"./nip11": "./nip11.ts",
|
||||||
"./nip13": "./nip13.ts",
|
"./nip13": "./nip13.ts",
|
||||||
|
"./nip17": "./nip17.ts",
|
||||||
"./nip18": "./nip18.ts",
|
"./nip18": "./nip18.ts",
|
||||||
"./nip19": "./nip19.ts",
|
"./nip19": "./nip19.ts",
|
||||||
"./nip21": "./nip21.ts",
|
"./nip21": "./nip21.ts",
|
||||||
@@ -34,6 +35,8 @@
|
|||||||
"./nip46": "./nip46.ts",
|
"./nip46": "./nip46.ts",
|
||||||
"./nip49": "./nip49.ts",
|
"./nip49": "./nip49.ts",
|
||||||
"./nip57": "./nip57.ts",
|
"./nip57": "./nip57.ts",
|
||||||
|
"./nip58": "./nip58.ts",
|
||||||
|
"./nip59": "./nip59.ts",
|
||||||
"./nip75": "./nip75.ts",
|
"./nip75": "./nip75.ts",
|
||||||
"./nip94": "./nip94.ts",
|
"./nip94": "./nip94.ts",
|
||||||
"./nip96": "./nip96.ts",
|
"./nip96": "./nip96.ts",
|
||||||
@@ -42,4 +45,4 @@
|
|||||||
"./fakejson": "./fakejson.ts",
|
"./fakejson": "./fakejson.ts",
|
||||||
"./utils": "./utils.ts"
|
"./utils": "./utils.ts"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
97
nip17.test.ts
Normal file
97
nip17.test.ts
Normal file
@@ -0,0 +1,97 @@
|
|||||||
|
import { test, expect } from 'bun:test'
|
||||||
|
import { getPublicKey } from './pure.ts'
|
||||||
|
import { decode } from './nip19.ts'
|
||||||
|
import { wrapEvent, wrapManyEvents, unwrapEvent } from './nip17.ts'
|
||||||
|
import { hexToBytes } from '@noble/hashes/utils'
|
||||||
|
|
||||||
|
const senderPrivateKey = decode(`nsec1p0ht6p3wepe47sjrgesyn4m50m6avk2waqudu9rl324cg2c4ufesyp6rdg`).data
|
||||||
|
|
||||||
|
const sk1 = hexToBytes('f09ac9b695d0a4c6daa418fe95b977eea20f54d9545592bc36a4f9e14f3eb840')
|
||||||
|
const sk2 = hexToBytes('5393a825e5892d8e18d4a5ea61ced105e8bb2a106f42876be3a40522e0b13747')
|
||||||
|
|
||||||
|
const recipients = [
|
||||||
|
{ publicKey: getPublicKey(sk1), relayUrl: 'wss://relay1.com' },
|
||||||
|
{ publicKey: getPublicKey(sk2) }, // No relay URL for this recipient
|
||||||
|
]
|
||||||
|
const message = 'Hello, this is a direct message!'
|
||||||
|
const conversationTitle = 'Private Group Conversation' // Optional
|
||||||
|
const replyTo = { eventId: 'previousEventId123' } // Optional, for replies
|
||||||
|
|
||||||
|
const wrappedEvent = wrapEvent(senderPrivateKey, recipients[0], message, conversationTitle, replyTo)
|
||||||
|
|
||||||
|
test('wrapEvent', () => {
|
||||||
|
const expected = {
|
||||||
|
content: '',
|
||||||
|
id: '',
|
||||||
|
created_at: 1728537932,
|
||||||
|
kind: 1059,
|
||||||
|
pubkey: '',
|
||||||
|
sig: '',
|
||||||
|
tags: [['p', 'b60849e5aae4113b236f9deb34f6f85605b4c53930651309a0d60c7ea721aad0']],
|
||||||
|
[Symbol('verified')]: true,
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(wrappedEvent.kind).toEqual(expected.kind)
|
||||||
|
expect(wrappedEvent.tags).toEqual(expected.tags)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('wrapManyEvents', () => {
|
||||||
|
const expected = [
|
||||||
|
{
|
||||||
|
kind: 1059,
|
||||||
|
content: '',
|
||||||
|
created_at: 1729581521,
|
||||||
|
tags: [['p', '611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9']],
|
||||||
|
pubkey: '',
|
||||||
|
id: '',
|
||||||
|
sig: '',
|
||||||
|
[Symbol('verified')]: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 1059,
|
||||||
|
content: '',
|
||||||
|
created_at: 1729594619,
|
||||||
|
tags: [['p', 'b60849e5aae4113b236f9deb34f6f85605b4c53930651309a0d60c7ea721aad0']],
|
||||||
|
pubkey: '',
|
||||||
|
id: '',
|
||||||
|
sig: '',
|
||||||
|
[Symbol('verified')]: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 1059,
|
||||||
|
content: '',
|
||||||
|
created_at: 1729560014,
|
||||||
|
tags: [['p', '36f7288c84d85ca6aa189dc3581d63ce140b7eeef5ae759421c5b5a3627312db']],
|
||||||
|
pubkey: '',
|
||||||
|
id: '',
|
||||||
|
sig: '',
|
||||||
|
[Symbol('verified')]: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const wrappedEvents = wrapManyEvents(senderPrivateKey, recipients, message, conversationTitle, replyTo)
|
||||||
|
|
||||||
|
wrappedEvents.forEach((event, index) => {
|
||||||
|
expect(event.kind).toEqual(expected[index].kind)
|
||||||
|
expect(event.tags).toEqual(expected[index].tags)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
test('unwrapEvent', () => {
|
||||||
|
const expected = {
|
||||||
|
kind: 14,
|
||||||
|
content: 'Hello, this is a direct message!',
|
||||||
|
pubkey: '611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9',
|
||||||
|
tags: [
|
||||||
|
['p', 'b60849e5aae4113b236f9deb34f6f85605b4c53930651309a0d60c7ea721aad0', 'wss://relay1.com'],
|
||||||
|
['e', 'previousEventId123', '', 'reply'],
|
||||||
|
['subject', 'Private Group Conversation'],
|
||||||
|
],
|
||||||
|
}
|
||||||
|
const result = unwrapEvent(wrappedEvent, sk1)
|
||||||
|
|
||||||
|
expect(result.kind).toEqual(expected.kind)
|
||||||
|
expect(result.content).toEqual(expected.content)
|
||||||
|
expect(result.pubkey).toEqual(expected.pubkey)
|
||||||
|
expect(result.tags).toEqual(expected.tags)
|
||||||
|
})
|
||||||
77
nip17.ts
Normal file
77
nip17.ts
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
import { PrivateDirectMessage } from './kinds.ts'
|
||||||
|
import { EventTemplate, NostrEvent, getPublicKey } from './pure.ts'
|
||||||
|
import * as nip59 from './nip59.ts'
|
||||||
|
|
||||||
|
type Recipient = {
|
||||||
|
publicKey: string
|
||||||
|
relayUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
type ReplyTo = {
|
||||||
|
eventId: string
|
||||||
|
relayUrl?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
function createEvent(
|
||||||
|
recipients: Recipient | Recipient[],
|
||||||
|
message: string,
|
||||||
|
conversationTitle?: string,
|
||||||
|
replyTo?: ReplyTo,
|
||||||
|
): EventTemplate {
|
||||||
|
const baseEvent: EventTemplate = {
|
||||||
|
created_at: Math.ceil(Date.now() / 1000),
|
||||||
|
kind: PrivateDirectMessage,
|
||||||
|
tags: [],
|
||||||
|
content: message,
|
||||||
|
}
|
||||||
|
|
||||||
|
const recipientsArray = Array.isArray(recipients) ? recipients : [recipients]
|
||||||
|
|
||||||
|
recipientsArray.forEach(({ publicKey, relayUrl }) => {
|
||||||
|
baseEvent.tags.push(relayUrl ? ['p', publicKey, relayUrl] : ['p', publicKey])
|
||||||
|
})
|
||||||
|
|
||||||
|
if (replyTo) {
|
||||||
|
baseEvent.tags.push(['e', replyTo.eventId, replyTo.relayUrl || '', 'reply'])
|
||||||
|
}
|
||||||
|
|
||||||
|
if (conversationTitle) {
|
||||||
|
baseEvent.tags.push(['subject', conversationTitle])
|
||||||
|
}
|
||||||
|
|
||||||
|
return baseEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrapEvent(
|
||||||
|
senderPrivateKey: Uint8Array,
|
||||||
|
recipient: Recipient,
|
||||||
|
message: string,
|
||||||
|
conversationTitle?: string,
|
||||||
|
replyTo?: ReplyTo,
|
||||||
|
): NostrEvent {
|
||||||
|
const event = createEvent(recipient, message, conversationTitle, replyTo)
|
||||||
|
return nip59.wrapEvent(event, senderPrivateKey, recipient.publicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function wrapManyEvents(
|
||||||
|
senderPrivateKey: Uint8Array,
|
||||||
|
recipients: Recipient[],
|
||||||
|
message: string,
|
||||||
|
conversationTitle?: string,
|
||||||
|
replyTo?: ReplyTo,
|
||||||
|
): NostrEvent[] {
|
||||||
|
if (!recipients || recipients.length === 0) {
|
||||||
|
throw new Error('At least one recipient is required.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderPublicKey = getPublicKey(senderPrivateKey)
|
||||||
|
|
||||||
|
// wrap the event for the sender and then for each recipient
|
||||||
|
return [{ publicKey: senderPublicKey }, ...recipients].map(recipient =>
|
||||||
|
wrapEvent(senderPrivateKey, recipient, message, conversationTitle, replyTo),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const unwrapEvent = nip59.unwrapEvent
|
||||||
|
|
||||||
|
export const unwrapManyEvents = nip59.unwrapManyEvents
|
||||||
8
nip28.ts
8
nip28.ts
@@ -1,5 +1,11 @@
|
|||||||
import { Event, finalizeEvent } from './pure.ts'
|
import { Event, finalizeEvent } from './pure.ts'
|
||||||
import { ChannelCreation, ChannelHideMessage, ChannelMessage, ChannelMetadata as KindChannelMetadata, ChannelMuteUser } from './kinds.ts'
|
import {
|
||||||
|
ChannelCreation,
|
||||||
|
ChannelHideMessage,
|
||||||
|
ChannelMessage,
|
||||||
|
ChannelMetadata as KindChannelMetadata,
|
||||||
|
ChannelMuteUser,
|
||||||
|
} from './kinds.ts'
|
||||||
|
|
||||||
export interface ChannelMetadata {
|
export interface ChannelMetadata {
|
||||||
name: string
|
name: string
|
||||||
|
|||||||
13
nip46.ts
13
nip46.ts
@@ -1,4 +1,3 @@
|
|||||||
import { hexToBytes } from '@noble/hashes/utils'
|
|
||||||
import { NostrEvent, UnsignedEvent, VerifiedEvent } from './core.ts'
|
import { NostrEvent, UnsignedEvent, VerifiedEvent } from './core.ts'
|
||||||
import { generateSecretKey, finalizeEvent, getPublicKey, verifyEvent } from './pure.ts'
|
import { generateSecretKey, finalizeEvent, getPublicKey, verifyEvent } from './pure.ts'
|
||||||
import { AbstractSimplePool, SubCloser } from './abstract-pool.ts'
|
import { AbstractSimplePool, SubCloser } from './abstract-pool.ts'
|
||||||
@@ -49,7 +48,7 @@ export async function parseBunkerInput(input: string): Promise<BunkerPointer | n
|
|||||||
return queryBunkerProfile(input)
|
return queryBunkerProfile(input)
|
||||||
}
|
}
|
||||||
|
|
||||||
async function queryBunkerProfile(nip05: string): Promise<BunkerPointer | null> {
|
export async function queryBunkerProfile(nip05: string): Promise<BunkerPointer | null> {
|
||||||
const match = nip05.match(NIP05_REGEX)
|
const match = nip05.match(NIP05_REGEX)
|
||||||
if (!match) return null
|
if (!match) return null
|
||||||
|
|
||||||
@@ -236,7 +235,7 @@ export class BunkerSigner {
|
|||||||
async signEvent(event: UnsignedEvent): Promise<VerifiedEvent> {
|
async signEvent(event: UnsignedEvent): Promise<VerifiedEvent> {
|
||||||
let resp = await this.sendRequest('sign_event', [JSON.stringify(event)])
|
let resp = await this.sendRequest('sign_event', [JSON.stringify(event)])
|
||||||
let signed: NostrEvent = JSON.parse(resp)
|
let signed: NostrEvent = JSON.parse(resp)
|
||||||
if (signed.pubkey === this.bp.pubkey && verifyEvent(signed)) {
|
if (verifyEvent(signed)) {
|
||||||
return signed
|
return signed
|
||||||
} else {
|
} else {
|
||||||
throw new Error(`event returned from bunker is improperly signed: ${JSON.stringify(signed)}`)
|
throw new Error(`event returned from bunker is improperly signed: ${JSON.stringify(signed)}`)
|
||||||
@@ -251,11 +250,6 @@ export class BunkerSigner {
|
|||||||
return await this.sendRequest('nip04_decrypt', [thirdPartyPubkey, ciphertext])
|
return await this.sendRequest('nip04_decrypt', [thirdPartyPubkey, ciphertext])
|
||||||
}
|
}
|
||||||
|
|
||||||
async nip44GetKey(thirdPartyPubkey: string): Promise<Uint8Array> {
|
|
||||||
let resp = await this.sendRequest('nip44_get_key', [thirdPartyPubkey])
|
|
||||||
return hexToBytes(resp)
|
|
||||||
}
|
|
||||||
|
|
||||||
async nip44Encrypt(thirdPartyPubkey: string, plaintext: string): Promise<string> {
|
async nip44Encrypt(thirdPartyPubkey: string, plaintext: string): Promise<string> {
|
||||||
return await this.sendRequest('nip44_encrypt', [thirdPartyPubkey, plaintext])
|
return await this.sendRequest('nip44_encrypt', [thirdPartyPubkey, plaintext])
|
||||||
}
|
}
|
||||||
@@ -298,9 +292,6 @@ export async function createAccount(
|
|||||||
return rpc
|
return rpc
|
||||||
}
|
}
|
||||||
|
|
||||||
// @deprecated use fetchBunkerProviders instead
|
|
||||||
export const fetchCustodialBunkers = fetchBunkerProviders
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetches info on available providers that announce themselves using NIP-89 events.
|
* Fetches info on available providers that announce themselves using NIP-89 events.
|
||||||
* @returns A promise that resolves to an array of available bunker objects.
|
* @returns A promise that resolves to an array of available bunker objects.
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
import { test, expect } from 'bun:test'
|
import { test, expect } from 'bun:test'
|
||||||
import { wrapEvent, unwrapEvent } from './nip59.ts'
|
import { wrapEvent, wrapManyEvents, unwrapEvent, unwrapManyEvents } from './nip59.ts'
|
||||||
import { decode } from './nip19.ts'
|
import { decode } from './nip19.ts'
|
||||||
import { getPublicKey } from './pure.ts'
|
import { NostrEvent, getPublicKey } from './pure.ts'
|
||||||
|
import { SimplePool } from './pool.ts'
|
||||||
|
import { GiftWrap } from './kinds.ts'
|
||||||
|
import { hexToBytes } from '@noble/hashes/utils'
|
||||||
|
|
||||||
const senderPrivateKey = decode(`nsec1p0ht6p3wepe47sjrgesyn4m50m6avk2waqudu9rl324cg2c4ufesyp6rdg`).data
|
const senderPrivateKey = decode(`nsec1p0ht6p3wepe47sjrgesyn4m50m6avk2waqudu9rl324cg2c4ufesyp6rdg`).data
|
||||||
const recipientPrivateKey = decode(`nsec1uyyrnx7cgfp40fcskcr2urqnzekc20fj0er6de0q8qvhx34ahazsvs9p36`).data
|
const recipientPrivateKey = decode(`nsec1uyyrnx7cgfp40fcskcr2urqnzekc20fj0er6de0q8qvhx34ahazsvs9p36`).data
|
||||||
@@ -11,7 +14,7 @@ const event = {
|
|||||||
content: 'Are you going to the party tonight?',
|
content: 'Are you going to the party tonight?',
|
||||||
}
|
}
|
||||||
|
|
||||||
const wrapedEvent = wrapEvent(event, senderPrivateKey, recipientPublicKey)
|
const wrappedEvent = wrapEvent(event, senderPrivateKey, recipientPublicKey)
|
||||||
|
|
||||||
test('wrapEvent', () => {
|
test('wrapEvent', () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
@@ -30,6 +33,38 @@ test('wrapEvent', () => {
|
|||||||
expect(result.tags).toEqual(expected.tags)
|
expect(result.tags).toEqual(expected.tags)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('wrapManyEvent', () => {
|
||||||
|
const expected = [
|
||||||
|
{
|
||||||
|
kind: 1059,
|
||||||
|
content: '',
|
||||||
|
created_at: 1729581521,
|
||||||
|
tags: [['p', '611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9']],
|
||||||
|
pubkey: '',
|
||||||
|
id: '',
|
||||||
|
sig: '',
|
||||||
|
[Symbol('verified')]: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 1059,
|
||||||
|
content: '',
|
||||||
|
created_at: 1729594619,
|
||||||
|
tags: [['p', '166bf3765ebd1fc55decfe395beff2ea3b2a4e0a8946e7eb578512b555737c99']],
|
||||||
|
pubkey: '',
|
||||||
|
id: '',
|
||||||
|
sig: '',
|
||||||
|
[Symbol('verified')]: true,
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
const wrappedEvents = wrapManyEvents(event, senderPrivateKey, [recipientPublicKey])
|
||||||
|
|
||||||
|
wrappedEvents.forEach((event, index) => {
|
||||||
|
expect(event.kind).toEqual(expected[index].kind)
|
||||||
|
expect(event.tags).toEqual(expected[index].tags)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
test('unwrapEvent', () => {
|
test('unwrapEvent', () => {
|
||||||
const expected = {
|
const expected = {
|
||||||
kind: 1,
|
kind: 1,
|
||||||
@@ -37,10 +72,42 @@ test('unwrapEvent', () => {
|
|||||||
pubkey: '611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9',
|
pubkey: '611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9',
|
||||||
tags: [],
|
tags: [],
|
||||||
}
|
}
|
||||||
const result = unwrapEvent(wrapedEvent, recipientPrivateKey)
|
const result = unwrapEvent(wrappedEvent, recipientPrivateKey)
|
||||||
|
|
||||||
expect(result.kind).toEqual(expected.kind)
|
expect(result.kind).toEqual(expected.kind)
|
||||||
expect(result.content).toEqual(expected.content)
|
expect(result.content).toEqual(expected.content)
|
||||||
expect(result.pubkey).toEqual(expected.pubkey)
|
expect(result.pubkey).toEqual(expected.pubkey)
|
||||||
expect(result.tags).toEqual(expected.tags)
|
expect(result.tags).toEqual(expected.tags)
|
||||||
})
|
})
|
||||||
|
|
||||||
|
test('getWrappedEvents and unwrapManyEvents', async () => {
|
||||||
|
const expected = [
|
||||||
|
{
|
||||||
|
created_at: 1729721879,
|
||||||
|
content: 'Hello!',
|
||||||
|
tags: [['p', '33d6bb037bf2e8c4571708e480e42d141bedc5a562b4884ec233b22d6fdea6aa']],
|
||||||
|
kind: 14,
|
||||||
|
pubkey: 'c0f56665e73eedc90b9565ecb34d961a2eb7ac1e2747899e4f73a813f940bc22',
|
||||||
|
id: 'aee0a3e6487b2ac8c1851cc84f3ae0fca9af8a9bdad85c4ba5fdf45d3ee817c3',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
created_at: 1729722025,
|
||||||
|
content: 'How are you?',
|
||||||
|
tags: [['p', '33d6bb037bf2e8c4571708e480e42d141bedc5a562b4884ec233b22d6fdea6aa']],
|
||||||
|
kind: 14,
|
||||||
|
pubkey: 'c0f56665e73eedc90b9565ecb34d961a2eb7ac1e2747899e4f73a813f940bc22',
|
||||||
|
id: '212387ec5efee7d6eb20b747121e9fc1adb798de6c3185e932335bb1bcc61a77',
|
||||||
|
},
|
||||||
|
]
|
||||||
|
const relays = ['wss://relay.damus.io', 'wss://nos.lol']
|
||||||
|
const privateKey = hexToBytes('582c3e7902c10c84d1cfe899a102e56bde628972d58d63011163ce0cdf4279b6')
|
||||||
|
const publicKey = '33d6bb037bf2e8c4571708e480e42d141bedc5a562b4884ec233b22d6fdea6aa'
|
||||||
|
|
||||||
|
const pool = new SimplePool()
|
||||||
|
const wrappedEvents: NostrEvent[] = await pool.querySync(relays, { kinds: [GiftWrap], '#p': [publicKey] })
|
||||||
|
const unwrappedEvents = unwrapManyEvents(wrappedEvents, privateKey)
|
||||||
|
|
||||||
|
unwrappedEvents.forEach((event, index) => {
|
||||||
|
expect(event).toEqual(expected[index])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
54
nip59.ts
54
nip59.ts
@@ -1,4 +1,4 @@
|
|||||||
import { EventTemplate, UnsignedEvent, Event } from './core.ts'
|
import { EventTemplate, UnsignedEvent, NostrEvent } from './core.ts'
|
||||||
import { getConversationKey, decrypt, encrypt } from './nip44.ts'
|
import { getConversationKey, decrypt, encrypt } from './nip44.ts'
|
||||||
import { getEventHash, generateSecretKey, finalizeEvent, getPublicKey } from './pure.ts'
|
import { getEventHash, generateSecretKey, finalizeEvent, getPublicKey } from './pure.ts'
|
||||||
import { Seal, GiftWrap } from './kinds.ts'
|
import { Seal, GiftWrap } from './kinds.ts'
|
||||||
@@ -15,10 +15,10 @@ const nip44ConversationKey = (privateKey: Uint8Array, publicKey: string) => getC
|
|||||||
const nip44Encrypt = (data: EventTemplate, privateKey: Uint8Array, publicKey: string) =>
|
const nip44Encrypt = (data: EventTemplate, privateKey: Uint8Array, publicKey: string) =>
|
||||||
encrypt(JSON.stringify(data), nip44ConversationKey(privateKey, publicKey))
|
encrypt(JSON.stringify(data), nip44ConversationKey(privateKey, publicKey))
|
||||||
|
|
||||||
const nip44Decrypt = (data: Event, privateKey: Uint8Array) =>
|
const nip44Decrypt = (data: NostrEvent, privateKey: Uint8Array) =>
|
||||||
JSON.parse(decrypt(data.content, nip44ConversationKey(privateKey, data.pubkey)))
|
JSON.parse(decrypt(data.content, nip44ConversationKey(privateKey, data.pubkey)))
|
||||||
|
|
||||||
export function createRumor(event: Partial<UnsignedEvent>, privateKey: Uint8Array) {
|
export function createRumor(event: Partial<UnsignedEvent>, privateKey: Uint8Array): Rumor {
|
||||||
const rumor = {
|
const rumor = {
|
||||||
created_at: now(),
|
created_at: now(),
|
||||||
content: '',
|
content: '',
|
||||||
@@ -32,7 +32,7 @@ export function createRumor(event: Partial<UnsignedEvent>, privateKey: Uint8Arra
|
|||||||
return rumor as Rumor
|
return rumor as Rumor
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createSeal(rumor: Rumor, privateKey: Uint8Array, recipientPublicKey: string) {
|
export function createSeal(rumor: Rumor, privateKey: Uint8Array, recipientPublicKey: string): NostrEvent {
|
||||||
return finalizeEvent(
|
return finalizeEvent(
|
||||||
{
|
{
|
||||||
kind: Seal,
|
kind: Seal,
|
||||||
@@ -41,10 +41,10 @@ export function createSeal(rumor: Rumor, privateKey: Uint8Array, recipientPublic
|
|||||||
tags: [],
|
tags: [],
|
||||||
},
|
},
|
||||||
privateKey,
|
privateKey,
|
||||||
) as Event
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function createWrap(seal: Event, recipientPublicKey: string) {
|
export function createWrap(seal: NostrEvent, recipientPublicKey: string): NostrEvent {
|
||||||
const randomKey = generateSecretKey()
|
const randomKey = generateSecretKey()
|
||||||
|
|
||||||
return finalizeEvent(
|
return finalizeEvent(
|
||||||
@@ -55,17 +55,53 @@ export function createWrap(seal: Event, recipientPublicKey: string) {
|
|||||||
tags: [['p', recipientPublicKey]],
|
tags: [['p', recipientPublicKey]],
|
||||||
},
|
},
|
||||||
randomKey,
|
randomKey,
|
||||||
) as Event
|
) as NostrEvent
|
||||||
}
|
}
|
||||||
|
|
||||||
export function wrapEvent(event: Partial<UnsignedEvent>, senderPrivateKey: Uint8Array, recipientPublicKey: string) {
|
export function wrapEvent(
|
||||||
|
event: Partial<UnsignedEvent>,
|
||||||
|
senderPrivateKey: Uint8Array,
|
||||||
|
recipientPublicKey: string,
|
||||||
|
): NostrEvent {
|
||||||
const rumor = createRumor(event, senderPrivateKey)
|
const rumor = createRumor(event, senderPrivateKey)
|
||||||
|
|
||||||
const seal = createSeal(rumor, senderPrivateKey, recipientPublicKey)
|
const seal = createSeal(rumor, senderPrivateKey, recipientPublicKey)
|
||||||
return createWrap(seal, recipientPublicKey)
|
return createWrap(seal, recipientPublicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
export function unwrapEvent(wrap: Event, recipientPrivateKey: Uint8Array) {
|
export function wrapManyEvents(
|
||||||
|
event: Partial<UnsignedEvent>,
|
||||||
|
senderPrivateKey: Uint8Array,
|
||||||
|
recipientsPublicKeys: string[],
|
||||||
|
): NostrEvent[] {
|
||||||
|
if (!recipientsPublicKeys || recipientsPublicKeys.length === 0) {
|
||||||
|
throw new Error('At least one recipient is required.')
|
||||||
|
}
|
||||||
|
|
||||||
|
const senderPublicKey = getPublicKey(senderPrivateKey)
|
||||||
|
|
||||||
|
const wrappeds = [wrapEvent(event, senderPrivateKey, senderPublicKey)]
|
||||||
|
|
||||||
|
recipientsPublicKeys.forEach(recipientPublicKey => {
|
||||||
|
wrappeds.push(wrapEvent(event, senderPrivateKey, recipientPublicKey))
|
||||||
|
})
|
||||||
|
|
||||||
|
return wrappeds
|
||||||
|
}
|
||||||
|
|
||||||
|
export function unwrapEvent(wrap: NostrEvent, recipientPrivateKey: Uint8Array): Rumor {
|
||||||
const unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey)
|
const unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey)
|
||||||
return nip44Decrypt(unwrappedSeal, recipientPrivateKey)
|
return nip44Decrypt(unwrappedSeal, recipientPrivateKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function unwrapManyEvents(wrappedEvents: NostrEvent[], recipientPrivateKey: Uint8Array): Rumor[] {
|
||||||
|
let unwrappedEvents: Rumor[] = []
|
||||||
|
|
||||||
|
wrappedEvents.forEach(e => {
|
||||||
|
unwrappedEvents.push(unwrapEvent(e, recipientPrivateKey))
|
||||||
|
})
|
||||||
|
|
||||||
|
unwrappedEvents.sort((a, b) => a.created_at - b.created_at)
|
||||||
|
|
||||||
|
return unwrappedEvents
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "nostr-tools",
|
"name": "nostr-tools",
|
||||||
"version": "2.9.1",
|
"version": "2.9.4",
|
||||||
"description": "Tools for making a Nostr client.",
|
"description": "Tools for making a Nostr client.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -103,6 +103,11 @@
|
|||||||
"require": "./lib/cjs/nip13.js",
|
"require": "./lib/cjs/nip13.js",
|
||||||
"types": "./lib/types/nip13.d.ts"
|
"types": "./lib/types/nip13.d.ts"
|
||||||
},
|
},
|
||||||
|
"./nip17": {
|
||||||
|
"import": "./lib/esm/nip17.js",
|
||||||
|
"require": "./lib/cjs/nip17.js",
|
||||||
|
"types": "./lib/types/nip17.d.ts"
|
||||||
|
},
|
||||||
"./nip18": {
|
"./nip18": {
|
||||||
"import": "./lib/esm/nip18.js",
|
"import": "./lib/esm/nip18.js",
|
||||||
"require": "./lib/cjs/nip18.js",
|
"require": "./lib/cjs/nip18.js",
|
||||||
|
|||||||
Reference in New Issue
Block a user