Compare commits

...

7 Commits

Author SHA1 Message Date
fiatjaf
4c2d2b5ce6 nip46: fix getPublicKey() by making it actually call "get_public_key". 2024-10-23 16:38:14 -03:00
António Conselheiro
aba266b8e6 Suggestion: export kinds as named types (#447)
* including kinds for nip17 and nip59

* including kinds as types

* solving linter with prettier
2024-10-23 10:39:39 -03:00
Asai Toshiya
d7dcc75ebe nip19: completely remove nrelay. 2024-10-22 13:06:34 -03:00
fiatjaf
b18510b460 nip13: speed improvements. 2024-10-22 13:03:29 -03:00
Egge
b04e0d16c0 test: fixed nip06 assertion 2024-10-20 10:53:08 -03:00
Egge
633696bf46 nip06: return Uint8 instead of string 2024-10-20 10:53:08 -03:00
ciegovolador
bf975c9a87 fix(nip59) formated code 2024-10-18 07:20:37 -03:00
12 changed files with 166 additions and 76 deletions

View File

@@ -1,5 +1,6 @@
import { test, expect } from 'bun:test' import { expect, test } from 'bun:test'
import { classifyKind } from './kinds.ts' import { classifyKind, isKind, Repost, ShortTextNote } from './kinds.ts'
import { finalizeEvent, generateSecretKey } from './pure.ts'
test('kind classification', () => { test('kind classification', () => {
expect(classifyKind(1)).toBe('regular') expect(classifyKind(1)).toBe('regular')
@@ -19,3 +20,22 @@ test('kind classification', () => {
expect(classifyKind(40000)).toBe('unknown') expect(classifyKind(40000)).toBe('unknown')
expect(classifyKind(255)).toBe('unknown') expect(classifyKind(255)).toBe('unknown')
}) })
test('kind type guard', () => {
const privateKey = generateSecretKey()
const repostedEvent = finalizeEvent(
{
kind: ShortTextNote,
tags: [
['e', 'replied event id'],
['p', 'replied event pubkey'],
],
content: 'Replied to a post',
created_at: 1617932115,
},
privateKey,
)
expect(isKind(repostedEvent, ShortTextNote)).toBeTrue()
expect(isKind(repostedEvent, Repost)).toBeFalse()
})

View File

@@ -1,3 +1,5 @@
import { NostrEvent, validateEvent } from './pure.ts'
/** Events are **regular**, which means they're all expected to be stored by relays. */ /** Events are **regular**, which means they're all expected to be stored by relays. */
export function isRegularKind(kind: number): boolean { export function isRegularKind(kind: number): boolean {
return (1000 <= kind && kind < 10000) || [1, 2, 4, 5, 6, 7, 8, 16, 40, 41, 42, 43, 44].includes(kind) return (1000 <= kind && kind < 10000) || [1, 2, 4, 5, 6, 7, 8, 16, 40, 41, 42, 43, 44].includes(kind)
@@ -30,80 +32,162 @@ export function classifyKind(kind: number): KindClassification {
return 'unknown' return 'unknown'
} }
export function isKind<T extends number>(event: unknown, kind: T | Array<T>): event is NostrEvent & { kind: T } {
const kindAsArray: number[] = kind instanceof Array ? kind : [kind]
return (validateEvent(event) && kindAsArray.includes(event.kind)) || false
}
export const Metadata = 0 export const Metadata = 0
export type Metadata = typeof Metadata
export const ShortTextNote = 1 export const ShortTextNote = 1
export type ShortTextNote = typeof ShortTextNote
export const RecommendRelay = 2 export const RecommendRelay = 2
export type RecommendRelay = typeof RecommendRelay
export const Contacts = 3 export const Contacts = 3
export type Contacts = typeof Contacts
export const EncryptedDirectMessage = 4 export const EncryptedDirectMessage = 4
export type EncryptedDirectMessage = typeof EncryptedDirectMessage
export const EventDeletion = 5 export const EventDeletion = 5
export type EventDeletion = typeof EventDeletion
export const Repost = 6 export const Repost = 6
export type Repost = typeof Repost
export const Reaction = 7 export const Reaction = 7
export type Reaction = typeof Reaction
export const BadgeAward = 8 export const BadgeAward = 8
export type BadgeAward = typeof BadgeAward
export const Seal = 13 export const Seal = 13
export type Seal = typeof Seal
export const PrivateDirectMessage = 14 export const PrivateDirectMessage = 14
export type PrivateDirectMessage = typeof PrivateDirectMessage
export const GenericRepost = 16 export const GenericRepost = 16
export type GenericRepost = typeof GenericRepost
export const ChannelCreation = 40 export const ChannelCreation = 40
export type ChannelCreation = typeof ChannelCreation
export const ChannelMetadata = 41 export const ChannelMetadata = 41
export type ChannelMetadata = typeof ChannelMetadata
export const ChannelMessage = 42 export const ChannelMessage = 42
export type ChannelMessage = typeof ChannelMessage
export const ChannelHideMessage = 43 export const ChannelHideMessage = 43
export type ChannelHideMessage = typeof ChannelHideMessage
export const ChannelMuteUser = 44 export const ChannelMuteUser = 44
export type ChannelMuteUser = typeof ChannelMuteUser
export const OpenTimestamps = 1040 export const OpenTimestamps = 1040
export type OpenTimestamps = typeof OpenTimestamps
export const GiftWrap = 1059 export const GiftWrap = 1059
export type GiftWrap = typeof GiftWrap
export const FileMetadata = 1063 export const FileMetadata = 1063
export type FileMetadata = typeof FileMetadata
export const LiveChatMessage = 1311 export const LiveChatMessage = 1311
export type LiveChatMessage = typeof LiveChatMessage
export const ProblemTracker = 1971 export const ProblemTracker = 1971
export type ProblemTracker = typeof ProblemTracker
export const Report = 1984 export const Report = 1984
export type Report = typeof Report
export const Reporting = 1984 export const Reporting = 1984
export type Reporting = typeof Reporting
export const Label = 1985 export const Label = 1985
export type Label = typeof Label
export const CommunityPostApproval = 4550 export const CommunityPostApproval = 4550
export type CommunityPostApproval = typeof CommunityPostApproval
export const JobRequest = 5999 export const JobRequest = 5999
export type JobRequest = typeof JobRequest
export const JobResult = 6999 export const JobResult = 6999
export type JobResult = typeof JobResult
export const JobFeedback = 7000 export const JobFeedback = 7000
export type JobFeedback = typeof JobFeedback
export const ZapGoal = 9041 export const ZapGoal = 9041
export type ZapGoal = typeof ZapGoal
export const ZapRequest = 9734 export const ZapRequest = 9734
export type ZapRequest = typeof ZapRequest
export const Zap = 9735 export const Zap = 9735
export type Zap = typeof Zap
export const Highlights = 9802 export const Highlights = 9802
export type Highlights = typeof Highlights
export const Mutelist = 10000 export const Mutelist = 10000
export type Mutelist = typeof Mutelist
export const Pinlist = 10001 export const Pinlist = 10001
export type Pinlist = typeof Pinlist
export const RelayList = 10002 export const RelayList = 10002
export type RelayList = typeof RelayList
export const BookmarkList = 10003 export const BookmarkList = 10003
export type BookmarkList = typeof BookmarkList
export const CommunitiesList = 10004 export const CommunitiesList = 10004
export type CommunitiesList = typeof CommunitiesList
export const PublicChatsList = 10005 export const PublicChatsList = 10005
export type PublicChatsList = typeof PublicChatsList
export const BlockedRelaysList = 10006 export const BlockedRelaysList = 10006
export type BlockedRelaysList = typeof BlockedRelaysList
export const SearchRelaysList = 10007 export const SearchRelaysList = 10007
export type SearchRelaysList = typeof SearchRelaysList
export const InterestsList = 10015 export const InterestsList = 10015
export type InterestsList = typeof InterestsList
export const UserEmojiList = 10030 export const UserEmojiList = 10030
export type UserEmojiList = typeof UserEmojiList
export const DirectMessageRelaysList = 10050 export const DirectMessageRelaysList = 10050
export type DirectMessageRelaysList = typeof DirectMessageRelaysList
export const FileServerPreference = 10096 export const FileServerPreference = 10096
export type FileServerPreference = typeof FileServerPreference
export const NWCWalletInfo = 13194 export const NWCWalletInfo = 13194
export type NWCWalletInfo = typeof NWCWalletInfo
export const LightningPubRPC = 21000 export const LightningPubRPC = 21000
export type LightningPubRPC = typeof LightningPubRPC
export const ClientAuth = 22242 export const ClientAuth = 22242
export type ClientAuth = typeof ClientAuth
export const NWCWalletRequest = 23194 export const NWCWalletRequest = 23194
export type NWCWalletRequest = typeof NWCWalletRequest
export const NWCWalletResponse = 23195 export const NWCWalletResponse = 23195
export type NWCWalletResponse = typeof NWCWalletResponse
export const NostrConnect = 24133 export const NostrConnect = 24133
export type NostrConnect = typeof NostrConnect
export const HTTPAuth = 27235 export const HTTPAuth = 27235
export type HTTPAuth = typeof HTTPAuth
export const Followsets = 30000 export const Followsets = 30000
export type Followsets = typeof Followsets
export const Genericlists = 30001 export const Genericlists = 30001
export type Genericlists = typeof Genericlists
export const Relaysets = 30002 export const Relaysets = 30002
export type Relaysets = typeof Relaysets
export const Bookmarksets = 30003 export const Bookmarksets = 30003
export type Bookmarksets = typeof Bookmarksets
export const Curationsets = 30004 export const Curationsets = 30004
export type Curationsets = typeof Curationsets
export const ProfileBadges = 30008 export const ProfileBadges = 30008
export type ProfileBadges = typeof ProfileBadges
export const BadgeDefinition = 30009 export const BadgeDefinition = 30009
export type BadgeDefinition = typeof BadgeDefinition
export const Interestsets = 30015 export const Interestsets = 30015
export type Interestsets = typeof Interestsets
export const CreateOrUpdateStall = 30017 export const CreateOrUpdateStall = 30017
export type CreateOrUpdateStall = typeof CreateOrUpdateStall
export const CreateOrUpdateProduct = 30018 export const CreateOrUpdateProduct = 30018
export type CreateOrUpdateProduct = typeof CreateOrUpdateProduct
export const LongFormArticle = 30023 export const LongFormArticle = 30023
export type LongFormArticle = typeof LongFormArticle
export const DraftLong = 30024 export const DraftLong = 30024
export type DraftLong = typeof DraftLong
export const Emojisets = 30030 export const Emojisets = 30030
export type Emojisets = typeof Emojisets
export const Application = 30078 export const Application = 30078
export type Application = typeof Application
export const LiveEvent = 30311 export const LiveEvent = 30311
export type LiveEvent = typeof LiveEvent
export const UserStatuses = 30315 export const UserStatuses = 30315
export type UserStatuses = typeof UserStatuses
export const ClassifiedListing = 30402 export const ClassifiedListing = 30402
export type ClassifiedListing = typeof ClassifiedListing
export const DraftClassifiedListing = 30403 export const DraftClassifiedListing = 30403
export type DraftClassifiedListing = typeof DraftClassifiedListing
export const Date = 31922 export const Date = 31922
export type Date = typeof Date
export const Time = 31923 export const Time = 31923
export type Time = typeof Time
export const Calendar = 31924 export const Calendar = 31924
export type Calendar = typeof Calendar
export const CalendarEventRSVP = 31925 export const CalendarEventRSVP = 31925
export type CalendarEventRSVP = typeof CalendarEventRSVP
export const Handlerrecommendation = 31989 export const Handlerrecommendation = 31989
export type Handlerrecommendation = typeof Handlerrecommendation
export const Handlerinformation = 31990 export const Handlerinformation = 31990
export type Handlerinformation = typeof Handlerinformation
export const CommunityDefinition = 34550 export const CommunityDefinition = 34550
export type CommunityDefinition = typeof CommunityDefinition

View File

@@ -5,38 +5,39 @@ import {
extendedKeysFromSeedWords, extendedKeysFromSeedWords,
accountFromExtendedKey, accountFromExtendedKey,
} from './nip06.ts' } from './nip06.ts'
import { hexToBytes } from '@noble/hashes/utils'
test('generate private key from a mnemonic', async () => { test('generate private key from a mnemonic', async () => {
const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong'
const privateKey = privateKeyFromSeedWords(mnemonic) const privateKey = privateKeyFromSeedWords(mnemonic)
expect(privateKey).toEqual('c26cf31d8ba425b555ca27d00ca71b5008004f2f662470f8c8131822ec129fe2') expect(privateKey).toEqual(hexToBytes('c26cf31d8ba425b555ca27d00ca71b5008004f2f662470f8c8131822ec129fe2'))
}) })
test('generate private key for account 1 from a mnemonic', async () => { test('generate private key for account 1 from a mnemonic', async () => {
const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong'
const privateKey = privateKeyFromSeedWords(mnemonic, undefined, 1) const privateKey = privateKeyFromSeedWords(mnemonic, undefined, 1)
expect(privateKey).toEqual('b5fc7f229de3fb5c189063e3b3fc6c921d8f4366cff5bd31c6f063493665eb2b') expect(privateKey).toEqual(hexToBytes('b5fc7f229de3fb5c189063e3b3fc6c921d8f4366cff5bd31c6f063493665eb2b'))
}) })
test('generate private key from a mnemonic and passphrase', async () => { test('generate private key from a mnemonic and passphrase', async () => {
const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong'
const passphrase = '123' const passphrase = '123'
const privateKey = privateKeyFromSeedWords(mnemonic, passphrase) const privateKey = privateKeyFromSeedWords(mnemonic, passphrase)
expect(privateKey).toEqual('55a22b8203273d0aaf24c22c8fbe99608e70c524b17265641074281c8b978ae4') expect(privateKey).toEqual(hexToBytes('55a22b8203273d0aaf24c22c8fbe99608e70c524b17265641074281c8b978ae4'))
}) })
test('generate private key for account 1 from a mnemonic and passphrase', async () => { test('generate private key for account 1 from a mnemonic and passphrase', async () => {
const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong'
const passphrase = '123' const passphrase = '123'
const privateKey = privateKeyFromSeedWords(mnemonic, passphrase, 1) const privateKey = privateKeyFromSeedWords(mnemonic, passphrase, 1)
expect(privateKey).toEqual('2e0f7bd9e3c3ebcdff1a90fb49c913477e7c055eba1a415d571b6a8c714c7135') expect(privateKey).toEqual(hexToBytes('2e0f7bd9e3c3ebcdff1a90fb49c913477e7c055eba1a415d571b6a8c714c7135'))
}) })
test('generate private and public key for account 1 from a mnemonic and passphrase', async () => { test('generate private and public key for account 1 from a mnemonic and passphrase', async () => {
const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong' const mnemonic = 'zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo zoo wrong'
const passphrase = '123' const passphrase = '123'
const { privateKey, publicKey } = accountFromSeedWords(mnemonic, passphrase, 1) const { privateKey, publicKey } = accountFromSeedWords(mnemonic, passphrase, 1)
expect(privateKey).toEqual('2e0f7bd9e3c3ebcdff1a90fb49c913477e7c055eba1a415d571b6a8c714c7135') expect(privateKey).toEqual(hexToBytes('2e0f7bd9e3c3ebcdff1a90fb49c913477e7c055eba1a415d571b6a8c714c7135'))
expect(publicKey).toEqual('13f55f4f01576570ea342eb7d2b611f9dc78f8dc601aeb512011e4e73b90cf0a') expect(publicKey).toEqual('13f55f4f01576570ea342eb7d2b611f9dc78f8dc601aeb512011e4e73b90cf0a')
}) })
@@ -63,7 +64,7 @@ test('generate account from extended private key', () => {
'xprv9z78fizET65qsCaRr1MSutTSGk1fcKfSt1sBqmuWShtkjRJJ4WCKcSnha6EmgNzFSsyom3MWtydHyPtJtSLZQUtictVQtM2vkPcguh6TQCH' 'xprv9z78fizET65qsCaRr1MSutTSGk1fcKfSt1sBqmuWShtkjRJJ4WCKcSnha6EmgNzFSsyom3MWtydHyPtJtSLZQUtictVQtM2vkPcguh6TQCH'
const { privateKey, publicKey } = accountFromExtendedKey(xprv) const { privateKey, publicKey } = accountFromExtendedKey(xprv)
expect(privateKey).toBe('5f29af3b9676180290e77a4efad265c4c2ff28a5302461f73597fda26bb25731') expect(privateKey).toEqual(hexToBytes('5f29af3b9676180290e77a4efad265c4c2ff28a5302461f73597fda26bb25731'))
expect(publicKey).toBe('e8bcf3823669444d0b49ad45d65088635d9fd8500a75b5f20b59abefa56a144f') expect(publicKey).toBe('e8bcf3823669444d0b49ad45d65088635d9fd8500a75b5f20b59abefa56a144f')
}) })

View File

@@ -5,11 +5,11 @@ import { HDKey } from '@scure/bip32'
const DERIVATION_PATH = `m/44'/1237'` const DERIVATION_PATH = `m/44'/1237'`
export function privateKeyFromSeedWords(mnemonic: string, passphrase?: string, accountIndex = 0): string { export function privateKeyFromSeedWords(mnemonic: string, passphrase?: string, accountIndex = 0): Uint8Array {
let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase)) let root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase))
let privateKey = root.derive(`${DERIVATION_PATH}/${accountIndex}'/0/0`).privateKey let privateKey = root.derive(`${DERIVATION_PATH}/${accountIndex}'/0/0`).privateKey
if (!privateKey) throw new Error('could not derive private key') if (!privateKey) throw new Error('could not derive private key')
return bytesToHex(privateKey) return privateKey
} }
export function accountFromSeedWords( export function accountFromSeedWords(
@@ -17,14 +17,14 @@ export function accountFromSeedWords(
passphrase?: string, passphrase?: string,
accountIndex = 0, accountIndex = 0,
): { ): {
privateKey: string privateKey: Uint8Array
publicKey: string publicKey: string
} { } {
const root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase)) const root = HDKey.fromMasterSeed(mnemonicToSeedSync(mnemonic, passphrase))
const seed = root.derive(`${DERIVATION_PATH}/${accountIndex}'/0/0`) const seed = root.derive(`${DERIVATION_PATH}/${accountIndex}'/0/0`)
const privateKey = bytesToHex(seed.privateKey!)
const publicKey = bytesToHex(seed.publicKey!.slice(1)) const publicKey = bytesToHex(seed.publicKey!.slice(1))
if (!privateKey && !publicKey) { const privateKey = seed.privateKey
if (!privateKey || !publicKey) {
throw new Error('could not derive key pair') throw new Error('could not derive key pair')
} }
return { privateKey, publicKey } return { privateKey, publicKey }
@@ -50,7 +50,7 @@ export function accountFromExtendedKey(
base58key: string, base58key: string,
accountIndex = 0, accountIndex = 0,
): { ): {
privateKey?: string privateKey?: Uint8Array
publicKey: string publicKey: string
} { } {
let extendedKey = HDKey.fromExtendedKey(base58key) let extendedKey = HDKey.fromExtendedKey(base58key)
@@ -59,7 +59,7 @@ export function accountFromExtendedKey(
let publicKey = bytesToHex(child.publicKey!.slice(1)) let publicKey = bytesToHex(child.publicKey!.slice(1))
if (!publicKey) throw new Error('could not derive public key') if (!publicKey) throw new Error('could not derive public key')
if (version === 'xprv') { if (version === 'xprv') {
let privateKey = bytesToHex(child.privateKey!) let privateKey = child.privateKey!
if (!privateKey) throw new Error('could not derive private key') if (!privateKey) throw new Error('could not derive private key')
return { privateKey, publicKey } return { privateKey, publicKey }
} }

View File

@@ -2,9 +2,14 @@ import { test, expect } from 'bun:test'
import { getPow, minePow } from './nip13.ts' import { getPow, minePow } from './nip13.ts'
test('identifies proof-of-work difficulty', async () => { test('identifies proof-of-work difficulty', async () => {
const id = '000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358' ;[
const difficulty = getPow(id) ['000006d8c378af1779d2feebc7603a125d99eca0ccf1085959b307f64e5dd358', 21],
expect(difficulty).toEqual(21) ['6bf5b4f434813c64b523d2b0e6efe18f3bd0cbbd0a5effd8ece9e00fd2531996', 1],
['00003479309ecdb46b1c04ce129d2709378518588bed6776e60474ebde3159ae', 18],
['01a76167d41add96be4959d9e618b7a35f26551d62c43c11e5e64094c6b53c83', 7],
['ac4f44bae06a45ebe88cfbd3c66358750159650a26c0d79e8ccaa92457fca4f6', 0],
['0000000000000000006cfbd3c66358750159650a26c0d79e8ccaa92457fca4f6', 73],
].forEach(([id, diff]) => expect(getPow(id as string)).toEqual(diff as number))
}) })
test('mines POW for an event', async () => { test('mines POW for an event', async () => {

View File

@@ -1,15 +1,19 @@
import { type UnsignedEvent, type Event, getEventHash } from './pure.ts' import { bytesToHex } from '@noble/hashes/utils'
import { type UnsignedEvent, type Event } from './pure.ts'
import { sha256 } from '@noble/hashes/sha256'
import { utf8Encoder } from './utils.ts'
/** Get POW difficulty from a Nostr hex ID. */ /** Get POW difficulty from a Nostr hex ID. */
export function getPow(hex: string): number { export function getPow(hex: string): number {
let count = 0 let count = 0
for (let i = 0; i < hex.length; i++) { for (let i = 0; i < 64; i += 8) {
const nibble = parseInt(hex[i], 16) const nibble = parseInt(hex.substring(i, i + 8), 16)
if (nibble === 0) { if (nibble === 0) {
count += 4 count += 32
} else { } else {
count += Math.clz32(nibble) - 28 count += Math.clz32(nibble)
break break
} }
} }
@@ -20,8 +24,6 @@ export function getPow(hex: string): number {
/** /**
* Mine an event with the desired POW. This function mutates the event. * 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. * Note that this operation is synchronous and should be run in a worker context to avoid blocking the main thread.
*
* Adapted from Snort: https://git.v0l.io/Kieran/snort/src/commit/4df6c19248184218c4c03728d61e94dae5f2d90c/packages/system/src/pow-util.ts#L14-L36
*/ */
export function minePow(unsigned: UnsignedEvent, difficulty: number): Omit<Event, 'sig'> { export function minePow(unsigned: UnsignedEvent, difficulty: number): Omit<Event, 'sig'> {
let count = 0 let count = 0
@@ -41,7 +43,7 @@ export function minePow(unsigned: UnsignedEvent, difficulty: number): Omit<Event
tag[1] = (++count).toString() tag[1] = (++count).toString()
event.id = getEventHash(event) event.id = fastEventHash(event)
if (getPow(event.id) >= difficulty) { if (getPow(event.id) >= difficulty) {
break break
@@ -50,3 +52,9 @@ export function minePow(unsigned: UnsignedEvent, difficulty: number): Omit<Event
return event return event
} }
export function fastEventHash(evt: UnsignedEvent): string {
return bytesToHex(
sha256(utf8Encoder.encode(JSON.stringify([0, evt.pubkey, evt.created_at, evt.kind, evt.tags, evt.content]))),
)
}

View File

@@ -172,26 +172,6 @@ describe('NostrTypeGuard', () => {
expect(is).toBeFalse() expect(is).toBeFalse()
}) })
test('isNRelay', () => {
const is = NostrTypeGuard.isNRelay('nrelay1qqt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueq4r295t')
expect(is).toBeTrue()
})
test('isNRelay with invalid nrelay', () => {
const is = NostrTypeGuard.isNRelay('nrelay1qqt8wumn8ghj7un9d3shjtnwdaehgu3wvfskueã4r295t')
expect(is).toBeFalse()
})
test('isNRelay with invalid nrelay', () => {
const is = NostrTypeGuard.isNRelay(
'nevent1qqst8cujky046negxgwwm5ynqwn53t8aqjr6afd8g59nfqwxpdhylpcpzamhxue69uhhyetvv9ujuetcv9khqmr99e3k7mg8arnc9',
)
expect(is).toBeFalse()
})
test('isNEvent', () => { test('isNEvent', () => {
const is = NostrTypeGuard.isNEvent( const is = NostrTypeGuard.isNEvent(
'nevent1qqst8cujky046negxgwwm5ynqwn53t8aqjr6afd8g59nfqwxpdhylpcpzamhxue69uhhyetvv9ujuetcv9khqmr99e3k7mg8arnc9', 'nevent1qqst8cujky046negxgwwm5ynqwn53t8aqjr6afd8g59nfqwxpdhylpcpzamhxue69uhhyetvv9ujuetcv9khqmr99e3k7mg8arnc9',

View File

@@ -4,7 +4,6 @@ import { bech32 } from '@scure/base'
import { utf8Decoder, utf8Encoder } from './utils.ts' import { utf8Decoder, utf8Encoder } from './utils.ts'
export type NProfile = `nprofile1${string}` export type NProfile = `nprofile1${string}`
export type NRelay = `nrelay1${string}`
export type NEvent = `nevent1${string}` export type NEvent = `nevent1${string}`
export type NAddr = `naddr1${string}` export type NAddr = `naddr1${string}`
export type NSec = `nsec1${string}` export type NSec = `nsec1${string}`
@@ -14,7 +13,6 @@ export type Ncryptsec = `ncryptsec1${string}`
export const NostrTypeGuard = { export const NostrTypeGuard = {
isNProfile: (value?: string | null): value is NProfile => /^nprofile1[a-z\d]+$/.test(value || ''), isNProfile: (value?: string | null): value is NProfile => /^nprofile1[a-z\d]+$/.test(value || ''),
isNRelay: (value?: string | null): value is NRelay => /^nrelay1[a-z\d]+$/.test(value || ''),
isNEvent: (value?: string | null): value is NEvent => /^nevent1[a-z\d]+$/.test(value || ''), isNEvent: (value?: string | null): value is NEvent => /^nevent1[a-z\d]+$/.test(value || ''),
isNAddr: (value?: string | null): value is NAddr => /^naddr1[a-z\d]+$/.test(value || ''), isNAddr: (value?: string | null): value is NAddr => /^naddr1[a-z\d]+$/.test(value || ''),
isNSec: (value?: string | null): value is NSec => /^nsec1[a-z\d]{58}$/.test(value || ''), isNSec: (value?: string | null): value is NSec => /^nsec1[a-z\d]{58}$/.test(value || ''),

View File

@@ -207,11 +207,13 @@ export class BunkerSigner {
} }
/** /**
* This was supposed to call the "get_public_key" method on the bunker, * Calls the "get_public_key" method on the bunker.
* but instead we just returns the public key we already know. * (before we would return the public key hardcoded in the bunker parameters, but
* that is not correct as that may be the bunker pubkey and the actual signer
* pubkey may be different.)
*/ */
async getPublicKey(): Promise<string> { async getPublicKey(): Promise<string> {
return this.bp.pubkey return await this.sendRequest('get_public_key', [])
} }
/** /**

View File

@@ -3,39 +3,38 @@ import { wrapEvent, unwrapEvent } from './nip59.ts'
import { decode } from './nip19.ts' import { decode } from './nip19.ts'
import { getPublicKey } from './pure.ts' import { getPublicKey } from './pure.ts'
const senderPrivateKey = decode(`nsec1p0ht6p3wepe47sjrgesyn4m50m6avk2waqudu9rl324cg2c4ufesyp6rdg`).data const senderPrivateKey = decode(`nsec1p0ht6p3wepe47sjrgesyn4m50m6avk2waqudu9rl324cg2c4ufesyp6rdg`).data
const recipientPrivateKey = decode(`nsec1uyyrnx7cgfp40fcskcr2urqnzekc20fj0er6de0q8qvhx34ahazsvs9p36`).data const recipientPrivateKey = decode(`nsec1uyyrnx7cgfp40fcskcr2urqnzekc20fj0er6de0q8qvhx34ahazsvs9p36`).data
const recipientPublicKey = getPublicKey(recipientPrivateKey) const recipientPublicKey = getPublicKey(recipientPrivateKey)
const event = { const event = {
kind: 1, kind: 1,
content: "Are you going to the party tonight?", content: 'Are you going to the party tonight?',
} }
const wrapedEvent = wrapEvent(event, senderPrivateKey, recipientPublicKey) const wrapedEvent = wrapEvent(event, senderPrivateKey, recipientPublicKey)
test('wrapEvent', () => { test('wrapEvent', () => {
const expected = { const expected = {
content: '', id: '', created_at: 1728537932, kind: 1059, pubkey: '', sig: '', tags: [ content: '',
[ id: '',
"p", created_at: 1728537932,
"166bf3765ebd1fc55decfe395beff2ea3b2a4e0a8946e7eb578512b555737c99" kind: 1059,
] pubkey: '',
], sig: '',
tags: [['p', '166bf3765ebd1fc55decfe395beff2ea3b2a4e0a8946e7eb578512b555737c99']],
[Symbol('verified')]: true, [Symbol('verified')]: true,
} }
const result = wrapEvent(event, senderPrivateKey, recipientPublicKey) const result = wrapEvent(event, senderPrivateKey, recipientPublicKey)
expect(result.kind).toEqual(expected.kind) expect(result.kind).toEqual(expected.kind)
expect(result.tags).toEqual(expected.tags) expect(result.tags).toEqual(expected.tags)
}) })
test('unwrapEvent', () => { test('unwrapEvent', () => {
const expected = { const expected = {
kind: 1, kind: 1,
content: "Are you going to the party tonight?", content: 'Are you going to the party tonight?',
pubkey: "611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9", pubkey: '611df01bfcf85c26ae65453b772d8f1dfd25c264621c0277e1fc1518686faef9',
tags: [], tags: [],
} }
const result = unwrapEvent(wrapedEvent, recipientPrivateKey) const result = unwrapEvent(wrapedEvent, recipientPrivateKey)
@@ -44,6 +43,4 @@ test('unwrapEvent', () => {
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)
}) })

View File

@@ -1,4 +1,3 @@
import { EventTemplate, UnsignedEvent, Event } from './core.ts' import { EventTemplate, UnsignedEvent, Event } 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'
@@ -9,11 +8,9 @@ type Rumor = UnsignedEvent & { id: string }
const TWO_DAYS = 2 * 24 * 60 * 60 const TWO_DAYS = 2 * 24 * 60 * 60
const now = () => Math.round(Date.now() / 1000) const now = () => Math.round(Date.now() / 1000)
const randomNow = () => Math.round(now() - (Math.random() * TWO_DAYS)) const randomNow = () => Math.round(now() - Math.random() * TWO_DAYS)
const nip44ConversationKey = (privateKey: Uint8Array, publicKey: string) => getConversationKey(privateKey, publicKey)
const nip44ConversationKey = (privateKey: Uint8Array, publicKey: string) =>
getConversationKey(privateKey, publicKey)
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))
@@ -24,7 +21,7 @@ const nip44Decrypt = (data: Event, privateKey: Uint8Array) =>
export function createRumor(event: Partial<UnsignedEvent>, privateKey: Uint8Array) { export function createRumor(event: Partial<UnsignedEvent>, privateKey: Uint8Array) {
const rumor = { const rumor = {
created_at: now(), created_at: now(),
content: "", content: '',
tags: [], tags: [],
...event, ...event,
pubkey: getPublicKey(privateKey), pubkey: getPublicKey(privateKey),
@@ -43,7 +40,7 @@ export function createSeal(rumor: Rumor, privateKey: Uint8Array, recipientPublic
created_at: randomNow(), created_at: randomNow(),
tags: [], tags: [],
}, },
privateKey privateKey,
) as Event ) as Event
} }
@@ -55,14 +52,13 @@ export function createWrap(seal: Event, recipientPublicKey: string) {
kind: GiftWrap, kind: GiftWrap,
content: nip44Encrypt(seal, randomKey, recipientPublicKey), content: nip44Encrypt(seal, randomKey, recipientPublicKey),
created_at: randomNow(), created_at: randomNow(),
tags: [["p", recipientPublicKey]], tags: [['p', recipientPublicKey]],
}, },
randomKey randomKey,
) as Event ) as Event
} }
export function wrapEvent(event: Partial<UnsignedEvent>, senderPrivateKey: Uint8Array, recipientPublicKey: string) { export function wrapEvent(event: Partial<UnsignedEvent>, senderPrivateKey: Uint8Array, recipientPublicKey: string) {
const rumor = createRumor(event, senderPrivateKey) const rumor = createRumor(event, senderPrivateKey)
const seal = createSeal(rumor, senderPrivateKey, recipientPublicKey) const seal = createSeal(rumor, senderPrivateKey, recipientPublicKey)
@@ -70,7 +66,6 @@ export function wrapEvent(event: Partial<UnsignedEvent>, senderPrivateKey: Uint8
} }
export function unwrapEvent(wrap: Event, recipientPrivateKey: Uint8Array) { export function unwrapEvent(wrap: Event, recipientPrivateKey: Uint8Array) {
const unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey) const unwrappedSeal = nip44Decrypt(wrap, recipientPrivateKey)
return nip44Decrypt(unwrappedSeal, recipientPrivateKey) return nip44Decrypt(unwrappedSeal, recipientPrivateKey)
} }

View File

@@ -1,7 +1,7 @@
{ {
"type": "module", "type": "module",
"name": "nostr-tools", "name": "nostr-tools",
"version": "2.8.1", "version": "2.9.0",
"description": "Tools for making a Nostr client.", "description": "Tools for making a Nostr client.",
"repository": { "repository": {
"type": "git", "type": "git",