applesauce-signers 1.0.0 → 2.0.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.
- package/README.md +1 -1
- package/dist/helpers/encryption.d.ts +1 -1
- package/dist/helpers/encryption.js +1 -6
- package/dist/signers/nostr-connect-signer.d.ts +7 -4
- package/dist/signers/nostr-connect-signer.js +30 -9
- package/dist/signers/password-signer.d.ts +9 -0
- package/dist/signers/password-signer.js +23 -0
- package/dist/signers/readonly-signer.d.ts +2 -0
- package/dist/signers/readonly-signer.js +7 -0
- package/dist/signers/serial-port-signer.d.ts +1 -1
- package/dist/signers/serial-port-signer.js +4 -4
- package/dist/signers/simple-signer.d.ts +2 -0
- package/dist/signers/simple-signer.js +5 -0
- package/package.json +4 -4
- package/dist/logger.d.ts +0 -2
- package/dist/logger.js +0 -2
- package/dist/signers/nostr-connect-signer.test.d.ts +0 -1
- package/dist/signers/nostr-connect-signer.test.js +0 -23
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@ For detailed documentation and API reference, see:
|
|
|
8
8
|
|
|
9
9
|
- [Signers Documentation](https://hzrd149.github.io/applesauce/signers/signers.html)
|
|
10
10
|
- [Nostr Connect Documentation](https://hzrd149.github.io/applesauce/signers/nostr-connect.html)
|
|
11
|
-
- [API Reference](https://hzrd149.github.io/applesauce/typedoc/modules/
|
|
11
|
+
- [API Reference](https://hzrd149.github.io/applesauce/typedoc/modules/applesauce-signers.html)
|
|
12
12
|
|
|
13
13
|
## Available Signers
|
|
14
14
|
|
|
@@ -2,4 +2,4 @@
|
|
|
2
2
|
* Checks if a string is encrypted with NIP-04 or NIP-44
|
|
3
3
|
* @see https://github.com/nostr-protocol/nips/pull/1248#issuecomment-2437731316
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
5
|
+
export { isNIP04Encrypted as isNIP04 } from "applesauce-core/helpers/encryption";
|
|
@@ -2,9 +2,4 @@
|
|
|
2
2
|
* Checks if a string is encrypted with NIP-04 or NIP-44
|
|
3
3
|
* @see https://github.com/nostr-protocol/nips/pull/1248#issuecomment-2437731316
|
|
4
4
|
*/
|
|
5
|
-
export
|
|
6
|
-
const l = ciphertext.length;
|
|
7
|
-
if (l < 28)
|
|
8
|
-
return false;
|
|
9
|
-
return (ciphertext[l - 28] == "?" && ciphertext[l - 27] == "i" && ciphertext[l - 26] == "v" && ciphertext[l - 25] == "=");
|
|
10
|
-
}
|
|
5
|
+
export { isNIP04Encrypted as isNIP04 } from "applesauce-core/helpers/encryption";
|
|
@@ -82,8 +82,10 @@ interface Observer<T> {
|
|
|
82
82
|
type Subscribable<T extends unknown> = {
|
|
83
83
|
subscribe: (observer: Partial<Observer<T>>) => Unsubscribable;
|
|
84
84
|
};
|
|
85
|
-
|
|
86
|
-
export type
|
|
85
|
+
/** A method used to subscribe to events on a set of relays */
|
|
86
|
+
export type NostrSubscriptionMethod = (relays: string[], filters: Filter[]) => Subscribable<NostrEvent | string>;
|
|
87
|
+
/** A method used for publishing an event, can return a Promise that completes when published or an Observable that completes when published*/
|
|
88
|
+
export type NostrPublishMethod = (relays: string[], event: NostrEvent) => Promise<any> | Subscribable<any>;
|
|
87
89
|
export type NostrConnectAppMetadata = {
|
|
88
90
|
name?: string;
|
|
89
91
|
image?: string;
|
|
@@ -98,7 +100,8 @@ export declare class NostrConnectSigner implements Nip07Interface {
|
|
|
98
100
|
protected log: import("debug").Debugger;
|
|
99
101
|
/** The local client signer */
|
|
100
102
|
signer: SimpleSigner;
|
|
101
|
-
|
|
103
|
+
/** Whether the signer is listening for events */
|
|
104
|
+
listening: boolean;
|
|
102
105
|
/** Whether the signer is connected to the remote signer */
|
|
103
106
|
isConnected: boolean;
|
|
104
107
|
/** The users pubkey */
|
|
@@ -143,7 +146,7 @@ export declare class NostrConnectSigner implements Nip07Interface {
|
|
|
143
146
|
connect(secret?: string | undefined, permissions?: string[]): Promise<"ack">;
|
|
144
147
|
private waitingPromise;
|
|
145
148
|
/** Wait for a remote signer to connect */
|
|
146
|
-
waitForSigner(): Promise<void>;
|
|
149
|
+
waitForSigner(abort?: AbortSignal): Promise<void>;
|
|
147
150
|
/** Request to create an account on the remote signer */
|
|
148
151
|
createAccount(username: string, domain: string, email?: string, permissions?: string[]): Promise<string>;
|
|
149
152
|
/** Ensure the signer is connected to the remote signer */
|
|
@@ -40,7 +40,8 @@ export class NostrConnectSigner {
|
|
|
40
40
|
log = logger.extend("NostrConnectSigner");
|
|
41
41
|
/** The local client signer */
|
|
42
42
|
signer;
|
|
43
|
-
|
|
43
|
+
/** Whether the signer is listening for events */
|
|
44
|
+
listening = false;
|
|
44
45
|
/** Whether the signer is connected to the remote signer */
|
|
45
46
|
isConnected = false;
|
|
46
47
|
/** The users pubkey */
|
|
@@ -92,9 +93,9 @@ export class NostrConnectSigner {
|
|
|
92
93
|
req;
|
|
93
94
|
/** Open the connection */
|
|
94
95
|
async open() {
|
|
95
|
-
if (this.
|
|
96
|
+
if (this.listening)
|
|
96
97
|
return;
|
|
97
|
-
this.
|
|
98
|
+
this.listening = true;
|
|
98
99
|
const pubkey = await this.signer.getPublicKey();
|
|
99
100
|
// Setup subscription
|
|
100
101
|
this.req = this.subscriptionMethod(this.relays, [
|
|
@@ -103,15 +104,24 @@ export class NostrConnectSigner {
|
|
|
103
104
|
"#p": [pubkey],
|
|
104
105
|
},
|
|
105
106
|
]).subscribe({
|
|
106
|
-
next: (event) => this.handleEvent(event),
|
|
107
|
+
next: (event) => typeof event !== "string" && this.handleEvent(event),
|
|
107
108
|
});
|
|
108
109
|
this.log("Opened", this.relays);
|
|
109
110
|
}
|
|
110
111
|
/** Close the connection */
|
|
111
112
|
async close() {
|
|
112
|
-
this.
|
|
113
|
+
this.listening = false;
|
|
113
114
|
this.isConnected = false;
|
|
114
|
-
|
|
115
|
+
// Close the current subscription
|
|
116
|
+
if (this.req) {
|
|
117
|
+
this.req.unsubscribe();
|
|
118
|
+
this.req = undefined;
|
|
119
|
+
}
|
|
120
|
+
// Cancel waiting promise
|
|
121
|
+
if (this.waitingPromise) {
|
|
122
|
+
this.waitingPromise.reject(new Error("Closed"));
|
|
123
|
+
this.waitingPromise = null;
|
|
124
|
+
}
|
|
115
125
|
this.log("Closed");
|
|
116
126
|
}
|
|
117
127
|
requests = new Map();
|
|
@@ -185,10 +195,16 @@ export class NostrConnectSigner {
|
|
|
185
195
|
const request = { id, method, params };
|
|
186
196
|
const encrypted = await this.signer.nip44.encrypt(this.remote, JSON.stringify(request));
|
|
187
197
|
const event = await this.createRequestEvent(encrypted, this.remote, kind);
|
|
188
|
-
this.log(`Sending
|
|
198
|
+
this.log(`Sending ${id} (${method}) ${JSON.stringify(params)}`);
|
|
189
199
|
const p = createDefer();
|
|
190
200
|
this.requests.set(id, p);
|
|
191
|
-
|
|
201
|
+
const result = this.publishMethod?.(this.relays, event);
|
|
202
|
+
// Handle returned Promise or Observable
|
|
203
|
+
if (result instanceof Promise)
|
|
204
|
+
await result;
|
|
205
|
+
else if ("subscribe" in result)
|
|
206
|
+
await new Promise((res) => result.subscribe({ complete: res }));
|
|
207
|
+
this.log(`Sent ${id} (${method})`);
|
|
192
208
|
return p;
|
|
193
209
|
}
|
|
194
210
|
/** Connect to remote signer */
|
|
@@ -216,11 +232,16 @@ export class NostrConnectSigner {
|
|
|
216
232
|
}
|
|
217
233
|
waitingPromise = null;
|
|
218
234
|
/** Wait for a remote signer to connect */
|
|
219
|
-
waitForSigner() {
|
|
235
|
+
waitForSigner(abort) {
|
|
220
236
|
if (this.isConnected)
|
|
221
237
|
return Promise.resolve();
|
|
222
238
|
this.open();
|
|
223
239
|
this.waitingPromise = createDefer();
|
|
240
|
+
abort?.addEventListener("abort", () => {
|
|
241
|
+
this.waitingPromise?.reject(new Error("Aborted"));
|
|
242
|
+
this.waitingPromise = null;
|
|
243
|
+
this.close();
|
|
244
|
+
}, true);
|
|
224
245
|
return this.waitingPromise;
|
|
225
246
|
}
|
|
226
247
|
/** Request to create an account on the remote signer */
|
|
@@ -17,13 +17,22 @@ export declare class PasswordSigner implements Nip07Interface {
|
|
|
17
17
|
constructor();
|
|
18
18
|
unlockPromise?: Deferred<void>;
|
|
19
19
|
protected requestUnlock(): Deferred<void> | undefined;
|
|
20
|
+
/** Sets the ncryptsec from the key and password */
|
|
20
21
|
setPassword(password: string): Promise<void>;
|
|
22
|
+
/** Tests if the provided password is correct by decrypting the ncryptsec */
|
|
21
23
|
testPassword(password: string): Promise<void>;
|
|
24
|
+
/** Unlocks the signer by decrypting the ncryptsec using the provided password */
|
|
22
25
|
unlock(password: string): Promise<void>;
|
|
26
|
+
/** Locks the signer by removing the unencrypted key from memory */
|
|
27
|
+
lock(): void;
|
|
23
28
|
getPublicKey(): Promise<string>;
|
|
24
29
|
signEvent(event: EventTemplate): Promise<import("nostr-tools").VerifiedEvent>;
|
|
25
30
|
nip04Encrypt(pubkey: string, plaintext: string): Promise<string>;
|
|
26
31
|
nip04Decrypt(pubkey: string, ciphertext: string): Promise<string>;
|
|
27
32
|
nip44Encrypt(pubkey: string, plaintext: string): Promise<string>;
|
|
28
33
|
nip44Decrypt(pubkey: string, ciphertext: string): Promise<string>;
|
|
34
|
+
/** Creates a PasswordSigner from a hex private key or NIP-19 nsec and password */
|
|
35
|
+
static fromPrivateKey(privateKey: Uint8Array | string, password: string): Promise<PasswordSigner>;
|
|
36
|
+
/** Creates a PasswordSigner from a ncryptsec and unlocks it with the provided password */
|
|
37
|
+
static fromNcryptsec(ncryptsec: string, password?: string): Promise<PasswordSigner>;
|
|
29
38
|
}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { finalizeEvent, getPublicKey, nip04, nip44 } from "nostr-tools";
|
|
2
2
|
import { encrypt, decrypt } from "nostr-tools/nip49";
|
|
3
3
|
import { createDefer } from "applesauce-core/promise";
|
|
4
|
+
import { normalizeToSecretKey } from "applesauce-core/helpers";
|
|
4
5
|
/** A NIP-49 (Private Key Encryption) signer */
|
|
5
6
|
export class PasswordSigner {
|
|
6
7
|
key = null;
|
|
@@ -30,11 +31,13 @@ export class PasswordSigner {
|
|
|
30
31
|
this.unlockPromise = p;
|
|
31
32
|
return p;
|
|
32
33
|
}
|
|
34
|
+
/** Sets the ncryptsec from the key and password */
|
|
33
35
|
async setPassword(password) {
|
|
34
36
|
if (!this.key)
|
|
35
37
|
throw new Error("Cant set password until unlocked");
|
|
36
38
|
this.ncryptsec = encrypt(this.key, password);
|
|
37
39
|
}
|
|
40
|
+
/** Tests if the provided password is correct by decrypting the ncryptsec */
|
|
38
41
|
async testPassword(password) {
|
|
39
42
|
if (this.ncryptsec) {
|
|
40
43
|
const key = decrypt(this.ncryptsec, password);
|
|
@@ -44,6 +47,7 @@ export class PasswordSigner {
|
|
|
44
47
|
else
|
|
45
48
|
throw new Error("Missing ncryptsec");
|
|
46
49
|
}
|
|
50
|
+
/** Unlocks the signer by decrypting the ncryptsec using the provided password */
|
|
47
51
|
async unlock(password) {
|
|
48
52
|
if (this.key)
|
|
49
53
|
return;
|
|
@@ -55,6 +59,10 @@ export class PasswordSigner {
|
|
|
55
59
|
else
|
|
56
60
|
throw new Error("Missing ncryptsec");
|
|
57
61
|
}
|
|
62
|
+
/** Locks the signer by removing the unencrypted key from memory */
|
|
63
|
+
lock() {
|
|
64
|
+
this.key = null;
|
|
65
|
+
}
|
|
58
66
|
// public methods
|
|
59
67
|
async getPublicKey() {
|
|
60
68
|
await this.requestUnlock();
|
|
@@ -82,4 +90,19 @@ export class PasswordSigner {
|
|
|
82
90
|
await this.requestUnlock();
|
|
83
91
|
return nip44.v2.decrypt(ciphertext, nip44.v2.utils.getConversationKey(this.key, pubkey));
|
|
84
92
|
}
|
|
93
|
+
/** Creates a PasswordSigner from a hex private key or NIP-19 nsec and password */
|
|
94
|
+
static async fromPrivateKey(privateKey, password) {
|
|
95
|
+
const signer = new PasswordSigner();
|
|
96
|
+
signer.key = normalizeToSecretKey(privateKey);
|
|
97
|
+
await signer.setPassword(password);
|
|
98
|
+
return signer;
|
|
99
|
+
}
|
|
100
|
+
/** Creates a PasswordSigner from a ncryptsec and unlocks it with the provided password */
|
|
101
|
+
static async fromNcryptsec(ncryptsec, password) {
|
|
102
|
+
const signer = new PasswordSigner();
|
|
103
|
+
signer.ncryptsec = ncryptsec;
|
|
104
|
+
if (password)
|
|
105
|
+
await signer.unlock(password);
|
|
106
|
+
return signer;
|
|
107
|
+
}
|
|
85
108
|
}
|
|
@@ -19,4 +19,6 @@ export declare class ReadonlySigner implements Nip07Interface {
|
|
|
19
19
|
nip04Decrypt(): string;
|
|
20
20
|
nip44Encrypt(): string;
|
|
21
21
|
nip44Decrypt(): string;
|
|
22
|
+
/** Creates a ReadonlySigner from a hex public key or NIP-19 npub */
|
|
23
|
+
static fromPubkey(pubkey: string): ReadonlySigner;
|
|
22
24
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { isHexKey, normalizeToPubkey } from "applesauce-core/helpers";
|
|
1
2
|
/** A signer that only implements getPublicKey and throws on ever other method */
|
|
2
3
|
export class ReadonlySigner {
|
|
3
4
|
pubkey;
|
|
@@ -5,6 +6,8 @@ export class ReadonlySigner {
|
|
|
5
6
|
nip44;
|
|
6
7
|
constructor(pubkey) {
|
|
7
8
|
this.pubkey = pubkey;
|
|
9
|
+
if (!isHexKey(pubkey))
|
|
10
|
+
throw new Error("Invalid public key");
|
|
8
11
|
this.nip04 = {
|
|
9
12
|
encrypt: this.nip04Encrypt.bind(this),
|
|
10
13
|
decrypt: this.nip04Decrypt.bind(this),
|
|
@@ -35,4 +38,8 @@ export class ReadonlySigner {
|
|
|
35
38
|
nip44Decrypt() {
|
|
36
39
|
throw new Error("Cant decrypt with readonly");
|
|
37
40
|
}
|
|
41
|
+
/** Creates a ReadonlySigner from a hex public key or NIP-19 npub */
|
|
42
|
+
static fromPubkey(pubkey) {
|
|
43
|
+
return new ReadonlySigner(normalizeToPubkey(pubkey));
|
|
44
|
+
}
|
|
38
45
|
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { EventTemplate, verifyEvent } from "nostr-tools";
|
|
2
1
|
import { Deferred } from "applesauce-core/promise";
|
|
2
|
+
import { EventTemplate, verifyEvent } from "nostr-tools";
|
|
3
3
|
import { Nip07Interface } from "../nip-07.js";
|
|
4
4
|
type Callback = () => void;
|
|
5
5
|
type DeviceOpts = {
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/// <reference types="@types/dom-serial" />
|
|
2
|
-
import {
|
|
3
|
-
import { base64 } from "@scure/base";
|
|
4
|
-
import { randomBytes, hexToBytes, bytesToHex } from "@noble/hashes/utils";
|
|
2
|
+
import { bytesToHex, hexToBytes, randomBytes } from "@noble/hashes/utils";
|
|
5
3
|
import { Point } from "@noble/secp256k1";
|
|
4
|
+
import { base64 } from "@scure/base";
|
|
5
|
+
import { logger } from "applesauce-core";
|
|
6
6
|
import { createDefer } from "applesauce-core/promise";
|
|
7
|
-
import {
|
|
7
|
+
import { getEventHash, verifyEvent } from "nostr-tools";
|
|
8
8
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
9
9
|
function xOnlyToXY(p) {
|
|
10
10
|
return Point.fromHex(p).toHex().substring(2);
|
|
@@ -13,4 +13,6 @@ export declare class SimpleSigner {
|
|
|
13
13
|
encrypt: (pubkey: string, plaintext: string) => Promise<string>;
|
|
14
14
|
decrypt: (pubkey: string, ciphertext: string) => Promise<string>;
|
|
15
15
|
};
|
|
16
|
+
/** Creates a SimpleSigner from a hex private key or NIP-19 nsec */
|
|
17
|
+
static fromKey(privateKey: Uint8Array | string): SimpleSigner;
|
|
16
18
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { normalizeToSecretKey } from "applesauce-core/helpers";
|
|
1
2
|
import { finalizeEvent, generateSecretKey, getPublicKey, nip04, nip44 } from "nostr-tools";
|
|
2
3
|
/** A Simple NIP-07 signer class */
|
|
3
4
|
export class SimpleSigner {
|
|
@@ -19,4 +20,8 @@ export class SimpleSigner {
|
|
|
19
20
|
encrypt: async (pubkey, plaintext) => nip44.v2.encrypt(plaintext, nip44.v2.utils.getConversationKey(this.key, pubkey)),
|
|
20
21
|
decrypt: async (pubkey, ciphertext) => nip44.v2.decrypt(ciphertext, nip44.v2.utils.getConversationKey(this.key, pubkey)),
|
|
21
22
|
};
|
|
23
|
+
/** Creates a SimpleSigner from a hex private key or NIP-19 nsec */
|
|
24
|
+
static fromKey(privateKey) {
|
|
25
|
+
return new SimpleSigner(normalizeToSecretKey(privateKey));
|
|
26
|
+
}
|
|
22
27
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-signers",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "Signer classes for applesauce",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -36,16 +36,16 @@
|
|
|
36
36
|
"@noble/hashes": "^1.7.1",
|
|
37
37
|
"@noble/secp256k1": "^1.7.1",
|
|
38
38
|
"@scure/base": "^1.2.4",
|
|
39
|
-
"applesauce-core": "^
|
|
39
|
+
"applesauce-core": "^2.0.0",
|
|
40
40
|
"debug": "^4.4.0",
|
|
41
41
|
"nanoid": "^5.0.9",
|
|
42
|
-
"nostr-tools": "^2.
|
|
42
|
+
"nostr-tools": "^2.13"
|
|
43
43
|
},
|
|
44
44
|
"devDependencies": {
|
|
45
45
|
"@types/debug": "^4.1.12",
|
|
46
46
|
"@types/dom-serial": "^1.0.6",
|
|
47
47
|
"typescript": "^5.8.3",
|
|
48
|
-
"vitest": "^3.1.
|
|
48
|
+
"vitest": "^3.1.3"
|
|
49
49
|
},
|
|
50
50
|
"funding": {
|
|
51
51
|
"type": "lightning",
|
package/dist/logger.d.ts
DELETED
package/dist/logger.js
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export {};
|
|
@@ -1,23 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from "vitest";
|
|
2
|
-
import { NostrConnectSigner } from "./nostr-connect-signer.js";
|
|
3
|
-
import { SimpleSigner } from "./simple-signer.js";
|
|
4
|
-
describe("NostrConnectSigner", () => {
|
|
5
|
-
describe("connection", () => {
|
|
6
|
-
it("should call subscription method with filters", async () => {
|
|
7
|
-
const relays = ["wss://relay.signer.com"];
|
|
8
|
-
const subscription = vi.fn().mockReturnValue({ subscribe: vi.fn() });
|
|
9
|
-
const publish = vi.fn(async () => { });
|
|
10
|
-
const client = new SimpleSigner();
|
|
11
|
-
const remote = new SimpleSigner();
|
|
12
|
-
const signer = new NostrConnectSigner({
|
|
13
|
-
relays,
|
|
14
|
-
remote: await remote.getPublicKey(),
|
|
15
|
-
signer: client,
|
|
16
|
-
subscriptionMethod: subscription,
|
|
17
|
-
publishMethod: publish,
|
|
18
|
-
});
|
|
19
|
-
signer.connect();
|
|
20
|
-
expect(subscription).toHaveBeenCalledWith(relays, [{ "#p": [await client.getPublicKey()], kinds: [24133] }]);
|
|
21
|
-
});
|
|
22
|
-
});
|
|
23
|
-
});
|