@did-btcr2/cli 0.10.3 → 0.12.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.
Files changed (94) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/cjs/index.js +1028 -114
  3. package/dist/esm/src/cli.js +31 -13
  4. package/dist/esm/src/cli.js.map +1 -1
  5. package/dist/esm/src/commands/completion.js +36 -0
  6. package/dist/esm/src/commands/completion.js.map +1 -0
  7. package/dist/esm/src/commands/config.js +69 -0
  8. package/dist/esm/src/commands/config.js.map +1 -0
  9. package/dist/esm/src/commands/create.js +109 -30
  10. package/dist/esm/src/commands/create.js.map +1 -1
  11. package/dist/esm/src/commands/deactivate.js +21 -8
  12. package/dist/esm/src/commands/deactivate.js.map +1 -1
  13. package/dist/esm/src/commands/index.js +4 -0
  14. package/dist/esm/src/commands/index.js.map +1 -1
  15. package/dist/esm/src/commands/key.js +175 -0
  16. package/dist/esm/src/commands/key.js.map +1 -0
  17. package/dist/esm/src/commands/profile.js +63 -0
  18. package/dist/esm/src/commands/profile.js.map +1 -0
  19. package/dist/esm/src/commands/update.js +19 -9
  20. package/dist/esm/src/commands/update.js.map +1 -1
  21. package/dist/esm/src/config.js +119 -12
  22. package/dist/esm/src/config.js.map +1 -1
  23. package/dist/esm/src/keystore/atomic.js +64 -0
  24. package/dist/esm/src/keystore/atomic.js.map +1 -0
  25. package/dist/esm/src/keystore/envelope.js +123 -0
  26. package/dist/esm/src/keystore/envelope.js.map +1 -0
  27. package/dist/esm/src/keystore/error.js +16 -0
  28. package/dist/esm/src/keystore/error.js.map +1 -0
  29. package/dist/esm/src/keystore/file-backed-key-manager.js +78 -0
  30. package/dist/esm/src/keystore/file-backed-key-manager.js.map +1 -0
  31. package/dist/esm/src/keystore/file-key-store.js +184 -0
  32. package/dist/esm/src/keystore/file-key-store.js.map +1 -0
  33. package/dist/esm/src/keystore/passphrase.js +87 -0
  34. package/dist/esm/src/keystore/passphrase.js.map +1 -0
  35. package/dist/esm/src/keystore/paths.js +20 -0
  36. package/dist/esm/src/keystore/paths.js.map +1 -0
  37. package/dist/esm/src/keystore/resolve-key-ref.js +47 -0
  38. package/dist/esm/src/keystore/resolve-key-ref.js.map +1 -0
  39. package/dist/types/src/cli.d.ts +6 -2
  40. package/dist/types/src/cli.d.ts.map +1 -1
  41. package/dist/types/src/commands/completion.d.ts +5 -0
  42. package/dist/types/src/commands/completion.d.ts.map +1 -0
  43. package/dist/types/src/commands/config.d.ts +5 -0
  44. package/dist/types/src/commands/config.d.ts.map +1 -0
  45. package/dist/types/src/commands/create.d.ts +19 -1
  46. package/dist/types/src/commands/create.d.ts.map +1 -1
  47. package/dist/types/src/commands/deactivate.d.ts.map +1 -1
  48. package/dist/types/src/commands/index.d.ts +4 -0
  49. package/dist/types/src/commands/index.d.ts.map +1 -1
  50. package/dist/types/src/commands/key.d.ts +10 -0
  51. package/dist/types/src/commands/key.d.ts.map +1 -0
  52. package/dist/types/src/commands/profile.d.ts +5 -0
  53. package/dist/types/src/commands/profile.d.ts.map +1 -0
  54. package/dist/types/src/commands/update.d.ts.map +1 -1
  55. package/dist/types/src/config.d.ts +57 -5
  56. package/dist/types/src/config.d.ts.map +1 -1
  57. package/dist/types/src/keystore/atomic.d.ts +19 -0
  58. package/dist/types/src/keystore/atomic.d.ts.map +1 -0
  59. package/dist/types/src/keystore/envelope.d.ts +64 -0
  60. package/dist/types/src/keystore/envelope.d.ts.map +1 -0
  61. package/dist/types/src/keystore/error.d.ts +14 -0
  62. package/dist/types/src/keystore/error.d.ts.map +1 -0
  63. package/dist/types/src/keystore/file-backed-key-manager.d.ts +41 -0
  64. package/dist/types/src/keystore/file-backed-key-manager.d.ts.map +1 -0
  65. package/dist/types/src/keystore/file-key-store.d.ts +52 -0
  66. package/dist/types/src/keystore/file-key-store.d.ts.map +1 -0
  67. package/dist/types/src/keystore/passphrase.d.ts +20 -0
  68. package/dist/types/src/keystore/passphrase.d.ts.map +1 -0
  69. package/dist/types/src/keystore/paths.d.ts +13 -0
  70. package/dist/types/src/keystore/paths.d.ts.map +1 -0
  71. package/dist/types/src/keystore/resolve-key-ref.d.ts +19 -0
  72. package/dist/types/src/keystore/resolve-key-ref.d.ts.map +1 -0
  73. package/dist/types/src/types.d.ts +93 -5
  74. package/dist/types/src/types.d.ts.map +1 -1
  75. package/package.json +9 -4
  76. package/src/cli.ts +37 -12
  77. package/src/commands/completion.ts +40 -0
  78. package/src/commands/config.ts +84 -0
  79. package/src/commands/create.ts +140 -52
  80. package/src/commands/deactivate.ts +25 -12
  81. package/src/commands/index.ts +4 -0
  82. package/src/commands/key.ts +193 -0
  83. package/src/commands/profile.ts +65 -0
  84. package/src/commands/update.ts +23 -13
  85. package/src/config.ts +165 -20
  86. package/src/keystore/atomic.ts +73 -0
  87. package/src/keystore/envelope.ts +172 -0
  88. package/src/keystore/error.ts +16 -0
  89. package/src/keystore/file-backed-key-manager.ts +99 -0
  90. package/src/keystore/file-key-store.ts +242 -0
  91. package/src/keystore/passphrase.ts +99 -0
  92. package/src/keystore/paths.ts +20 -0
  93. package/src/keystore/resolve-key-ref.ts +62 -0
  94. package/src/types.ts +31 -18
@@ -0,0 +1,123 @@
1
+ import { xchacha20poly1305 } from '@noble/ciphers/chacha.js';
2
+ import { argon2id } from '@noble/hashes/argon2.js';
3
+ import { randomBytes, utf8ToBytes } from '@noble/hashes/utils.js';
4
+ import { base64urlnopad } from '@scure/base';
5
+ import { KeyStoreError } from './error.js';
6
+ /** Current keystore secret-envelope format version. */
7
+ export const ENVELOPE_VERSION = 1;
8
+ /** Random salt length in bytes for argon2id. */
9
+ const SALT_BYTES = 16;
10
+ /** XChaCha20-Poly1305 extended nonce length in bytes (safe with random nonces). */
11
+ const NONCE_BYTES = 24;
12
+ /** Derived symmetric key length in bytes (the XChaCha20-Poly1305 key size). */
13
+ const KEY_BYTES = 32;
14
+ /**
15
+ * Production argon2id parameters: 3 passes over 64 MiB across 4 lanes, deriving
16
+ * a 32-byte key. Recorded in every envelope so the cost can be raised later
17
+ * without making previously sealed envelopes undecryptable.
18
+ */
19
+ export const DEFAULT_ARGON_PARAMS = { t: 3, m: 65536, p: 4, dkLen: KEY_BYTES };
20
+ /**
21
+ * Builds the header with a fixed key order so the additional-data bytes are
22
+ * byte-identical on the encrypt and decrypt paths.
23
+ */
24
+ function buildHeader(saltB64, params) {
25
+ return {
26
+ v: ENVELOPE_VERSION,
27
+ kdf: {
28
+ alg: 'argon2id',
29
+ salt: saltB64,
30
+ t: params.t,
31
+ m: params.m,
32
+ p: params.p,
33
+ dkLen: params.dkLen,
34
+ },
35
+ cipher: 'xchacha20poly1305',
36
+ };
37
+ }
38
+ /** Serializes the header into the AEAD additional-data byte string. */
39
+ function headerAad(header) {
40
+ return utf8ToBytes(JSON.stringify(header));
41
+ }
42
+ /**
43
+ * Stretches a passphrase into the symmetric key. The transient UTF-8 copy of
44
+ * the passphrase is zeroized here; the caller is responsible for zeroizing the
45
+ * returned key after use.
46
+ */
47
+ function deriveKey(passphrase, salt, params) {
48
+ const password = utf8ToBytes(passphrase);
49
+ try {
50
+ return argon2id(password, salt, { t: params.t, m: params.m, p: params.p, dkLen: params.dkLen });
51
+ }
52
+ finally {
53
+ password.fill(0);
54
+ }
55
+ }
56
+ /**
57
+ * Seals a secret under a passphrase into a {@link SecretEnvelope}. A fresh
58
+ * random salt and nonce are generated per call, so encrypting the same secret
59
+ * twice yields different envelopes.
60
+ *
61
+ * @param secret - The secret bytes to encrypt. Must be non-empty.
62
+ * @param passphrase - The passphrase the encryption key is derived from.
63
+ * @param params - argon2id cost parameters. Defaults to {@link DEFAULT_ARGON_PARAMS}.
64
+ * @returns The versioned, authenticated envelope.
65
+ * @throws {KeyStoreError} `ENVELOPE_ENCRYPT_ERROR` when `secret` is empty.
66
+ */
67
+ export function encryptSecret(secret, passphrase, params = DEFAULT_ARGON_PARAMS) {
68
+ if (secret.length === 0) {
69
+ throw new KeyStoreError('Cannot encrypt an empty secret.', 'ENVELOPE_ENCRYPT_ERROR');
70
+ }
71
+ const salt = randomBytes(SALT_BYTES);
72
+ const nonce = randomBytes(NONCE_BYTES);
73
+ const header = buildHeader(base64urlnopad.encode(salt), params);
74
+ const key = deriveKey(passphrase, salt, params);
75
+ try {
76
+ const ciphertext = xchacha20poly1305(key, nonce, headerAad(header)).encrypt(secret);
77
+ return {
78
+ ...header,
79
+ nonce: base64urlnopad.encode(nonce),
80
+ ciphertext: base64urlnopad.encode(ciphertext),
81
+ };
82
+ }
83
+ finally {
84
+ key.fill(0);
85
+ }
86
+ }
87
+ /**
88
+ * Opens a {@link SecretEnvelope} sealed by {@link encryptSecret} and returns the
89
+ * plaintext secret. A wrong passphrase, corrupted ciphertext, or a tampered
90
+ * header all fail authentication and raise `DECRYPT_ERROR`.
91
+ *
92
+ * @param env - The envelope to open.
93
+ * @param passphrase - The passphrase the envelope was sealed with.
94
+ * @returns The decrypted secret bytes.
95
+ * @throws {KeyStoreError} `ENVELOPE_VERSION_ERROR` for an unknown version or
96
+ * algorithm; `DECRYPT_ERROR` for failed authentication.
97
+ */
98
+ export function decryptSecret(env, passphrase) {
99
+ if (env.v !== ENVELOPE_VERSION) {
100
+ throw new KeyStoreError(`Unsupported keystore envelope version: ${String(env.v)}.`, 'ENVELOPE_VERSION_ERROR', { version: env.v });
101
+ }
102
+ if (env.kdf?.alg !== 'argon2id' || env.cipher !== 'xchacha20poly1305') {
103
+ throw new KeyStoreError('Unsupported keystore envelope algorithm.', 'ENVELOPE_VERSION_ERROR');
104
+ }
105
+ const params = { t: env.kdf.t, m: env.kdf.m, p: env.kdf.p, dkLen: env.kdf.dkLen };
106
+ const salt = base64urlnopad.decode(env.kdf.salt);
107
+ const nonce = base64urlnopad.decode(env.nonce);
108
+ const ciphertext = base64urlnopad.decode(env.ciphertext);
109
+ const header = buildHeader(env.kdf.salt, params);
110
+ const key = deriveKey(passphrase, salt, params);
111
+ try {
112
+ return xchacha20poly1305(key, nonce, headerAad(header)).decrypt(ciphertext);
113
+ }
114
+ catch (error) {
115
+ if (error instanceof KeyStoreError)
116
+ throw error;
117
+ throw new KeyStoreError('Keystore decryption failed: wrong passphrase or corrupted keystore.', 'DECRYPT_ERROR');
118
+ }
119
+ finally {
120
+ key.fill(0);
121
+ }
122
+ }
123
+ //# sourceMappingURL=envelope.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"envelope.js","sourceRoot":"","sources":["../../../../src/keystore/envelope.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,16 @@
1
+ import { DidMethodError } from '@did-btcr2/common';
2
+ /**
3
+ * Error raised by the CLI keystore layer: secret-envelope encryption and
4
+ * decryption, on-disk file permission enforcement, and passphrase acquisition.
5
+ *
6
+ * Unlike {@link CLIError} (whose `name` is fixed to `'CLIError'`), this follows
7
+ * the {@link DidMethodError} sibling convention where `name` mirrors the `type`
8
+ * code, so a thrown error's `name` reflects the specific failure category
9
+ * (for example `DECRYPT_ERROR` or `KEYSTORE_PERMISSION_ERROR`).
10
+ */
11
+ export class KeyStoreError extends DidMethodError {
12
+ constructor(message, type = 'KeyStoreError', data) {
13
+ super(message, { type, name: type, data });
14
+ }
15
+ }
16
+ //# sourceMappingURL=error.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"error.js","sourceRoot":"","sources":["../../../../src/keystore/error.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAC;AAEnD;;;;;;;;GAQG;AACH,MAAM,OAAO,aAAc,SAAQ,cAAc;IAC/C,YAAY,OAAe,EAAE,OAAe,eAAe,EAAE,IAA0B;QACrF,KAAK,CAAC,OAAO,EAAE,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;CACF"}
@@ -0,0 +1,78 @@
1
+ import { LocalKeyManager, } from '@did-btcr2/key-manager';
2
+ import { FileKeyStore } from './file-key-store.js';
3
+ /**
4
+ * A {@link KeyManager} backed by the encrypted on-disk {@link FileKeyStore}.
5
+ *
6
+ * It composes a {@link LocalKeyManager} over a {@link FileKeyStore} and adds the
7
+ * one thing the store interface cannot express: persisting the active-key
8
+ * pointer. `LocalKeyManager` tracks the active key only in process memory, so
9
+ * this wrapper mirrors every active-key change to the keystore file and
10
+ * re-applies the persisted pointer at construction. Read and signing
11
+ * operations delegate straight through.
12
+ *
13
+ * Injected as the api's KeyManager so every command reaches it uniformly via
14
+ * `api.kms`, and "the active key" survives across CLI invocations.
15
+ */
16
+ export class FileBackedKeyManager {
17
+ /** Capability probe: the local store supports exporting secret material. */
18
+ canExport = true;
19
+ #store;
20
+ #inner;
21
+ constructor(options) {
22
+ this.#store = new FileKeyStore(options);
23
+ this.#inner = new LocalKeyManager(this.#store);
24
+ // Apply the persisted active pointer only if the key still exists. A
25
+ // dangling pointer (from out-of-band file editing or a partial write) is
26
+ // ignored rather than thrown, so recovery commands stay usable; the next
27
+ // setActiveKey overwrites it. has() is a non-decrypting cache lookup.
28
+ const active = this.#store.getActive();
29
+ if (active && this.#store.has(active))
30
+ this.#inner.setActiveKey(active);
31
+ }
32
+ get activeKeyId() {
33
+ return this.#inner.activeKeyId;
34
+ }
35
+ setActiveKey(id) {
36
+ this.#inner.setActiveKey(id);
37
+ this.#store.setActive(id);
38
+ }
39
+ importKey(keyPair, options) {
40
+ const id = this.#inner.importKey(keyPair, options);
41
+ if (options?.setActive)
42
+ this.#store.setActive(id);
43
+ return id;
44
+ }
45
+ generateKey(options) {
46
+ const id = this.#inner.generateKey(options);
47
+ if (options?.setActive)
48
+ this.#store.setActive(id);
49
+ return id;
50
+ }
51
+ removeKey(id, options) {
52
+ // LocalKeyManager.removeKey calls FileKeyStore.delete, which already clears
53
+ // the persisted active pointer when the removed key was the active one.
54
+ this.#inner.removeKey(id, options);
55
+ }
56
+ listKeys() {
57
+ return this.#inner.listKeys();
58
+ }
59
+ getPublicKey(id) {
60
+ return this.#inner.getPublicKey(id);
61
+ }
62
+ getEntry(id) {
63
+ return this.#inner.getEntry(id);
64
+ }
65
+ sign(data, id, options) {
66
+ return this.#inner.sign(data, id, options);
67
+ }
68
+ verify(signature, data, id, options) {
69
+ return this.#inner.verify(signature, data, id, options);
70
+ }
71
+ digest(data) {
72
+ return this.#inner.digest(data);
73
+ }
74
+ exportKey(id) {
75
+ return this.#inner.exportKey(id);
76
+ }
77
+ }
78
+ //# sourceMappingURL=file-backed-key-manager.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-backed-key-manager.js","sourceRoot":"","sources":["../../../../src/keystore/file-backed-key-manager.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,184 @@
1
+ import { existsSync, readFileSync } from 'node:fs';
2
+ import { dirname } from 'node:path';
3
+ import { base64urlnopad } from '@scure/base';
4
+ import { assertSecurePerms, ensureDir, writeFileAtomic } from './atomic.js';
5
+ import { DEFAULT_ARGON_PARAMS, decryptSecret, encryptSecret } from './envelope.js';
6
+ import { KeyStoreError } from './error.js';
7
+ import { defaultKeystorePath } from './paths.js';
8
+ /** Current on-disk keystore file format version. */
9
+ export const KEYSTORE_VERSION = 1;
10
+ /**
11
+ * A Node-only, file-backed {@link KeyValueStore} that encrypts secret keys at
12
+ * rest. It satisfies the synchronous store contract by caching the parsed file
13
+ * in memory at construction and flushing the whole file atomically on every
14
+ * mutation.
15
+ *
16
+ * Secrets are materialized only through {@link FileKeyStore.get}. The
17
+ * {@link FileKeyStore.list} and {@link FileKeyStore.entries} projections omit
18
+ * secret keys and never decrypt, so enumerating the store never triggers a
19
+ * passphrase prompt.
20
+ */
21
+ export class FileKeyStore {
22
+ #path;
23
+ #getPassphrase;
24
+ #argonParams;
25
+ #cache = new Map();
26
+ #active;
27
+ constructor(options) {
28
+ this.#path = options.path ?? defaultKeystorePath();
29
+ this.#getPassphrase = options.getPassphrase;
30
+ this.#argonParams = options.argonParams ?? DEFAULT_ARGON_PARAMS;
31
+ ensureDir(dirname(this.#path), 0o700);
32
+ this.#load();
33
+ }
34
+ #load() {
35
+ if (!existsSync(this.#path))
36
+ return;
37
+ assertSecurePerms(this.#path);
38
+ let parsed;
39
+ try {
40
+ parsed = JSON.parse(readFileSync(this.#path, 'utf-8'));
41
+ }
42
+ catch {
43
+ throw new KeyStoreError(`Keystore at ${this.#path} is corrupt or unreadable.`, 'KEYSTORE_CORRUPT_ERROR', { path: this.#path });
44
+ }
45
+ if (parsed.v !== KEYSTORE_VERSION) {
46
+ throw new KeyStoreError(`Unsupported keystore version: ${String(parsed.v)}.`, 'KEYSTORE_VERSION_ERROR', { version: parsed.v });
47
+ }
48
+ this.#active = parsed.active;
49
+ for (const [id, stored] of Object.entries(parsed.keys ?? {})) {
50
+ let publicKey;
51
+ try {
52
+ if (typeof stored.publicKey !== 'string')
53
+ throw new Error('missing publicKey');
54
+ publicKey = base64urlnopad.decode(stored.publicKey);
55
+ }
56
+ catch {
57
+ throw new KeyStoreError(`Keystore entry ${id} has a malformed public key.`, 'KEYSTORE_CORRUPT_ERROR', { path: this.#path, keyId: id });
58
+ }
59
+ if (publicKey.length !== 33) {
60
+ throw new KeyStoreError(`Keystore entry ${id} has a ${publicKey.length}-byte public key; expected 33.`, 'KEYSTORE_CORRUPT_ERROR', { path: this.#path, keyId: id });
61
+ }
62
+ this.#cache.set(id, {
63
+ publicKey,
64
+ ...(stored.tags && { tags: stored.tags }),
65
+ ...(stored.secret && { secret: stored.secret }),
66
+ });
67
+ }
68
+ }
69
+ #flush() {
70
+ const keys = {};
71
+ for (const [id, entry] of this.#cache) {
72
+ keys[id] = {
73
+ publicKey: base64urlnopad.encode(entry.publicKey),
74
+ ...(entry.tags && { tags: entry.tags }),
75
+ ...(entry.secret && { secret: entry.secret }),
76
+ };
77
+ }
78
+ const file = {
79
+ v: KEYSTORE_VERSION,
80
+ ...(this.#active && { active: this.#active }),
81
+ keys,
82
+ };
83
+ writeFileAtomic(this.#path, `${JSON.stringify(file, null, 2)}\n`, 0o600);
84
+ }
85
+ get(id) {
86
+ const entry = this.#cache.get(id);
87
+ if (!entry)
88
+ return undefined;
89
+ const result = {
90
+ publicKey: entry.publicKey,
91
+ ...(entry.tags && { tags: entry.tags }),
92
+ };
93
+ if (entry.secret) {
94
+ // Materialize the secret lazily, only when it is actually accessed, so
95
+ // reads that need just public material (an active-key existence check,
96
+ // getPublicKey, getEntry) never trigger a passphrase prompt. The property
97
+ // is non-enumerable so spreading or serializing the entry cannot silently
98
+ // decrypt the secret.
99
+ const sealed = entry.secret;
100
+ Object.defineProperty(result, 'secretKey', {
101
+ configurable: true,
102
+ enumerable: false,
103
+ get: () => {
104
+ entry.decrypted ??= decryptSecret(sealed, this.#getPassphrase());
105
+ return entry.decrypted;
106
+ },
107
+ });
108
+ }
109
+ return result;
110
+ }
111
+ has(id) {
112
+ return this.#cache.has(id);
113
+ }
114
+ set(id, value) {
115
+ const secret = value.secretKey
116
+ ? encryptSecret(value.secretKey, this.#getPassphrase(), this.#argonParams)
117
+ : undefined;
118
+ this.#cache.set(id, {
119
+ publicKey: value.publicKey,
120
+ ...(value.tags && { tags: value.tags }),
121
+ ...(secret && { secret }),
122
+ ...(value.secretKey && { decrypted: value.secretKey }),
123
+ });
124
+ this.#flush();
125
+ }
126
+ delete(id) {
127
+ const existed = this.#cache.delete(id);
128
+ if (existed) {
129
+ if (this.#active === id)
130
+ this.#active = undefined;
131
+ this.#flush();
132
+ }
133
+ return existed;
134
+ }
135
+ clear() {
136
+ this.#cache.clear();
137
+ this.#active = undefined;
138
+ this.#flush();
139
+ }
140
+ /** All stored values with secret keys omitted. Never decrypts, never prompts. */
141
+ list() {
142
+ return this.entries().map(([, value]) => value);
143
+ }
144
+ /**
145
+ * All entries as id-value tuples with secret keys omitted. Never decrypts,
146
+ * never prompts: {@link FileKeyStore.get} is the only secret-materializing
147
+ * path, so callers that only need identifiers (such as `listKeys`) do not
148
+ * force a passphrase prompt. This deviates intentionally from the in-memory
149
+ * store, which returns stored values verbatim.
150
+ */
151
+ entries() {
152
+ const out = [];
153
+ for (const [id, entry] of this.#cache) {
154
+ out.push([id, {
155
+ publicKey: entry.publicKey,
156
+ ...(entry.tags && { tags: entry.tags }),
157
+ }]);
158
+ }
159
+ return out;
160
+ }
161
+ close() {
162
+ for (const entry of this.#cache.values()) {
163
+ entry.decrypted?.fill(0);
164
+ entry.decrypted = undefined;
165
+ }
166
+ this.#cache.clear();
167
+ }
168
+ /** The persisted active-key identifier, or undefined if none is set. */
169
+ getActive() {
170
+ return this.#active;
171
+ }
172
+ /**
173
+ * Persists the active-key pointer in the keystore file. Passing undefined
174
+ * clears it. Throws if the identifier is not a known key.
175
+ */
176
+ setActive(id) {
177
+ if (id !== undefined && !this.#cache.has(id)) {
178
+ throw new KeyStoreError(`Cannot set unknown key as active: ${id}.`, 'KEY_NOT_FOUND_ERROR', { keyId: id });
179
+ }
180
+ this.#active = id;
181
+ this.#flush();
182
+ }
183
+ }
184
+ //# sourceMappingURL=file-key-store.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"file-key-store.js","sourceRoot":"","sources":["../../../../src/keystore/file-key-store.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,87 @@
1
+ import { readFileSync, readSync } from 'node:fs';
2
+ import { KeyStoreError } from './error.js';
3
+ /** Environment variable that supplies the keystore passphrase for unattended use. */
4
+ export const ENV_KEYSTORE_PASSPHRASE = 'BTCR2_KEYSTORE_PASSPHRASE';
5
+ /**
6
+ * Acquires a passphrase without ever reading it from a command-line flag value
7
+ * (which would leak into process listings and shell history). Resolution order:
8
+ * the {@link ENV_KEYSTORE_PASSPHRASE} environment variable, a passphrase file,
9
+ * then a non-echoing terminal prompt. Throws if none is available and standard
10
+ * input is not a terminal.
11
+ */
12
+ export function acquirePassphrase(options = {}) {
13
+ // All sources are normalized identically (at most one trailing newline
14
+ // removed) so the KDF input is source-independent.
15
+ const fromEnv = process.env[ENV_KEYSTORE_PASSPHRASE];
16
+ if (fromEnv)
17
+ return assertNonEmpty(fromEnv.replace(/\r?\n$/, ''));
18
+ if (options.passphraseFile) {
19
+ return assertNonEmpty(readFileSync(options.passphraseFile, 'utf-8').replace(/\r?\n$/, ''));
20
+ }
21
+ if (!process.stdin.isTTY) {
22
+ throw new KeyStoreError(`No passphrase available. Set ${ENV_KEYSTORE_PASSPHRASE}, pass --passphrase-file, or run in a terminal.`, 'PASSPHRASE_REQUIRED_ERROR');
23
+ }
24
+ const passphrase = promptHidden(options.prompt ?? 'Keystore passphrase: ');
25
+ if (options.confirm) {
26
+ const again = promptHidden('Confirm passphrase: ');
27
+ if (passphrase !== again) {
28
+ throw new KeyStoreError('Passphrases did not match.', 'PASSPHRASE_MISMATCH_ERROR');
29
+ }
30
+ }
31
+ return assertNonEmpty(passphrase);
32
+ }
33
+ /** Rejects an empty or whitespace-only passphrase, which would seal the keystore with no protection. */
34
+ function assertNonEmpty(passphrase) {
35
+ if (passphrase.trim() === '') {
36
+ throw new KeyStoreError('A non-empty keystore passphrase is required.', 'PASSPHRASE_REQUIRED_ERROR');
37
+ }
38
+ return passphrase;
39
+ }
40
+ /**
41
+ * Reads a line from the terminal synchronously without echoing keystrokes.
42
+ * Bytes are accumulated and decoded as UTF-8 so multibyte passphrases survive.
43
+ * This path runs only when standard input is a terminal.
44
+ */
45
+ function promptHidden(label) {
46
+ process.stderr.write(label);
47
+ const stdin = process.stdin;
48
+ const wasRaw = stdin.isRaw ?? false;
49
+ stdin.setRawMode(true);
50
+ const byte = Buffer.alloc(1);
51
+ const bytes = [];
52
+ try {
53
+ for (;;) {
54
+ let read = 0;
55
+ try {
56
+ read = readSync(stdin.fd, byte, 0, 1, null);
57
+ }
58
+ catch (error) {
59
+ const code = error.code;
60
+ if (code === 'EAGAIN')
61
+ continue; // no byte ready yet on a non-blocking TTY
62
+ if (code === 'EOF')
63
+ break;
64
+ throw error;
65
+ }
66
+ if (read === 0)
67
+ break;
68
+ const ch = byte[0];
69
+ if (ch === 0x0a || ch === 0x0d)
70
+ break; // LF or CR ends the line
71
+ if (ch === 0x03) { // Ctrl-C aborts
72
+ throw new KeyStoreError('Passphrase entry aborted.', 'PASSPHRASE_REQUIRED_ERROR');
73
+ }
74
+ if (ch === 0x7f || ch === 0x08) { // DEL or backspace
75
+ bytes.pop();
76
+ continue;
77
+ }
78
+ bytes.push(ch);
79
+ }
80
+ }
81
+ finally {
82
+ stdin.setRawMode(wasRaw);
83
+ process.stderr.write('\n');
84
+ }
85
+ return Buffer.from(bytes).toString('utf-8');
86
+ }
87
+ //# sourceMappingURL=passphrase.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"passphrase.js","sourceRoot":"","sources":["../../../../src/keystore/passphrase.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,20 @@
1
+ import { homedir } from 'node:os';
2
+ import { join } from 'node:path';
3
+ /**
4
+ * Default keystore file path, following the XDG Base Directory Specification's
5
+ * data directory. Secret key material is data a user accumulates, so it lives
6
+ * under the data directory, kept separate from the configuration directory used
7
+ * for portable settings.
8
+ *
9
+ * Resolution order:
10
+ * 1. `$XDG_DATA_HOME/btcr2/keystore.json`
11
+ * 2. `%LOCALAPPDATA%/btcr2/keystore.json` (Windows)
12
+ * 3. `~/.local/share/btcr2/keystore.json` (fallback)
13
+ */
14
+ export function defaultKeystorePath() {
15
+ const base = process.env.XDG_DATA_HOME
16
+ ?? process.env.LOCALAPPDATA
17
+ ?? join(homedir(), '.local', 'share');
18
+ return join(base, 'btcr2', 'keystore.json');
19
+ }
20
+ //# sourceMappingURL=paths.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paths.js","sourceRoot":"","sources":["../../../../src/keystore/paths.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC;;;;;;;;;;GAUG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,aAAa;WACjC,OAAO,CAAC,GAAG,CAAC,YAAY;WACxB,IAAI,CAAC,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;IACxC,OAAO,IAAI,CAAC,IAAI,EAAE,OAAO,EAAE,eAAe,CAAC,CAAC;AAC9C,CAAC"}
@@ -0,0 +1,47 @@
1
+ import { CLIError } from '../error.js';
2
+ /** Extracts the 32-hex fingerprint from a `urn:kms:secp256k1:<hex>` identifier. */
3
+ function fingerprintOf(id) {
4
+ return /^urn:kms:secp256k1:([0-9a-f]{32})$/.exec(id)?.[1];
5
+ }
6
+ /**
7
+ * Resolves a user-supplied key reference to a key identifier. Resolution order:
8
+ * 1. No reference: the active key (errors if none is set).
9
+ * 2. Exact URN identifier match.
10
+ * 3. Unique fingerprint-prefix match (against the hex tail of the URN).
11
+ * 4. Unique `name` tag match.
12
+ *
13
+ * Reads only public material (listKeys + getEntry), so resolving a reference
14
+ * never decrypts a secret or prompts for a passphrase.
15
+ *
16
+ * @param kms The key manager to resolve against.
17
+ * @param ref The reference to resolve. When omitted, the active key is used.
18
+ * @returns The resolved key identifier.
19
+ * @throws {CLIError} If no key matches, the reference is ambiguous, or no
20
+ * reference is given and no active key is set.
21
+ */
22
+ export function resolveKeyRef(kms, ref) {
23
+ if (!ref) {
24
+ if (!kms.activeKeyId) {
25
+ throw new CLIError('No key specified and no active key is set. Use --key <ref> or set one with `btcr2 key use <ref>`.', 'INVALID_ARGUMENT_ERROR');
26
+ }
27
+ return kms.activeKeyId;
28
+ }
29
+ const ids = kms.listKeys();
30
+ if (ids.includes(ref))
31
+ return ref;
32
+ const prefix = ref.toLowerCase();
33
+ const byPrefix = ids.filter(id => fingerprintOf(id)?.startsWith(prefix));
34
+ if (byPrefix.length === 1)
35
+ return byPrefix[0];
36
+ if (byPrefix.length > 1) {
37
+ throw new CLIError(`Ambiguous key reference "${ref}" matches ${byPrefix.length} keys by fingerprint.`, 'KEY_REF_AMBIGUOUS_ERROR', { ref });
38
+ }
39
+ const byName = ids.filter(id => kms.getEntry(id).tags?.name === ref);
40
+ if (byName.length === 1)
41
+ return byName[0];
42
+ if (byName.length > 1) {
43
+ throw new CLIError(`Ambiguous key name "${ref}" matches ${byName.length} keys.`, 'KEY_REF_AMBIGUOUS_ERROR', { ref });
44
+ }
45
+ throw new CLIError(`No key matches reference "${ref}".`, 'KEY_NOT_FOUND_ERROR', { ref });
46
+ }
47
+ //# sourceMappingURL=resolve-key-ref.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"resolve-key-ref.js","sourceRoot":"","sources":["../../../../src/keystore/resolve-key-ref.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,CAAC;AAEvC,mFAAmF;AACnF,SAAS,aAAa,CAAC,EAAiB;IACtC,OAAO,oCAAoC,CAAC,IAAI,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;AAC5D,CAAC;AAED;;;;;;;;;;;;;;;GAeG;AACH,MAAM,UAAU,aAAa,CAAC,GAAe,EAAE,GAAY;IACzD,IAAI,CAAC,GAAG,EAAE,CAAC;QACT,IAAI,CAAC,GAAG,CAAC,WAAW,EAAE,CAAC;YACrB,MAAM,IAAI,QAAQ,CAChB,mGAAmG,EACnG,wBAAwB,CACzB,CAAC;QACJ,CAAC;QACD,OAAO,GAAG,CAAC,WAAW,CAAC;IACzB,CAAC;IAED,MAAM,GAAG,GAAG,GAAG,CAAC,QAAQ,EAAE,CAAC;IAE3B,IAAI,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;QAAE,OAAO,GAAG,CAAC;IAElC,MAAM,MAAM,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IACjC,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,aAAa,CAAC,EAAE,CAAC,EAAE,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC;IACzE,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,QAAQ,CAAC,CAAC,CAAC,CAAC;IAC9C,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,QAAQ,CAChB,4BAA4B,GAAG,aAAa,QAAQ,CAAC,MAAM,uBAAuB,EAClF,yBAAyB,EACzB,EAAE,GAAG,EAAE,CACR,CAAC;IACJ,CAAC;IAED,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,IAAI,KAAK,GAAG,CAAC,CAAC;IACrE,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,MAAM,CAAC,CAAC,CAAC,CAAC;IAC1C,IAAI,MAAM,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,QAAQ,CAChB,uBAAuB,GAAG,aAAa,MAAM,CAAC,MAAM,QAAQ,EAC5D,yBAAyB,EACzB,EAAE,GAAG,EAAE,CACR,CAAC;IACJ,CAAC;IAED,MAAM,IAAI,QAAQ,CAAC,6BAA6B,GAAG,IAAI,EAAE,qBAAqB,EAAE,EAAE,GAAG,EAAE,CAAC,CAAC;AAC3F,CAAC"}
@@ -13,9 +13,13 @@ export declare class DidBtcr2Cli {
13
13
  * {@link defaultApiFactory} which uses public endpoints (mempool.space)
14
14
  * for known networks and localhost Polar for regtest.
15
15
  *
16
- * @param factory - Optional API factory. Defaults to {@link defaultApiFactory}.
16
+ * @param factory - Optional API factory for keystore-free commands (create,
17
+ * resolve). Defaults to {@link defaultApiFactory}.
18
+ * @param keystoreFactory - Optional keystore-aware API factory for commands
19
+ * that need a signing identity (key, update, deactivate). Defaults to
20
+ * {@link keystoreApiFactory}.
17
21
  */
18
- constructor(factory?: ApiFactory);
22
+ constructor(factory?: ApiFactory, keystoreFactory?: ApiFactory);
19
23
  /**
20
24
  * Runs the CLI with the provided argv or process.argv.
21
25
  * @param {string[]} [argv] - Optional array of command-line arguments.
@@ -1 +1 @@
1
- {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/cli.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AAOpD,OAAO,EAAqB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAKjE;;GAEG;AACH,qBAAa,WAAW;IACtB,SAAgB,OAAO,EAAE,OAAO,CAAC;IAEjC;;;;;;;;;OASG;gBACS,OAAO,GAAE,UAA8B;IAuBnD;;;;OAIG;IACU,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CASjD"}
1
+ {"version":3,"file":"cli.d.ts","sourceRoot":"","sources":["../../../src/cli.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAkB,MAAM,WAAW,CAAC;AAWpD,OAAO,EAAyC,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAIrF;;GAEG;AACH,qBAAa,WAAW;IACtB,SAAgB,OAAO,EAAE,OAAO,CAAC;IAEjC;;;;;;;;;;;;;OAaG;gBAED,OAAO,GAAE,UAA8B,EACvC,eAAe,GAAE,UAA+B;IA+BlD;;;;OAIG;IACU,GAAG,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,IAAI,CAAC;CASjD"}
@@ -0,0 +1,5 @@
1
+ import type { Command } from 'commander';
2
+ import type { GlobalOptions } from '../types.js';
3
+ /** Registers the `completion` command, which prints a shell completion script to stdout. */
4
+ export declare function registerCompletionCommand(program: Command, _globals: () => GlobalOptions): void;
5
+ //# sourceMappingURL=completion.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"completion.d.ts","sourceRoot":"","sources":["../../../../src/commands/completion.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEzC,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,aAAa,CAAC;AAIjD,4FAA4F;AAC5F,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,MAAM,aAAa,GAAG,IAAI,CAO/F"}
@@ -0,0 +1,5 @@
1
+ import type { Command } from 'commander';
2
+ import type { GlobalOptions } from '../types.js';
3
+ /** Registers the `config` command group for reading and writing CLI configuration. */
4
+ export declare function registerConfigCommand(program: Command, globals: () => GlobalOptions): void;
5
+ //# sourceMappingURL=config.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../../../src/commands/config.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAezC,OAAO,KAAK,EAAiB,aAAa,EAAE,MAAM,aAAa,CAAC;AAGhE,sFAAsF;AACtF,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,GAAG,IAAI,CAuD1F"}