nostr-crypto-utils 0.1.4
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/LICENSE +21 -0
- package/README.md +184 -0
- package/dist/index.d.ts +37 -0
- package/dist/index.js +104 -0
- package/dist/types/index.d.ts +24 -0
- package/dist/types/index.js +1 -0
- package/package.json +40 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2023 Human Java Enterprises
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
# @humanjavaenterprises/nostr-crypto-utils
|
|
2
|
+
|
|
3
|
+
A comprehensive cryptographic utilities package for NOSTR applications, designed to work seamlessly with [@humanjavaenterprises/nostr-nsec-seedphrase](https://github.com/humanjavaenterprises/nostr-nsec-seedphrase).
|
|
4
|
+
|
|
5
|
+
⚠️ **Important Security Notice**
|
|
6
|
+
|
|
7
|
+
This library handles cryptographic keys and seed phrases that are critical for securing your Nostr identity and data. Just like Bitcoin, any seed phrase or private key (`nsec`) generated by this library must be stored with the utmost security and care.
|
|
8
|
+
|
|
9
|
+
Developers using this library must inform their users about the critical nature of managing seed phrases, `nsec`, and hex keys. It is the user's responsibility to securely store and manage these keys. The library and its authors disclaim any responsibility or liability for lost keys, seed phrases, or data resulting from mismanagement.
|
|
10
|
+
|
|
11
|
+
## Features
|
|
12
|
+
|
|
13
|
+
- 🔑 Complete key pair management (generation, validation, public key derivation)
|
|
14
|
+
- 📝 Event signing and verification
|
|
15
|
+
- 🔒 NIP-04 encryption and decryption
|
|
16
|
+
- 🌱 Seed phrase support via integration with nostr-nsec-seedphrase
|
|
17
|
+
- 📦 Modern ESM package with full TypeScript support
|
|
18
|
+
- ⚡️ Built on established crypto libraries (noble-curves, noble-hashes)
|
|
19
|
+
- 🤝 Compatible with nostr-tools as a peer dependency
|
|
20
|
+
|
|
21
|
+
## Installation
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
npm install @humanjavaenterprises/nostr-crypto-utils @humanjavaenterprises/nostr-nsec-seedphrase nostr-tools
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
## Architecture Overview
|
|
28
|
+
|
|
29
|
+
This library serves as a crucial middleware layer in NOSTR applications:
|
|
30
|
+
|
|
31
|
+
```
|
|
32
|
+
┌─────────────────────────────────────────────────────┐
|
|
33
|
+
│ Your NOSTR App │
|
|
34
|
+
└───────────────────────┬─────────────────────────────┘
|
|
35
|
+
│
|
|
36
|
+
┌───────────────────────▼─────────────────────────────┐
|
|
37
|
+
│ @humanjavaenterprises/nostr-crypto-utils │
|
|
38
|
+
│ │
|
|
39
|
+
│ ┌─────────────────┐ ┌──────────────────┐ │
|
|
40
|
+
│ │ Key Manager │ │ Event Handler │ │
|
|
41
|
+
│ └────────┬────────┘ └────────┬─────────┘ │
|
|
42
|
+
│ │ │ │
|
|
43
|
+
│ ┌────────▼────────┐ ┌───────▼─────────┐ │
|
|
44
|
+
│ │ nostr-nsec- │ │ nostr-tools │ │
|
|
45
|
+
│ │ seedphrase │ │ │ │
|
|
46
|
+
│ └─────────────────┘ └─────────────────┘ │
|
|
47
|
+
└─────────────────────────────────────────────────────┘
|
|
48
|
+
│
|
|
49
|
+
┌───────────────────────▼─────────────────────────────┐
|
|
50
|
+
│ NOSTR Protocol / Relays │
|
|
51
|
+
└─────────────────────────────────────────────────────┘
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
## Usage Examples
|
|
55
|
+
|
|
56
|
+
### Key Management
|
|
57
|
+
|
|
58
|
+
```typescript
|
|
59
|
+
import { generateKeyPair, derivePublicKey, validateKeyPair } from '@humanjavaenterprises/nostr-crypto-utils';
|
|
60
|
+
|
|
61
|
+
// Generate a new key pair
|
|
62
|
+
const keyPair = await generateKeyPair();
|
|
63
|
+
console.log(keyPair);
|
|
64
|
+
// { privateKey: '...', publicKey: '...' }
|
|
65
|
+
|
|
66
|
+
// Generate from seed phrase
|
|
67
|
+
const seedPhrase = 'abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about';
|
|
68
|
+
const keyPairFromSeed = await generateKeyPair(seedPhrase);
|
|
69
|
+
|
|
70
|
+
// Derive public key from private key
|
|
71
|
+
const publicKey = await derivePublicKey(keyPair.privateKey);
|
|
72
|
+
|
|
73
|
+
// Validate a key pair
|
|
74
|
+
const validation = await validateKeyPair(keyPair.publicKey, keyPair.privateKey);
|
|
75
|
+
console.log(validation);
|
|
76
|
+
// { isValid: true, error: undefined }
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Event Operations
|
|
80
|
+
|
|
81
|
+
```typescript
|
|
82
|
+
import { signEvent, verifySignature } from '@humanjavaenterprises/nostr-crypto-utils';
|
|
83
|
+
|
|
84
|
+
// Create and sign a NOSTR event
|
|
85
|
+
const event = {
|
|
86
|
+
kind: 1,
|
|
87
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
88
|
+
tags: [],
|
|
89
|
+
content: 'Hello NOSTR!'
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
const signedEvent = await signEvent(event, keyPair.privateKey);
|
|
93
|
+
console.log(signedEvent);
|
|
94
|
+
// { id: '...', pubkey: '...', sig: '...', ...event }
|
|
95
|
+
|
|
96
|
+
// Verify an event signature
|
|
97
|
+
const isValid = await verifySignature(signedEvent);
|
|
98
|
+
console.log(isValid); // true
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
### Encryption (NIP-04)
|
|
102
|
+
|
|
103
|
+
```typescript
|
|
104
|
+
import { encrypt, decrypt } from '@humanjavaenterprises/nostr-crypto-utils';
|
|
105
|
+
|
|
106
|
+
// Encrypt a message
|
|
107
|
+
const encrypted = await encrypt(
|
|
108
|
+
'Secret message',
|
|
109
|
+
recipientPublicKey,
|
|
110
|
+
senderPrivateKey
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
// Decrypt a message
|
|
114
|
+
const decrypted = await decrypt(
|
|
115
|
+
encrypted,
|
|
116
|
+
senderPublicKey,
|
|
117
|
+
recipientPrivateKey
|
|
118
|
+
);
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
## Integration Examples
|
|
122
|
+
|
|
123
|
+
### Authentication Flow
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
import { generateKeyPair, signEvent } from '@humanjavaenterprises/nostr-crypto-utils';
|
|
127
|
+
|
|
128
|
+
async function authenticateUser(seedPhrase?: string) {
|
|
129
|
+
// Generate or recover keys
|
|
130
|
+
const keyPair = await generateKeyPair(seedPhrase);
|
|
131
|
+
|
|
132
|
+
// Create auth event
|
|
133
|
+
const authEvent = {
|
|
134
|
+
kind: 22242,
|
|
135
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
136
|
+
tags: [['challenge', 'authentication-challenge']],
|
|
137
|
+
content: 'Authenticating...'
|
|
138
|
+
};
|
|
139
|
+
|
|
140
|
+
// Sign the event
|
|
141
|
+
const signedAuthEvent = await signEvent(authEvent, keyPair.privateKey);
|
|
142
|
+
|
|
143
|
+
return signedAuthEvent;
|
|
144
|
+
}
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
### Secure Messaging
|
|
148
|
+
|
|
149
|
+
```typescript
|
|
150
|
+
import { generateKeyPair, encrypt, decrypt } from '@humanjavaenterprises/nostr-crypto-utils';
|
|
151
|
+
|
|
152
|
+
async function secureMessaging() {
|
|
153
|
+
// Generate keys for both parties
|
|
154
|
+
const alice = await generateKeyPair();
|
|
155
|
+
const bob = await generateKeyPair();
|
|
156
|
+
|
|
157
|
+
// Alice encrypts a message for Bob
|
|
158
|
+
const encrypted = await encrypt(
|
|
159
|
+
'Hey Bob!',
|
|
160
|
+
bob.publicKey,
|
|
161
|
+
alice.privateKey
|
|
162
|
+
);
|
|
163
|
+
|
|
164
|
+
// Bob decrypts Alice's message
|
|
165
|
+
const decrypted = await decrypt(
|
|
166
|
+
encrypted,
|
|
167
|
+
alice.publicKey,
|
|
168
|
+
bob.privateKey
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
## Contributing
|
|
174
|
+
|
|
175
|
+
Contributions are welcome! Please feel free to submit a Pull Request.
|
|
176
|
+
|
|
177
|
+
## License
|
|
178
|
+
|
|
179
|
+
MIT
|
|
180
|
+
|
|
181
|
+
## Related Projects
|
|
182
|
+
|
|
183
|
+
- [@humanjavaenterprises/nostr-nsec-seedphrase](https://github.com/humanjavaenterprises/nostr-nsec-seedphrase) - Seed phrase management for NOSTR
|
|
184
|
+
- [nostr-tools](https://github.com/nbd-wtf/nostr-tools) - Core NOSTR functionality
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import type { KeyPair, NostrEvent, SignedNostrEvent, ValidationResult } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* Generate a private key for use with NOSTR
|
|
4
|
+
*/
|
|
5
|
+
export declare function generatePrivateKey(): string;
|
|
6
|
+
/**
|
|
7
|
+
* Get a public key from a private key
|
|
8
|
+
*/
|
|
9
|
+
export declare function getPublicKey(privateKey: string): string;
|
|
10
|
+
/**
|
|
11
|
+
* Generate a new key pair
|
|
12
|
+
*/
|
|
13
|
+
export declare function generateKeyPair(): KeyPair;
|
|
14
|
+
/**
|
|
15
|
+
* Get the hash of a NOSTR event
|
|
16
|
+
*/
|
|
17
|
+
export declare function getEventHash(event: NostrEvent): string;
|
|
18
|
+
/**
|
|
19
|
+
* Sign a NOSTR event
|
|
20
|
+
*/
|
|
21
|
+
export declare function signEvent(event: NostrEvent, privateKey: string): Promise<SignedNostrEvent>;
|
|
22
|
+
/**
|
|
23
|
+
* Verify a signature
|
|
24
|
+
*/
|
|
25
|
+
export declare function verifySignature(event: SignedNostrEvent): boolean;
|
|
26
|
+
/**
|
|
27
|
+
* Validate a key pair
|
|
28
|
+
*/
|
|
29
|
+
export declare function validateKeyPair(publicKey: string, privateKey: string): ValidationResult;
|
|
30
|
+
/**
|
|
31
|
+
* Encrypt a message using NIP-04
|
|
32
|
+
*/
|
|
33
|
+
export declare function encrypt(message: string, recipientPubKey: string, senderPrivKey: string): Promise<string>;
|
|
34
|
+
/**
|
|
35
|
+
* Decrypt a message using NIP-04
|
|
36
|
+
*/
|
|
37
|
+
export declare function decrypt(encryptedMessage: string, senderPubKey: string, recipientPrivKey: string): Promise<string>;
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
import { schnorr } from '@noble/curves/secp256k1';
|
|
2
|
+
import { bytesToHex, hexToBytes } from '@noble/hashes/utils';
|
|
3
|
+
import { sha256 } from '@noble/hashes/sha256';
|
|
4
|
+
import { randomBytes } from '@noble/hashes/utils';
|
|
5
|
+
import * as secp256k1 from '@noble/secp256k1';
|
|
6
|
+
import { generateSecretKey, getPublicKey as getNostrPublicKey } from 'nostr-tools';
|
|
7
|
+
/**
|
|
8
|
+
* Generate a private key for use with NOSTR
|
|
9
|
+
*/
|
|
10
|
+
export function generatePrivateKey() {
|
|
11
|
+
return bytesToHex(generateSecretKey());
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Get a public key from a private key
|
|
15
|
+
*/
|
|
16
|
+
export function getPublicKey(privateKey) {
|
|
17
|
+
return getNostrPublicKey(hexToBytes(privateKey));
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Generate a new key pair
|
|
21
|
+
*/
|
|
22
|
+
export function generateKeyPair() {
|
|
23
|
+
const privateKey = generatePrivateKey();
|
|
24
|
+
const publicKey = getPublicKey(privateKey);
|
|
25
|
+
return { privateKey, publicKey };
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get the hash of a NOSTR event
|
|
29
|
+
*/
|
|
30
|
+
export function getEventHash(event) {
|
|
31
|
+
const serialized = JSON.stringify([
|
|
32
|
+
0,
|
|
33
|
+
event.pubkey,
|
|
34
|
+
event.created_at,
|
|
35
|
+
event.kind,
|
|
36
|
+
event.tags,
|
|
37
|
+
event.content,
|
|
38
|
+
]);
|
|
39
|
+
const hash = sha256(new TextEncoder().encode(serialized));
|
|
40
|
+
return bytesToHex(hash);
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Sign a NOSTR event
|
|
44
|
+
*/
|
|
45
|
+
export async function signEvent(event, privateKey) {
|
|
46
|
+
const hash = getEventHash(event);
|
|
47
|
+
const signature = bytesToHex(await schnorr.sign(hexToBytes(hash), hexToBytes(privateKey)));
|
|
48
|
+
return {
|
|
49
|
+
...event,
|
|
50
|
+
id: hash,
|
|
51
|
+
sig: signature,
|
|
52
|
+
pubkey: getPublicKey(privateKey)
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Verify a signature
|
|
57
|
+
*/
|
|
58
|
+
export function verifySignature(event) {
|
|
59
|
+
const hash = getEventHash(event);
|
|
60
|
+
return schnorr.verify(hexToBytes(event.sig), hexToBytes(hash), hexToBytes(event.pubkey));
|
|
61
|
+
}
|
|
62
|
+
/**
|
|
63
|
+
* Validate a key pair
|
|
64
|
+
*/
|
|
65
|
+
export function validateKeyPair(publicKey, privateKey) {
|
|
66
|
+
try {
|
|
67
|
+
const derivedPubKey = getPublicKey(privateKey);
|
|
68
|
+
return {
|
|
69
|
+
isValid: derivedPubKey === publicKey,
|
|
70
|
+
error: derivedPubKey !== publicKey ? 'Public key does not match derived key' : undefined
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
return {
|
|
75
|
+
isValid: false,
|
|
76
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred'
|
|
77
|
+
};
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Encrypt a message using NIP-04
|
|
82
|
+
*/
|
|
83
|
+
export async function encrypt(message, recipientPubKey, senderPrivKey) {
|
|
84
|
+
const sharedSecret = secp256k1.getSharedSecret(senderPrivKey, '02' + recipientPubKey);
|
|
85
|
+
const iv = randomBytes(16);
|
|
86
|
+
const key = sha256(sharedSecret);
|
|
87
|
+
const textEncoder = new TextEncoder();
|
|
88
|
+
const cryptoKey = await crypto.subtle.importKey('raw', key, { name: 'AES-CBC' }, false, ['encrypt']);
|
|
89
|
+
const encrypted = await crypto.subtle.encrypt({ name: 'AES-CBC', iv }, cryptoKey, textEncoder.encode(message));
|
|
90
|
+
return bytesToHex(iv) + bytesToHex(new Uint8Array(encrypted));
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Decrypt a message using NIP-04
|
|
94
|
+
*/
|
|
95
|
+
export async function decrypt(encryptedMessage, senderPubKey, recipientPrivKey) {
|
|
96
|
+
const sharedSecret = secp256k1.getSharedSecret(recipientPrivKey, '02' + senderPubKey);
|
|
97
|
+
const key = sha256(sharedSecret);
|
|
98
|
+
const iv = hexToBytes(encryptedMessage.slice(0, 32));
|
|
99
|
+
const ciphertext = hexToBytes(encryptedMessage.slice(32));
|
|
100
|
+
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
|
+
return textDecoder.decode(new Uint8Array(decrypted));
|
|
104
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
export interface KeyPair {
|
|
2
|
+
privateKey: string;
|
|
3
|
+
publicKey: string;
|
|
4
|
+
}
|
|
5
|
+
export interface NostrEvent {
|
|
6
|
+
kind: number;
|
|
7
|
+
created_at: number;
|
|
8
|
+
content: string;
|
|
9
|
+
tags: string[][];
|
|
10
|
+
pubkey?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface SignedNostrEvent extends NostrEvent {
|
|
13
|
+
id: string;
|
|
14
|
+
sig: string;
|
|
15
|
+
pubkey: string;
|
|
16
|
+
}
|
|
17
|
+
export interface EncryptionResult {
|
|
18
|
+
ciphertext: string;
|
|
19
|
+
iv: string;
|
|
20
|
+
}
|
|
21
|
+
export interface ValidationResult {
|
|
22
|
+
isValid: boolean;
|
|
23
|
+
error?: string;
|
|
24
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/package.json
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "nostr-crypto-utils",
|
|
3
|
+
"version": "0.1.4",
|
|
4
|
+
"description": "Cryptographic utilities for NOSTR",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist"
|
|
10
|
+
],
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "tsc",
|
|
13
|
+
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
|
|
14
|
+
"prepublishOnly": "npm run build"
|
|
15
|
+
},
|
|
16
|
+
"keywords": [
|
|
17
|
+
"nostr",
|
|
18
|
+
"crypto",
|
|
19
|
+
"utilities"
|
|
20
|
+
],
|
|
21
|
+
"author": "Human Java Enterprises",
|
|
22
|
+
"license": "MIT",
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@noble/curves": "^1.2.0",
|
|
25
|
+
"@noble/hashes": "^1.3.2",
|
|
26
|
+
"@noble/secp256k1": "^2.0.0",
|
|
27
|
+
"@humanjavaenterprises/nostr-nsec-seedphrase": "^0.1.0",
|
|
28
|
+
"nostr-tools": "^2.1.4"
|
|
29
|
+
},
|
|
30
|
+
"peerDependencies": {
|
|
31
|
+
"nostr-tools": "^2.1.4"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@types/jest": "^29.5.5",
|
|
35
|
+
"@types/node": "^20.8.0",
|
|
36
|
+
"jest": "^29.7.0",
|
|
37
|
+
"ts-jest": "^29.1.1",
|
|
38
|
+
"typescript": "^5.2.2"
|
|
39
|
+
}
|
|
40
|
+
}
|