nostr-crypto-utils 0.3.0 → 0.4.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 (40) hide show
  1. package/README.md +265 -0
  2. package/dist/crypto/events.d.ts +3 -4
  3. package/dist/crypto/events.js +17 -15
  4. package/dist/crypto/index.d.ts +60 -5
  5. package/dist/crypto/index.js +243 -5
  6. package/dist/crypto/keys.d.ts +4 -4
  7. package/dist/crypto/keys.js +8 -7
  8. package/dist/event/creation.js +2 -2
  9. package/dist/event/signing.js +5 -5
  10. package/dist/index.d.ts +5 -8
  11. package/dist/index.js +7 -13
  12. package/dist/integration/index.js +3 -2
  13. package/dist/nips/nip-01.d.ts +6 -0
  14. package/dist/nips/nip-01.js +24 -8
  15. package/dist/nips/nip-04.js +4 -4
  16. package/dist/protocol/index.d.ts +3 -2
  17. package/dist/protocol/index.js +46 -22
  18. package/dist/types/base.d.ts +64 -52
  19. package/dist/types/base.js +9 -16
  20. package/dist/types/guards.d.ts +7 -7
  21. package/dist/types/guards.js +94 -58
  22. package/dist/types/index.d.ts +4 -67
  23. package/dist/types/index.js +3 -12
  24. package/dist/types/messages.d.ts +5 -1
  25. package/dist/types/protocol.d.ts +3 -18
  26. package/dist/types/protocol.js +1 -22
  27. package/dist/utils/events.d.ts +35 -0
  28. package/dist/utils/events.js +62 -0
  29. package/dist/utils/functions.d.ts +22 -9
  30. package/dist/utils/functions.js +61 -5
  31. package/dist/utils/index.d.ts +1 -1
  32. package/dist/utils/integration.d.ts +9 -12
  33. package/dist/utils/integration.js +80 -50
  34. package/dist/utils/logger.d.ts +7 -2
  35. package/dist/utils/logger.js +20 -1
  36. package/dist/utils/validation.d.ts +9 -25
  37. package/dist/utils/validation.js +47 -80
  38. package/dist/validation/index.d.ts +22 -6
  39. package/dist/validation/index.js +120 -44
  40. package/package.json +1 -1
package/README.md CHANGED
@@ -78,6 +78,271 @@ Together, these libraries provide:
78
78
  - Comprehensive event handling
79
79
  - Minimal bundle size and dependencies
80
80
 
81
+ ## Delegate Token Creation (NIP-26)
82
+
83
+ You can use this library to create delegate tokens for use on web servers or other applications. This implements [NIP-26](https://github.com/nostr-protocol/nips/blob/master/26.md) for delegation of signing authority.
84
+
85
+ ### Basic Delegation Example
86
+
87
+ ```typescript
88
+ import { createDelegation, validateDelegation } from '@humanjavaenterprises/nostr-crypto-utils';
89
+
90
+ // Create a delegation token (delegator's perspective)
91
+ const delegatorKeyPair = await generateKeyPair();
92
+ const delegateePubkey = 'npub1...'; // The public key you're delegating to
93
+
94
+ const delegation = await createDelegation({
95
+ delegatorPrivkey: delegatorKeyPair.privateKey,
96
+ delegateePubkey,
97
+ conditions: {
98
+ kind: 1, // Only allow text notes
99
+ until: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60 // 30 days
100
+ }
101
+ });
102
+
103
+ // Validate a delegation token (delegatee's perspective)
104
+ const isValid = await validateDelegation({
105
+ token: delegation.token,
106
+ delegatorPubkey: delegatorKeyPair.publicKey,
107
+ delegateePubkey,
108
+ kind: 1
109
+ });
110
+ ```
111
+
112
+ ### Web Server Example
113
+
114
+ Here's how to use delegation tokens in a web server context:
115
+
116
+ ```typescript
117
+ import { createEvent, signEventWithDelegation } from '@humanjavaenterprises/nostr-crypto-utils';
118
+
119
+ // On your server, store these securely
120
+ const DELEGATE_PRIVKEY = 'nsec1...'; // Your server's private key
121
+ const DELEGATION_TOKEN = 'nostr:delegation:...'; // Token from the delegator
122
+ const DELEGATOR_PUBKEY = 'npub1...'; // Delegator's public key
123
+
124
+ // Create and sign an event on behalf of the delegator
125
+ const event = await createEvent({
126
+ kind: 1,
127
+ content: 'Posted via delegation!',
128
+ pubkey: DELEGATOR_PUBKEY, // Original delegator's pubkey
129
+ created_at: Math.floor(Date.now() / 1000)
130
+ });
131
+
132
+ const signedEvent = await signEventWithDelegation({
133
+ event,
134
+ delegatePrivkey: DELEGATE_PRIVKEY,
135
+ delegation: {
136
+ token: DELEGATION_TOKEN,
137
+ conditions: {
138
+ kind: 1,
139
+ until: Math.floor(Date.now() / 1000) + 30 * 24 * 60 * 60
140
+ }
141
+ }
142
+ });
143
+ ```
144
+
145
+ ### Conditional Delegation
146
+
147
+ You can create more specific delegations with conditions:
148
+
149
+ ```typescript
150
+ // Create a delegation with multiple conditions
151
+ const delegation = await createDelegation({
152
+ delegatorPrivkey: delegatorKeyPair.privateKey,
153
+ delegateePubkey,
154
+ conditions: {
155
+ kinds: [1, 6], // Allow only text notes and reposts
156
+ until: Math.floor(Date.now() / 1000) + 7 * 24 * 60 * 60, // 1 week
157
+ since: Math.floor(Date.now() / 1000), // Starting from now
158
+ tags: [
159
+ ['t', 'nostr'], // Only allow posts with tag 't' = 'nostr'
160
+ ['p', delegateePubkey] // Only allow mentions of the delegatee
161
+ ]
162
+ }
163
+ });
164
+ ```
165
+
166
+ ### Security Considerations
167
+
168
+ When working with delegated tokens:
169
+
170
+ 1. **Store Securely**: Always store delegation tokens and private keys securely
171
+ 2. **Check Expiration**: Validate the delegation's time constraints before using
172
+ 3. **Validate Conditions**: Check all conditions before signing or accepting delegated events
173
+ 4. **Limit Scope**: Only delegate the minimum required permissions
174
+ 5. **Monitor Usage**: Keep track of how delegated tokens are being used
175
+
176
+ For more details on delegation, see the [NIP-26 specification](https://github.com/nostr-protocol/nips/blob/master/26.md).
177
+
178
+ ## Type System Improvements
179
+
180
+ ### Enhanced Type System
181
+
182
+ - **Consolidated Type Definitions**: Improved consistency and safety through unified type definitions
183
+ - **Better Type Inference**: Enhanced type inference for easier development and better code completion
184
+ - **Stricter Type Checks**: Improved type safety with stricter checks for better error prevention
185
+
186
+ ### Improved Type Documentation
187
+
188
+ - **Better JSDoc Comments**: Improved documentation with clear and concise JSDoc comments
189
+ - **NIP References**: Added references to relevant NIPs for better understanding of the underlying protocol
190
+
191
+ ## Troubleshooting
192
+
193
+ ### Common Issues and Solutions
194
+
195
+ #### 1. Invalid Delegation Token
196
+
197
+ ```typescript
198
+ // ❌ Common mistake: Using expired delegation
199
+ const delegation = await createDelegation({
200
+ delegatorPrivkey,
201
+ delegateePubkey,
202
+ conditions: {
203
+ until: Math.floor(Date.now() / 1000) - 3600 // Already expired!
204
+ }
205
+ });
206
+
207
+ // ✅ Correct: Ensure future expiration
208
+ const delegation = await createDelegation({
209
+ delegatorPrivkey,
210
+ delegateePubkey,
211
+ conditions: {
212
+ until: Math.floor(Date.now() / 1000) + (24 * 60 * 60) // 24 hours from now
213
+ }
214
+ });
215
+ ```
216
+
217
+ #### 2. Signature Verification Failures
218
+
219
+ ```typescript
220
+ // ❌ Common mistake: Using wrong key format
221
+ const wrongPubkey = 'npub1...'; // Using bech32 format directly
222
+ const event = await signEventWithDelegation({ pubkey: wrongPubkey, ... });
223
+
224
+ // ✅ Correct: Convert from bech32 to hex format first
225
+ import { nip19 } from 'nostr-tools';
226
+ const { data: pubkeyHex } = nip19.decode(pubkey);
227
+ const event = await signEventWithDelegation({ pubkey: pubkeyHex, ... });
228
+ ```
229
+
230
+ #### 3. Permission Issues
231
+
232
+ ```typescript
233
+ // ❌ Common mistake: Mismatched event kinds
234
+ const delegation = await createDelegation({
235
+ conditions: { kind: 1 } // Only allows kind 1
236
+ });
237
+ const event = createEvent({ kind: 4 }); // Trying to create kind 4
238
+ // This will fail!
239
+
240
+ // ✅ Correct: Match delegation conditions
241
+ const delegation = await createDelegation({
242
+ conditions: { kinds: [1, 4] } // Allow both kinds
243
+ });
244
+ const event = createEvent({ kind: 4 }); // Now works!
245
+ ```
246
+
247
+ #### 4. Token Format Issues
248
+
249
+ ```typescript
250
+ // ❌ Common mistake: Invalid token parsing
251
+ const token = 'nostr:delegation:...';
252
+ const parts = token.split(':'); // Naive splitting
253
+
254
+ // ✅ Correct: Use proper token parsing
255
+ import { parseDelegationToken } from '@humanjavaenterprises/nostr-crypto-utils';
256
+ const { delegator, conditions } = parseDelegationToken(token);
257
+ ```
258
+
259
+ ### Debug Mode
260
+
261
+ Enable debug mode to get detailed logging:
262
+
263
+ ```typescript
264
+ import { setDebugLevel } from '@humanjavaenterprises/nostr-crypto-utils';
265
+
266
+ // Enable debug logging
267
+ setDebugLevel('debug');
268
+
269
+ // Or for even more detail
270
+ setDebugLevel('trace');
271
+ ```
272
+
273
+ ### Common Error Messages
274
+
275
+ | Error Message | Likely Cause | Solution |
276
+ |--------------|--------------|----------|
277
+ | "Invalid delegation token" | Token expired or malformed | Check token expiration and format |
278
+ | "Signature verification failed" | Wrong key format or corrupted signature | Verify key formats and conversion |
279
+ | "Condition check failed" | Event doesn't match delegation conditions | Check event kind and other conditions |
280
+ | "Invalid pubkey format" | Using bech32 instead of hex | Convert pubkey to correct format |
281
+ | "Token expired" | Delegation token past expiration | Create new delegation with future expiration |
282
+
283
+ ### Validation Checks
284
+
285
+ When troubleshooting, verify these common points:
286
+
287
+ 1. **Key Formats**
288
+ - Private keys should be in hex format
289
+ - Public keys should be in hex format (not bech32)
290
+ - Signatures should be 64 bytes in hex format
291
+
292
+ 2. **Time Constraints**
293
+ - Token expiration should be in the future
294
+ - Check system clock synchronization
295
+ - Use UTC timestamps consistently
296
+
297
+ 3. **Permission Scope**
298
+ - Verify event kinds match delegation conditions
299
+ - Check any tag restrictions
300
+ - Confirm time window restrictions
301
+
302
+ 4. **Network Issues**
303
+ - Verify relay connections
304
+ - Check for rate limiting
305
+ - Confirm proper websocket handling
306
+
307
+ ### Testing Tools
308
+
309
+ ```typescript
310
+ // Test delegation validity
311
+ const testDelegation = async (delegation) => {
312
+ const result = await validateDelegation({
313
+ token: delegation.token,
314
+ delegatorPubkey: delegation.delegator,
315
+ delegateePubkey: delegation.delegatee,
316
+ kind: 1
317
+ });
318
+
319
+ console.log('Delegation valid:', result.isValid);
320
+ if (!result.isValid) {
321
+ console.error('Validation error:', result.error);
322
+ }
323
+
324
+ return result;
325
+ };
326
+
327
+ // Test event signing
328
+ const testEventSigning = async (event, delegation) => {
329
+ try {
330
+ const signed = await signEventWithDelegation({
331
+ event,
332
+ delegatePrivkey: delegation.privateKey,
333
+ delegation: delegation.token
334
+ });
335
+ console.log('Event signed successfully:', signed.id);
336
+ return true;
337
+ } catch (error) {
338
+ console.error('Signing failed:', error);
339
+ return false;
340
+ }
341
+ };
342
+ ```
343
+
344
+ For more help, join our [Discord community](https://discord.gg/nostr) or [open an issue](https://github.com/humanjavaenterprises/nostr-crypto-utils/issues).
345
+
81
346
  ## Installation
82
347
 
83
348
  ```bash
@@ -2,11 +2,10 @@
2
2
  * @module crypto/events
3
3
  * @description Event signing and verification for Nostr
4
4
  */
5
- import type { NostrEvent, SignedNostrEvent, PublicKey } from '../types/base';
6
- import { NostrEventKind } from '../types/base';
5
+ import { NostrEvent, SignedNostrEvent } from '../types';
7
6
  interface EventInput {
8
- pubkey: PublicKey | string;
9
- kind?: NostrEventKind;
7
+ pubkey: string;
8
+ kind?: number;
10
9
  content?: string;
11
10
  tags?: string[][];
12
11
  created_at?: number;
@@ -2,12 +2,12 @@
2
2
  * @module crypto/events
3
3
  * @description Event signing and verification for Nostr
4
4
  */
5
+ import { logger } from '../utils/logger';
5
6
  import { schnorr } from '@noble/curves/secp256k1';
6
7
  import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
7
8
  import { sha256 } from '@noble/hashes/sha256';
8
9
  import { NostrEventKind } from '../types/base';
9
10
  import { createPublicKey } from './keys';
10
- import { logger } from '../utils';
11
11
  /**
12
12
  * Creates an unsigned Nostr event
13
13
  */
@@ -20,15 +20,15 @@ export function createEvent(params) {
20
20
  if (!params.pubkey) {
21
21
  throw new Error('pubkey is required');
22
22
  }
23
- // Convert string pubkey to PublicKey object if needed
24
- const pubkeyObj = typeof params.pubkey === 'string'
25
- ? createPublicKey(params.pubkey)
26
- : params.pubkey;
23
+ // Validate pubkey format
24
+ if (!/^[0-9a-f]{64}$/i.test(params.pubkey)) {
25
+ throw new Error('Invalid public key format');
26
+ }
27
27
  return {
28
28
  kind: params.kind || NostrEventKind.TEXT_NOTE,
29
29
  created_at: params.created_at,
30
30
  content: params.content || '',
31
- pubkey: pubkeyObj,
31
+ pubkey: params.pubkey,
32
32
  tags: params.tags || []
33
33
  };
34
34
  }
@@ -49,7 +49,7 @@ export async function signEvent(event, privateKey) {
49
49
  // Create canonical event representation as per NIP-01
50
50
  const serialized = JSON.stringify([
51
51
  0,
52
- event.pubkey.hex,
52
+ event.pubkey,
53
53
  event.created_at,
54
54
  event.kind,
55
55
  event.tags || [],
@@ -67,8 +67,8 @@ export async function signEvent(event, privateKey) {
67
67
  };
68
68
  }
69
69
  catch (error) {
70
- logger.error('Failed to sign event:', error);
71
- throw new Error('Failed to sign event: ' + (error instanceof Error ? error.message : String(error)));
70
+ logger.error({ error }, 'Failed to sign event');
71
+ throw error;
72
72
  }
73
73
  }
74
74
  /**
@@ -79,7 +79,7 @@ export async function verifySignature(event) {
79
79
  // Create canonical event representation as per NIP-01
80
80
  const serialized = JSON.stringify([
81
81
  0,
82
- event.pubkey.hex,
82
+ event.pubkey,
83
83
  event.created_at,
84
84
  event.kind,
85
85
  event.tags || [],
@@ -90,22 +90,24 @@ export async function verifySignature(event) {
90
90
  // Verify event ID matches hash
91
91
  const calculatedId = bytesToHex(eventHash);
92
92
  if (calculatedId !== event.id) {
93
- logger.error('Event ID mismatch', { calculated: calculatedId, actual: event.id });
93
+ logger.error({ calculated: calculatedId, actual: event.id }, 'Event ID mismatch');
94
94
  return false;
95
95
  }
96
- // Convert signature and use schnorr public key
96
+ // Convert signature and pubkey
97
97
  const sigBytes = hexToBytes(event.sig);
98
+ const pubkeyObj = createPublicKey(event.pubkey);
99
+ const pubkeyBytes = pubkeyObj.schnorrBytes;
98
100
  // Verify signature using schnorr (sig, message, pubKey)
99
101
  try {
100
- return schnorr.verify(sigBytes, eventHash, event.pubkey.schnorrBytes);
102
+ return schnorr.verify(sigBytes, eventHash, pubkeyBytes);
101
103
  }
102
104
  catch (error) {
103
- logger.error('Schnorr verification error:', error);
105
+ logger.error({ error }, 'Schnorr verification error');
104
106
  return false;
105
107
  }
106
108
  }
107
109
  catch (error) {
108
- logger.error('Failed to verify signature:', error);
110
+ logger.error({ error }, 'Failed to verify signature');
109
111
  return false;
110
112
  }
111
113
  }
@@ -8,13 +8,17 @@
8
8
  export interface CryptoSubtle {
9
9
  subtle: {
10
10
  generateKey(algorithm: RsaHashedKeyGenParams | EcKeyGenParams, extractable: boolean, keyUsages: readonly KeyUsage[]): Promise<CryptoKeyPair>;
11
- importKey(format: 'raw' | 'pkcs8' | 'spki', keyData: ArrayBuffer, algorithm: RsaHashedImportParams | EcKeyImportParams, extractable: boolean, keyUsages: readonly KeyUsage[]): Promise<CryptoKey>;
11
+ importKey(format: 'raw' | 'pkcs8' | 'spki', keyData: ArrayBuffer, algorithm: RsaHashedImportParams | EcKeyImportParams | AesKeyAlgorithm, extractable: boolean, keyUsages: readonly KeyUsage[]): Promise<CryptoKey>;
12
12
  exportKey(format: 'raw' | 'pkcs8' | 'spki', key: CryptoKey): Promise<ArrayBuffer>;
13
13
  encrypt(algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, key: CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer>;
14
14
  decrypt(algorithm: RsaOaepParams | AesCtrParams | AesCbcParams | AesGcmParams, key: CryptoKey, data: ArrayBuffer): Promise<ArrayBuffer>;
15
15
  };
16
16
  getRandomValues<T extends Uint8Array | Int8Array | Uint16Array | Int16Array | Uint32Array | Int32Array>(array: T): T;
17
17
  }
18
+ interface AesKeyAlgorithm {
19
+ name: string;
20
+ length: number;
21
+ }
18
22
  /**
19
23
  * Crypto implementation that works in both Node.js and browser environments
20
24
  */
@@ -24,7 +28,58 @@ export declare class CustomCrypto implements CryptoSubtle {
24
28
  constructor();
25
29
  }
26
30
  export declare const customCrypto: CustomCrypto;
27
- export { generateKeyPair, getPublicKey, validateKeyPair } from './keys';
28
- export { createEvent, signEvent, verifySignature } from './events';
29
- export { encrypt as encryptMessage, decrypt as decryptMessage } from './encryption';
30
- export { generateKeyPair as generatePrivateKey } from './keys';
31
+ import { NostrEvent, SignedNostrEvent, KeyPair, PublicKeyDetails } from '../types';
32
+ /**
33
+ * Generates a new key pair
34
+ * @returns Generated key pair
35
+ */
36
+ export declare function generateKeyPair(): Promise<KeyPair>;
37
+ /**
38
+ * Gets the public key from a private key
39
+ * @param privateKey - Private key in hex format
40
+ * @returns Public key details
41
+ */
42
+ export declare function getPublicKey(privateKey: string): Promise<PublicKeyDetails>;
43
+ /**
44
+ * Validates a key pair
45
+ * @param keyPair - Key pair to validate
46
+ * @returns True if valid
47
+ */
48
+ export declare function validateKeyPair(keyPair: KeyPair): Promise<boolean>;
49
+ /**
50
+ * Creates a new event
51
+ * @param event - Event data
52
+ * @returns Created event
53
+ */
54
+ export declare function createEvent(event: Partial<NostrEvent>): NostrEvent;
55
+ /**
56
+ * Signs an event
57
+ * @param event - Event to sign
58
+ * @param privateKey - Private key to sign with
59
+ * @returns Signed event
60
+ */
61
+ export declare function signEvent(event: NostrEvent, privateKey: string): Promise<SignedNostrEvent>;
62
+ /**
63
+ * Verifies an event signature
64
+ * @param event - Signed event to verify
65
+ * @returns True if signature is valid
66
+ */
67
+ export declare function verifySignature(event: SignedNostrEvent): Promise<boolean>;
68
+ /**
69
+ * Encrypts a message
70
+ * @param message - Message to encrypt
71
+ * @param privateKey - Sender's private key
72
+ * @param recipientPubKey - Recipient's public key hex
73
+ * @returns Encrypted message
74
+ */
75
+ export declare function encryptMessage(message: string, privateKey: string, recipientPubKey: string): Promise<string>;
76
+ /**
77
+ * Decrypts a message
78
+ * @param encryptedMessage - Encrypted message
79
+ * @param privateKey - Recipient's private key
80
+ * @param senderPubKey - Sender's public key hex
81
+ * @returns Decrypted message
82
+ * @throws Error if decryption fails
83
+ */
84
+ export declare function decryptMessage(encryptedMessage: string, privateKey: string, senderPubKey: string): Promise<string>;
85
+ export {};