@did-btcr2/cli 0.15.0 → 0.17.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 +47 -6
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +836 -122
- package/dist/esm/src/cli.js +7 -4
- package/dist/esm/src/cli.js.map +1 -1
- package/dist/esm/src/commands/config.js +11 -15
- package/dist/esm/src/commands/config.js.map +1 -1
- package/dist/esm/src/commands/create.js +4 -1
- package/dist/esm/src/commands/create.js.map +1 -1
- package/dist/esm/src/commands/deactivate.js +3 -1
- package/dist/esm/src/commands/deactivate.js.map +1 -1
- package/dist/esm/src/commands/index.js +2 -0
- package/dist/esm/src/commands/index.js.map +1 -1
- package/dist/esm/src/commands/init.js +69 -0
- package/dist/esm/src/commands/init.js.map +1 -0
- package/dist/esm/src/commands/keystore.js +180 -0
- package/dist/esm/src/commands/keystore.js.map +1 -0
- package/dist/esm/src/commands/profile.js +1 -1
- package/dist/esm/src/commands/profile.js.map +1 -1
- package/dist/esm/src/commands/update.js +3 -1
- package/dist/esm/src/commands/update.js.map +1 -1
- package/dist/esm/src/config.js +109 -31
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/keystore/file-key-store.js +388 -32
- package/dist/esm/src/keystore/file-key-store.js.map +1 -1
- package/dist/esm/src/keystore/passphrase.js +53 -10
- package/dist/esm/src/keystore/passphrase.js.map +1 -1
- package/dist/esm/src/keystore/paths.js +6 -18
- package/dist/esm/src/keystore/paths.js.map +1 -1
- package/dist/esm/src/keystore/session.js +250 -0
- package/dist/esm/src/keystore/session.js.map +1 -0
- package/dist/esm/src/paths.js +71 -0
- package/dist/esm/src/paths.js.map +1 -0
- package/dist/esm/src/types.js.map +1 -1
- package/dist/types/src/cli.d.ts.map +1 -1
- package/dist/types/src/commands/config.d.ts.map +1 -1
- package/dist/types/src/commands/create.d.ts.map +1 -1
- package/dist/types/src/commands/deactivate.d.ts.map +1 -1
- package/dist/types/src/commands/index.d.ts +2 -0
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/commands/init.d.ts +13 -0
- package/dist/types/src/commands/init.d.ts.map +1 -0
- package/dist/types/src/commands/keystore.d.ts +11 -0
- package/dist/types/src/commands/keystore.d.ts.map +1 -0
- package/dist/types/src/commands/update.d.ts.map +1 -1
- package/dist/types/src/config.d.ts +38 -14
- package/dist/types/src/config.d.ts.map +1 -1
- package/dist/types/src/keystore/file-key-store.d.ts +102 -10
- package/dist/types/src/keystore/file-key-store.d.ts.map +1 -1
- package/dist/types/src/keystore/passphrase.d.ts +26 -1
- package/dist/types/src/keystore/passphrase.d.ts.map +1 -1
- package/dist/types/src/keystore/paths.d.ts +6 -10
- package/dist/types/src/keystore/paths.d.ts.map +1 -1
- package/dist/types/src/keystore/session.d.ts +105 -0
- package/dist/types/src/keystore/session.d.ts.map +1 -0
- package/dist/types/src/paths.d.ts +64 -0
- package/dist/types/src/paths.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +53 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +8 -3
- package/src/commands/config.ts +12 -14
- package/src/commands/create.ts +4 -1
- package/src/commands/deactivate.ts +3 -1
- package/src/commands/index.ts +2 -0
- package/src/commands/init.ts +80 -0
- package/src/commands/keystore.ts +232 -0
- package/src/commands/profile.ts +1 -1
- package/src/commands/update.ts +3 -1
- package/src/config.ts +118 -35
- package/src/keystore/file-key-store.ts +498 -43
- package/src/keystore/passphrase.ts +71 -8
- package/src/keystore/paths.ts +6 -19
- package/src/keystore/session.ts +303 -0
- package/src/paths.ts +92 -0
- package/src/types.ts +16 -1
package/dist/cjs/index.js
CHANGED
|
@@ -28,6 +28,7 @@ __export(index_exports, {
|
|
|
28
28
|
REDACTED: () => REDACTED,
|
|
29
29
|
SUPPORTED_NETWORKS: () => SUPPORTED_NETWORKS,
|
|
30
30
|
VERSION: () => VERSION,
|
|
31
|
+
assertKeystoreAllowedForNetwork: () => assertKeystoreAllowedForNetwork,
|
|
31
32
|
blankToUndef: () => blankToUndef,
|
|
32
33
|
defaultApiFactory: () => defaultApiFactory,
|
|
33
34
|
defaultConfigPath: () => defaultConfigPath,
|
|
@@ -47,7 +48,9 @@ __export(index_exports, {
|
|
|
47
48
|
registerConfigCommand: () => registerConfigCommand,
|
|
48
49
|
registerCreateCommand: () => registerCreateCommand,
|
|
49
50
|
registerDeactivateCommand: () => registerDeactivateCommand,
|
|
51
|
+
registerInitCommand: () => registerInitCommand,
|
|
50
52
|
registerKeyCommand: () => registerKeyCommand,
|
|
53
|
+
registerKeystoreCommand: () => registerKeystoreCommand,
|
|
51
54
|
registerProfileCommand: () => registerProfileCommand,
|
|
52
55
|
registerResolveCommand: () => registerResolveCommand,
|
|
53
56
|
registerUpdateCommand: () => registerUpdateCommand,
|
|
@@ -57,6 +60,7 @@ __export(index_exports, {
|
|
|
57
60
|
resolveDefaultNetwork: () => resolveDefaultNetwork,
|
|
58
61
|
resolveEffectiveConfig: () => resolveEffectiveConfig,
|
|
59
62
|
resolveKeystorePath: () => resolveKeystorePath,
|
|
63
|
+
resolveKeystoreProtection: () => resolveKeystoreProtection,
|
|
60
64
|
resolveOutputFormat: () => resolveOutputFormat,
|
|
61
65
|
resolveSecretRef: () => resolveSecretRef,
|
|
62
66
|
resolveSigningKeyRef: () => resolveSigningKeyRef,
|
|
@@ -64,7 +68,8 @@ __export(index_exports, {
|
|
|
64
68
|
scrubUrlUserinfo: () => scrubUrlUserinfo,
|
|
65
69
|
setConfigPath: () => setConfigPath,
|
|
66
70
|
unsetConfigPath: () => unsetConfigPath,
|
|
67
|
-
writeConfigFile: () => writeConfigFile
|
|
71
|
+
writeConfigFile: () => writeConfigFile,
|
|
72
|
+
writeDefaultConfigFile: () => writeDefaultConfigFile
|
|
68
73
|
});
|
|
69
74
|
module.exports = __toCommonJS(index_exports);
|
|
70
75
|
|
|
@@ -76,15 +81,14 @@ var importMetaUrl = /* @__PURE__ */ getImportMetaUrl();
|
|
|
76
81
|
var import_common3 = require("@did-btcr2/common");
|
|
77
82
|
var import_commander = require("commander");
|
|
78
83
|
|
|
79
|
-
// src/commands/
|
|
80
|
-
var
|
|
84
|
+
// src/commands/init.ts
|
|
85
|
+
var import_node_fs7 = require("fs");
|
|
81
86
|
|
|
82
87
|
// src/config.ts
|
|
83
88
|
var import_api = require("@did-btcr2/api");
|
|
84
89
|
var import_method = require("@did-btcr2/method");
|
|
85
|
-
var
|
|
86
|
-
var
|
|
87
|
-
var import_node_path4 = require("path");
|
|
90
|
+
var import_node_fs6 = require("fs");
|
|
91
|
+
var import_node_path5 = require("path");
|
|
88
92
|
|
|
89
93
|
// src/error.ts
|
|
90
94
|
var import_common = require("@did-btcr2/common");
|
|
@@ -163,6 +167,8 @@ var import_key_manager = require("@did-btcr2/key-manager");
|
|
|
163
167
|
// src/keystore/file-key-store.ts
|
|
164
168
|
var import_node_fs3 = require("fs");
|
|
165
169
|
var import_node_path3 = require("path");
|
|
170
|
+
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
171
|
+
var import_utils2 = require("@noble/hashes/utils.js");
|
|
166
172
|
var import_base2 = require("@scure/base");
|
|
167
173
|
|
|
168
174
|
// src/keystore/envelope.ts
|
|
@@ -333,7 +339,7 @@ function withFileLock(lockPath, fn, options = {}) {
|
|
|
333
339
|
}
|
|
334
340
|
}
|
|
335
341
|
|
|
336
|
-
// src/
|
|
342
|
+
// src/paths.ts
|
|
337
343
|
var import_node_os = require("os");
|
|
338
344
|
var import_node_path2 = require("path");
|
|
339
345
|
|
|
@@ -351,14 +357,43 @@ function blankToUndef(value) {
|
|
|
351
357
|
return value.trim() === "" ? void 0 : value;
|
|
352
358
|
}
|
|
353
359
|
|
|
354
|
-
// src/
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
360
|
+
// src/paths.ts
|
|
361
|
+
var ENV_HOME = "BTCR2_HOME";
|
|
362
|
+
var CONFIG_FILENAME = "config.json";
|
|
363
|
+
var KEYSTORE_FILENAME = "keystore.json";
|
|
364
|
+
var SESSION_FILENAME = "session.json";
|
|
365
|
+
function resolveHome(overrides) {
|
|
366
|
+
return blankToUndef(overrides?.home) ?? blankToUndef(process.env[ENV_HOME]) ?? platformDefaultHome();
|
|
367
|
+
}
|
|
368
|
+
function platformDefaultHome() {
|
|
369
|
+
if (process.platform === "win32") {
|
|
370
|
+
const base = blankToUndef(process.env.LOCALAPPDATA) ?? blankToUndef(process.env.APPDATA) ?? (0, import_node_os.homedir)();
|
|
371
|
+
return (0, import_node_path2.join)(base, "btcr2");
|
|
372
|
+
}
|
|
373
|
+
return (0, import_node_path2.join)((0, import_node_os.homedir)(), ".btcr2");
|
|
374
|
+
}
|
|
375
|
+
function defaultConfigPath(overrides) {
|
|
376
|
+
return (0, import_node_path2.join)(resolveHome(overrides), CONFIG_FILENAME);
|
|
377
|
+
}
|
|
378
|
+
function defaultKeystorePath(overrides) {
|
|
379
|
+
return (0, import_node_path2.join)(resolveHome(overrides), KEYSTORE_FILENAME);
|
|
380
|
+
}
|
|
381
|
+
function defaultSessionPath(overrides) {
|
|
382
|
+
return (0, import_node_path2.join)(resolveHome(overrides), SESSION_FILENAME);
|
|
358
383
|
}
|
|
359
384
|
|
|
360
385
|
// src/keystore/file-key-store.ts
|
|
361
386
|
var KEYSTORE_VERSION = 1;
|
|
387
|
+
var VERIFIER_PLAINTEXT = (0, import_utils2.utf8ToBytes)("did-btcr2-keystore-verifier-v1");
|
|
388
|
+
function bytesEqual(a, b) {
|
|
389
|
+
if (a.length !== b.length) return false;
|
|
390
|
+
let diff = 0;
|
|
391
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
392
|
+
return diff === 0;
|
|
393
|
+
}
|
|
394
|
+
function sameEnvelope(a, b) {
|
|
395
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
396
|
+
}
|
|
362
397
|
var FileKeyStore = class {
|
|
363
398
|
#path;
|
|
364
399
|
#lockPath;
|
|
@@ -367,12 +402,15 @@ var FileKeyStore = class {
|
|
|
367
402
|
#argonParams;
|
|
368
403
|
#cache = /* @__PURE__ */ new Map();
|
|
369
404
|
#active;
|
|
405
|
+
#protection;
|
|
406
|
+
#verifier;
|
|
370
407
|
constructor(options) {
|
|
371
408
|
this.#path = options.path ?? defaultKeystorePath();
|
|
372
409
|
this.#lockPath = `${this.#path}.lock`;
|
|
373
410
|
this.#lockOptions = options.lock ?? {};
|
|
374
411
|
this.#getPassphrase = options.getPassphrase;
|
|
375
412
|
this.#argonParams = options.argonParams ?? DEFAULT_ARGON_PARAMS;
|
|
413
|
+
this.#protection = options.protection ?? "passphrase";
|
|
376
414
|
ensureDir((0, import_node_path3.dirname)(this.#path), 448);
|
|
377
415
|
this.#loadFromDisk();
|
|
378
416
|
}
|
|
@@ -396,7 +434,26 @@ var FileKeyStore = class {
|
|
|
396
434
|
{ version: parsed.v }
|
|
397
435
|
);
|
|
398
436
|
}
|
|
437
|
+
if (parsed.protection !== "none" && parsed.protection !== "passphrase") {
|
|
438
|
+
throw new KeyStoreError(
|
|
439
|
+
`Keystore at ${this.#path} has no recognized protection header; it was not written by this CLI.`,
|
|
440
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
441
|
+
{ path: this.#path }
|
|
442
|
+
);
|
|
443
|
+
}
|
|
444
|
+
this.#protection = parsed.protection;
|
|
445
|
+
this.#verifier = parsed.verifier;
|
|
399
446
|
this.#active = parsed.active;
|
|
447
|
+
const sealedPresent = Object.values(parsed.keys ?? {}).some(
|
|
448
|
+
(k) => k && typeof k === "object" && k.secret !== void 0
|
|
449
|
+
);
|
|
450
|
+
if (this.#protection === "passphrase" && sealedPresent && this.#verifier === void 0) {
|
|
451
|
+
throw new KeyStoreError(
|
|
452
|
+
`Keystore at ${this.#path} holds sealed keys but no passphrase verifier; it is corrupt or was tampered with.`,
|
|
453
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
454
|
+
{ path: this.#path }
|
|
455
|
+
);
|
|
456
|
+
}
|
|
400
457
|
for (const [id, stored] of Object.entries(parsed.keys ?? {})) {
|
|
401
458
|
let publicKey;
|
|
402
459
|
try {
|
|
@@ -416,24 +473,67 @@ var FileKeyStore = class {
|
|
|
416
473
|
{ path: this.#path, keyId: id }
|
|
417
474
|
);
|
|
418
475
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
476
|
+
const entry = { publicKey, ...stored.tags && { tags: stored.tags } };
|
|
477
|
+
if (stored.plainSecret !== void 0) {
|
|
478
|
+
if (this.#protection !== "none") {
|
|
479
|
+
throw new KeyStoreError(
|
|
480
|
+
`Keystore entry ${id} holds a plaintext secret in an encrypted keystore at ${this.#path}.`,
|
|
481
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
482
|
+
{ path: this.#path, keyId: id }
|
|
483
|
+
);
|
|
484
|
+
}
|
|
485
|
+
entry.plaintext = true;
|
|
486
|
+
entry.decrypted = this.#decodePlainSecret(stored.plainSecret, id);
|
|
487
|
+
} else if (stored.secret) {
|
|
488
|
+
if (this.#protection !== "passphrase") {
|
|
489
|
+
throw new KeyStoreError(
|
|
490
|
+
`Keystore entry ${id} holds a sealed secret in a dev keystore at ${this.#path}.`,
|
|
491
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
492
|
+
{ path: this.#path, keyId: id }
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
entry.secret = stored.secret;
|
|
496
|
+
}
|
|
497
|
+
this.#cache.set(id, entry);
|
|
424
498
|
}
|
|
425
499
|
}
|
|
500
|
+
/** Decodes and length-checks a dev-keystore plaintext secret. */
|
|
501
|
+
#decodePlainSecret(encoded, id) {
|
|
502
|
+
let secret;
|
|
503
|
+
try {
|
|
504
|
+
secret = import_base2.base64urlnopad.decode(encoded);
|
|
505
|
+
} catch {
|
|
506
|
+
throw new KeyStoreError(
|
|
507
|
+
`Keystore entry ${id} has a malformed plaintext secret.`,
|
|
508
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
509
|
+
{ path: this.#path, keyId: id }
|
|
510
|
+
);
|
|
511
|
+
}
|
|
512
|
+
if (secret.length !== 32) {
|
|
513
|
+
throw new KeyStoreError(
|
|
514
|
+
`Keystore entry ${id} has a ${secret.length}-byte secret; expected 32.`,
|
|
515
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
516
|
+
{ path: this.#path, keyId: id }
|
|
517
|
+
);
|
|
518
|
+
}
|
|
519
|
+
return secret;
|
|
520
|
+
}
|
|
426
521
|
#flush() {
|
|
427
522
|
const keys = {};
|
|
428
523
|
for (const [id, entry] of this.#cache) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
524
|
+
const stored = { publicKey: import_base2.base64urlnopad.encode(entry.publicKey) };
|
|
525
|
+
if (entry.tags) stored.tags = entry.tags;
|
|
526
|
+
if (entry.plaintext && entry.decrypted) {
|
|
527
|
+
stored.plainSecret = import_base2.base64urlnopad.encode(entry.decrypted);
|
|
528
|
+
} else if (entry.secret) {
|
|
529
|
+
stored.secret = entry.secret;
|
|
530
|
+
}
|
|
531
|
+
keys[id] = stored;
|
|
434
532
|
}
|
|
435
533
|
const file = {
|
|
436
534
|
v: KEYSTORE_VERSION,
|
|
535
|
+
protection: this.#protection,
|
|
536
|
+
...this.#verifier && { verifier: this.#verifier },
|
|
437
537
|
...this.#active && { active: this.#active },
|
|
438
538
|
keys
|
|
439
539
|
};
|
|
@@ -445,7 +545,8 @@ var FileKeyStore = class {
|
|
|
445
545
|
* mutation applies on top of whatever other processes have written. Secrets
|
|
446
546
|
* already decrypted this session are carried over for entries whose sealed
|
|
447
547
|
* envelope is byte-identical on disk, so a mid-session write does not force a
|
|
448
|
-
* re-prompt for keys it did not touch.
|
|
548
|
+
* re-prompt for keys it did not touch. (Dev-keystore plaintext secrets are
|
|
549
|
+
* reloaded from disk, so they need no carry.)
|
|
449
550
|
*/
|
|
450
551
|
#reload() {
|
|
451
552
|
const carried = /* @__PURE__ */ new Map();
|
|
@@ -476,6 +577,53 @@ var FileKeyStore = class {
|
|
|
476
577
|
this.#flush();
|
|
477
578
|
}, this.#lockOptions);
|
|
478
579
|
}
|
|
580
|
+
/**
|
|
581
|
+
* Verifies a candidate passphrase against the keystore verifier, throwing when
|
|
582
|
+
* it does not open the sentinel. A no-op only on a fresh keystore whose
|
|
583
|
+
* passphrase has not been established yet (no verifier to check against).
|
|
584
|
+
*/
|
|
585
|
+
#assertPassphrase(passphrase) {
|
|
586
|
+
if (!this.#verifier) return;
|
|
587
|
+
let plain;
|
|
588
|
+
try {
|
|
589
|
+
plain = decryptSecret(this.#verifier, passphrase);
|
|
590
|
+
} catch {
|
|
591
|
+
throw new KeyStoreError(
|
|
592
|
+
`Incorrect passphrase for the keystore at ${this.#path}.`,
|
|
593
|
+
"DECRYPT_ERROR",
|
|
594
|
+
{ path: this.#path }
|
|
595
|
+
);
|
|
596
|
+
}
|
|
597
|
+
if (!bytesEqual(plain, VERIFIER_PLAINTEXT)) {
|
|
598
|
+
throw new KeyStoreError(
|
|
599
|
+
`Keystore verifier at ${this.#path} did not match; the file may be corrupt.`,
|
|
600
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
601
|
+
{ path: this.#path }
|
|
602
|
+
);
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
/** Passphrase for opening an existing secret: verified when a verifier exists. */
|
|
606
|
+
#openPassphrase() {
|
|
607
|
+
const passphrase = this.#getPassphrase();
|
|
608
|
+
this.#assertPassphrase(passphrase);
|
|
609
|
+
return passphrase;
|
|
610
|
+
}
|
|
611
|
+
/**
|
|
612
|
+
* Passphrase for sealing a secret. When the keystore's passphrase is already
|
|
613
|
+
* established (a verifier exists), prompt once and verify it. Otherwise this is
|
|
614
|
+
* establishment on a fresh keystore: prompt with confirm and mint a verifier for
|
|
615
|
+
* the caller to persist alongside the first sealed key.
|
|
616
|
+
*/
|
|
617
|
+
#sealPassphrase() {
|
|
618
|
+
if (this.#verifier) {
|
|
619
|
+
const passphrase2 = this.#getPassphrase();
|
|
620
|
+
this.#assertPassphrase(passphrase2);
|
|
621
|
+
return { passphrase: passphrase2 };
|
|
622
|
+
}
|
|
623
|
+
const passphrase = this.#getPassphrase({ confirm: true });
|
|
624
|
+
const newVerifier = encryptSecret(VERIFIER_PLAINTEXT, passphrase, this.#argonParams);
|
|
625
|
+
return { passphrase, newVerifier };
|
|
626
|
+
}
|
|
479
627
|
get(id) {
|
|
480
628
|
const entry = this.#cache.get(id);
|
|
481
629
|
if (!entry) return void 0;
|
|
@@ -483,13 +631,20 @@ var FileKeyStore = class {
|
|
|
483
631
|
publicKey: entry.publicKey,
|
|
484
632
|
...entry.tags && { tags: entry.tags }
|
|
485
633
|
};
|
|
486
|
-
if (entry.
|
|
634
|
+
if (entry.plaintext && entry.decrypted) {
|
|
635
|
+
const secret = entry.decrypted;
|
|
636
|
+
Object.defineProperty(result, "secretKey", {
|
|
637
|
+
configurable: true,
|
|
638
|
+
enumerable: false,
|
|
639
|
+
get: () => secret
|
|
640
|
+
});
|
|
641
|
+
} else if (entry.secret) {
|
|
487
642
|
const sealed = entry.secret;
|
|
488
643
|
Object.defineProperty(result, "secretKey", {
|
|
489
644
|
configurable: true,
|
|
490
645
|
enumerable: false,
|
|
491
646
|
get: () => {
|
|
492
|
-
entry.decrypted ??= decryptSecret(sealed, this.#
|
|
647
|
+
entry.decrypted ??= decryptSecret(sealed, this.#openPassphrase());
|
|
493
648
|
return entry.decrypted;
|
|
494
649
|
}
|
|
495
650
|
});
|
|
@@ -500,12 +655,45 @@ var FileKeyStore = class {
|
|
|
500
655
|
return this.#cache.has(id);
|
|
501
656
|
}
|
|
502
657
|
set(id, value) {
|
|
503
|
-
|
|
658
|
+
if (this.#protection === "none") {
|
|
659
|
+
this.#mutate(() => {
|
|
660
|
+
this.#cache.set(id, {
|
|
661
|
+
publicKey: value.publicKey,
|
|
662
|
+
...value.tags && { tags: value.tags },
|
|
663
|
+
...value.secretKey && { plaintext: true, decrypted: value.secretKey }
|
|
664
|
+
});
|
|
665
|
+
});
|
|
666
|
+
return;
|
|
667
|
+
}
|
|
668
|
+
const verifierAtSeal = this.#verifier;
|
|
669
|
+
let sealed;
|
|
670
|
+
let newVerifier;
|
|
671
|
+
let passphrase;
|
|
672
|
+
if (value.secretKey) {
|
|
673
|
+
const resolved = this.#sealPassphrase();
|
|
674
|
+
passphrase = resolved.passphrase;
|
|
675
|
+
newVerifier = resolved.newVerifier;
|
|
676
|
+
sealed = encryptSecret(value.secretKey, passphrase, this.#argonParams);
|
|
677
|
+
}
|
|
504
678
|
this.#mutate(() => {
|
|
679
|
+
if (value.secretKey) {
|
|
680
|
+
if (this.#verifier === void 0 && newVerifier !== void 0) {
|
|
681
|
+
this.#verifier = newVerifier;
|
|
682
|
+
} else if (this.#verifier !== void 0 && passphrase !== void 0) {
|
|
683
|
+
if (verifierAtSeal !== void 0 && !sameEnvelope(this.#verifier, verifierAtSeal)) {
|
|
684
|
+
throw new KeyStoreError(
|
|
685
|
+
`The keystore passphrase at ${this.#path} changed concurrently; re-run the command.`,
|
|
686
|
+
"KEYSTORE_CONCURRENT_CHANGE_ERROR",
|
|
687
|
+
{ path: this.#path }
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
this.#assertPassphrase(passphrase);
|
|
691
|
+
}
|
|
692
|
+
}
|
|
505
693
|
this.#cache.set(id, {
|
|
506
694
|
publicKey: value.publicKey,
|
|
507
695
|
...value.tags && { tags: value.tags },
|
|
508
|
-
...
|
|
696
|
+
...sealed && { secret: sealed },
|
|
509
697
|
...value.secretKey && { decrypted: value.secretKey }
|
|
510
698
|
});
|
|
511
699
|
});
|
|
@@ -568,7 +756,138 @@ var FileKeyStore = class {
|
|
|
568
756
|
this.#active = id;
|
|
569
757
|
});
|
|
570
758
|
}
|
|
759
|
+
/** The keystore's protection mode. */
|
|
760
|
+
get protection() {
|
|
761
|
+
return this.#protection;
|
|
762
|
+
}
|
|
763
|
+
/**
|
|
764
|
+
* Re-seals every sealed secret and the verifier under a new passphrase (ADR
|
|
765
|
+
* 080), returning the number of secrets re-sealed. Verifies the current
|
|
766
|
+
* passphrase against the verifier first. Refused on a dev keystore, which has
|
|
767
|
+
* no passphrase.
|
|
768
|
+
*/
|
|
769
|
+
changePassphrase(oldPassphrase, newPassphrase) {
|
|
770
|
+
if (this.#protection === "none") {
|
|
771
|
+
throw new KeyStoreError(
|
|
772
|
+
`The keystore at ${this.#path} is an unencrypted dev keystore; it has no passphrase to change.`,
|
|
773
|
+
"KEYSTORE_PROTECTION_ERROR",
|
|
774
|
+
{ path: this.#path }
|
|
775
|
+
);
|
|
776
|
+
}
|
|
777
|
+
if (this.#verifier) this.#assertPassphrase(oldPassphrase);
|
|
778
|
+
const resealed = /* @__PURE__ */ new Map();
|
|
779
|
+
for (const [id, entry] of this.#cache) {
|
|
780
|
+
if (!entry.secret) continue;
|
|
781
|
+
let plain;
|
|
782
|
+
try {
|
|
783
|
+
plain = decryptSecret(entry.secret, oldPassphrase);
|
|
784
|
+
} catch {
|
|
785
|
+
throw new KeyStoreError(
|
|
786
|
+
`Incorrect current passphrase for the keystore at ${this.#path}.`,
|
|
787
|
+
"DECRYPT_ERROR",
|
|
788
|
+
{ path: this.#path }
|
|
789
|
+
);
|
|
790
|
+
}
|
|
791
|
+
resealed.set(id, { from: entry.secret, to: encryptSecret(plain, newPassphrase, this.#argonParams), plain });
|
|
792
|
+
}
|
|
793
|
+
const newVerifier = encryptSecret(VERIFIER_PLAINTEXT, newPassphrase, this.#argonParams);
|
|
794
|
+
let rekeyed = 0;
|
|
795
|
+
this.#mutate(() => {
|
|
796
|
+
for (const [id, entry] of this.#cache) {
|
|
797
|
+
if (!entry.secret) continue;
|
|
798
|
+
const pre = resealed.get(id);
|
|
799
|
+
if (!pre || !sameEnvelope(pre.from, entry.secret)) {
|
|
800
|
+
throw new KeyStoreError(
|
|
801
|
+
`The keystore at ${this.#path} changed while its passphrase was being changed; re-run the command.`,
|
|
802
|
+
"KEYSTORE_CONCURRENT_CHANGE_ERROR",
|
|
803
|
+
{ path: this.#path }
|
|
804
|
+
);
|
|
805
|
+
}
|
|
806
|
+
entry.secret = pre.to;
|
|
807
|
+
entry.decrypted = pre.plain;
|
|
808
|
+
rekeyed++;
|
|
809
|
+
}
|
|
810
|
+
this.#verifier = newVerifier;
|
|
811
|
+
});
|
|
812
|
+
return rekeyed;
|
|
813
|
+
}
|
|
571
814
|
};
|
|
815
|
+
function keystoreSummary(path) {
|
|
816
|
+
const absent = { protection: "absent", established: false, keyCount: 0, active: void 0 };
|
|
817
|
+
if (!(0, import_node_fs3.existsSync)(path)) return absent;
|
|
818
|
+
let parsed;
|
|
819
|
+
try {
|
|
820
|
+
parsed = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
|
|
821
|
+
} catch {
|
|
822
|
+
return absent;
|
|
823
|
+
}
|
|
824
|
+
const keys = parsed.keys && typeof parsed.keys === "object" ? parsed.keys : {};
|
|
825
|
+
const keyCount = Object.keys(keys).length;
|
|
826
|
+
const active = typeof parsed.active === "string" ? parsed.active : void 0;
|
|
827
|
+
if (parsed.protection === "none") {
|
|
828
|
+
return { protection: "dev", established: true, keyCount, active };
|
|
829
|
+
}
|
|
830
|
+
if (parsed.protection === "passphrase") {
|
|
831
|
+
return { protection: "encrypted", established: parsed.verifier !== void 0, keyCount, active };
|
|
832
|
+
}
|
|
833
|
+
return absent;
|
|
834
|
+
}
|
|
835
|
+
function keystoreProtection(path) {
|
|
836
|
+
return keystoreSummary(path).protection;
|
|
837
|
+
}
|
|
838
|
+
function keystoreVerifierId(path) {
|
|
839
|
+
if (!(0, import_node_fs3.existsSync)(path)) return void 0;
|
|
840
|
+
let parsed;
|
|
841
|
+
try {
|
|
842
|
+
parsed = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
|
|
843
|
+
} catch {
|
|
844
|
+
return void 0;
|
|
845
|
+
}
|
|
846
|
+
if (!parsed.verifier) return void 0;
|
|
847
|
+
return import_base2.base64urlnopad.encode((0, import_sha2.sha256)((0, import_utils2.utf8ToBytes)(JSON.stringify(parsed.verifier))));
|
|
848
|
+
}
|
|
849
|
+
function verifyKeystorePassphrase(path, passphrase) {
|
|
850
|
+
if (!(0, import_node_fs3.existsSync)(path)) return false;
|
|
851
|
+
let parsed;
|
|
852
|
+
try {
|
|
853
|
+
parsed = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
|
|
854
|
+
} catch {
|
|
855
|
+
return false;
|
|
856
|
+
}
|
|
857
|
+
if (parsed.protection !== "passphrase" || !parsed.verifier) return false;
|
|
858
|
+
try {
|
|
859
|
+
return bytesEqual(decryptSecret(parsed.verifier, passphrase), VERIFIER_PLAINTEXT);
|
|
860
|
+
} catch {
|
|
861
|
+
return false;
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
function initKeystore(path, options) {
|
|
865
|
+
ensureDir((0, import_node_path3.dirname)(path), 448);
|
|
866
|
+
let file;
|
|
867
|
+
if (options.protection === "none") {
|
|
868
|
+
file = { v: KEYSTORE_VERSION, protection: "none", keys: {} };
|
|
869
|
+
} else {
|
|
870
|
+
const passphrase = options.getPassphrase({ confirm: true });
|
|
871
|
+
const verifier = encryptSecret(VERIFIER_PLAINTEXT, passphrase, options.argonParams ?? DEFAULT_ARGON_PARAMS);
|
|
872
|
+
file = { v: KEYSTORE_VERSION, protection: "passphrase", verifier, keys: {} };
|
|
873
|
+
}
|
|
874
|
+
writeFileAtomic(path, `${JSON.stringify(file, null, 2)}
|
|
875
|
+
`, 384);
|
|
876
|
+
}
|
|
877
|
+
function changeKeystorePassphrase(path, oldPassphrase, newPassphrase, argonParams) {
|
|
878
|
+
const store = new FileKeyStore({
|
|
879
|
+
path,
|
|
880
|
+
...argonParams && { argonParams },
|
|
881
|
+
getPassphrase: () => {
|
|
882
|
+
throw new KeyStoreError(
|
|
883
|
+
`Unexpected passphrase prompt while changing the passphrase for ${path}.`,
|
|
884
|
+
"KEYSTORE_INTERNAL_ERROR",
|
|
885
|
+
{ path }
|
|
886
|
+
);
|
|
887
|
+
}
|
|
888
|
+
});
|
|
889
|
+
return store.changePassphrase(oldPassphrase, newPassphrase);
|
|
890
|
+
}
|
|
572
891
|
|
|
573
892
|
// src/keystore/file-backed-key-manager.ts
|
|
574
893
|
var FileBackedKeyManager = class {
|
|
@@ -629,10 +948,14 @@ var FileBackedKeyManager = class {
|
|
|
629
948
|
var import_node_fs4 = require("fs");
|
|
630
949
|
var ENV_KEYSTORE_PASSPHRASE = "BTCR2_KEYSTORE_PASSPHRASE";
|
|
631
950
|
function acquirePassphrase(options = {}) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
951
|
+
if (!options.forcePrompt) {
|
|
952
|
+
const fromEnv = process.env[ENV_KEYSTORE_PASSPHRASE];
|
|
953
|
+
if (fromEnv) return assertNonEmpty(fromEnv.replace(/\r?\n$/, ""));
|
|
954
|
+
if (options.passphraseFile) {
|
|
955
|
+
return assertNonEmpty((0, import_node_fs4.readFileSync)(options.passphraseFile, "utf-8").replace(/\r?\n$/, ""));
|
|
956
|
+
}
|
|
957
|
+
const fromSession = options.beforePrompt?.();
|
|
958
|
+
if (fromSession) return assertNonEmpty(fromSession);
|
|
636
959
|
}
|
|
637
960
|
if (!process.stdin.isTTY) {
|
|
638
961
|
throw new KeyStoreError(
|
|
@@ -655,6 +978,12 @@ function assertNonEmpty(passphrase) {
|
|
|
655
978
|
}
|
|
656
979
|
return passphrase;
|
|
657
980
|
}
|
|
981
|
+
var IDLE_WAIT = new Int32Array(new SharedArrayBuffer(4));
|
|
982
|
+
var IDLE_POLL_MS = 20;
|
|
983
|
+
function dropLastUtf8Char(bytes) {
|
|
984
|
+
while (bytes.length > 0 && (bytes[bytes.length - 1] & 192) === 128) bytes.pop();
|
|
985
|
+
bytes.pop();
|
|
986
|
+
}
|
|
658
987
|
function promptHidden(label) {
|
|
659
988
|
process.stderr.write(label);
|
|
660
989
|
const stdin = process.stdin;
|
|
@@ -669,7 +998,10 @@ function promptHidden(label) {
|
|
|
669
998
|
read = (0, import_node_fs4.readSync)(stdin.fd, byte, 0, 1, null);
|
|
670
999
|
} catch (error) {
|
|
671
1000
|
const code = error.code;
|
|
672
|
-
if (code === "EAGAIN")
|
|
1001
|
+
if (code === "EAGAIN") {
|
|
1002
|
+
Atomics.wait(IDLE_WAIT, 0, 0, IDLE_POLL_MS);
|
|
1003
|
+
continue;
|
|
1004
|
+
}
|
|
673
1005
|
if (code === "EOF") break;
|
|
674
1006
|
throw error;
|
|
675
1007
|
}
|
|
@@ -680,7 +1012,7 @@ function promptHidden(label) {
|
|
|
680
1012
|
throw new KeyStoreError("Passphrase entry aborted.", "PASSPHRASE_REQUIRED_ERROR");
|
|
681
1013
|
}
|
|
682
1014
|
if (ch === 127 || ch === 8) {
|
|
683
|
-
bytes
|
|
1015
|
+
dropLastUtf8Char(bytes);
|
|
684
1016
|
continue;
|
|
685
1017
|
}
|
|
686
1018
|
bytes.push(ch);
|
|
@@ -692,16 +1024,168 @@ function promptHidden(label) {
|
|
|
692
1024
|
return Buffer.from(bytes).toString("utf-8");
|
|
693
1025
|
}
|
|
694
1026
|
|
|
1027
|
+
// src/keystore/session.ts
|
|
1028
|
+
var import_node_fs5 = require("fs");
|
|
1029
|
+
var import_node_path4 = require("path");
|
|
1030
|
+
var import_utils3 = require("@noble/hashes/utils.js");
|
|
1031
|
+
var import_base3 = require("@scure/base");
|
|
1032
|
+
var SESSION_VERSION = 1;
|
|
1033
|
+
var DEFAULT_SESSION_TTL_MS = 60 * 60 * 1e3;
|
|
1034
|
+
var MAX_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1035
|
+
var ENV_KEYSTORE_TTL = "BTCR2_KEYSTORE_TTL";
|
|
1036
|
+
function writeSession(sessionPath, input) {
|
|
1037
|
+
const createdAt = Date.now();
|
|
1038
|
+
const session = {
|
|
1039
|
+
v: SESSION_VERSION,
|
|
1040
|
+
keystore: (0, import_node_path4.resolve)(input.keystorePath),
|
|
1041
|
+
verifierId: input.verifierId,
|
|
1042
|
+
passphrase: import_base3.base64urlnopad.encode((0, import_utils3.utf8ToBytes)(input.passphrase)),
|
|
1043
|
+
allowMainnet: input.allowMainnet ?? false,
|
|
1044
|
+
createdAt,
|
|
1045
|
+
expiresAt: createdAt + input.ttlMs,
|
|
1046
|
+
ttlSeconds: Math.round(input.ttlMs / 1e3)
|
|
1047
|
+
};
|
|
1048
|
+
ensureDir((0, import_node_path4.dirname)(sessionPath), 448);
|
|
1049
|
+
writeFileAtomic(sessionPath, `${JSON.stringify(session, null, 2)}
|
|
1050
|
+
`, 384);
|
|
1051
|
+
return session;
|
|
1052
|
+
}
|
|
1053
|
+
function clearSession(sessionPath) {
|
|
1054
|
+
let existed = false;
|
|
1055
|
+
try {
|
|
1056
|
+
existed = (0, import_node_fs5.existsSync)(sessionPath);
|
|
1057
|
+
if (existed) (0, import_node_fs5.rmSync)(sessionPath, { force: true });
|
|
1058
|
+
} catch {
|
|
1059
|
+
existed = false;
|
|
1060
|
+
}
|
|
1061
|
+
sweepSessionTemps(sessionPath);
|
|
1062
|
+
return existed;
|
|
1063
|
+
}
|
|
1064
|
+
function sweepSessionTemps(sessionPath) {
|
|
1065
|
+
const dir = (0, import_node_path4.dirname)(sessionPath);
|
|
1066
|
+
const prefix = `.${(0, import_node_path4.basename)(sessionPath)}.`;
|
|
1067
|
+
try {
|
|
1068
|
+
for (const name of (0, import_node_fs5.readdirSync)(dir)) {
|
|
1069
|
+
if (name.startsWith(prefix) && name.endsWith(".tmp")) {
|
|
1070
|
+
try {
|
|
1071
|
+
(0, import_node_fs5.rmSync)((0, import_node_path4.join)(dir, name), { force: true });
|
|
1072
|
+
} catch {
|
|
1073
|
+
}
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
function readLiveSessionPassphrase(sessionPath, keystorePath, currentVerifierId, isMainnetOperation = false) {
|
|
1080
|
+
const verdict = evaluateSession(sessionPath, keystorePath, currentVerifierId, Date.now());
|
|
1081
|
+
if (verdict.status === "live") {
|
|
1082
|
+
if (isMainnetOperation && !verdict.session.allowMainnet) return void 0;
|
|
1083
|
+
try {
|
|
1084
|
+
return Buffer.from(import_base3.base64urlnopad.decode(verdict.session.passphrase)).toString("utf-8");
|
|
1085
|
+
} catch {
|
|
1086
|
+
clearSession(sessionPath);
|
|
1087
|
+
return void 0;
|
|
1088
|
+
}
|
|
1089
|
+
}
|
|
1090
|
+
if (verdict.status === "expired" || verdict.status === "stale" || verdict.status === "future" || verdict.status === "malformed") {
|
|
1091
|
+
clearSession(sessionPath);
|
|
1092
|
+
}
|
|
1093
|
+
return void 0;
|
|
1094
|
+
}
|
|
1095
|
+
function readSessionStatus(sessionPath, keystorePath, currentVerifierId) {
|
|
1096
|
+
const now = Date.now();
|
|
1097
|
+
const verdict = evaluateSession(sessionPath, keystorePath, currentVerifierId, now);
|
|
1098
|
+
if (verdict.status !== "live") return { active: false };
|
|
1099
|
+
return {
|
|
1100
|
+
active: true,
|
|
1101
|
+
expiresAt: verdict.session.expiresAt,
|
|
1102
|
+
secondsRemaining: Math.max(0, Math.round((verdict.session.expiresAt - now) / 1e3)),
|
|
1103
|
+
allowMainnet: verdict.session.allowMainnet
|
|
1104
|
+
};
|
|
1105
|
+
}
|
|
1106
|
+
function parseTtlToMs(raw) {
|
|
1107
|
+
const match = /^(\d+)([smh]?)$/.exec(raw.trim());
|
|
1108
|
+
if (!match) return void 0;
|
|
1109
|
+
const n = Number(match[1]);
|
|
1110
|
+
if (!Number.isFinite(n)) return void 0;
|
|
1111
|
+
const unitMs = match[2] === "h" ? 36e5 : match[2] === "m" ? 6e4 : 1e3;
|
|
1112
|
+
return n * unitMs;
|
|
1113
|
+
}
|
|
1114
|
+
function evaluateSession(sessionPath, keystorePath, currentVerifierId, now) {
|
|
1115
|
+
const session = secureReadSession(sessionPath);
|
|
1116
|
+
if (!session) return { status: "none" };
|
|
1117
|
+
if (session.v !== SESSION_VERSION) return { status: "malformed" };
|
|
1118
|
+
if (typeof session.passphrase !== "string" || typeof session.keystore !== "string" || typeof session.verifierId !== "string" || typeof session.allowMainnet !== "boolean" || typeof session.createdAt !== "number" || typeof session.expiresAt !== "number") {
|
|
1119
|
+
return { status: "malformed" };
|
|
1120
|
+
}
|
|
1121
|
+
if (session.createdAt > now) return { status: "future" };
|
|
1122
|
+
if (now >= session.expiresAt) return { status: "expired" };
|
|
1123
|
+
if ((0, import_node_path4.resolve)(session.keystore) !== (0, import_node_path4.resolve)(keystorePath)) return { status: "foreign" };
|
|
1124
|
+
if (currentVerifierId === void 0 || session.verifierId !== currentVerifierId) return { status: "stale" };
|
|
1125
|
+
return { status: "live", session };
|
|
1126
|
+
}
|
|
1127
|
+
function secureReadSession(sessionPath) {
|
|
1128
|
+
let raw;
|
|
1129
|
+
if (process.platform === "win32") {
|
|
1130
|
+
try {
|
|
1131
|
+
raw = (0, import_node_fs5.readFileSync)(sessionPath, "utf-8");
|
|
1132
|
+
} catch {
|
|
1133
|
+
return void 0;
|
|
1134
|
+
}
|
|
1135
|
+
} else {
|
|
1136
|
+
let fd;
|
|
1137
|
+
try {
|
|
1138
|
+
fd = (0, import_node_fs5.openSync)(sessionPath, import_node_fs5.constants.O_RDONLY | import_node_fs5.constants.O_NOFOLLOW);
|
|
1139
|
+
} catch {
|
|
1140
|
+
return void 0;
|
|
1141
|
+
}
|
|
1142
|
+
try {
|
|
1143
|
+
const st = (0, import_node_fs5.fstatSync)(fd);
|
|
1144
|
+
const myUid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
1145
|
+
if (!st.isFile() || myUid !== void 0 && st.uid !== myUid || (st.mode & 63) !== 0) {
|
|
1146
|
+
try {
|
|
1147
|
+
(0, import_node_fs5.rmSync)(sessionPath, { force: true });
|
|
1148
|
+
} catch {
|
|
1149
|
+
}
|
|
1150
|
+
return void 0;
|
|
1151
|
+
}
|
|
1152
|
+
raw = (0, import_node_fs5.readFileSync)(fd, "utf-8");
|
|
1153
|
+
} catch {
|
|
1154
|
+
return void 0;
|
|
1155
|
+
} finally {
|
|
1156
|
+
try {
|
|
1157
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
1158
|
+
} catch {
|
|
1159
|
+
}
|
|
1160
|
+
}
|
|
1161
|
+
}
|
|
1162
|
+
try {
|
|
1163
|
+
return JSON.parse(raw);
|
|
1164
|
+
} catch {
|
|
1165
|
+
return void 0;
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
|
|
695
1169
|
// src/config.ts
|
|
696
1170
|
var CONFIG_SCHEMA_VERSION = 1;
|
|
697
1171
|
function writeConfigFile(path, mutate) {
|
|
698
1172
|
const raw = readConfigFile(path) ?? {};
|
|
699
1173
|
mutate(raw);
|
|
700
1174
|
raw.schemaVersion = CONFIG_SCHEMA_VERSION;
|
|
701
|
-
ensureDir((0,
|
|
1175
|
+
ensureDir((0, import_node_path5.dirname)(path), 448);
|
|
702
1176
|
writeFileAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
703
1177
|
`, 384);
|
|
704
1178
|
}
|
|
1179
|
+
function writeDefaultConfigFile(path) {
|
|
1180
|
+
const scaffold = {
|
|
1181
|
+
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
1182
|
+
defaults: { output: "text" },
|
|
1183
|
+
profiles: Object.fromEntries(SUPPORTED_NETWORKS.map((n) => [n, {}]))
|
|
1184
|
+
};
|
|
1185
|
+
ensureDir((0, import_node_path5.dirname)(path), 448);
|
|
1186
|
+
writeFileAtomic(path, `${JSON.stringify(scaffold, null, 2)}
|
|
1187
|
+
`, 384);
|
|
1188
|
+
}
|
|
705
1189
|
function getConfigPath(config, path) {
|
|
706
1190
|
return path.split(".").reduce(
|
|
707
1191
|
(node, key) => node?.[key],
|
|
@@ -760,14 +1244,10 @@ function readEnvOverrides() {
|
|
|
760
1244
|
casRpcUrl: env(ENV_VARS.CAS_RPC_URL)
|
|
761
1245
|
};
|
|
762
1246
|
}
|
|
763
|
-
function defaultConfigPath() {
|
|
764
|
-
const base = blankToUndef(process.env.XDG_CONFIG_HOME) ?? blankToUndef(process.env.APPDATA) ?? (0, import_node_path4.join)((0, import_node_os2.homedir)(), ".config");
|
|
765
|
-
return (0, import_node_path4.join)(base, "btcr2", "config.json");
|
|
766
|
-
}
|
|
767
1247
|
function parseConfigFileRaw(path) {
|
|
768
1248
|
let content;
|
|
769
1249
|
try {
|
|
770
|
-
content = (0,
|
|
1250
|
+
content = (0, import_node_fs6.readFileSync)(path, "utf-8");
|
|
771
1251
|
} catch (error) {
|
|
772
1252
|
if (error.code === "ENOENT") return void 0;
|
|
773
1253
|
throw new CLIError(
|
|
@@ -822,7 +1302,7 @@ function resolveActiveProfile(file, overrides) {
|
|
|
822
1302
|
return { name, network };
|
|
823
1303
|
}
|
|
824
1304
|
function resolveDefaultNetwork(overrides) {
|
|
825
|
-
const configPath = overrides?.config ?? defaultConfigPath();
|
|
1305
|
+
const configPath = overrides?.config ?? defaultConfigPath(overrides);
|
|
826
1306
|
const file = readConfigFile(configPath);
|
|
827
1307
|
const explicit = file?.defaults?.network;
|
|
828
1308
|
if (explicit && SUPPORTED_NETWORKS.includes(explicit)) return explicit;
|
|
@@ -833,7 +1313,7 @@ function resolveDefaultNetwork(overrides) {
|
|
|
833
1313
|
function profileNetworkMismatch(network, overrides) {
|
|
834
1314
|
let file;
|
|
835
1315
|
try {
|
|
836
|
-
file = readConfigFile(overrides?.config ?? defaultConfigPath());
|
|
1316
|
+
file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
837
1317
|
} catch {
|
|
838
1318
|
return void 0;
|
|
839
1319
|
}
|
|
@@ -847,7 +1327,7 @@ function resolveOutputFormat(options) {
|
|
|
847
1327
|
process.env.BTCR2_OUTPUT || void 0
|
|
848
1328
|
];
|
|
849
1329
|
try {
|
|
850
|
-
candidates.push(readConfigFile(options.config ?? defaultConfigPath())?.defaults?.output);
|
|
1330
|
+
candidates.push(readConfigFile(options.config ?? defaultConfigPath(options))?.defaults?.output);
|
|
851
1331
|
} catch {
|
|
852
1332
|
}
|
|
853
1333
|
for (const candidate of candidates) {
|
|
@@ -873,7 +1353,7 @@ function resolveRpcUnit(overrides, env, fileOverrides) {
|
|
|
873
1353
|
}
|
|
874
1354
|
function resolveConnectionConfig(network, overrides) {
|
|
875
1355
|
if (!network) return {};
|
|
876
|
-
const configPath = overrides?.config ?? defaultConfigPath();
|
|
1356
|
+
const configPath = overrides?.config ?? defaultConfigPath(overrides);
|
|
877
1357
|
const file = readConfigFile(configPath);
|
|
878
1358
|
const { name: activeProfile } = resolveActiveProfile(file, overrides);
|
|
879
1359
|
const profileName = activeProfile ?? network;
|
|
@@ -959,7 +1439,7 @@ function trimTrailingNewline(value) {
|
|
|
959
1439
|
}
|
|
960
1440
|
function readSecretFile(path, source) {
|
|
961
1441
|
try {
|
|
962
|
-
return trimTrailingNewline((0,
|
|
1442
|
+
return trimTrailingNewline((0, import_node_fs6.readFileSync)(path, "utf-8"));
|
|
963
1443
|
} catch (error) {
|
|
964
1444
|
throw new CLIError(
|
|
965
1445
|
`Could not read the RPC password ${source} at ${path}: ${error.message}`,
|
|
@@ -985,7 +1465,7 @@ function readRpcPassFile() {
|
|
|
985
1465
|
return readSecretFile(path, `file named by ${ENV_RPC_PASS_FILE}`);
|
|
986
1466
|
}
|
|
987
1467
|
function resolveBroadcastOptions(network, overrides, flags) {
|
|
988
|
-
const file = readConfigFile(overrides?.config ?? defaultConfigPath());
|
|
1468
|
+
const file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
989
1469
|
const { name: activeProfile } = resolveActiveProfile(file, overrides);
|
|
990
1470
|
const profileBtc = file?.profiles?.[activeProfile ?? network]?.btc;
|
|
991
1471
|
const options = {};
|
|
@@ -1007,7 +1487,7 @@ function parseFeeRate(raw) {
|
|
|
1007
1487
|
return rate;
|
|
1008
1488
|
}
|
|
1009
1489
|
function resolveEffectiveConfig(network, overrides) {
|
|
1010
|
-
const file = readConfigFile(overrides?.config ?? defaultConfigPath());
|
|
1490
|
+
const file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
1011
1491
|
const { name: activeProfile } = resolveActiveProfile(file, overrides);
|
|
1012
1492
|
const profileName = activeProfile ?? network;
|
|
1013
1493
|
const fileOv = file ? profileToOverrides(file, profileName) : {};
|
|
@@ -1093,17 +1573,56 @@ async function runDoctor(network, overrides) {
|
|
|
1093
1573
|
function defaultApiFactory(network, overrides) {
|
|
1094
1574
|
return (0, import_api.createApi)(resolveConnectionConfig(network, overrides));
|
|
1095
1575
|
}
|
|
1096
|
-
function buildKeystoreKms(overrides) {
|
|
1576
|
+
function buildKeystoreKms(overrides, network) {
|
|
1577
|
+
const keystorePath = resolveKeystorePath(overrides);
|
|
1578
|
+
const sessionPath = defaultSessionPath(overrides);
|
|
1579
|
+
const isMainnetOperation = network === "bitcoin";
|
|
1097
1580
|
return new FileBackedKeyManager({
|
|
1098
|
-
path:
|
|
1099
|
-
|
|
1581
|
+
path: keystorePath,
|
|
1582
|
+
// The store decides when to confirm: it passes `confirm: true` only while
|
|
1583
|
+
// establishing a fresh keystore's passphrase, so a first-key typo is caught
|
|
1584
|
+
// by a second entry (ADR 080). confirm is a no-op for env/file sources.
|
|
1585
|
+
//
|
|
1586
|
+
// On the non-establishing path, a cached session (ADR 081) is consulted below
|
|
1587
|
+
// the env var / --passphrase-file and above the interactive prompt. It is
|
|
1588
|
+
// wired ONLY when not confirming, so establishment never consults the session
|
|
1589
|
+
// and a first passphrase is always entered fresh and twice. The session is
|
|
1590
|
+
// bound to this keystore's verifier, so a rotated passphrase invalidates it.
|
|
1591
|
+
getPassphrase: (opts) => acquirePassphrase({
|
|
1592
|
+
passphraseFile: overrides?.passphraseFile,
|
|
1593
|
+
confirm: opts?.confirm,
|
|
1594
|
+
...opts?.confirm ? {} : {
|
|
1595
|
+
beforePrompt: () => readLiveSessionPassphrase(sessionPath, keystorePath, keystoreVerifierId(keystorePath), isMainnetOperation)
|
|
1596
|
+
}
|
|
1597
|
+
})
|
|
1100
1598
|
});
|
|
1101
1599
|
}
|
|
1102
|
-
function
|
|
1103
|
-
return
|
|
1600
|
+
function resolveKeystoreProtection(overrides) {
|
|
1601
|
+
return keystoreProtection(resolveKeystorePath(overrides));
|
|
1602
|
+
}
|
|
1603
|
+
function assertKeystoreAllowedForNetwork(network, overrides) {
|
|
1604
|
+
if (network !== "bitcoin") return;
|
|
1605
|
+
if (resolveKeystoreProtection(overrides) !== "dev") return;
|
|
1606
|
+
const path = resolveKeystorePath(overrides);
|
|
1607
|
+
throw new CLIError(
|
|
1608
|
+
`Refusing a mainnet (bitcoin) operation with the unencrypted dev keystore at ${path}. Dev keystores hold plaintext keys and are for testnet/regtest throwaway material only. Establish an encrypted keystore (btcr2 keystore init) for mainnet keys.`,
|
|
1609
|
+
"DEV_KEYSTORE_MAINNET_ERROR",
|
|
1610
|
+
{ path, network }
|
|
1611
|
+
);
|
|
1612
|
+
}
|
|
1613
|
+
function resolveKeystorePath(overrides, options) {
|
|
1614
|
+
const fromFlag = blankToUndef(overrides?.keystore);
|
|
1615
|
+
if (fromFlag) return fromFlag;
|
|
1616
|
+
let identity;
|
|
1617
|
+
try {
|
|
1618
|
+
identity = activeProfileIdentity(overrides);
|
|
1619
|
+
} catch (error) {
|
|
1620
|
+
if (!options?.lenient) throw error;
|
|
1621
|
+
}
|
|
1622
|
+
return blankToUndef(identity?.keystore) ?? defaultKeystorePath(overrides);
|
|
1104
1623
|
}
|
|
1105
1624
|
function activeProfileIdentity(overrides) {
|
|
1106
|
-
const file = readConfigFile(overrides?.config ?? defaultConfigPath());
|
|
1625
|
+
const file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
1107
1626
|
const { name } = resolveActiveProfile(file, overrides);
|
|
1108
1627
|
return name ? file?.profiles?.[name]?.identity : void 0;
|
|
1109
1628
|
}
|
|
@@ -1113,7 +1632,7 @@ function resolveSigningKeyRef(overrides) {
|
|
|
1113
1632
|
function keystoreApiFactory(network, overrides) {
|
|
1114
1633
|
return (0, import_api.createApi)({
|
|
1115
1634
|
...resolveConnectionConfig(network, overrides),
|
|
1116
|
-
kms: buildKeystoreKms(overrides)
|
|
1635
|
+
kms: buildKeystoreKms(overrides, network)
|
|
1117
1636
|
});
|
|
1118
1637
|
}
|
|
1119
1638
|
function deriveNetwork(did) {
|
|
@@ -1128,6 +1647,86 @@ function deriveNetwork(did) {
|
|
|
1128
1647
|
return network;
|
|
1129
1648
|
}
|
|
1130
1649
|
|
|
1650
|
+
// src/output.ts
|
|
1651
|
+
var REDACTED = "********";
|
|
1652
|
+
var SECRET_KEY = /(pass|secret|token|auth|api[-_]?key|credential|bearer)/i;
|
|
1653
|
+
function isSecretKey(key) {
|
|
1654
|
+
return SECRET_KEY.test(key);
|
|
1655
|
+
}
|
|
1656
|
+
function scrubUrlUserinfo(value) {
|
|
1657
|
+
if (value === void 0) return void 0;
|
|
1658
|
+
return value.replace(/^([a-z][a-z0-9+.-]*:\/\/[^/@:]+):[^/@]*@/i, `$1:${REDACTED}@`);
|
|
1659
|
+
}
|
|
1660
|
+
function redactSecrets(value, keyName) {
|
|
1661
|
+
if (Array.isArray(value)) {
|
|
1662
|
+
return value.map((item) => redactSecrets(item));
|
|
1663
|
+
}
|
|
1664
|
+
if (value !== null && typeof value === "object") {
|
|
1665
|
+
const out = {};
|
|
1666
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1667
|
+
out[key] = redactSecrets(val, key);
|
|
1668
|
+
}
|
|
1669
|
+
return out;
|
|
1670
|
+
}
|
|
1671
|
+
if (keyName !== void 0 && isSecretKey(keyName)) {
|
|
1672
|
+
return value === void 0 ? void 0 : REDACTED;
|
|
1673
|
+
}
|
|
1674
|
+
return typeof value === "string" ? scrubUrlUserinfo(value) : value;
|
|
1675
|
+
}
|
|
1676
|
+
function formatResult(result, options) {
|
|
1677
|
+
if (options.output === "json") {
|
|
1678
|
+
return JSON.stringify(result, null, 2);
|
|
1679
|
+
}
|
|
1680
|
+
const { data } = result;
|
|
1681
|
+
return typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// src/commands/init.ts
|
|
1685
|
+
function registerInitCommand(program, globals) {
|
|
1686
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
1687
|
+
program.command("init").description("Set up the btcr2 home: create the directory, a default config, and establish the keystore.").option("--dev", "Establish an UNENCRYPTED dev keystore: plaintext keys, no passphrase. Testnet only.", false).option("--force", "Re-create the config and keystore even if they already exist.", false).action((options) => {
|
|
1688
|
+
const g = globals();
|
|
1689
|
+
const home = resolveHome(g);
|
|
1690
|
+
const configPath = g.config ?? defaultConfigPath(g);
|
|
1691
|
+
const keystorePath = resolveKeystorePath(g);
|
|
1692
|
+
ensureDir(home, 448);
|
|
1693
|
+
const created = [];
|
|
1694
|
+
if (!(0, import_node_fs7.existsSync)(configPath) || options.force) {
|
|
1695
|
+
writeDefaultConfigFile(configPath);
|
|
1696
|
+
created.push("config");
|
|
1697
|
+
}
|
|
1698
|
+
const keystoreExists = (0, import_node_fs7.existsSync)(keystorePath);
|
|
1699
|
+
if (keystoreExists && options.force && !g.quiet) {
|
|
1700
|
+
process.stderr.write(
|
|
1701
|
+
`note: a keystore already exists at ${keystorePath} and was left intact. To re-establish it (discarding its keys), run "btcr2 keystore init --force".
|
|
1702
|
+
`
|
|
1703
|
+
);
|
|
1704
|
+
}
|
|
1705
|
+
if (!keystoreExists) {
|
|
1706
|
+
if (options.dev && !g.quiet) {
|
|
1707
|
+
process.stderr.write(
|
|
1708
|
+
"warning: establishing an UNENCRYPTED dev keystore. Keys are stored in plaintext. Use it only for disposable testnet material; mainnet operations will be refused.\n"
|
|
1709
|
+
);
|
|
1710
|
+
}
|
|
1711
|
+
initKeystore(keystorePath, {
|
|
1712
|
+
protection: options.dev ? "none" : "passphrase",
|
|
1713
|
+
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
|
|
1714
|
+
});
|
|
1715
|
+
clearSession(defaultSessionPath(g));
|
|
1716
|
+
created.push("keystore");
|
|
1717
|
+
}
|
|
1718
|
+
const protection = keystoreSummary(keystorePath).protection;
|
|
1719
|
+
print({ action: "init", data: { home, config: configPath, keystore: keystorePath, created, protection } });
|
|
1720
|
+
if (!g.quiet && g.output !== "json") {
|
|
1721
|
+
process.stderr.write(`btcr2 home ready at ${home}. Next: btcr2 key generate --set-active
|
|
1722
|
+
`);
|
|
1723
|
+
}
|
|
1724
|
+
});
|
|
1725
|
+
}
|
|
1726
|
+
|
|
1727
|
+
// src/commands/create.ts
|
|
1728
|
+
var import_utils4 = require("@noble/hashes/utils.js");
|
|
1729
|
+
|
|
1131
1730
|
// src/keystore/resolve-key-ref.ts
|
|
1132
1731
|
function fingerprintOf(id) {
|
|
1133
1732
|
return /^urn:kms:secp256k1:([0-9a-f]{32})$/.exec(id)?.[1];
|
|
@@ -1166,40 +1765,6 @@ function resolveKeyRef(kms, ref) {
|
|
|
1166
1765
|
throw new CLIError(`No key matches reference "${ref}".`, "KEY_NOT_FOUND_ERROR", { ref });
|
|
1167
1766
|
}
|
|
1168
1767
|
|
|
1169
|
-
// src/output.ts
|
|
1170
|
-
var REDACTED = "********";
|
|
1171
|
-
var SECRET_KEY = /(pass|secret|token|auth|api[-_]?key|credential|bearer)/i;
|
|
1172
|
-
function isSecretKey(key) {
|
|
1173
|
-
return SECRET_KEY.test(key);
|
|
1174
|
-
}
|
|
1175
|
-
function scrubUrlUserinfo(value) {
|
|
1176
|
-
if (value === void 0) return void 0;
|
|
1177
|
-
return value.replace(/^([a-z][a-z0-9+.-]*:\/\/[^/@:]+):[^/@]*@/i, `$1:${REDACTED}@`);
|
|
1178
|
-
}
|
|
1179
|
-
function redactSecrets(value, keyName) {
|
|
1180
|
-
if (Array.isArray(value)) {
|
|
1181
|
-
return value.map((item) => redactSecrets(item));
|
|
1182
|
-
}
|
|
1183
|
-
if (value !== null && typeof value === "object") {
|
|
1184
|
-
const out = {};
|
|
1185
|
-
for (const [key, val] of Object.entries(value)) {
|
|
1186
|
-
out[key] = redactSecrets(val, key);
|
|
1187
|
-
}
|
|
1188
|
-
return out;
|
|
1189
|
-
}
|
|
1190
|
-
if (keyName !== void 0 && isSecretKey(keyName)) {
|
|
1191
|
-
return value === void 0 ? void 0 : REDACTED;
|
|
1192
|
-
}
|
|
1193
|
-
return typeof value === "string" ? scrubUrlUserinfo(value) : value;
|
|
1194
|
-
}
|
|
1195
|
-
function formatResult(result, options) {
|
|
1196
|
-
if (options.output === "json") {
|
|
1197
|
-
return JSON.stringify(result, null, 2);
|
|
1198
|
-
}
|
|
1199
|
-
const { data } = result;
|
|
1200
|
-
return typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
1201
|
-
}
|
|
1202
|
-
|
|
1203
1768
|
// src/commands/create.ts
|
|
1204
1769
|
var EXPECTED_BYTES = {
|
|
1205
1770
|
k: { length: 33, label: "secp256k1 compressed public key (33 bytes)" },
|
|
@@ -1268,14 +1833,15 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
1268
1833
|
const publicKey2 = api2.kms.getPublicKey(keyId2);
|
|
1269
1834
|
const did2 = api2.createDid("deterministic", publicKey2, { network });
|
|
1270
1835
|
print(
|
|
1271
|
-
{ action: "create", data: did2, keyId: keyId2, publicKey: (0,
|
|
1836
|
+
{ action: "create", data: did2, keyId: keyId2, publicKey: (0, import_utils4.bytesToHex)(publicKey2) },
|
|
1272
1837
|
`Using stored key ${keyId2}.`
|
|
1273
1838
|
);
|
|
1274
1839
|
return;
|
|
1275
1840
|
}
|
|
1841
|
+
assertKeystoreAllowedForNetwork(network, overrides);
|
|
1276
1842
|
const api = keystoreFactory(void 0, overrides);
|
|
1277
1843
|
const { did, keyId } = api.generateDid({ network, setActive: true });
|
|
1278
|
-
const publicKey = (0,
|
|
1844
|
+
const publicKey = (0, import_utils4.bytesToHex)(api.kms.getPublicKey(keyId));
|
|
1279
1845
|
print(
|
|
1280
1846
|
{ action: "create", data: did, keyId, publicKey },
|
|
1281
1847
|
`Generated and stored key ${keyId} (now the active key).`
|
|
@@ -1284,6 +1850,7 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
1284
1850
|
}
|
|
1285
1851
|
function overridesFromGlobals(g) {
|
|
1286
1852
|
return {
|
|
1853
|
+
home: g.home,
|
|
1287
1854
|
config: g.config,
|
|
1288
1855
|
profile: g.profile,
|
|
1289
1856
|
keystore: g.keystore,
|
|
@@ -1305,7 +1872,7 @@ function parseGenesisBytes(hex, type) {
|
|
|
1305
1872
|
const expected = EXPECTED_BYTES[type];
|
|
1306
1873
|
let bytes;
|
|
1307
1874
|
try {
|
|
1308
|
-
bytes = (0,
|
|
1875
|
+
bytes = (0, import_utils4.hexToBytes)(hex.trim());
|
|
1309
1876
|
} catch {
|
|
1310
1877
|
throw new CLIError(
|
|
1311
1878
|
`Invalid bytes: not valid hex. Expected ${expected.label}.`,
|
|
@@ -1420,6 +1987,7 @@ function registerUpdateCommand(program, factory, globals) {
|
|
|
1420
1987
|
);
|
|
1421
1988
|
}
|
|
1422
1989
|
const network = deriveNetwork(did);
|
|
1990
|
+
assertKeystoreAllowedForNetwork(network, globals());
|
|
1423
1991
|
const api = factory(network, globals());
|
|
1424
1992
|
const keyId = resolveKeyRef(api.kms.kms, resolveSigningKeyRef(globals()));
|
|
1425
1993
|
const signer = new import_key_manager2.KeyManagerSigner(api.kms.kms, keyId);
|
|
@@ -1517,6 +2085,7 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
1517
2085
|
);
|
|
1518
2086
|
}
|
|
1519
2087
|
const network = deriveNetwork(did);
|
|
2088
|
+
assertKeystoreAllowedForNetwork(network, globals());
|
|
1520
2089
|
const api = factory(network, globals());
|
|
1521
2090
|
const keyId = resolveKeyRef(api.kms.kms, resolveSigningKeyRef(globals()));
|
|
1522
2091
|
const signer = new import_key_manager3.KeyManagerSigner(api.kms.kms, keyId);
|
|
@@ -1563,8 +2132,8 @@ function parseJsonArg2(flagName) {
|
|
|
1563
2132
|
|
|
1564
2133
|
// src/commands/key.ts
|
|
1565
2134
|
var import_keypair = require("@did-btcr2/keypair");
|
|
1566
|
-
var
|
|
1567
|
-
var
|
|
2135
|
+
var import_utils5 = require("@noble/hashes/utils.js");
|
|
2136
|
+
var import_node_fs8 = require("fs");
|
|
1568
2137
|
function registerKeyCommand(program, factory, globals) {
|
|
1569
2138
|
const key = program.command("key").description("Manage keypairs in the encrypted keystore.");
|
|
1570
2139
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
@@ -1573,7 +2142,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1573
2142
|
assertNameAvailable(api.kms.kms, options.name);
|
|
1574
2143
|
const setActive = options.setActive ?? false;
|
|
1575
2144
|
const id = api.kms.generateKey({ ...options.name && { tags: { name: options.name } }, setActive });
|
|
1576
|
-
print({ action: "key-generate", data: { keyId: id, publicKey: (0,
|
|
2145
|
+
print({ action: "key-generate", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(api.kms.getPublicKey(id)), active: setActive } });
|
|
1577
2146
|
});
|
|
1578
2147
|
key.command("list").alias("ls").description("List stored keys.").action(() => {
|
|
1579
2148
|
const kms = factory(void 0, globals()).kms.kms;
|
|
@@ -1593,7 +2162,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1593
2162
|
const kms = factory(void 0, globals()).kms.kms;
|
|
1594
2163
|
const id = resolveKeyRef(kms, ref);
|
|
1595
2164
|
const entry = kms.getEntry(id);
|
|
1596
|
-
print({ action: "key-show", data: { keyId: id, publicKey: (0,
|
|
2165
|
+
print({ action: "key-show", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(entry.publicKey), ...entry.tags && { tags: entry.tags } } });
|
|
1597
2166
|
});
|
|
1598
2167
|
key.command("import").description("Import a key: a secret from a hex file, or a public key as watch-only.").option("--secret-file <path>", "Path to a file containing a 32-byte secret key as hex.").option("--public <hex>", "A 33-byte compressed public key as hex (imported watch-only).").option("--name <name>", "A human-friendly name, stored as a tag.").option("--set-active", "Make this the active key.", false).action((options) => {
|
|
1599
2168
|
if (Boolean(options.secretFile) === Boolean(options.public)) {
|
|
@@ -1604,13 +2173,13 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1604
2173
|
const keyPair = options.secretFile ? new import_keypair.SchnorrKeyPair({ secretKey: readHexFile(options.secretFile, 32, "--secret-file") }) : new import_keypair.SchnorrKeyPair({ publicKey: parseHex(options.public ?? "", 33, "--public") });
|
|
1605
2174
|
const setActive = options.setActive ?? false;
|
|
1606
2175
|
const id = api.kms.import(keyPair, { ...options.name && { tags: { name: options.name } }, setActive });
|
|
1607
|
-
print({ action: "key-import", data: { keyId: id, publicKey: (0,
|
|
2176
|
+
print({ action: "key-import", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(api.kms.getPublicKey(id)), watchOnly: !options.secretFile, active: setActive } });
|
|
1608
2177
|
});
|
|
1609
2178
|
key.command("export <ref>").description("Export a key. Public material by default; --secret writes the secret to a file.").option("--secret", "Export the secret key. Requires --out.", false).option("--out <path>", "Write the exported secret to this file (created 0600).").action((ref, options) => {
|
|
1610
2179
|
const api = factory(void 0, globals());
|
|
1611
2180
|
const id = resolveKeyRef(api.kms.kms, ref);
|
|
1612
2181
|
if (!options.secret) {
|
|
1613
|
-
print({ action: "key-export", data: { keyId: id, publicKey: (0,
|
|
2182
|
+
print({ action: "key-export", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(api.kms.getPublicKey(id)) } });
|
|
1614
2183
|
return;
|
|
1615
2184
|
}
|
|
1616
2185
|
if (!options.out) {
|
|
@@ -1621,7 +2190,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1621
2190
|
throw new CLIError(`Key ${id} is watch-only and has no secret to export.`, "INVALID_ARGUMENT_ERROR", { keyId: id });
|
|
1622
2191
|
}
|
|
1623
2192
|
process.stderr.write("warning: writing an unencrypted secret key to disk. Protect this file and delete it when done.\n");
|
|
1624
|
-
writeSecretFile(options.out, (0,
|
|
2193
|
+
writeSecretFile(options.out, (0, import_utils5.bytesToHex)(keyPair.secretKey.bytes));
|
|
1625
2194
|
print({ action: "key-export", data: { keyId: id, secretWrittenTo: options.out } });
|
|
1626
2195
|
});
|
|
1627
2196
|
key.command("delete <ref>").alias("rm").description("Delete a key from the keystore.").option("--force", "Delete even if it is the active key.", false).action((ref, options) => {
|
|
@@ -1646,7 +2215,7 @@ function assertNameAvailable(kms, name) {
|
|
|
1646
2215
|
function parseHex(hex, expectedBytes, label) {
|
|
1647
2216
|
let bytes;
|
|
1648
2217
|
try {
|
|
1649
|
-
bytes = (0,
|
|
2218
|
+
bytes = (0, import_utils5.hexToBytes)(hex.trim());
|
|
1650
2219
|
} catch {
|
|
1651
2220
|
throw new CLIError(`Invalid hex for ${label}.`, "INVALID_ARGUMENT_ERROR", { label });
|
|
1652
2221
|
}
|
|
@@ -1662,7 +2231,7 @@ function parseHex(hex, expectedBytes, label) {
|
|
|
1662
2231
|
function readHexFile(path, expectedBytes, label) {
|
|
1663
2232
|
let content;
|
|
1664
2233
|
try {
|
|
1665
|
-
content = (0,
|
|
2234
|
+
content = (0, import_node_fs8.readFileSync)(path, "utf-8");
|
|
1666
2235
|
} catch {
|
|
1667
2236
|
throw new CLIError(`Cannot read ${label} at ${path}.`, "INVALID_ARGUMENT_ERROR", { label, path });
|
|
1668
2237
|
}
|
|
@@ -1671,7 +2240,7 @@ function readHexFile(path, expectedBytes, label) {
|
|
|
1671
2240
|
function writeSecretFile(path, contents) {
|
|
1672
2241
|
let fd;
|
|
1673
2242
|
try {
|
|
1674
|
-
fd = (0,
|
|
2243
|
+
fd = (0, import_node_fs8.openSync)(path, "wx", 384);
|
|
1675
2244
|
} catch (error) {
|
|
1676
2245
|
if (error.code === "EEXIST") {
|
|
1677
2246
|
throw new CLIError(`Refusing to overwrite existing file ${path}. Choose a new --out path.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
@@ -1679,15 +2248,156 @@ function writeSecretFile(path, contents) {
|
|
|
1679
2248
|
throw error;
|
|
1680
2249
|
}
|
|
1681
2250
|
try {
|
|
1682
|
-
(0,
|
|
2251
|
+
(0, import_node_fs8.writeFileSync)(fd, contents);
|
|
1683
2252
|
} finally {
|
|
1684
|
-
(0,
|
|
2253
|
+
(0, import_node_fs8.closeSync)(fd);
|
|
1685
2254
|
}
|
|
1686
2255
|
}
|
|
1687
2256
|
|
|
2257
|
+
// src/commands/keystore.ts
|
|
2258
|
+
var import_node_fs9 = require("fs");
|
|
2259
|
+
function registerKeystoreCommand(program, globals) {
|
|
2260
|
+
const keystore = program.command("keystore").description("Establish, inspect, re-key, and unlock the keystore.");
|
|
2261
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
2262
|
+
keystore.command("init").description("Establish the keystore (encrypted by default). Prompts for a passphrase and confirms it.").option("--dev", "Create an UNENCRYPTED dev keystore: plaintext keys, no passphrase. Testnet only; mainnet operations are refused.", false).option("--force", "Re-establish even if a keystore already exists (discards its keys).", false).action((options) => {
|
|
2263
|
+
const g = globals();
|
|
2264
|
+
const path = resolveKeystorePath(g);
|
|
2265
|
+
if ((0, import_node_fs9.existsSync)(path) && !options.force) {
|
|
2266
|
+
throw new CLIError(
|
|
2267
|
+
`A keystore already exists at ${path}. Use --force to re-establish it (this discards its keys).`,
|
|
2268
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2269
|
+
{ path }
|
|
2270
|
+
);
|
|
2271
|
+
}
|
|
2272
|
+
if ((0, import_node_fs9.existsSync)(path) && options.force && !g.quiet) {
|
|
2273
|
+
const { keyCount } = keystoreSummary(path);
|
|
2274
|
+
if (keyCount > 0) {
|
|
2275
|
+
process.stderr.write(
|
|
2276
|
+
`warning: re-establishing the keystore at ${path} permanently discards its ${keyCount} existing key(s).
|
|
2277
|
+
`
|
|
2278
|
+
);
|
|
2279
|
+
}
|
|
2280
|
+
}
|
|
2281
|
+
if (options.dev && !g.quiet) {
|
|
2282
|
+
process.stderr.write(
|
|
2283
|
+
"warning: creating an UNENCRYPTED dev keystore. Keys are stored in plaintext. Use it only for disposable testnet material; mainnet operations will be refused.\n"
|
|
2284
|
+
);
|
|
2285
|
+
}
|
|
2286
|
+
initKeystore(path, {
|
|
2287
|
+
protection: options.dev ? "none" : "passphrase",
|
|
2288
|
+
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
|
|
2289
|
+
});
|
|
2290
|
+
clearSession(defaultSessionPath(g));
|
|
2291
|
+
print({ action: "keystore-init", data: { path, protection: options.dev ? "dev" : "encrypted" } });
|
|
2292
|
+
});
|
|
2293
|
+
keystore.command("status").description("Show the keystore path, protection mode, key count, and session state. Never decrypts or prompts.").action(() => {
|
|
2294
|
+
const g = globals();
|
|
2295
|
+
const path = resolveKeystorePath(g, { lenient: true });
|
|
2296
|
+
const summary = keystoreSummary(path);
|
|
2297
|
+
const session = readSessionStatus(defaultSessionPath(g), path, keystoreVerifierId(path));
|
|
2298
|
+
if (summary.protection === "dev" && !g.quiet && g.output !== "json") {
|
|
2299
|
+
process.stderr.write("warning: this is an UNENCRYPTED dev keystore; keys are stored in plaintext.\n");
|
|
2300
|
+
}
|
|
2301
|
+
print({ action: "keystore-status", data: { path, ...summary, session } });
|
|
2302
|
+
});
|
|
2303
|
+
keystore.command("change-passphrase").alias("passwd").description("Change the keystore passphrase, re-sealing every key under the new one. Encrypted keystores only.").action(() => {
|
|
2304
|
+
const g = globals();
|
|
2305
|
+
const path = resolveKeystorePath(g);
|
|
2306
|
+
const summary = keystoreSummary(path);
|
|
2307
|
+
if (summary.protection === "absent") {
|
|
2308
|
+
throw new CLIError(`No keystore at ${path}. Run "btcr2 keystore init" first.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
2309
|
+
}
|
|
2310
|
+
if (summary.protection === "dev") {
|
|
2311
|
+
throw new CLIError(
|
|
2312
|
+
`The keystore at ${path} is an unencrypted dev keystore; there is no passphrase to change.`,
|
|
2313
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2314
|
+
{ path }
|
|
2315
|
+
);
|
|
2316
|
+
}
|
|
2317
|
+
const oldPassphrase = acquirePassphrase({ passphraseFile: g.passphraseFile, prompt: "Current keystore passphrase: " });
|
|
2318
|
+
const newPassphrase = acquirePassphrase({ forcePrompt: true, confirm: true, prompt: "New keystore passphrase: " });
|
|
2319
|
+
const rekeyed = changeKeystorePassphrase(path, oldPassphrase, newPassphrase);
|
|
2320
|
+
clearSession(defaultSessionPath(g));
|
|
2321
|
+
print({ action: "keystore-change-passphrase", data: { path, rekeyed } });
|
|
2322
|
+
});
|
|
2323
|
+
keystore.command("unlock").description("Cache the keystore passphrase for a session so later commands do not re-prompt (ADR 081).").option("--ttl <duration>", `Session lifetime: bare seconds or an s/m/h suffix (default 1h, max 24h). Also $${ENV_KEYSTORE_TTL}.`).option("--allow-mainnet", "Permit unlocking when the active network is mainnet (bitcoin); this suspends per-use passphrase auth for the session.", false).action((options) => {
|
|
2324
|
+
const g = globals();
|
|
2325
|
+
const path = resolveKeystorePath(g);
|
|
2326
|
+
const summary = keystoreSummary(path);
|
|
2327
|
+
if (summary.protection === "absent") {
|
|
2328
|
+
throw new CLIError(`No keystore at ${path}. Run "btcr2 init" or "btcr2 keystore init" first.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
2329
|
+
}
|
|
2330
|
+
if (summary.protection === "dev") {
|
|
2331
|
+
throw new CLIError(
|
|
2332
|
+
`The keystore at ${path} is an unencrypted dev keystore; it has no passphrase to cache, so no unlock is needed.`,
|
|
2333
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2334
|
+
{ path }
|
|
2335
|
+
);
|
|
2336
|
+
}
|
|
2337
|
+
if (!summary.established) {
|
|
2338
|
+
throw new CLIError(
|
|
2339
|
+
`The keystore at ${path} has no passphrase established yet. Establish one with "btcr2 keystore init" or the first "btcr2 key generate".`,
|
|
2340
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2341
|
+
{ path }
|
|
2342
|
+
);
|
|
2343
|
+
}
|
|
2344
|
+
if (!options.allowMainnet && resolveDefaultNetwork(g) === "bitcoin") {
|
|
2345
|
+
throw new CLIError(
|
|
2346
|
+
`Refusing to unlock for a mainnet (bitcoin) context: caching the passphrase suspends per-use authentication for the session. Pass --allow-mainnet to override, or keep signing mainnet updates with a per-use passphrase prompt.`,
|
|
2347
|
+
"MAINNET_UNLOCK_REFUSED_ERROR",
|
|
2348
|
+
{ path }
|
|
2349
|
+
);
|
|
2350
|
+
}
|
|
2351
|
+
const ttlMs = resolveSessionTtl(options.ttl);
|
|
2352
|
+
const passphrase = acquirePassphrase({ passphraseFile: g.passphraseFile, prompt: "Keystore passphrase: " });
|
|
2353
|
+
if (!verifyKeystorePassphrase(path, passphrase)) {
|
|
2354
|
+
throw new CLIError(`Incorrect passphrase for the keystore at ${path}; no session was created.`, "DECRYPT_ERROR", { path });
|
|
2355
|
+
}
|
|
2356
|
+
const verifierId = keystoreVerifierId(path);
|
|
2357
|
+
if (!verifierId) {
|
|
2358
|
+
throw new CLIError(`The keystore at ${path} has no verifier to bind a session to.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
2359
|
+
}
|
|
2360
|
+
const session = writeSession(defaultSessionPath(g), {
|
|
2361
|
+
keystorePath: path,
|
|
2362
|
+
verifierId,
|
|
2363
|
+
passphrase,
|
|
2364
|
+
ttlMs,
|
|
2365
|
+
allowMainnet: !!options.allowMainnet
|
|
2366
|
+
});
|
|
2367
|
+
print({ action: "keystore-unlock", data: { keystore: path, expiresAt: session.expiresAt, ttlSeconds: session.ttlSeconds } });
|
|
2368
|
+
});
|
|
2369
|
+
keystore.command("lock").description("Revoke the cached session so later commands prompt for the passphrase again (ADR 081).").action(() => {
|
|
2370
|
+
const g = globals();
|
|
2371
|
+
const sessionPath = defaultSessionPath(g);
|
|
2372
|
+
const cleared = clearSession(sessionPath);
|
|
2373
|
+
print({ action: "keystore-lock", data: { path: sessionPath, cleared } });
|
|
2374
|
+
});
|
|
2375
|
+
}
|
|
2376
|
+
function resolveSessionTtl(flag) {
|
|
2377
|
+
const fromFlag = blankToUndef(flag);
|
|
2378
|
+
const raw = fromFlag ?? blankToUndef(process.env[ENV_KEYSTORE_TTL]);
|
|
2379
|
+
if (raw === void 0) return DEFAULT_SESSION_TTL_MS;
|
|
2380
|
+
const source = fromFlag !== void 0 ? "--ttl" : `$${ENV_KEYSTORE_TTL}`;
|
|
2381
|
+
const ms = parseTtlToMs(raw);
|
|
2382
|
+
if (ms === void 0 || ms <= 0) {
|
|
2383
|
+
throw new CLIError(
|
|
2384
|
+
`Invalid ${source} "${raw}": expected seconds or a value with an s/m/h suffix, e.g. 3600, 45m, or 2h.`,
|
|
2385
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2386
|
+
{ value: raw, source }
|
|
2387
|
+
);
|
|
2388
|
+
}
|
|
2389
|
+
if (ms > MAX_SESSION_TTL_MS) {
|
|
2390
|
+
throw new CLIError(
|
|
2391
|
+
`${source} "${raw}" exceeds the 24h maximum for a cached passphrase.`,
|
|
2392
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2393
|
+
{ value: raw, source }
|
|
2394
|
+
);
|
|
2395
|
+
}
|
|
2396
|
+
return ms;
|
|
2397
|
+
}
|
|
2398
|
+
|
|
1688
2399
|
// src/commands/config.ts
|
|
1689
|
-
var
|
|
1690
|
-
var import_node_path5 = require("path");
|
|
2400
|
+
var import_node_fs10 = require("fs");
|
|
1691
2401
|
|
|
1692
2402
|
// src/config-schema.ts
|
|
1693
2403
|
var CONFIG_SCHEMA = {
|
|
@@ -1812,21 +2522,14 @@ function walk(obj, prefix, issues) {
|
|
|
1812
2522
|
// src/commands/config.ts
|
|
1813
2523
|
function registerConfigCommand(program, globals) {
|
|
1814
2524
|
const config = program.command("config").description("Read and write CLI configuration.");
|
|
1815
|
-
const path = () => globals().config ?? defaultConfigPath();
|
|
2525
|
+
const path = () => globals().config ?? defaultConfigPath(globals());
|
|
1816
2526
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
1817
2527
|
config.command("init").description("Create a default config file with one profile per network.").option("--force", "Overwrite an existing config file.", false).action((options) => {
|
|
1818
2528
|
const p = path();
|
|
1819
|
-
if ((0,
|
|
2529
|
+
if ((0, import_node_fs10.existsSync)(p) && !options.force) {
|
|
1820
2530
|
throw new CLIError(`Config already exists at ${p}. Use --force to overwrite.`, "INVALID_ARGUMENT_ERROR", { path: p });
|
|
1821
2531
|
}
|
|
1822
|
-
|
|
1823
|
-
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
1824
|
-
defaults: { output: "text" },
|
|
1825
|
-
profiles: Object.fromEntries(SUPPORTED_NETWORKS.map((n) => [n, {}]))
|
|
1826
|
-
};
|
|
1827
|
-
ensureDir((0, import_node_path5.dirname)(p), 448);
|
|
1828
|
-
writeFileAtomic(p, `${JSON.stringify(scaffold, null, 2)}
|
|
1829
|
-
`, 384);
|
|
2532
|
+
writeDefaultConfigFile(p);
|
|
1830
2533
|
print({ action: "config-init", data: { path: p } });
|
|
1831
2534
|
});
|
|
1832
2535
|
config.command("get [path]").description("Print a value at a dotted path, or the whole config.").option("--show-secrets", "Reveal secret values (RPC password, etc.) instead of redacting them.", false).action((dotted, opts) => {
|
|
@@ -1871,10 +2574,14 @@ function registerConfigCommand(program, globals) {
|
|
|
1871
2574
|
}
|
|
1872
2575
|
print({ action: "config-effective", data });
|
|
1873
2576
|
});
|
|
1874
|
-
config.command("path").description("Print the resolved config-file and keystore paths.").action(() => {
|
|
2577
|
+
config.command("path").description("Print the resolved home directory, config-file, and keystore paths.").action(() => {
|
|
2578
|
+
const g = globals();
|
|
1875
2579
|
const data = {
|
|
1876
|
-
|
|
1877
|
-
|
|
2580
|
+
home: resolveHome(g),
|
|
2581
|
+
config: g.config ?? defaultConfigPath(g),
|
|
2582
|
+
// Diagnostic command: report the path even when the config is malformed
|
|
2583
|
+
// (this is a command you run to find and fix a broken config).
|
|
2584
|
+
keystore: resolveKeystorePath(g, { lenient: true })
|
|
1878
2585
|
};
|
|
1879
2586
|
print({ action: "config-path", data });
|
|
1880
2587
|
});
|
|
@@ -1927,7 +2634,7 @@ function isStringScalarPath(dotted) {
|
|
|
1927
2634
|
// src/commands/profile.ts
|
|
1928
2635
|
function registerProfileCommand(program, globals) {
|
|
1929
2636
|
const profile = program.command("profile").description("Manage configuration profiles.");
|
|
1930
|
-
const path = () => globals().config ?? defaultConfigPath();
|
|
2637
|
+
const path = () => globals().config ?? defaultConfigPath(globals());
|
|
1931
2638
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
1932
2639
|
profile.command("add <name>").description("Add an empty profile.").action((name) => {
|
|
1933
2640
|
writeConfigFile(path(), (raw) => {
|
|
@@ -2000,14 +2707,14 @@ function completionScript(shell) {
|
|
|
2000
2707
|
}
|
|
2001
2708
|
|
|
2002
2709
|
// src/version.ts
|
|
2003
|
-
var
|
|
2710
|
+
var import_node_fs11 = require("fs");
|
|
2004
2711
|
var import_node_path6 = require("path");
|
|
2005
2712
|
var import_node_url = require("url");
|
|
2006
2713
|
function readVersion() {
|
|
2007
2714
|
let dir = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
2008
2715
|
for (let i = 0; i < 5; i++) {
|
|
2009
2716
|
try {
|
|
2010
|
-
const pkg = JSON.parse((0,
|
|
2717
|
+
const pkg = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(dir, "package.json"), "utf-8"));
|
|
2011
2718
|
if (pkg.name === "@did-btcr2/cli") return pkg.version;
|
|
2012
2719
|
} catch {
|
|
2013
2720
|
}
|
|
@@ -2035,17 +2742,19 @@ var DidBtcr2Cli = class {
|
|
|
2035
2742
|
* {@link keystoreApiFactory}.
|
|
2036
2743
|
*/
|
|
2037
2744
|
constructor(factory = defaultApiFactory, keystoreFactory = keystoreApiFactory) {
|
|
2038
|
-
this.program = new import_commander.Command("btcr2").version(`btcr2 ${VERSION}`, "-v, --version", "Output the current version").description("CLI tool for the did:btcr2 method").option("-o, --output <format>", "Output format <json|text> (default: config defaults.output, else text)").option("--verbose", "Verbose output", false).option("--quiet", "Suppress non-essential output", false).option("-c, --config <path>", "Path to config file (default:
|
|
2745
|
+
this.program = new import_commander.Command("btcr2").version(`btcr2 ${VERSION}`, "-v, --version", "Output the current version").description("CLI tool for the did:btcr2 method").option("-o, --output <format>", "Output format <json|text> (default: config defaults.output, else text)").option("--verbose", "Verbose output", false).option("--quiet", "Suppress non-essential output", false).option("--home <dir>", "btcr2 home directory holding config.json + keystore.json (default: ~/.btcr2, %LOCALAPPDATA%\\btcr2 on Windows; overrides $BTCR2_HOME)").option("-c, --config <path>", "Path to config file (default: <home>/config.json)").option("--profile <name>", "Config profile name (default: auto-detected from network)").option("--btc-rest <url>", "Override Bitcoin REST endpoint (Esplora API)").option("--btc-rpc-url <url>", "Override Bitcoin Core RPC endpoint").option("--btc-rpc-user <user>", "Bitcoin Core RPC username").option("--btc-rpc-pass <pass>", "Bitcoin Core RPC password").option("--cas-gateway <url>", "IPFS HTTP gateway for CAS reads (read-only)").option("--cas-rpc-url <url>", "IPFS HTTP RPC endpoint for a writable CAS (reads + writes; enables --publish-to-cas)").option("--btc-timeout <ms>", "Bitcoin REST/RPC request timeout in milliseconds (default: unbounded)").option("--cas-timeout <ms>", "CAS request timeout in milliseconds (default: 30000; 0 disables)").option("--btc-rest-header <header>", 'Extra Bitcoin REST header "Key: Value" (repeatable)', collectHeader, []).option("--btc-rpc-wallet <name>", "Bitcoin Core wallet name for wallet-scoped RPCs").option("--btc-rpc-header <header>", 'Extra Bitcoin Core RPC header "Key: Value" (repeatable)', collectHeader, []).option("--keystore <path>", "Path to the keystore file (default: <home>/keystore.json)").option("--passphrase-file <path>", "Read the keystore passphrase from a file (unattended use)").option("--signing-key <ref>", "Key for create/update/deactivate signing: a URN, fingerprint prefix, or name");
|
|
2039
2746
|
const globals = () => this.program.opts();
|
|
2040
2747
|
this.program.hook("preAction", () => {
|
|
2041
2748
|
const opts = this.program.opts();
|
|
2042
|
-
opts.output = resolveOutputFormat({ output: opts.output, config: opts.config });
|
|
2749
|
+
opts.output = resolveOutputFormat({ output: opts.output, config: opts.config, home: opts.home });
|
|
2043
2750
|
});
|
|
2751
|
+
registerInitCommand(this.program, globals);
|
|
2044
2752
|
registerCreateCommand(this.program, factory, keystoreFactory, globals);
|
|
2045
2753
|
registerResolveCommand(this.program, factory, globals);
|
|
2046
2754
|
registerUpdateCommand(this.program, keystoreFactory, globals);
|
|
2047
2755
|
registerDeactivateCommand(this.program, keystoreFactory, globals);
|
|
2048
2756
|
registerKeyCommand(this.program, keystoreFactory, globals);
|
|
2757
|
+
registerKeystoreCommand(this.program, globals);
|
|
2049
2758
|
registerConfigCommand(this.program, globals);
|
|
2050
2759
|
registerProfileCommand(this.program, globals);
|
|
2051
2760
|
registerCompletionCommand(this.program, globals);
|
|
@@ -2095,6 +2804,7 @@ function handleError(error, verbose) {
|
|
|
2095
2804
|
REDACTED,
|
|
2096
2805
|
SUPPORTED_NETWORKS,
|
|
2097
2806
|
VERSION,
|
|
2807
|
+
assertKeystoreAllowedForNetwork,
|
|
2098
2808
|
blankToUndef,
|
|
2099
2809
|
defaultApiFactory,
|
|
2100
2810
|
defaultConfigPath,
|
|
@@ -2114,7 +2824,9 @@ function handleError(error, verbose) {
|
|
|
2114
2824
|
registerConfigCommand,
|
|
2115
2825
|
registerCreateCommand,
|
|
2116
2826
|
registerDeactivateCommand,
|
|
2827
|
+
registerInitCommand,
|
|
2117
2828
|
registerKeyCommand,
|
|
2829
|
+
registerKeystoreCommand,
|
|
2118
2830
|
registerProfileCommand,
|
|
2119
2831
|
registerResolveCommand,
|
|
2120
2832
|
registerUpdateCommand,
|
|
@@ -2124,6 +2836,7 @@ function handleError(error, verbose) {
|
|
|
2124
2836
|
resolveDefaultNetwork,
|
|
2125
2837
|
resolveEffectiveConfig,
|
|
2126
2838
|
resolveKeystorePath,
|
|
2839
|
+
resolveKeystoreProtection,
|
|
2127
2840
|
resolveOutputFormat,
|
|
2128
2841
|
resolveSecretRef,
|
|
2129
2842
|
resolveSigningKeyRef,
|
|
@@ -2131,5 +2844,6 @@ function handleError(error, verbose) {
|
|
|
2131
2844
|
scrubUrlUserinfo,
|
|
2132
2845
|
setConfigPath,
|
|
2133
2846
|
unsetConfigPath,
|
|
2134
|
-
writeConfigFile
|
|
2847
|
+
writeConfigFile,
|
|
2848
|
+
writeDefaultConfigFile
|
|
2135
2849
|
});
|