applesauce-signers 0.11.0 → 1.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 +98 -2
- package/dist/signers/extension-signer.d.ts +1 -0
- package/dist/signers/extension-signer.js +13 -1
- package/dist/signers/nostr-connect-signer.d.ts +29 -15
- package/dist/signers/nostr-connect-signer.js +26 -16
- package/dist/signers/nostr-connect-signer.test.js +6 -8
- package/package.json +6 -5
package/README.md
CHANGED
|
@@ -1,5 +1,101 @@
|
|
|
1
1
|
# applesauce-signer
|
|
2
2
|
|
|
3
|
-
A collection of signer classes for applesauce
|
|
3
|
+
A collection of signer classes for applesauce that are compatible with the [NIP-07](https://github.com/nostr-protocol/nips/blob/master/07.md) API.
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
## Documentation
|
|
6
|
+
|
|
7
|
+
For detailed documentation and API reference, see:
|
|
8
|
+
|
|
9
|
+
- [Signers Documentation](https://hzrd149.github.io/applesauce/signers/signers.html)
|
|
10
|
+
- [Nostr Connect Documentation](https://hzrd149.github.io/applesauce/signers/nostr-connect.html)
|
|
11
|
+
- [API Reference](https://hzrd149.github.io/applesauce/typedoc/modules/applesauce_signers.html)
|
|
12
|
+
|
|
13
|
+
## Available Signers
|
|
14
|
+
|
|
15
|
+
### Password Signer (NIP-49)
|
|
16
|
+
|
|
17
|
+
A secure signer that encrypts private keys using [NIP-49](https://github.com/nostr-protocol/nips/blob/master/49.md).
|
|
18
|
+
|
|
19
|
+
```ts
|
|
20
|
+
// Create a new password signer
|
|
21
|
+
const signer = new PasswordSigner();
|
|
22
|
+
|
|
23
|
+
// Set up with a new key and password
|
|
24
|
+
const randomBytes = new Uint8Array(64);
|
|
25
|
+
window.crypto.getRandomValues(randomBytes);
|
|
26
|
+
|
|
27
|
+
signer.key = randomBytes;
|
|
28
|
+
signer.setPassword("your-password");
|
|
29
|
+
|
|
30
|
+
// Unlock the signer when needed
|
|
31
|
+
await signer.unlock("your-password");
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Simple Signer
|
|
35
|
+
|
|
36
|
+
A basic signer that holds the secret key in memory with NIP-04 and NIP-44 encryption support.
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
// Create new signer with random key
|
|
40
|
+
const signer = new SimpleSigner();
|
|
41
|
+
|
|
42
|
+
// Or import existing key
|
|
43
|
+
const key = new Uint8Array(32);
|
|
44
|
+
window.crypto.getRandomValues(key);
|
|
45
|
+
const signer = new SimpleSigner(key);
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
### Nostr Connect Signer (NIP-46)
|
|
49
|
+
|
|
50
|
+
A client-side implementation for remote signing using [NIP-46](https://github.com/nostr-protocol/nips/blob/master/46.md).
|
|
51
|
+
|
|
52
|
+
```ts
|
|
53
|
+
// First, set up the required relay communication methods
|
|
54
|
+
import { Observable } from "rxjs";
|
|
55
|
+
|
|
56
|
+
// Define subscription method for receiving events
|
|
57
|
+
const subscriptionMethod = (filters, relays) => {
|
|
58
|
+
return new Observable((observer) => {
|
|
59
|
+
// Create subscription to relays
|
|
60
|
+
const cleanup = subscribeToRelays(relays, filters, (event) => {
|
|
61
|
+
observer.next(event);
|
|
62
|
+
});
|
|
63
|
+
return () => cleanup();
|
|
64
|
+
});
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Define publish method for sending events
|
|
68
|
+
const publishMethod = async (event, relays) => {
|
|
69
|
+
for (const relay of relays) await publishToRelay(relay, event);
|
|
70
|
+
};
|
|
71
|
+
|
|
72
|
+
// You can set these methods globally at app initialization
|
|
73
|
+
NostrConnectSigner.subscriptionMethod = subscriptionMethod;
|
|
74
|
+
NostrConnectSigner.publishMethod = publishMethod;
|
|
75
|
+
|
|
76
|
+
// Now create and use the signer
|
|
77
|
+
const signer = new NostrConnectSigner({
|
|
78
|
+
remote: "<remote signer pubkey>",
|
|
79
|
+
relays: ["wss://relay.example.com"],
|
|
80
|
+
// Or pass methods directly to the constructor
|
|
81
|
+
subscriptionMethod,
|
|
82
|
+
publishMethod,
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
// Create a connection URI for your app
|
|
86
|
+
const uri = signer.getNostrConnectURI({
|
|
87
|
+
name: "My App",
|
|
88
|
+
url: "https://example.com",
|
|
89
|
+
permissions: NostrConnectSigner.buildSigningPermissions([0, 1, 3]),
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// Connect using bunker URI
|
|
93
|
+
const bunkerSigner = await NostrConnectSigner.fromBunkerURI("bunker://...your-uri-here...", {
|
|
94
|
+
permissions: NostrConnectSigner.buildSigningPermissions([0, 1, 3]),
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Other Signers
|
|
99
|
+
|
|
100
|
+
- **Serial Port Signer**: For hardware signing devices (Chrome browsers only)
|
|
101
|
+
- **Amber Clipboard Signer**: Integration with Amber wallet's web API
|
|
@@ -13,6 +13,7 @@ export declare class ExtensionSigner implements Nip07Interface {
|
|
|
13
13
|
encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
|
|
14
14
|
decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
|
|
15
15
|
} | undefined;
|
|
16
|
+
protected pubkey: string | undefined;
|
|
16
17
|
getPublicKey(): string | Promise<string>;
|
|
17
18
|
getRelays(): Record<string, {
|
|
18
19
|
read: boolean;
|
|
@@ -9,10 +9,22 @@ export class ExtensionSigner {
|
|
|
9
9
|
get nip44() {
|
|
10
10
|
return window.nostr?.nip44;
|
|
11
11
|
}
|
|
12
|
+
pubkey = undefined;
|
|
12
13
|
getPublicKey() {
|
|
13
14
|
if (!window.nostr)
|
|
14
15
|
throw new ExtensionMissingError("Signer extension missing");
|
|
15
|
-
|
|
16
|
+
if (this.pubkey)
|
|
17
|
+
return this.pubkey;
|
|
18
|
+
const p = window.nostr.getPublicKey();
|
|
19
|
+
if (p instanceof Promise)
|
|
20
|
+
return p.then((pubkey) => {
|
|
21
|
+
this.pubkey = pubkey;
|
|
22
|
+
return pubkey;
|
|
23
|
+
});
|
|
24
|
+
else {
|
|
25
|
+
this.pubkey = p;
|
|
26
|
+
return p;
|
|
27
|
+
}
|
|
16
28
|
}
|
|
17
29
|
getRelays() {
|
|
18
30
|
if (!window.nostr)
|
|
@@ -64,17 +64,26 @@ export type NostrConnectSignerOptions = {
|
|
|
64
64
|
remote?: string;
|
|
65
65
|
/** Users pubkey */
|
|
66
66
|
pubkey?: string;
|
|
67
|
-
};
|
|
68
|
-
export type NostrConnectConnectionMethods = {
|
|
69
|
-
/** A method that is called when the subscription needs to be updated */
|
|
70
|
-
onSubOpen: (filters: Filter[], relays: string[], onEvent: (event: NostrEvent) => void) => Promise<void>;
|
|
71
|
-
/** A method called when the subscription should be closed */
|
|
72
|
-
onSubClose: () => Promise<void>;
|
|
73
|
-
/** A method that is called when an event needs to be published */
|
|
74
|
-
onPublishEvent: (event: NostrEvent, relays: string[]) => Promise<void>;
|
|
75
67
|
/** A method for handling "auth" requests */
|
|
76
68
|
onAuth?: (url: string) => Promise<void>;
|
|
69
|
+
/** A method for subscribing to relays */
|
|
70
|
+
subscriptionMethod?: NostrSubscriptionMethod;
|
|
71
|
+
/** A method for publishing events */
|
|
72
|
+
publishMethod?: NostrPublishMethod;
|
|
73
|
+
};
|
|
74
|
+
interface Unsubscribable {
|
|
75
|
+
unsubscribe(): void;
|
|
76
|
+
}
|
|
77
|
+
interface Observer<T> {
|
|
78
|
+
next: (value: T) => void;
|
|
79
|
+
error: (err: any) => void;
|
|
80
|
+
complete: () => void;
|
|
81
|
+
}
|
|
82
|
+
type Subscribable<T extends unknown> = {
|
|
83
|
+
subscribe: (observer: Partial<Observer<T>>) => Unsubscribable;
|
|
77
84
|
};
|
|
85
|
+
export type NostrSubscriptionMethod = (relays: string[], filters: Filter[]) => Subscribable<NostrEvent>;
|
|
86
|
+
export type NostrPublishMethod = (relays: string[], event: NostrEvent) => void | Promise<void>;
|
|
78
87
|
export type NostrConnectAppMetadata = {
|
|
79
88
|
name?: string;
|
|
80
89
|
image?: string;
|
|
@@ -82,12 +91,10 @@ export type NostrConnectAppMetadata = {
|
|
|
82
91
|
permissions?: string[];
|
|
83
92
|
};
|
|
84
93
|
export declare class NostrConnectSigner implements Nip07Interface {
|
|
85
|
-
/** A method that is called when the subscription needs to be updated */
|
|
86
|
-
onSubOpen?: (filters: Filter[], relays: string[], onEvent: (event: NostrEvent) => void) => Promise<void>;
|
|
87
|
-
/** A method called when the subscription should be closed */
|
|
88
|
-
onSubClose?: () => Promise<void>;
|
|
89
94
|
/** A method that is called when an event needs to be published */
|
|
90
|
-
|
|
95
|
+
protected publishMethod: NostrPublishMethod;
|
|
96
|
+
/** The active nostr subscription */
|
|
97
|
+
protected subscriptionMethod: NostrSubscriptionMethod;
|
|
91
98
|
protected log: import("debug").Debugger;
|
|
92
99
|
/** The local client signer */
|
|
93
100
|
signer: SimpleSigner;
|
|
@@ -102,6 +109,7 @@ export declare class NostrConnectSigner implements Nip07Interface {
|
|
|
102
109
|
remote?: string;
|
|
103
110
|
/** Client pubkey */
|
|
104
111
|
get clientPubkey(): string;
|
|
112
|
+
/** A method for handling "auth" requests */
|
|
105
113
|
onAuth: (url: string) => Promise<void>;
|
|
106
114
|
verifyEvent: typeof verifyEvent;
|
|
107
115
|
/** A secret used when initiating a connection from the client side */
|
|
@@ -114,7 +122,13 @@ export declare class NostrConnectSigner implements Nip07Interface {
|
|
|
114
122
|
encrypt: (pubkey: string, plaintext: string) => Promise<string> | string;
|
|
115
123
|
decrypt: (pubkey: string, ciphertext: string) => Promise<string> | string;
|
|
116
124
|
} | undefined;
|
|
117
|
-
|
|
125
|
+
/** A fallback method to use for subscriptionMethod if none is pass in when creating the signer */
|
|
126
|
+
static subscriptionMethod: NostrSubscriptionMethod | undefined;
|
|
127
|
+
/** A fallback method to use for publishMethod if none is pass in when creating the signer */
|
|
128
|
+
static publishMethod: NostrPublishMethod | undefined;
|
|
129
|
+
constructor(opts: NostrConnectSignerOptions);
|
|
130
|
+
/** The currently active REQ subscription */
|
|
131
|
+
protected req?: Unsubscribable;
|
|
118
132
|
/** Open the connection */
|
|
119
133
|
open(): Promise<void>;
|
|
120
134
|
/** Close the connection */
|
|
@@ -155,7 +169,7 @@ export declare class NostrConnectSigner implements Nip07Interface {
|
|
|
155
169
|
/** Builds an array of signing permissions for event kinds */
|
|
156
170
|
static buildSigningPermissions(kinds: number[]): string[];
|
|
157
171
|
/** Create a {@link NostrConnectSigner} from a bunker:// URI */
|
|
158
|
-
static fromBunkerURI(uri: string, options
|
|
172
|
+
static fromBunkerURI(uri: string, options?: Omit<NostrConnectSignerOptions, "relays"> & {
|
|
159
173
|
permissions?: string[];
|
|
160
174
|
signer?: SimpleSigner;
|
|
161
175
|
}): Promise<NostrConnectSigner>;
|
|
@@ -33,14 +33,10 @@ async function defaultHandleAuth(url) {
|
|
|
33
33
|
window.open(url, "auth", "width=400,height=600,resizable=no,status=no,location=no,toolbar=no,menubar=no");
|
|
34
34
|
}
|
|
35
35
|
export class NostrConnectSigner {
|
|
36
|
-
/** A method that is called when the subscription needs to be updated */
|
|
37
|
-
onSubOpen;
|
|
38
|
-
/** A method called when the subscription should be closed */
|
|
39
|
-
onSubClose;
|
|
40
36
|
/** A method that is called when an event needs to be published */
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
37
|
+
publishMethod;
|
|
38
|
+
/** The active nostr subscription */
|
|
39
|
+
subscriptionMethod;
|
|
44
40
|
log = logger.extend("NostrConnectSigner");
|
|
45
41
|
/** The local client signer */
|
|
46
42
|
signer;
|
|
@@ -57,19 +53,29 @@ export class NostrConnectSigner {
|
|
|
57
53
|
get clientPubkey() {
|
|
58
54
|
return getPublicKey(this.signer.key);
|
|
59
55
|
}
|
|
56
|
+
/** A method for handling "auth" requests */
|
|
60
57
|
onAuth = defaultHandleAuth;
|
|
61
58
|
verifyEvent = verifyEvent;
|
|
62
59
|
/** A secret used when initiating a connection from the client side */
|
|
63
60
|
clientSecret = nanoid(12);
|
|
64
61
|
nip04;
|
|
65
62
|
nip44;
|
|
63
|
+
/** A fallback method to use for subscriptionMethod if none is pass in when creating the signer */
|
|
64
|
+
static subscriptionMethod = undefined;
|
|
65
|
+
/** A fallback method to use for publishMethod if none is pass in when creating the signer */
|
|
66
|
+
static publishMethod = undefined;
|
|
66
67
|
constructor(opts) {
|
|
67
68
|
this.relays = opts.relays;
|
|
68
69
|
this.pubkey = opts.pubkey;
|
|
69
70
|
this.remote = opts.remote;
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
71
|
+
const subscriptionMethod = opts.subscriptionMethod || NostrConnectSigner.subscriptionMethod;
|
|
72
|
+
if (!subscriptionMethod)
|
|
73
|
+
throw new Error("Missing subscriptionMethod, either pass a method or set NostrConnectSigner.subscriptionMethod");
|
|
74
|
+
const publishMethod = opts.publishMethod || NostrConnectSigner.publishMethod;
|
|
75
|
+
if (!publishMethod)
|
|
76
|
+
throw new Error("Missing publishMethod, either pass a method or set NostrConnectSigner.publishMethod");
|
|
77
|
+
this.subscriptionMethod = subscriptionMethod;
|
|
78
|
+
this.publishMethod = publishMethod;
|
|
73
79
|
if (opts.onAuth)
|
|
74
80
|
this.onAuth = opts.onAuth;
|
|
75
81
|
this.signer = opts?.signer || new SimpleSigner();
|
|
@@ -82,6 +88,8 @@ export class NostrConnectSigner {
|
|
|
82
88
|
decrypt: this.nip44Decrypt.bind(this),
|
|
83
89
|
};
|
|
84
90
|
}
|
|
91
|
+
/** The currently active REQ subscription */
|
|
92
|
+
req;
|
|
85
93
|
/** Open the connection */
|
|
86
94
|
async open() {
|
|
87
95
|
if (this.subscriptionOpen)
|
|
@@ -89,19 +97,21 @@ export class NostrConnectSigner {
|
|
|
89
97
|
this.subscriptionOpen = true;
|
|
90
98
|
const pubkey = await this.signer.getPublicKey();
|
|
91
99
|
// Setup subscription
|
|
92
|
-
|
|
100
|
+
this.req = this.subscriptionMethod(this.relays, [
|
|
93
101
|
{
|
|
94
102
|
kinds: [kinds.NostrConnect],
|
|
95
103
|
"#p": [pubkey],
|
|
96
104
|
},
|
|
97
|
-
]
|
|
105
|
+
]).subscribe({
|
|
106
|
+
next: (event) => this.handleEvent(event),
|
|
107
|
+
});
|
|
98
108
|
this.log("Opened", this.relays);
|
|
99
109
|
}
|
|
100
110
|
/** Close the connection */
|
|
101
111
|
async close() {
|
|
102
112
|
this.subscriptionOpen = false;
|
|
103
113
|
this.isConnected = false;
|
|
104
|
-
|
|
114
|
+
this.req?.unsubscribe();
|
|
105
115
|
this.log("Closed");
|
|
106
116
|
}
|
|
107
117
|
requests = new Map();
|
|
@@ -147,7 +157,7 @@ export class NostrConnectSigner {
|
|
|
147
157
|
}
|
|
148
158
|
}
|
|
149
159
|
else
|
|
150
|
-
p.reject(response);
|
|
160
|
+
p.reject(new Error(response.error));
|
|
151
161
|
}
|
|
152
162
|
else if (response.result) {
|
|
153
163
|
this.log("Got Response", response.id, response.result);
|
|
@@ -178,7 +188,7 @@ export class NostrConnectSigner {
|
|
|
178
188
|
this.log(`Sending request ${id} (${method}) ${JSON.stringify(params)}`);
|
|
179
189
|
const p = createDefer();
|
|
180
190
|
this.requests.set(id, p);
|
|
181
|
-
await this.
|
|
191
|
+
await this.publishMethod?.(this.relays, event);
|
|
182
192
|
return p;
|
|
183
193
|
}
|
|
184
194
|
/** Connect to remote signer */
|
|
@@ -321,7 +331,7 @@ export class NostrConnectSigner {
|
|
|
321
331
|
static async fromBunkerURI(uri, options) {
|
|
322
332
|
const { remote, relays, secret } = NostrConnectSigner.parseBunkerURI(uri);
|
|
323
333
|
const client = new NostrConnectSigner({ relays, remote, ...options });
|
|
324
|
-
await client.connect(secret, options
|
|
334
|
+
await client.connect(secret, options?.permissions);
|
|
325
335
|
return client;
|
|
326
336
|
}
|
|
327
337
|
}
|
|
@@ -3,23 +3,21 @@ import { NostrConnectSigner } from "./nostr-connect-signer.js";
|
|
|
3
3
|
import { SimpleSigner } from "./simple-signer.js";
|
|
4
4
|
describe("NostrConnectSigner", () => {
|
|
5
5
|
describe("connection", () => {
|
|
6
|
-
it("should call
|
|
6
|
+
it("should call subscription method with filters", async () => {
|
|
7
7
|
const relays = ["wss://relay.signer.com"];
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const onPublishEvent = vi.fn(async () => { });
|
|
8
|
+
const subscription = vi.fn().mockReturnValue({ subscribe: vi.fn() });
|
|
9
|
+
const publish = vi.fn(async () => { });
|
|
11
10
|
const client = new SimpleSigner();
|
|
12
11
|
const remote = new SimpleSigner();
|
|
13
12
|
const signer = new NostrConnectSigner({
|
|
14
|
-
onSubOpen,
|
|
15
|
-
onSubClose,
|
|
16
|
-
onPublishEvent,
|
|
17
13
|
relays,
|
|
18
14
|
remote: await remote.getPublicKey(),
|
|
19
15
|
signer: client,
|
|
16
|
+
subscriptionMethod: subscription,
|
|
17
|
+
publishMethod: publish,
|
|
20
18
|
});
|
|
21
19
|
signer.connect();
|
|
22
|
-
expect(
|
|
20
|
+
expect(subscription).toHaveBeenCalledWith(relays, [{ "#p": [await client.getPublicKey()], kinds: [24133] }]);
|
|
23
21
|
});
|
|
24
22
|
});
|
|
25
23
|
});
|
package/package.json
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "applesauce-signers",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "1.0.0",
|
|
4
4
|
"description": "Signer classes for applesauce",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"types": "dist/index.d.ts",
|
|
8
8
|
"keywords": [
|
|
9
|
-
"nostr"
|
|
9
|
+
"nostr",
|
|
10
|
+
"applesauce"
|
|
10
11
|
],
|
|
11
12
|
"author": "hzrd149",
|
|
12
13
|
"license": "MIT",
|
|
@@ -35,7 +36,7 @@
|
|
|
35
36
|
"@noble/hashes": "^1.7.1",
|
|
36
37
|
"@noble/secp256k1": "^1.7.1",
|
|
37
38
|
"@scure/base": "^1.2.4",
|
|
38
|
-
"applesauce-core": "^0.
|
|
39
|
+
"applesauce-core": "^1.0.0",
|
|
39
40
|
"debug": "^4.4.0",
|
|
40
41
|
"nanoid": "^5.0.9",
|
|
41
42
|
"nostr-tools": "^2.10.4"
|
|
@@ -43,8 +44,8 @@
|
|
|
43
44
|
"devDependencies": {
|
|
44
45
|
"@types/debug": "^4.1.12",
|
|
45
46
|
"@types/dom-serial": "^1.0.6",
|
|
46
|
-
"typescript": "^5.
|
|
47
|
-
"vitest": "^3.
|
|
47
|
+
"typescript": "^5.8.3",
|
|
48
|
+
"vitest": "^3.1.1"
|
|
48
49
|
},
|
|
49
50
|
"funding": {
|
|
50
51
|
"type": "lightning",
|