@did-btcr2/kms 0.1.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/src/kms.ts ADDED
@@ -0,0 +1,324 @@
1
+ import { Bytes, HashBytes, KeyBytes, KeyIdentifier, KeyManagerError, SignatureBytes } from '@did-btcr2/common';
2
+ import { SchnorrKeyPair } from '@did-btcr2/keypair';
3
+ import { sha256 } from '@noble/hashes/sha2.js';
4
+ import { KeyManager } from './interface.js';
5
+ import { KeyValueStore, MemoryStore } from './store.js';
6
+
7
+ /**
8
+ * Class for managing cryptographic keys for the BTCR2 DID method.
9
+ * @class Kms
10
+ * @type {Kms}
11
+ */
12
+ export class Kms implements KeyManager {
13
+ /**
14
+ * Singleton instance of the Kms.
15
+ * @private
16
+ * @type {KeyManager}
17
+ */
18
+ static #instance?: Kms;
19
+
20
+ /**
21
+ * The `store` is a private variable in `KeyManager`. It is a `KeyValueStore` instance used for
22
+ * storing and managing cryptographic keys. It allows the `KeyManager` class to save,
23
+ * retrieve, and handle keys efficiently within the local Key Management System (KMS) context.
24
+ * This variable can be configured to use different storage backends, like in-memory storage or
25
+ * persistent storage, providing flexibility in key management according to the application's
26
+ * requirements.
27
+ * @private
28
+ * @type {KeyValueStore<KeyIdentifier, KeyBytes>} The key store for managing cryptographic keys.
29
+ */
30
+ #store: KeyValueStore<KeyIdentifier, KeyBytes>;
31
+
32
+
33
+ /**
34
+ * The `#activeKeyId` property is a string that points to the currently active key.
35
+ * It is used to identify the key that will be used for signing and verifying operations.
36
+ * This property is optional and can be set to a specific key ID when initializing the
37
+ * `KeyManager` instance. If not set, the key manager will use the default key id.
38
+ * @private
39
+ * @type {KeyIdentifier}
40
+ */
41
+ #activeKeyId?: KeyIdentifier;
42
+
43
+ /**
44
+ * Creates an instance of KeyManager.
45
+ * @param {KeyValueStore<KeyIdentifier, KeyBytes>} store An optional property to specify a custom
46
+ * `KeyValueStore` instance for key management. If not provided, {@link KeyManager} uses a default `MemoryStore`
47
+ * instance. This store is responsible for managing cryptographic keys, allowing them to be retrieved, stored, and
48
+ * managed during cryptographic operations.
49
+ */
50
+ constructor(store?: KeyValueStore<KeyIdentifier, KeyBytes>) {
51
+ // Set the default key store to a MemoryStore instance
52
+ this.#store = store ?? new MemoryStore<KeyIdentifier, KeyBytes>();
53
+ }
54
+
55
+ /**
56
+ * Gets the ID of the active key.
57
+ * @returns {KeyIdentifier | undefined} The ID of the active key.
58
+ */
59
+ get activeKeyId(): KeyIdentifier | undefined {
60
+ return this.#activeKeyId;
61
+ }
62
+
63
+ /**
64
+ * Gets the key pair associated with the given ID or the active key if no ID is provided.
65
+ * @param {KeyIdentifier} [id] The ID of the key to get.
66
+ * @returns {KeyBytes} A promise resolving to the key pair.
67
+ * @throws {KeyManagerError} If the key is not found or no active key is set.
68
+ */
69
+ #getKeyOrThrow(id?: KeyIdentifier): SchnorrKeyPair {
70
+ // Get the key id
71
+ const keyId = id ?? this.#activeKeyId;
72
+ // Throw an error if no active key is set
73
+ if (!keyId) {
74
+ throw new KeyManagerError('No active key set', 'ACTIVE_KEY_URI_NOT_SET');
75
+ }
76
+
77
+ // Get the secret key from the store, throw an error if not found
78
+ const _secretKey = this.#store.get(keyId);
79
+ if (!_secretKey) {
80
+ throw new KeyManagerError(`Key not found: ${keyId}`, 'KEY_NOT_FOUND');
81
+ }
82
+
83
+ // Create a key pair from the secret key
84
+ const kp = new SchnorrKeyPair({ secretKey: _secretKey });
85
+
86
+ // Return the secret key
87
+ return kp;
88
+ }
89
+
90
+ /**
91
+ * Checks if a key with the given ID exists in the key store.
92
+ * @param {KeyIdentifier} id The ID of the key to check.
93
+ * @returns {boolean} A promise resolving to a boolean indicating if the key exists.
94
+ */
95
+ #exists(id: KeyIdentifier): boolean {
96
+ const key = this.#store.get(id);
97
+ return !!key;
98
+ }
99
+
100
+ /**
101
+ * Removes a key from the key store.
102
+ * @param {KeyIdentifier} id The key identifier of the key to remove.
103
+ * @param {{ force?: boolean }} options The options for removing the key.
104
+ * @param {boolean} [options.force] Whether to force the removal of the key.
105
+ * @returns {void} A promise that resolves when the key is removed.
106
+ * @throws {KeyManagerError} If attempting to remove the active key without force.
107
+ */
108
+ removeKey(id: KeyIdentifier, options: { force?: boolean } = {}): void {
109
+ // Check if trying to remove the active key without force
110
+ if (this.#activeKeyId === id && !options.force) {
111
+ throw new KeyManagerError('Cannot remove active key (use "force": true or switch active key)', 'ACTIVE_KEY_DELETE');
112
+ }
113
+
114
+ // Check if the key exists, if not throw an error
115
+ if (!this.#exists(id)) {
116
+ throw new KeyManagerError(`Key not found: ${id}`, 'KEY_NOT_FOUND');
117
+ }
118
+
119
+ // Remove the key from the store
120
+ this.#store.delete(id);
121
+
122
+ // Clear the active key if it was the one removed
123
+ if (this.#activeKeyId === id) {
124
+ this.#activeKeyId = undefined;
125
+ }
126
+ }
127
+
128
+ /**
129
+ * Lists all key identifiers in the key store.
130
+ * @returns {Promise<KeyIdentifier[]>} A promise that resolves to an array of key identifiers.
131
+ */
132
+ listKeys(): KeyIdentifier[] {
133
+ return this.#store.entries().flatMap(([k, _]) => [k as KeyIdentifier]);
134
+ }
135
+
136
+ /**
137
+ * Sets the active key to the key associated with the given ID.
138
+ * @param {KeyIdentifier} id The ID of the key to set as active.
139
+ * @returns {Promise<void>} A promise that resolves when the active key is set.
140
+ * @throws {KeyManagerError} If the key is not found.
141
+ */
142
+ setActiveKey(id: KeyIdentifier): void {
143
+ // Check if the key exists, if not throw an error
144
+ this.#getKeyOrThrow(id);
145
+
146
+ // Set the active key ID
147
+ this.#activeKeyId = id;
148
+ }
149
+
150
+ /**
151
+ * Gets the public key associated with the given ID or the active key if no ID is provided.
152
+ * @param {KeyIdentifier} [id] The ID of the key to get the public key for.
153
+ * @returns {Promise<KeyBytes>} A promise resolving to the public key bytes.
154
+ */
155
+ getPublicKey(id?: KeyIdentifier): KeyBytes {
156
+ // Get the key pair from the store
157
+ const { publicKey } = this.#getKeyOrThrow(id);
158
+
159
+ // Return the public key bytes
160
+ return publicKey.compressed;
161
+ }
162
+
163
+ /**
164
+ * Signs the given data using the key associated with the key ID.
165
+ * @param {Bytes} data The data to sign.
166
+ * @param {KeyIdentifier} [id] The ID of the key to sign the data with.
167
+ * @returns {Promise<SignatureBytes>} A promise resolving to the signature of the data.
168
+ */
169
+ sign(data: Bytes, id?: KeyIdentifier): SignatureBytes {
170
+ // Get the key from the store
171
+ const { secretKey } = this.#getKeyOrThrow(id);
172
+
173
+ // Check if the key can sign
174
+ if(!secretKey) {
175
+ throw new KeyManagerError(`Key ID ${id} is not a signer`, 'KEY_NOT_SIGNER');
176
+ }
177
+
178
+ // Sign the data using the key and return the signature
179
+ return secretKey.sign(data);
180
+ }
181
+
182
+ /**
183
+ * Verifies a signature using the key associated with the key ID.
184
+ * @param {KeyIdentifier} id The ID of the key to verify the signature with.
185
+ * @param {SignatureBytes} signature The signature to verify.
186
+ * @param {Hex} data The data to verify the signature with.
187
+ * @returns {Promise<boolean>} A promise resolving to a boolean indicating the verification result.
188
+ */
189
+ verify(signature: SignatureBytes, data: Bytes, id?: KeyIdentifier): boolean {
190
+ // Get the key from the store
191
+ const { publicKey } = this.#getKeyOrThrow(id);
192
+
193
+ // Verify the signature using the multikey
194
+ return publicKey.verify(signature, data);
195
+ }
196
+
197
+ /**
198
+ * Imports a key pair into the key store.
199
+ * @param {SchnorrKeyPair} keyPair The key pair to import.
200
+ * @param {{ id?: KeyIdentifier; setActive?: boolean }} options The options for importing the key pair.
201
+ * @param {KeyIdentifier} [options.id] The ID of the key to import (optional).
202
+ * @param {boolean} [options.setActive] Whether to set the key as active (optional, default: true).
203
+ * @returns {Promise<KeyIdentifier>} A promise resolving to the ID of the imported key.
204
+ */
205
+ importKey(
206
+ keyPair: SchnorrKeyPair,
207
+ options: {
208
+ id?: KeyIdentifier;
209
+ setActive?: boolean
210
+ } = {}
211
+ ): KeyIdentifier {
212
+ // Ensure the key pair has a public key
213
+ if(!keyPair.publicKey) {
214
+ keyPair.publicKey = keyPair.secretKey.computePublicKey();
215
+ }
216
+
217
+ // Determine the key ID
218
+ const id = options.id ?? (keyPair.publicKey.hex as string);
219
+
220
+ // Check if the key already exists
221
+ if (this.#exists(id)) {
222
+ throw new KeyManagerError(`Key already exists: ${id}`, 'KEY_FOUND');
223
+ }
224
+
225
+ // Store the key pair in the key store
226
+ this.#store.set(id, keyPair.secretKey.bytes);
227
+
228
+ // Determine whether to set the key as active, defaulting to true
229
+ const setActive = options.setActive ?? true;
230
+
231
+ // Set the active key if specified
232
+ if (setActive) {
233
+ this.#activeKeyId = id;
234
+ }
235
+
236
+ // Return the key ID
237
+ return id;
238
+ }
239
+
240
+ /**
241
+ * Computes the hash of the given data.
242
+ * @param {Uint8Array} data The data to hash.
243
+ * @returns {HashBytes} The hash of the data.
244
+ */
245
+ digest(data: Uint8Array): HashBytes {
246
+ return sha256(data);
247
+ }
248
+
249
+ /**
250
+ * Generates a new key pair and stores it in the key store.
251
+ * @returns {KeyIdentifier} The key identifier of the generated key.
252
+ */
253
+ generateKey(): KeyIdentifier {
254
+ // Generate a new Schnorr key pair
255
+ const kp = SchnorrKeyPair.generate();
256
+
257
+ // Store the key pair in the key store
258
+ const id = kp.publicKey.hex;
259
+ this.#store.set(id, kp.secretKey.bytes);
260
+
261
+ // Set the active key to the newly generated key
262
+ this.#activeKeyId = id;
263
+
264
+ // Return the key ID
265
+ return id;
266
+ }
267
+
268
+ /**
269
+ * Initializes a singleton KeyManager instance.
270
+ * @param {SchnorrKeyPair} keyPair The secret key to import.
271
+ * @param {string} id The ID to set as the active key.
272
+ * @returns {void}
273
+ */
274
+ static initialize(keyPair: SchnorrKeyPair, id: string): Kms {
275
+ // Check if the KeyManager instance is already initialized
276
+ if (Kms.#instance) {
277
+ console.warn('WARNING: Kms global instance is already initialized.');
278
+ return Kms.#instance;
279
+ }
280
+
281
+ // Check if the keypair is provided
282
+ if(!keyPair) {
283
+ // Log a warning message if not provided
284
+ console.warn('WARNING: secretKey not provided, generating new SchnorrKeyPair ...');
285
+ }
286
+
287
+ // Generate a new keypair if not provided
288
+ keyPair ??= SchnorrKeyPair.generate();
289
+
290
+ // Initialize the singleton key manager with the keypair
291
+ Kms.#instance = new Kms();
292
+
293
+ // Import the keypair into the key store
294
+ Kms.#instance.importKey(keyPair, { setActive: true, id });
295
+
296
+ // Set the active key URI7
297
+ Kms.#instance.#activeKeyId = id;
298
+
299
+ // Log the active key ID
300
+ console.info(`Kms initialized with Active Key ID: ${Kms.#instance.#activeKeyId}`);
301
+
302
+ // Return the singleton instance
303
+ return Kms.#instance;
304
+ }
305
+
306
+ /**
307
+ * Retrieves a keypair from the key store using the provided key ID.
308
+ * @public
309
+ * @param {KeyIdentifier} id The ID of the keypair to retrieve.
310
+ * @returns {Promise<SchnorrKeyPair | undefined>} The retrieved keypair, or undefined if not found.
311
+ */
312
+ static getKey(id?: KeyIdentifier): SchnorrKeyPair | undefined {
313
+ // Ensure the Kms instance is initialized
314
+ if(!Kms.#instance) {
315
+ throw new KeyManagerError('Kms instance not initialized', 'KMS_NOT_INITIALIZED');
316
+ }
317
+
318
+ // Use the active key ID if not provided
319
+ id ??= Kms.#instance.activeKeyId;
320
+
321
+ // Instantiate a new Kms with the default key store
322
+ return Kms.#instance.#getKeyOrThrow(id);
323
+ }
324
+ }
package/src/signer.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { AvailableNetworks } from '@did-btcr2/bitcoin';
2
+ import { KeyBytes, Bytes, SignatureBytes } from '@did-btcr2/common';
3
+ import { SchnorrKeyPair } from '@did-btcr2/keypair';
4
+
5
+ /**
6
+ * Class representing a signer for cryptographic operations.
7
+ * Remains for backwards compatibility. Plan to migrate to Kms.
8
+ * @class Signer
9
+ * @type {Signer}
10
+ */
11
+ export class Signer {
12
+ /**
13
+ * The key pair used for signing.
14
+ * @type {SchnorrKeyPair}
15
+ */
16
+ keyPair: SchnorrKeyPair;
17
+
18
+ /**
19
+ * The network associated with the signer.
20
+ * @type {keyof AvailableNetworks}
21
+ */
22
+ network: keyof AvailableNetworks;
23
+
24
+ /**
25
+ * Creates an instance of Signer.
26
+ * @param {{ keyPair: SchnorrKeyPair; network: keyof AvailableNetworks; }} params The parameters for the signer.
27
+ * @param {SchnorrKeyPair} params.keyPair The key pair used for signing.
28
+ * @param {keyof AvailableNetworks} params.network The network associated with the signer.
29
+ */
30
+ constructor(params: { keyPair: SchnorrKeyPair; network: keyof AvailableNetworks; }) {
31
+ this.keyPair = params.keyPair;
32
+ this.network = params.network;
33
+ }
34
+
35
+ /**
36
+ * Gets the public key bytes.
37
+ * @returns {KeyBytes} The public key bytes.
38
+ */
39
+ get publicKey(): KeyBytes {
40
+ return this.keyPair.publicKey.compressed;
41
+ }
42
+
43
+ /**
44
+ * Signs the given hash using ECDSA.
45
+ * @param {Bytes} hash The hash to sign.
46
+ * @returns {SignatureBytes} The signature of the hash.
47
+ */
48
+ signEcdsa(hash: Bytes): SignatureBytes {
49
+ return this.keyPair.secretKey.sign(hash, { scheme: 'ecdsa' });
50
+ };
51
+
52
+ /**
53
+ * Signs the given hash using Schnorr signature.
54
+ * @param {Bytes} hash The hash to sign.
55
+ * @returns {SignatureBytes} The Schnorr signature of the hash.
56
+ */
57
+ sign(hash: Bytes): SignatureBytes {
58
+ return this.keyPair.secretKey.sign(hash);
59
+ }
60
+ }
package/src/store.ts ADDED
@@ -0,0 +1,153 @@
1
+ /**
2
+ * Re-implmentation of a interface for a generic key-value store.
3
+ */
4
+ export interface KeyValueStore<K, V> {
5
+ /**
6
+ * Clears the store, removing all key-value pairs.
7
+ *
8
+ * @returns {void} when the store has been cleared.
9
+ */
10
+ clear(): void;
11
+
12
+ /**
13
+ * Closes the store, freeing up any resources used. After calling this method, no other operations can be performed on the store.
14
+ *
15
+ * @returns {void} when the store has been closed.
16
+ */
17
+ close(): void;
18
+
19
+ /**
20
+ * Deletes a key-value pair from the store.
21
+ *
22
+ * @param {K} key - The key of the value to delete.
23
+ * @returns {boolean | void} True if the element existed and has been removed, or false if the element does not exist.
24
+ */
25
+ delete(key: K): boolean | void;
26
+
27
+ /**
28
+ * Fetches a value from the store given its key.
29
+ *
30
+ * @param {K} key - The key of the value to retrieve.
31
+ * @returns {V | undefined} The value associated with the key, or `undefined` if no value exists for that key.
32
+ */
33
+ get(key: K): V | undefined;
34
+
35
+ /**
36
+ * Sets the value for a key in the store.
37
+ *
38
+ * @param {K} key - The key under which to store the value.
39
+ * @param {V} value - The value to be stored.
40
+ * @returns {void} once the value has been set.
41
+ */
42
+ set(key: K, value: V): void;
43
+
44
+ /**
45
+ * Fetches the keys and values as a nested array.
46
+ *
47
+ * @returns {Array<[K, V]>} An array of key-value pair arrays in the store.
48
+ */
49
+ entries(): Array<[K, V]>;
50
+ }
51
+
52
+ /**
53
+ * Re-implementation of a simple in-memory key-value store.
54
+ *
55
+ * The `MemoryStore` class is an implementation of
56
+ * `KeyValueStore` that holds data in memory.
57
+ *
58
+ * It provides a basic key-value store that works synchronously and keeps all
59
+ * data in memory. This can be used for testing, or for handling small amounts
60
+ * of data with simple key-value semantics.
61
+ *
62
+ * Example usage:
63
+ *
64
+ * ```ts
65
+ * const memoryStore = new MemoryStore<string, number>();
66
+ * await memoryStore.set("key1", 1);
67
+ * const value = await memoryStore.get("key1");
68
+ * console.log(value); // 1
69
+ * ```
70
+ *
71
+ * @public
72
+ */
73
+ export class MemoryStore<K, V> implements KeyValueStore<K, V> {
74
+ /**
75
+ * A private field that contains the Map used as the key-value store.
76
+ */
77
+ private store: Map<K, V> = new Map();
78
+
79
+ /**
80
+ * Clears all entries in the key-value store.
81
+ *
82
+ * @returns {void} returns once the operation is complete.
83
+ */
84
+ clear(): void {
85
+ this.store.clear();
86
+ }
87
+
88
+ /**
89
+ * This operation is no-op for `MemoryStore`.
90
+ */
91
+ close(): void {
92
+ /** no-op */
93
+ }
94
+
95
+ /**
96
+ * Deletes an entry from the key-value store by its key.
97
+ *
98
+ * @param {K} id - The key of the entry to delete.
99
+ * @returns {boolean} a boolean indicating whether the entry was successfully deleted.
100
+ */
101
+ delete(id: K): boolean {
102
+ return this.store.delete(id);
103
+ }
104
+
105
+ /**
106
+ * Retrieves the value of an entry by its key.
107
+ *
108
+ * @param {K} id - The key of the entry to retrieve.
109
+ * @returns {V | undefined} the value of the entry, or `undefined` if the entry does not exist.
110
+ */
111
+ get(id: K): V | undefined {
112
+ return this.store.get(id);
113
+ }
114
+
115
+ /**
116
+ * Checks for the presence of an entry by key.
117
+ *
118
+ * @param {K} id - The key to check for the existence of.
119
+ * @returns {boolean} a boolean indicating whether an element with the specified key exists or not.
120
+ */
121
+ has(id: K): boolean {
122
+ return this.store.has(id);
123
+ }
124
+
125
+ /**
126
+ * Retrieves all values in the key-value store.
127
+
128
+ * @returns {Array<V>} an array of all values in the store.
129
+ */
130
+ list(): Array<V> {
131
+ return Array.from(this.store.values());
132
+ }
133
+
134
+ /**
135
+ * Retrieves all entries in the key-value store.
136
+ *
137
+ * @returns {Array<[K, V]>} an array of key-value pairs in the store.
138
+ */
139
+ entries(): Array<[K, V]> {
140
+ return Array.from(this.store.entries());
141
+ }
142
+
143
+ /**
144
+ * Sets the value of an entry in the key-value store.
145
+ *
146
+ * @param {K} id - The key of the entry to set.
147
+ * @param {V} key - The new value for the entry.
148
+ * @returns {void} once operation is complete.
149
+ */
150
+ set(id: K, key: V): void {
151
+ this.store.set(id, key);
152
+ }
153
+ }