libp2r2p 0.8.0 → 0.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/README.md +83 -2
  2. package/base16/index.js +6 -3
  3. package/base36/index.js +12 -8
  4. package/base62/index.js +15 -11
  5. package/base64/index.js +18 -2
  6. package/base93/index.js +19 -7
  7. package/content-key/event/index.js +40 -12
  8. package/double-dh/index.js +21 -6
  9. package/ecdh/index.js +12 -1
  10. package/error/index.js +32 -0
  11. package/event/helpers/serialize.js +31 -0
  12. package/event/index.js +116 -0
  13. package/i18n/index.js +15 -13
  14. package/idb/index.js +5 -3
  15. package/idb-queue/index.js +21 -20
  16. package/index.js +10 -0
  17. package/key/index.js +31 -18
  18. package/kind/index.js +244 -0
  19. package/nip04/index.js +47 -0
  20. package/nip05/index.js +61 -0
  21. package/nip19/index.js +176 -43
  22. package/nip44/helpers.js +95 -0
  23. package/nip44/index.js +14 -0
  24. package/nip44-v3/index.js +35 -16
  25. package/nip46/helpers/frame.js +6 -6
  26. package/nip46/helpers/url.js +8 -6
  27. package/nip46/services/bunker-signer.js +7 -6
  28. package/nip46/services/client.js +8 -7
  29. package/nip46/services/server-session.js +8 -7
  30. package/nip46/services/transport.js +9 -8
  31. package/nip96/index.js +285 -0
  32. package/nip98/index.js +56 -0
  33. package/nwt/index.js +241 -0
  34. package/package.json +22 -3
  35. package/private-channel/helpers/chunks.js +7 -6
  36. package/private-channel/helpers/event.js +5 -4
  37. package/private-channel/index.js +63 -61
  38. package/private-channel/services/received-chunks.js +4 -3
  39. package/private-message/index.js +32 -31
  40. package/private-messenger/index.js +23 -22
  41. package/private-messenger/recovery/index.js +15 -14
  42. package/private-messenger/services/channel-state.js +2 -1
  43. package/relay/helpers/hll.js +3 -1
  44. package/relay/services/query.js +3 -3
  45. package/relay/services/relay-connection.js +222 -121
  46. package/relay/services/relay-pool.js +13 -10
  47. package/temporary-storage/index.js +3 -1
  48. package/url/index.js +131 -0
  49. package/web-storage-queue/index.js +8 -6
package/event/index.js ADDED
@@ -0,0 +1,116 @@
1
+ import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js'
2
+ import { sha256 } from '@noble/hashes/sha2.js'
3
+
4
+ import { bytesToBase16, base16ToBytes } from '../base16/index.js'
5
+ import { ValidationError } from '../error/index.js'
6
+ import {
7
+ isAddressableKind,
8
+ isEphemeralKind,
9
+ isRegularKind,
10
+ isReplaceableKind
11
+ } from '../kind/index.js'
12
+ import {
13
+ assertSerializableEvent,
14
+ isSerializableEvent,
15
+ serializeEvent,
16
+ serializableEventError
17
+ } from './helpers/serialize.js'
18
+
19
+ const HEX_32 = /^[0-9a-f]{64}$/
20
+ const HEX_64 = /^[0-9a-f]{128}$/
21
+ const textEncoder = new TextEncoder()
22
+
23
+ function copyTags (tags) {
24
+ return Array.isArray(tags) ? tags.map(tag => Array.isArray(tag) ? tag.slice() : tag) : tags
25
+ }
26
+
27
+ export { assertSerializableEvent, isSerializableEvent }
28
+
29
+ export function getEventHash (event) {
30
+ return bytesToBase16(sha256(textEncoder.encode(serializeEvent(event))))
31
+ }
32
+
33
+ export function finalizeEvent (template, secretKey) {
34
+ if (!(secretKey instanceof Uint8Array) || secretKey.length !== 32 || !secp256k1.utils.isValidSecretKey(secretKey)) {
35
+ throw new ValidationError('INVALID_SECRET_KEY')
36
+ }
37
+ const pubkey = bytesToBase16(schnorr.getPublicKey(secretKey))
38
+ const event = { ...template, tags: copyTags(template?.tags), pubkey }
39
+ const id = getEventHash(event)
40
+ return { ...event, id, sig: bytesToBase16(schnorr.sign(base16ToBytes(id), secretKey)) }
41
+ }
42
+
43
+ function eventValidationError (event) {
44
+ const structureError = serializableEventError(event)
45
+ if (structureError) return structureError
46
+ if (typeof event.id !== 'string' || !HEX_32.test(event.id)) return 'INVALID_EVENT_ID'
47
+ if (typeof event.sig !== 'string' || !HEX_64.test(event.sig)) return 'INVALID_EVENT_SIGNATURE'
48
+ try {
49
+ const id = getEventHash(event)
50
+ if (id !== event.id) return 'EVENT_ID_MISMATCH'
51
+ if (!schnorr.verify(base16ToBytes(event.sig), base16ToBytes(id), base16ToBytes(event.pubkey))) {
52
+ return 'INVALID_EVENT_SIGNATURE'
53
+ }
54
+ } catch {
55
+ return 'INVALID_EVENT_SIGNATURE'
56
+ }
57
+ return null
58
+ }
59
+
60
+ export function isValidEvent (event) {
61
+ return eventValidationError(event) === null
62
+ }
63
+
64
+ export function assertValidEvent (event) {
65
+ const code = eventValidationError(event)
66
+ if (code) throw new ValidationError(code)
67
+ return event
68
+ }
69
+
70
+ function hasTagDefinedClassification (event, classification) {
71
+ if (!event || typeof event !== 'object' || !Array.isArray(event.tags)) return false
72
+
73
+ if (classification === 'ephemeral') {
74
+ if (!Number.isSafeInteger(event.created_at) || event.created_at < 0) return false
75
+ const timestamp = String(event.created_at)
76
+ return event.tags.some(tag => Array.isArray(tag) && tag[0] === 'expiration' && tag[1] === timestamp)
77
+ }
78
+
79
+ const dTag = event.tags.find(tag => Array.isArray(tag) && tag[0] === 'd')
80
+ if (typeof dTag?.[1] !== 'string') return false
81
+ if (classification === 'replaceable') return dTag[1] === ''
82
+ if (classification === 'addressable') return dTag[1] !== ''
83
+ return false
84
+ }
85
+
86
+ export function classifyEvent (event, { includeLegacyKindRanges = true } = {}) {
87
+ if (!event || typeof event !== 'object') return []
88
+ const options = { includeLegacyKindRanges }
89
+ return [
90
+ ['regular', isRegularEvent],
91
+ ['replaceable', isReplaceableEvent],
92
+ ['ephemeral', isEphemeralEvent],
93
+ ['addressable', isAddressableEvent]
94
+ ].filter(([, predicate]) => predicate(event, options)).map(([classification]) => classification)
95
+ }
96
+
97
+ export function isRegularEvent (event, options) {
98
+ if (!event || typeof event !== 'object') return false
99
+ return (options?.includeLegacyKindRanges !== false && isRegularKind(event.kind)) ||
100
+ (!isReplaceableEvent(event, options) && !isAddressableEvent(event, options))
101
+ }
102
+
103
+ export function isReplaceableEvent (event, options) {
104
+ return (options?.includeLegacyKindRanges !== false && isReplaceableKind(event?.kind)) ||
105
+ hasTagDefinedClassification(event, 'replaceable')
106
+ }
107
+
108
+ export function isEphemeralEvent (event, options) {
109
+ return (options?.includeLegacyKindRanges !== false && isEphemeralKind(event?.kind)) ||
110
+ hasTagDefinedClassification(event, 'ephemeral')
111
+ }
112
+
113
+ export function isAddressableEvent (event, options) {
114
+ return (options?.includeLegacyKindRanges !== false && isAddressableKind(event?.kind)) ||
115
+ hasTagDefinedClassification(event, 'addressable')
116
+ }
package/i18n/index.js CHANGED
@@ -1,9 +1,11 @@
1
+ import { ValidationError } from '../error/index.js'
2
+
1
3
  const DEFAULT_LOCALE = 'en'
2
4
  const INTERPOLATION_RE = /{{\s*([A-Za-z0-9_.-]+)\s*}}/g
3
5
 
4
6
  function assertLocales (locales) {
5
7
  if (!locales || typeof locales !== 'object' || Array.isArray(locales)) {
6
- throw new TypeError('locales should be an object')
8
+ throw new ValidationError('INVALID_LOCALES', { message: 'locales should be an object' })
7
9
  }
8
10
  }
9
11
 
@@ -17,21 +19,21 @@ function placeholderSignature (value) {
17
19
  function validateTranslationValue (key, locale, value, expectedPlaceholders) {
18
20
  if (typeof value === 'string') {
19
21
  if (placeholderSignature(value) !== expectedPlaceholders) {
20
- throw new Error(`placeholder mismatch for "${key}" (${locale})`)
22
+ throw new ValidationError('I18N_PLACEHOLDER_MISMATCH', { message: `placeholder mismatch for "${key}" (${locale})` })
21
23
  }
22
24
  return
23
25
  }
24
26
 
25
27
  if (!value || typeof value !== 'object' || Array.isArray(value) || typeof value.other !== 'string') {
26
- throw new TypeError(`translation for "${key}" (${locale}) should be a string or plural object with an other form`)
28
+ throw new ValidationError('INVALID_TRANSLATION', { message: `translation for "${key}" (${locale}) should be a string or plural object with an other form` })
27
29
  }
28
30
 
29
31
  for (const form of Object.values(value)) {
30
32
  if (typeof form !== 'string') {
31
- throw new TypeError(`plural forms for "${key}" (${locale}) should be strings`)
33
+ throw new ValidationError('INVALID_PLURAL_TRANSLATION', { message: `plural forms for "${key}" (${locale}) should be strings` })
32
34
  }
33
35
  if (placeholderSignature(form) !== expectedPlaceholders) {
34
- throw new Error(`placeholder mismatch for "${key}" (${locale})`)
36
+ throw new ValidationError('I18N_PLACEHOLDER_MISMATCH', { message: `placeholder mismatch for "${key}" (${locale})` })
35
37
  }
36
38
  }
37
39
  }
@@ -39,7 +41,7 @@ function validateTranslationValue (key, locale, value, expectedPlaceholders) {
39
41
  export function validateLocales (locales, options = {}) {
40
42
  assertLocales(locales)
41
43
  if (!options || typeof options !== 'object' || Array.isArray(options)) {
42
- throw new TypeError('validation options should be an object')
44
+ throw new ValidationError('INVALID_I18N_VALIDATION_OPTIONS', { message: 'validation options should be an object' })
43
45
  }
44
46
  const {
45
47
  requiredLocales = [],
@@ -47,20 +49,20 @@ export function validateLocales (locales, options = {}) {
47
49
  requireReferenceKey = false
48
50
  } = options
49
51
  if (!Array.isArray(requiredLocales) || requiredLocales.some(locale => typeof locale !== 'string' || !locale)) {
50
- throw new TypeError('requiredLocales should be an array of non-empty strings')
52
+ throw new ValidationError('INVALID_REQUIRED_LOCALES', { message: 'requiredLocales should be an array of non-empty strings' })
51
53
  }
52
54
  if (typeof referenceLocale !== 'string' || !referenceLocale) {
53
- throw new TypeError('referenceLocale should be a non-empty string')
55
+ throw new ValidationError('INVALID_REFERENCE_LOCALE', { message: 'referenceLocale should be a non-empty string' })
54
56
  }
55
57
 
56
58
  for (const [key, translations] of Object.entries(locales)) {
57
59
  if (!translations || typeof translations !== 'object' || Array.isArray(translations)) {
58
- throw new TypeError(`translations for "${key}" should be an object`)
60
+ throw new ValidationError('INVALID_TRANSLATIONS', { message: `translations for "${key}" should be an object` })
59
61
  }
60
62
 
61
63
  for (const locale of requiredLocales) {
62
64
  if (!Object.prototype.hasOwnProperty.call(translations, locale)) {
63
- throw new Error(`missing translation for "${key}" (${locale})`)
65
+ throw new ValidationError('MISSING_TRANSLATION', { message: `missing translation for "${key}" (${locale})` })
64
66
  }
65
67
  }
66
68
 
@@ -71,12 +73,12 @@ export function validateLocales (locales, options = {}) {
71
73
 
72
74
  if (requireReferenceKey) {
73
75
  if (!Object.prototype.hasOwnProperty.call(translations, referenceLocale)) {
74
- throw new Error(`missing reference translation for "${key}" (${referenceLocale})`)
76
+ throw new ValidationError('MISSING_REFERENCE_TRANSLATION', { message: `missing reference translation for "${key}" (${referenceLocale})` })
75
77
  }
76
78
  const reference = translations[referenceLocale]
77
79
  const referenceValue = typeof reference === 'string' ? reference : reference.other
78
80
  if (referenceValue !== key) {
79
- throw new Error(`reference translation should match key "${key}" (${referenceLocale})`)
81
+ throw new ValidationError('REFERENCE_TRANSLATION_MISMATCH', { message: `reference translation should match key "${key}" (${referenceLocale})` })
80
82
  }
81
83
  }
82
84
  }
@@ -227,7 +229,7 @@ export function getT (locales, {
227
229
  const canonicalFallback = canonicalizeLocale(fallbackLocale) ?? DEFAULT_LOCALE
228
230
 
229
231
  return function t (key, values) {
230
- if (typeof key !== 'string') throw new TypeError('translation key should be a string')
232
+ if (typeof key !== 'string') throw new ValidationError('INVALID_TRANSLATION_KEY', { message: 'translation key should be a string' })
231
233
  const translations = Object.prototype.hasOwnProperty.call(locales, key) ? locales[key] : null
232
234
  const template = selectTemplate(translations, requestedLocale, canonicalFallback, values) ?? key
233
235
  return interpolate(template, values)
package/idb/index.js CHANGED
@@ -1,3 +1,5 @@
1
+ import { ValidationError } from '../error/index.js'
2
+
1
3
  const READ_METHODS = new Set([
2
4
  'count',
3
5
  'get',
@@ -29,15 +31,15 @@ export async function run (method, args = [], storeName, indexName, {
29
31
  storeOrIndex
30
32
  } = {}) {
31
33
  if (!tx) {
32
- if (!db) throw new Error('IDB_DATABASE_REQUIRED')
33
- if (!storeName) throw new Error('IDB_STORE_REQUIRED')
34
+ if (!db) throw new ValidationError('IDB_DATABASE_REQUIRED')
35
+ if (!storeName) throw new ValidationError('IDB_STORE_REQUIRED')
34
36
  // Caller may pre-select it if it wants to use many different methods in a row
35
37
  txMode ??= READ_METHODS.has(method) ? 'readonly' : 'readwrite'
36
38
  tx = db.transaction([storeName], txMode)
37
39
  }
38
40
 
39
41
  if (!storeOrIndex) {
40
- if (!storeName) throw new Error('IDB_STORE_REQUIRED')
42
+ if (!storeName) throw new ValidationError('IDB_STORE_REQUIRED')
41
43
  const store = tx.objectStore(storeName)
42
44
  storeOrIndex = indexName ? store.index(indexName) : store
43
45
  }
@@ -1,4 +1,5 @@
1
1
  import { run } from '../idb/index.js'
2
+ import { ValidationError } from '../error/index.js'
2
3
 
3
4
  const encoder = new TextEncoder()
4
5
  const ITEMS_STORE = 'items'
@@ -56,7 +57,7 @@ function normalizeEvictionPolicy (policy) {
56
57
  if (policy === 'opposite-end' || policy === undefined || policy === null) return 'opposite-end'
57
58
  if (policy === 'fifo' || policy === 'head') return 'head'
58
59
  if (policy === 'lifo' || policy === 'tail') return 'tail'
59
- throw new Error('QUEUE_INVALID_EVICTION_POLICY')
60
+ throw new ValidationError('QUEUE_INVALID_EVICTION_POLICY')
60
61
  }
61
62
 
62
63
  function normalizeState (value) {
@@ -67,17 +68,17 @@ function normalizeState (value) {
67
68
  }
68
69
 
69
70
  function normalizeIndexes (indexes = {}) {
70
- if (!indexes || typeof indexes !== 'object' || Array.isArray(indexes)) throw new Error('QUEUE_INDEXES_INVALID')
71
+ if (!indexes || typeof indexes !== 'object' || Array.isArray(indexes)) throw new ValidationError('QUEUE_INDEXES_INVALID')
71
72
 
72
73
  return Object.entries(indexes).map(([name, definition]) => {
73
74
  const options = typeof definition === 'string' || Array.isArray(definition)
74
75
  ? { keyPath: definition }
75
76
  : definition
76
77
  const keyPath = options?.keyPath
77
- if (!name || (!Array.isArray(keyPath) && typeof keyPath !== 'string')) throw new Error('QUEUE_INDEX_INVALID')
78
- if (Array.isArray(keyPath) && keyPath.some(path => typeof path !== 'string' || !path)) throw new Error('QUEUE_INDEX_INVALID')
79
- if (typeof keyPath === 'string' && !keyPath) throw new Error('QUEUE_INDEX_INVALID')
80
- if (options.multiEntry && Array.isArray(keyPath)) throw new Error('QUEUE_INDEX_MULTI_ENTRY_COMPOUND')
78
+ if (!name || (!Array.isArray(keyPath) && typeof keyPath !== 'string')) throw new ValidationError('QUEUE_INDEX_INVALID')
79
+ if (Array.isArray(keyPath) && keyPath.some(path => typeof path !== 'string' || !path)) throw new ValidationError('QUEUE_INDEX_INVALID')
80
+ if (typeof keyPath === 'string' && !keyPath) throw new ValidationError('QUEUE_INDEX_INVALID')
81
+ if (options.multiEntry && Array.isArray(keyPath)) throw new ValidationError('QUEUE_INDEX_MULTI_ENTRY_COMPOUND')
81
82
  return {
82
83
  name,
83
84
  keyPath,
@@ -90,7 +91,7 @@ function normalizeIndexes (indexes = {}) {
90
91
  })
91
92
  }
92
93
 
93
- function keyPathEqual (a, b) {
94
+ function areKeyPathsEqual (a, b) {
94
95
  return JSON.stringify(a) === JSON.stringify(b)
95
96
  }
96
97
 
@@ -129,12 +130,12 @@ function ensureSchema (db, tx, indexDefinitions) {
129
130
  items = db.createObjectStore(ITEMS_STORE, { keyPath: 'position' })
130
131
  } else {
131
132
  items = tx.objectStore(ITEMS_STORE)
132
- if (!keyPathEqual(items.keyPath, 'position')) throw new Error('QUEUE_SCHEMA_MISMATCH')
133
+ if (!areKeyPathsEqual(items.keyPath, 'position')) throw new Error('QUEUE_SCHEMA_MISMATCH')
133
134
  }
134
135
 
135
136
  if (!db.objectStoreNames.contains(STATE_STORE)) {
136
137
  db.createObjectStore(STATE_STORE, { keyPath: 'key' })
137
- } else if (!keyPathEqual(tx.objectStore(STATE_STORE).keyPath, 'key')) {
138
+ } else if (!areKeyPathsEqual(tx.objectStore(STATE_STORE).keyPath, 'key')) {
138
139
  throw new Error('QUEUE_SCHEMA_MISMATCH')
139
140
  }
140
141
 
@@ -148,7 +149,7 @@ function ensureSchema (db, tx, indexDefinitions) {
148
149
  }
149
150
  const existing = items.index(definition.name)
150
151
  if (
151
- !keyPathEqual(existing.keyPath, definition.storedKeyPath) ||
152
+ !areKeyPathsEqual(existing.keyPath, definition.storedKeyPath) ||
152
153
  existing.unique !== definition.unique ||
153
154
  existing.multiEntry !== definition.multiEntry
154
155
  ) {
@@ -165,7 +166,7 @@ async function inspectSchema (db, indexDefinitions) {
165
166
  const done = transactionDone(tx)
166
167
  const items = tx.objectStore(ITEMS_STORE)
167
168
  const state = tx.objectStore(STATE_STORE)
168
- if (!keyPathEqual(items.keyPath, 'position') || !keyPathEqual(state.keyPath, 'key')) {
169
+ if (!areKeyPathsEqual(items.keyPath, 'position') || !areKeyPathsEqual(state.keyPath, 'key')) {
169
170
  await done
170
171
  return { missing: false, incompatible: true }
171
172
  }
@@ -178,7 +179,7 @@ async function inspectSchema (db, indexDefinitions) {
178
179
  }
179
180
  const existing = items.index(definition.name)
180
181
  if (
181
- !keyPathEqual(existing.keyPath, definition.storedKeyPath) ||
182
+ !areKeyPathsEqual(existing.keyPath, definition.storedKeyPath) ||
182
183
  existing.unique !== definition.unique ||
183
184
  existing.multiEntry !== definition.multiEntry
184
185
  ) {
@@ -241,13 +242,13 @@ function itemForStorage (position, item) {
241
242
 
242
243
  function assertIndex (index, length, { allowEnd = false } = {}) {
243
244
  const max = allowEnd ? length : length - 1
244
- if (!Number.isSafeInteger(index) || index < 0 || index > max) throw new Error('QUEUE_INDEX_OUT_OF_RANGE')
245
+ if (!Number.isSafeInteger(index) || index < 0 || index > max) throw new ValidationError('QUEUE_INDEX_OUT_OF_RANGE')
245
246
  }
246
247
 
247
- function validDirection (direction) {
248
+ function normalizeDirection (direction) {
248
249
  if (direction === undefined || direction === 'next') return 'next'
249
250
  if (direction === 'prev') return 'prev'
250
- throw new Error('QUEUE_INVALID_DIRECTION')
251
+ throw new ValidationError('QUEUE_INVALID_DIRECTION')
251
252
  }
252
253
 
253
254
  function isQuotaExceeded (err) {
@@ -265,7 +266,7 @@ export async function createQueue ({
265
266
  evictionPolicy = 'opposite-end',
266
267
  indexedDB = globalThis.indexedDB
267
268
  } = {}) {
268
- if (!prefix) throw new Error('QUEUE_PREFIX_REQUIRED')
269
+ if (!prefix) throw new ValidationError('QUEUE_PREFIX_REQUIRED')
269
270
 
270
271
  const indexDefinitions = normalizeIndexes(indexes)
271
272
  const db = await openQueueDatabase(indexedDB, prefix, indexDefinitions)
@@ -663,7 +664,7 @@ export async function createQueue ({
663
664
  }
664
665
 
665
666
  async function insertWhere (predicate, item, { appendIfMissing = false } = {}) {
666
- if (typeof predicate !== 'function') throw new Error('QUEUE_PREDICATE_REQUIRED')
667
+ if (typeof predicate !== 'function') throw new ValidationError('QUEUE_PREDICATE_REQUIRED')
667
668
  const requiredBytes = itemForStorage(0, item).byteSize
668
669
  return mutate(async (tx, state) => {
669
670
  const records = await recordsForState(tx, state)
@@ -683,7 +684,7 @@ export async function createQueue ({
683
684
  }
684
685
 
685
686
  async function removeWhere (predicate) {
686
- if (typeof predicate !== 'function') throw new Error('QUEUE_PREDICATE_REQUIRED')
687
+ if (typeof predicate !== 'function') throw new ValidationError('QUEUE_PREDICATE_REQUIRED')
687
688
  return mutate(async (tx, state) => {
688
689
  const records = await recordsForState(tx, state)
689
690
  const removed = new Set()
@@ -699,7 +700,7 @@ export async function createQueue ({
699
700
  }
700
701
 
701
702
  async function some (predicate) {
702
- if (typeof predicate !== 'function') throw new Error('QUEUE_PREDICATE_REQUIRED')
703
+ if (typeof predicate !== 'function') throw new ValidationError('QUEUE_PREDICATE_REQUIRED')
703
704
  return snapshot(async tx => {
704
705
  const state = await readState(tx)
705
706
  const records = await recordsForState(tx, state)
@@ -797,7 +798,7 @@ export async function createQueue ({
797
798
  }
798
799
 
799
800
  async function * storedItemsBy (indexName, query, { direction = 'next' } = {}) {
800
- direction = validDirection(direction)
801
+ direction = normalizeDirection(direction)
801
802
  const records = await snapshot(async tx => {
802
803
  const { result } = await run('getAll', [query], ITEMS_STORE, indexName, { tx })
803
804
  return result
package/index.js CHANGED
@@ -8,18 +8,28 @@ export * as contentKey from './content-key/index.js'
8
8
  export * as contentKeyEvent from './content-key/event/index.js'
9
9
  export * as doubleDh from './double-dh/index.js'
10
10
  export * as ecdh from './ecdh/index.js'
11
+ export * as error from './error/index.js'
12
+ export * as event from './event/index.js'
11
13
  export * as idb from './idb/index.js'
12
14
  export * as idbQueue from './idb-queue/index.js'
13
15
  export * as i18n from './i18n/index.js'
14
16
  export * as key from './key/index.js'
17
+ export * as kind from './kind/index.js'
15
18
  export * as network from './network/index.js'
19
+ export * as nip04 from './nip04/index.js'
20
+ export * as nip05 from './nip05/index.js'
16
21
  export * as nip19 from './nip19/index.js'
22
+ export * as nip44 from './nip44/index.js'
17
23
  export * as nip44v3 from './nip44-v3/index.js'
18
24
  export * as nip46 from './nip46/index.js'
25
+ export * as nip96 from './nip96/index.js'
26
+ export * as nip98 from './nip98/index.js'
27
+ export * as nwt from './nwt/index.js'
19
28
  export * as privateChannel from './private-channel/index.js'
20
29
  export * as privateMessage from './private-message/index.js'
21
30
  export * as privateMessenger from './private-messenger/index.js'
22
31
  export * as privateMessengerRecovery from './private-messenger/recovery/index.js'
23
32
  export * as relay from './relay/index.js'
24
33
  export * as temporaryStorage from './temporary-storage/index.js'
34
+ export * as url from './url/index.js'
25
35
  export * as webStorageQueue from './web-storage-queue/index.js'
package/key/index.js CHANGED
@@ -1,14 +1,23 @@
1
- import {
2
- generateSecretKey,
3
- getPublicKey,
4
- finalizeEvent,
5
- nip19
6
- } from 'nostr-tools'
1
+ import { schnorr, secp256k1 } from '@noble/curves/secp256k1.js'
7
2
 
8
3
  import { bytesToHex, hexToBytes } from '../base16/index.js'
4
+ import { ValidationError } from '../error/index.js'
5
+ import { finalizeEvent } from '../event/index.js'
6
+ import { nsecDecode, nsecEncode, npubDecode, npubEncode } from '../nip19/index.js'
9
7
 
10
8
  const HEX_SECKEY_REGEX = /^[0-9a-f]{64}$/i
11
9
 
10
+ export function generateSecretKey () {
11
+ return schnorr.utils.randomSecretKey()
12
+ }
13
+
14
+ export function getPublicKey (secretKey) {
15
+ if (!(secretKey instanceof Uint8Array) || secretKey.length !== 32 || !secp256k1.utils.isValidSecretKey(secretKey)) {
16
+ throw new ValidationError('INVALID_SECRET_KEY')
17
+ }
18
+ return bytesToHex(schnorr.getPublicKey(secretKey))
19
+ }
20
+
12
21
  export function generateKeypair () {
13
22
  const secretKey = generateSecretKey()
14
23
  const pubkey = getPublicKey(secretKey)
@@ -16,8 +25,8 @@ export function generateKeypair () {
16
25
  secretKey,
17
26
  seckey: bytesToHex(secretKey),
18
27
  pubkey,
19
- nsec: nip19.nsecEncode(secretKey),
20
- npub: nip19.npubEncode(pubkey)
28
+ nsec: nsecEncode(bytesToHex(secretKey)),
29
+ npub: npubEncode(pubkey)
21
30
  }
22
31
  }
23
32
 
@@ -29,17 +38,19 @@ export function keypairFromSeckey (raw) {
29
38
  if (HEX_SECKEY_REGEX.test(raw)) {
30
39
  secretKey = hexToBytes(raw.toLowerCase())
31
40
  } else {
32
- const decoded = nip19.decode(raw)
33
- if (decoded.type !== 'nsec') throw new Error('NOT_A_SECRET_KEY')
34
- secretKey = decoded.data
41
+ try {
42
+ secretKey = hexToBytes(nsecDecode(raw))
43
+ } catch (cause) {
44
+ throw new ValidationError('NOT_A_SECRET_KEY', { cause })
45
+ }
35
46
  }
36
47
  const pubkey = getPublicKey(secretKey)
37
48
  return {
38
49
  secretKey,
39
50
  seckey: bytesToHex(secretKey),
40
51
  pubkey,
41
- nsec: nip19.nsecEncode(secretKey),
42
- npub: nip19.npubEncode(pubkey)
52
+ nsec: nsecEncode(bytesToHex(secretKey)),
53
+ npub: npubEncode(pubkey)
43
54
  }
44
55
  }
45
56
 
@@ -47,17 +58,19 @@ export function keypairFromSeckey (raw) {
47
58
  // bech32 includes a checksum that protects against user-typo imports of a
48
59
  // pubkey they cannot sign for.
49
60
  export function pubkeyFromNpub (npub) {
50
- const decoded = nip19.decode(npub)
51
- if (decoded.type !== 'npub') throw new Error('NOT_AN_NPUB')
52
- return decoded.data
61
+ try {
62
+ return npubDecode(npub)
63
+ } catch (cause) {
64
+ throw new ValidationError('NOT_AN_NPUB', { cause })
65
+ }
53
66
  }
54
67
 
55
68
  export function nsecFromHex (hex) {
56
- return nip19.nsecEncode(hexToBytes(hex))
69
+ return nsecEncode(hex)
57
70
  }
58
71
 
59
72
  export function npubFromPubkey (pubkey) {
60
- return nip19.npubEncode(pubkey)
73
+ return npubEncode(pubkey)
61
74
  }
62
75
 
63
76
  function cleanProfileValue (value) {