nostr-crypto-utils 0.1.4 → 0.1.5
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.
- package/dist/index.d.ts +4 -2
- package/dist/index.js +65 -25
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import type { KeyPair, NostrEvent, SignedNostrEvent, ValidationResult } from './types';
|
|
1
|
+
import type { KeyPair, NostrEvent, SignedNostrEvent, ValidationResult, EncryptionResult } from './types';
|
|
2
|
+
export type { KeyPair, NostrEvent, SignedNostrEvent, ValidationResult, EncryptionResult };
|
|
2
3
|
/**
|
|
3
4
|
* Generate a private key for use with NOSTR
|
|
4
5
|
*/
|
|
@@ -9,8 +10,9 @@ export declare function generatePrivateKey(): string;
|
|
|
9
10
|
export declare function getPublicKey(privateKey: string): string;
|
|
10
11
|
/**
|
|
11
12
|
* Generate a new key pair
|
|
13
|
+
* @param seedPhrase Optional seed phrase to generate deterministic key pair
|
|
12
14
|
*/
|
|
13
|
-
export declare function generateKeyPair(): KeyPair;
|
|
15
|
+
export declare function generateKeyPair(seedPhrase?: string): KeyPair;
|
|
14
16
|
/**
|
|
15
17
|
* Get the hash of a NOSTR event
|
|
16
18
|
*/
|
package/dist/index.js
CHANGED
|
@@ -4,6 +4,9 @@ import { sha256 } from '@noble/hashes/sha256';
|
|
|
4
4
|
import { randomBytes } from '@noble/hashes/utils';
|
|
5
5
|
import * as secp256k1 from '@noble/secp256k1';
|
|
6
6
|
import { generateSecretKey, getPublicKey as getNostrPublicKey } from 'nostr-tools';
|
|
7
|
+
import { webcrypto } from 'node:crypto';
|
|
8
|
+
// Use Node.js crypto API
|
|
9
|
+
const crypto = webcrypto;
|
|
7
10
|
/**
|
|
8
11
|
* Generate a private key for use with NOSTR
|
|
9
12
|
*/
|
|
@@ -18,8 +21,18 @@ export function getPublicKey(privateKey) {
|
|
|
18
21
|
}
|
|
19
22
|
/**
|
|
20
23
|
* Generate a new key pair
|
|
24
|
+
* @param seedPhrase Optional seed phrase to generate deterministic key pair
|
|
21
25
|
*/
|
|
22
|
-
export function generateKeyPair() {
|
|
26
|
+
export function generateKeyPair(seedPhrase) {
|
|
27
|
+
if (seedPhrase) {
|
|
28
|
+
// Use the seed phrase to generate a deterministic private key
|
|
29
|
+
const encoder = new TextEncoder();
|
|
30
|
+
const seedBytes = encoder.encode(seedPhrase);
|
|
31
|
+
const hash = sha256(seedBytes);
|
|
32
|
+
const privateKey = bytesToHex(hash);
|
|
33
|
+
const publicKey = getPublicKey(privateKey);
|
|
34
|
+
return { privateKey, publicKey };
|
|
35
|
+
}
|
|
23
36
|
const privateKey = generatePrivateKey();
|
|
24
37
|
const publicKey = getPublicKey(privateKey);
|
|
25
38
|
return { privateKey, publicKey };
|
|
@@ -43,37 +56,60 @@ export function getEventHash(event) {
|
|
|
43
56
|
* Sign a NOSTR event
|
|
44
57
|
*/
|
|
45
58
|
export async function signEvent(event, privateKey) {
|
|
46
|
-
const
|
|
47
|
-
const
|
|
48
|
-
return {
|
|
59
|
+
const pubkey = getPublicKey(privateKey);
|
|
60
|
+
const eventToSign = {
|
|
49
61
|
...event,
|
|
62
|
+
pubkey,
|
|
63
|
+
created_at: event.created_at || Math.floor(Date.now() / 1000),
|
|
64
|
+
tags: event.tags || []
|
|
65
|
+
};
|
|
66
|
+
const hash = getEventHash(eventToSign);
|
|
67
|
+
const sig = bytesToHex(await schnorr.sign(hexToBytes(hash), hexToBytes(privateKey)));
|
|
68
|
+
return {
|
|
69
|
+
...eventToSign,
|
|
50
70
|
id: hash,
|
|
51
|
-
sig
|
|
52
|
-
pubkey: getPublicKey(privateKey)
|
|
71
|
+
sig
|
|
53
72
|
};
|
|
54
73
|
}
|
|
55
74
|
/**
|
|
56
75
|
* Verify a signature
|
|
57
76
|
*/
|
|
58
77
|
export function verifySignature(event) {
|
|
59
|
-
|
|
60
|
-
|
|
78
|
+
try {
|
|
79
|
+
const hash = getEventHash({
|
|
80
|
+
kind: event.kind,
|
|
81
|
+
created_at: event.created_at,
|
|
82
|
+
tags: event.tags,
|
|
83
|
+
content: event.content,
|
|
84
|
+
pubkey: event.pubkey
|
|
85
|
+
});
|
|
86
|
+
if (hash !== event.id) {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
89
|
+
return schnorr.verify(hexToBytes(event.sig), hexToBytes(hash), hexToBytes(event.pubkey));
|
|
90
|
+
}
|
|
91
|
+
catch (error) {
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
61
94
|
}
|
|
62
95
|
/**
|
|
63
96
|
* Validate a key pair
|
|
64
97
|
*/
|
|
65
98
|
export function validateKeyPair(publicKey, privateKey) {
|
|
66
99
|
try {
|
|
67
|
-
const
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
100
|
+
const derivedPublicKey = getPublicKey(privateKey);
|
|
101
|
+
if (derivedPublicKey !== publicKey) {
|
|
102
|
+
return {
|
|
103
|
+
isValid: false,
|
|
104
|
+
error: 'Public key does not match private key'
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
return { isValid: true };
|
|
72
108
|
}
|
|
73
109
|
catch (error) {
|
|
74
110
|
return {
|
|
75
111
|
isValid: false,
|
|
76
|
-
error:
|
|
112
|
+
error: 'Invalid key pair'
|
|
77
113
|
};
|
|
78
114
|
}
|
|
79
115
|
}
|
|
@@ -81,24 +117,28 @@ export function validateKeyPair(publicKey, privateKey) {
|
|
|
81
117
|
* Encrypt a message using NIP-04
|
|
82
118
|
*/
|
|
83
119
|
export async function encrypt(message, recipientPubKey, senderPrivKey) {
|
|
84
|
-
const
|
|
120
|
+
const sharedPoint = secp256k1.getSharedSecret(senderPrivKey, '02' + recipientPubKey);
|
|
121
|
+
const sharedX = sharedPoint.slice(1, 33);
|
|
85
122
|
const iv = randomBytes(16);
|
|
86
|
-
const key = sha256(sharedSecret);
|
|
87
123
|
const textEncoder = new TextEncoder();
|
|
88
|
-
const
|
|
89
|
-
const
|
|
90
|
-
|
|
124
|
+
const plaintext = textEncoder.encode(message);
|
|
125
|
+
const key = await crypto.subtle.importKey('raw', sharedX, { name: 'AES-CBC', length: 256 }, false, ['encrypt']);
|
|
126
|
+
const ciphertext = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, key, plaintext);
|
|
127
|
+
const ctb64 = Buffer.from(new Uint8Array(ciphertext)).toString('base64');
|
|
128
|
+
const ivb64 = Buffer.from(new Uint8Array(iv)).toString('base64');
|
|
129
|
+
return `${ctb64}?iv=${ivb64}`;
|
|
91
130
|
}
|
|
92
131
|
/**
|
|
93
132
|
* Decrypt a message using NIP-04
|
|
94
133
|
*/
|
|
95
134
|
export async function decrypt(encryptedMessage, senderPubKey, recipientPrivKey) {
|
|
96
|
-
const
|
|
97
|
-
const
|
|
98
|
-
const
|
|
99
|
-
const
|
|
135
|
+
const [ctb64, ivb64] = encryptedMessage.split('?iv=');
|
|
136
|
+
const sharedPoint = secp256k1.getSharedSecret(recipientPrivKey, '02' + senderPubKey);
|
|
137
|
+
const sharedX = sharedPoint.slice(1, 33);
|
|
138
|
+
const key = await crypto.subtle.importKey('raw', sharedX, { name: 'AES-CBC', length: 256 }, false, ['decrypt']);
|
|
139
|
+
const iv = Buffer.from(ivb64, 'base64');
|
|
140
|
+
const ciphertext = Buffer.from(ctb64, 'base64');
|
|
141
|
+
const decrypted = await crypto.subtle.decrypt({ name: 'AES-CBC', iv }, key, ciphertext);
|
|
100
142
|
const textDecoder = new TextDecoder();
|
|
101
|
-
const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['decrypt']);
|
|
102
|
-
const decrypted = await crypto.subtle.decrypt({ name: 'AES-CBC', iv }, cryptoKey, ciphertext);
|
|
103
143
|
return textDecoder.decode(new Uint8Array(decrypted));
|
|
104
144
|
}
|