@did-btcr2/cli 0.15.0 → 0.16.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 +563 -117
- 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 +63 -0
- package/dist/esm/src/commands/init.js.map +1 -0
- package/dist/esm/src/commands/keystore.js +81 -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 +85 -28
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/keystore/file-key-store.js +340 -32
- package/dist/esm/src/keystore/file-key-store.js.map +1 -1
- package/dist/esm/src/keystore/passphrase.js +40 -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/paths.js +59 -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 +10 -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 +86 -10
- package/dist/types/src/keystore/file-key-store.d.ts.map +1 -1
- package/dist/types/src/keystore/passphrase.d.ts +16 -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/paths.d.ts +54 -0
- package/dist/types/src/paths.d.ts.map +1 -0
- package/dist/types/src/types.d.ts +38 -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 +74 -0
- package/src/commands/keystore.ts +98 -0
- package/src/commands/profile.ts +1 -1
- package/src/commands/update.ts +3 -1
- package/src/config.ts +93 -32
- package/src/keystore/file-key-store.ts +455 -43
- package/src/keystore/passphrase.ts +48 -8
- package/src/keystore/paths.ts +6 -19
- package/src/paths.ts +79 -0
- package/src/types.ts +13 -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,14 +81,13 @@ 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_fs6 = 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
90
|
var import_node_fs5 = require("fs");
|
|
86
|
-
var import_node_os2 = require("os");
|
|
87
91
|
var import_node_path4 = require("path");
|
|
88
92
|
|
|
89
93
|
// src/error.ts
|
|
@@ -163,6 +167,7 @@ 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_utils2 = require("@noble/hashes/utils.js");
|
|
166
171
|
var import_base2 = require("@scure/base");
|
|
167
172
|
|
|
168
173
|
// src/keystore/envelope.ts
|
|
@@ -333,7 +338,7 @@ function withFileLock(lockPath, fn, options = {}) {
|
|
|
333
338
|
}
|
|
334
339
|
}
|
|
335
340
|
|
|
336
|
-
// src/
|
|
341
|
+
// src/paths.ts
|
|
337
342
|
var import_node_os = require("os");
|
|
338
343
|
var import_node_path2 = require("path");
|
|
339
344
|
|
|
@@ -351,14 +356,39 @@ function blankToUndef(value) {
|
|
|
351
356
|
return value.trim() === "" ? void 0 : value;
|
|
352
357
|
}
|
|
353
358
|
|
|
354
|
-
// src/
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
359
|
+
// src/paths.ts
|
|
360
|
+
var ENV_HOME = "BTCR2_HOME";
|
|
361
|
+
var CONFIG_FILENAME = "config.json";
|
|
362
|
+
var KEYSTORE_FILENAME = "keystore.json";
|
|
363
|
+
function resolveHome(overrides) {
|
|
364
|
+
return blankToUndef(overrides?.home) ?? blankToUndef(process.env[ENV_HOME]) ?? platformDefaultHome();
|
|
365
|
+
}
|
|
366
|
+
function platformDefaultHome() {
|
|
367
|
+
if (process.platform === "win32") {
|
|
368
|
+
const base = blankToUndef(process.env.LOCALAPPDATA) ?? blankToUndef(process.env.APPDATA) ?? (0, import_node_os.homedir)();
|
|
369
|
+
return (0, import_node_path2.join)(base, "btcr2");
|
|
370
|
+
}
|
|
371
|
+
return (0, import_node_path2.join)((0, import_node_os.homedir)(), ".btcr2");
|
|
372
|
+
}
|
|
373
|
+
function defaultConfigPath(overrides) {
|
|
374
|
+
return (0, import_node_path2.join)(resolveHome(overrides), CONFIG_FILENAME);
|
|
375
|
+
}
|
|
376
|
+
function defaultKeystorePath(overrides) {
|
|
377
|
+
return (0, import_node_path2.join)(resolveHome(overrides), KEYSTORE_FILENAME);
|
|
358
378
|
}
|
|
359
379
|
|
|
360
380
|
// src/keystore/file-key-store.ts
|
|
361
381
|
var KEYSTORE_VERSION = 1;
|
|
382
|
+
var VERIFIER_PLAINTEXT = (0, import_utils2.utf8ToBytes)("did-btcr2-keystore-verifier-v1");
|
|
383
|
+
function bytesEqual(a, b) {
|
|
384
|
+
if (a.length !== b.length) return false;
|
|
385
|
+
let diff = 0;
|
|
386
|
+
for (let i = 0; i < a.length; i++) diff |= a[i] ^ b[i];
|
|
387
|
+
return diff === 0;
|
|
388
|
+
}
|
|
389
|
+
function sameEnvelope(a, b) {
|
|
390
|
+
return JSON.stringify(a) === JSON.stringify(b);
|
|
391
|
+
}
|
|
362
392
|
var FileKeyStore = class {
|
|
363
393
|
#path;
|
|
364
394
|
#lockPath;
|
|
@@ -367,12 +397,15 @@ var FileKeyStore = class {
|
|
|
367
397
|
#argonParams;
|
|
368
398
|
#cache = /* @__PURE__ */ new Map();
|
|
369
399
|
#active;
|
|
400
|
+
#protection;
|
|
401
|
+
#verifier;
|
|
370
402
|
constructor(options) {
|
|
371
403
|
this.#path = options.path ?? defaultKeystorePath();
|
|
372
404
|
this.#lockPath = `${this.#path}.lock`;
|
|
373
405
|
this.#lockOptions = options.lock ?? {};
|
|
374
406
|
this.#getPassphrase = options.getPassphrase;
|
|
375
407
|
this.#argonParams = options.argonParams ?? DEFAULT_ARGON_PARAMS;
|
|
408
|
+
this.#protection = options.protection ?? "passphrase";
|
|
376
409
|
ensureDir((0, import_node_path3.dirname)(this.#path), 448);
|
|
377
410
|
this.#loadFromDisk();
|
|
378
411
|
}
|
|
@@ -396,7 +429,26 @@ var FileKeyStore = class {
|
|
|
396
429
|
{ version: parsed.v }
|
|
397
430
|
);
|
|
398
431
|
}
|
|
432
|
+
if (parsed.protection !== "none" && parsed.protection !== "passphrase") {
|
|
433
|
+
throw new KeyStoreError(
|
|
434
|
+
`Keystore at ${this.#path} has no recognized protection header; it was not written by this CLI.`,
|
|
435
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
436
|
+
{ path: this.#path }
|
|
437
|
+
);
|
|
438
|
+
}
|
|
439
|
+
this.#protection = parsed.protection;
|
|
440
|
+
this.#verifier = parsed.verifier;
|
|
399
441
|
this.#active = parsed.active;
|
|
442
|
+
const sealedPresent = Object.values(parsed.keys ?? {}).some(
|
|
443
|
+
(k) => k && typeof k === "object" && k.secret !== void 0
|
|
444
|
+
);
|
|
445
|
+
if (this.#protection === "passphrase" && sealedPresent && this.#verifier === void 0) {
|
|
446
|
+
throw new KeyStoreError(
|
|
447
|
+
`Keystore at ${this.#path} holds sealed keys but no passphrase verifier; it is corrupt or was tampered with.`,
|
|
448
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
449
|
+
{ path: this.#path }
|
|
450
|
+
);
|
|
451
|
+
}
|
|
400
452
|
for (const [id, stored] of Object.entries(parsed.keys ?? {})) {
|
|
401
453
|
let publicKey;
|
|
402
454
|
try {
|
|
@@ -416,24 +468,67 @@ var FileKeyStore = class {
|
|
|
416
468
|
{ path: this.#path, keyId: id }
|
|
417
469
|
);
|
|
418
470
|
}
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
471
|
+
const entry = { publicKey, ...stored.tags && { tags: stored.tags } };
|
|
472
|
+
if (stored.plainSecret !== void 0) {
|
|
473
|
+
if (this.#protection !== "none") {
|
|
474
|
+
throw new KeyStoreError(
|
|
475
|
+
`Keystore entry ${id} holds a plaintext secret in an encrypted keystore at ${this.#path}.`,
|
|
476
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
477
|
+
{ path: this.#path, keyId: id }
|
|
478
|
+
);
|
|
479
|
+
}
|
|
480
|
+
entry.plaintext = true;
|
|
481
|
+
entry.decrypted = this.#decodePlainSecret(stored.plainSecret, id);
|
|
482
|
+
} else if (stored.secret) {
|
|
483
|
+
if (this.#protection !== "passphrase") {
|
|
484
|
+
throw new KeyStoreError(
|
|
485
|
+
`Keystore entry ${id} holds a sealed secret in a dev keystore at ${this.#path}.`,
|
|
486
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
487
|
+
{ path: this.#path, keyId: id }
|
|
488
|
+
);
|
|
489
|
+
}
|
|
490
|
+
entry.secret = stored.secret;
|
|
491
|
+
}
|
|
492
|
+
this.#cache.set(id, entry);
|
|
424
493
|
}
|
|
425
494
|
}
|
|
495
|
+
/** Decodes and length-checks a dev-keystore plaintext secret. */
|
|
496
|
+
#decodePlainSecret(encoded, id) {
|
|
497
|
+
let secret;
|
|
498
|
+
try {
|
|
499
|
+
secret = import_base2.base64urlnopad.decode(encoded);
|
|
500
|
+
} catch {
|
|
501
|
+
throw new KeyStoreError(
|
|
502
|
+
`Keystore entry ${id} has a malformed plaintext secret.`,
|
|
503
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
504
|
+
{ path: this.#path, keyId: id }
|
|
505
|
+
);
|
|
506
|
+
}
|
|
507
|
+
if (secret.length !== 32) {
|
|
508
|
+
throw new KeyStoreError(
|
|
509
|
+
`Keystore entry ${id} has a ${secret.length}-byte secret; expected 32.`,
|
|
510
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
511
|
+
{ path: this.#path, keyId: id }
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return secret;
|
|
515
|
+
}
|
|
426
516
|
#flush() {
|
|
427
517
|
const keys = {};
|
|
428
518
|
for (const [id, entry] of this.#cache) {
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
}
|
|
519
|
+
const stored = { publicKey: import_base2.base64urlnopad.encode(entry.publicKey) };
|
|
520
|
+
if (entry.tags) stored.tags = entry.tags;
|
|
521
|
+
if (entry.plaintext && entry.decrypted) {
|
|
522
|
+
stored.plainSecret = import_base2.base64urlnopad.encode(entry.decrypted);
|
|
523
|
+
} else if (entry.secret) {
|
|
524
|
+
stored.secret = entry.secret;
|
|
525
|
+
}
|
|
526
|
+
keys[id] = stored;
|
|
434
527
|
}
|
|
435
528
|
const file = {
|
|
436
529
|
v: KEYSTORE_VERSION,
|
|
530
|
+
protection: this.#protection,
|
|
531
|
+
...this.#verifier && { verifier: this.#verifier },
|
|
437
532
|
...this.#active && { active: this.#active },
|
|
438
533
|
keys
|
|
439
534
|
};
|
|
@@ -445,7 +540,8 @@ var FileKeyStore = class {
|
|
|
445
540
|
* mutation applies on top of whatever other processes have written. Secrets
|
|
446
541
|
* already decrypted this session are carried over for entries whose sealed
|
|
447
542
|
* envelope is byte-identical on disk, so a mid-session write does not force a
|
|
448
|
-
* re-prompt for keys it did not touch.
|
|
543
|
+
* re-prompt for keys it did not touch. (Dev-keystore plaintext secrets are
|
|
544
|
+
* reloaded from disk, so they need no carry.)
|
|
449
545
|
*/
|
|
450
546
|
#reload() {
|
|
451
547
|
const carried = /* @__PURE__ */ new Map();
|
|
@@ -476,6 +572,53 @@ var FileKeyStore = class {
|
|
|
476
572
|
this.#flush();
|
|
477
573
|
}, this.#lockOptions);
|
|
478
574
|
}
|
|
575
|
+
/**
|
|
576
|
+
* Verifies a candidate passphrase against the keystore verifier, throwing when
|
|
577
|
+
* it does not open the sentinel. A no-op only on a fresh keystore whose
|
|
578
|
+
* passphrase has not been established yet (no verifier to check against).
|
|
579
|
+
*/
|
|
580
|
+
#assertPassphrase(passphrase) {
|
|
581
|
+
if (!this.#verifier) return;
|
|
582
|
+
let plain;
|
|
583
|
+
try {
|
|
584
|
+
plain = decryptSecret(this.#verifier, passphrase);
|
|
585
|
+
} catch {
|
|
586
|
+
throw new KeyStoreError(
|
|
587
|
+
`Incorrect passphrase for the keystore at ${this.#path}.`,
|
|
588
|
+
"DECRYPT_ERROR",
|
|
589
|
+
{ path: this.#path }
|
|
590
|
+
);
|
|
591
|
+
}
|
|
592
|
+
if (!bytesEqual(plain, VERIFIER_PLAINTEXT)) {
|
|
593
|
+
throw new KeyStoreError(
|
|
594
|
+
`Keystore verifier at ${this.#path} did not match; the file may be corrupt.`,
|
|
595
|
+
"KEYSTORE_CORRUPT_ERROR",
|
|
596
|
+
{ path: this.#path }
|
|
597
|
+
);
|
|
598
|
+
}
|
|
599
|
+
}
|
|
600
|
+
/** Passphrase for opening an existing secret: verified when a verifier exists. */
|
|
601
|
+
#openPassphrase() {
|
|
602
|
+
const passphrase = this.#getPassphrase();
|
|
603
|
+
this.#assertPassphrase(passphrase);
|
|
604
|
+
return passphrase;
|
|
605
|
+
}
|
|
606
|
+
/**
|
|
607
|
+
* Passphrase for sealing a secret. When the keystore's passphrase is already
|
|
608
|
+
* established (a verifier exists), prompt once and verify it. Otherwise this is
|
|
609
|
+
* establishment on a fresh keystore: prompt with confirm and mint a verifier for
|
|
610
|
+
* the caller to persist alongside the first sealed key.
|
|
611
|
+
*/
|
|
612
|
+
#sealPassphrase() {
|
|
613
|
+
if (this.#verifier) {
|
|
614
|
+
const passphrase2 = this.#getPassphrase();
|
|
615
|
+
this.#assertPassphrase(passphrase2);
|
|
616
|
+
return { passphrase: passphrase2 };
|
|
617
|
+
}
|
|
618
|
+
const passphrase = this.#getPassphrase({ confirm: true });
|
|
619
|
+
const newVerifier = encryptSecret(VERIFIER_PLAINTEXT, passphrase, this.#argonParams);
|
|
620
|
+
return { passphrase, newVerifier };
|
|
621
|
+
}
|
|
479
622
|
get(id) {
|
|
480
623
|
const entry = this.#cache.get(id);
|
|
481
624
|
if (!entry) return void 0;
|
|
@@ -483,13 +626,20 @@ var FileKeyStore = class {
|
|
|
483
626
|
publicKey: entry.publicKey,
|
|
484
627
|
...entry.tags && { tags: entry.tags }
|
|
485
628
|
};
|
|
486
|
-
if (entry.
|
|
629
|
+
if (entry.plaintext && entry.decrypted) {
|
|
630
|
+
const secret = entry.decrypted;
|
|
631
|
+
Object.defineProperty(result, "secretKey", {
|
|
632
|
+
configurable: true,
|
|
633
|
+
enumerable: false,
|
|
634
|
+
get: () => secret
|
|
635
|
+
});
|
|
636
|
+
} else if (entry.secret) {
|
|
487
637
|
const sealed = entry.secret;
|
|
488
638
|
Object.defineProperty(result, "secretKey", {
|
|
489
639
|
configurable: true,
|
|
490
640
|
enumerable: false,
|
|
491
641
|
get: () => {
|
|
492
|
-
entry.decrypted ??= decryptSecret(sealed, this.#
|
|
642
|
+
entry.decrypted ??= decryptSecret(sealed, this.#openPassphrase());
|
|
493
643
|
return entry.decrypted;
|
|
494
644
|
}
|
|
495
645
|
});
|
|
@@ -500,12 +650,45 @@ var FileKeyStore = class {
|
|
|
500
650
|
return this.#cache.has(id);
|
|
501
651
|
}
|
|
502
652
|
set(id, value) {
|
|
503
|
-
|
|
653
|
+
if (this.#protection === "none") {
|
|
654
|
+
this.#mutate(() => {
|
|
655
|
+
this.#cache.set(id, {
|
|
656
|
+
publicKey: value.publicKey,
|
|
657
|
+
...value.tags && { tags: value.tags },
|
|
658
|
+
...value.secretKey && { plaintext: true, decrypted: value.secretKey }
|
|
659
|
+
});
|
|
660
|
+
});
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
663
|
+
const verifierAtSeal = this.#verifier;
|
|
664
|
+
let sealed;
|
|
665
|
+
let newVerifier;
|
|
666
|
+
let passphrase;
|
|
667
|
+
if (value.secretKey) {
|
|
668
|
+
const resolved = this.#sealPassphrase();
|
|
669
|
+
passphrase = resolved.passphrase;
|
|
670
|
+
newVerifier = resolved.newVerifier;
|
|
671
|
+
sealed = encryptSecret(value.secretKey, passphrase, this.#argonParams);
|
|
672
|
+
}
|
|
504
673
|
this.#mutate(() => {
|
|
674
|
+
if (value.secretKey) {
|
|
675
|
+
if (this.#verifier === void 0 && newVerifier !== void 0) {
|
|
676
|
+
this.#verifier = newVerifier;
|
|
677
|
+
} else if (this.#verifier !== void 0 && passphrase !== void 0) {
|
|
678
|
+
if (verifierAtSeal !== void 0 && !sameEnvelope(this.#verifier, verifierAtSeal)) {
|
|
679
|
+
throw new KeyStoreError(
|
|
680
|
+
`The keystore passphrase at ${this.#path} changed concurrently; re-run the command.`,
|
|
681
|
+
"KEYSTORE_CONCURRENT_CHANGE_ERROR",
|
|
682
|
+
{ path: this.#path }
|
|
683
|
+
);
|
|
684
|
+
}
|
|
685
|
+
this.#assertPassphrase(passphrase);
|
|
686
|
+
}
|
|
687
|
+
}
|
|
505
688
|
this.#cache.set(id, {
|
|
506
689
|
publicKey: value.publicKey,
|
|
507
690
|
...value.tags && { tags: value.tags },
|
|
508
|
-
...
|
|
691
|
+
...sealed && { secret: sealed },
|
|
509
692
|
...value.secretKey && { decrypted: value.secretKey }
|
|
510
693
|
});
|
|
511
694
|
});
|
|
@@ -568,7 +751,112 @@ var FileKeyStore = class {
|
|
|
568
751
|
this.#active = id;
|
|
569
752
|
});
|
|
570
753
|
}
|
|
754
|
+
/** The keystore's protection mode. */
|
|
755
|
+
get protection() {
|
|
756
|
+
return this.#protection;
|
|
757
|
+
}
|
|
758
|
+
/**
|
|
759
|
+
* Re-seals every sealed secret and the verifier under a new passphrase (ADR
|
|
760
|
+
* 080), returning the number of secrets re-sealed. Verifies the current
|
|
761
|
+
* passphrase against the verifier first. Refused on a dev keystore, which has
|
|
762
|
+
* no passphrase.
|
|
763
|
+
*/
|
|
764
|
+
changePassphrase(oldPassphrase, newPassphrase) {
|
|
765
|
+
if (this.#protection === "none") {
|
|
766
|
+
throw new KeyStoreError(
|
|
767
|
+
`The keystore at ${this.#path} is an unencrypted dev keystore; it has no passphrase to change.`,
|
|
768
|
+
"KEYSTORE_PROTECTION_ERROR",
|
|
769
|
+
{ path: this.#path }
|
|
770
|
+
);
|
|
771
|
+
}
|
|
772
|
+
if (this.#verifier) this.#assertPassphrase(oldPassphrase);
|
|
773
|
+
const resealed = /* @__PURE__ */ new Map();
|
|
774
|
+
for (const [id, entry] of this.#cache) {
|
|
775
|
+
if (!entry.secret) continue;
|
|
776
|
+
let plain;
|
|
777
|
+
try {
|
|
778
|
+
plain = decryptSecret(entry.secret, oldPassphrase);
|
|
779
|
+
} catch {
|
|
780
|
+
throw new KeyStoreError(
|
|
781
|
+
`Incorrect current passphrase for the keystore at ${this.#path}.`,
|
|
782
|
+
"DECRYPT_ERROR",
|
|
783
|
+
{ path: this.#path }
|
|
784
|
+
);
|
|
785
|
+
}
|
|
786
|
+
resealed.set(id, { from: entry.secret, to: encryptSecret(plain, newPassphrase, this.#argonParams), plain });
|
|
787
|
+
}
|
|
788
|
+
const newVerifier = encryptSecret(VERIFIER_PLAINTEXT, newPassphrase, this.#argonParams);
|
|
789
|
+
let rekeyed = 0;
|
|
790
|
+
this.#mutate(() => {
|
|
791
|
+
for (const [id, entry] of this.#cache) {
|
|
792
|
+
if (!entry.secret) continue;
|
|
793
|
+
const pre = resealed.get(id);
|
|
794
|
+
if (!pre || !sameEnvelope(pre.from, entry.secret)) {
|
|
795
|
+
throw new KeyStoreError(
|
|
796
|
+
`The keystore at ${this.#path} changed while its passphrase was being changed; re-run the command.`,
|
|
797
|
+
"KEYSTORE_CONCURRENT_CHANGE_ERROR",
|
|
798
|
+
{ path: this.#path }
|
|
799
|
+
);
|
|
800
|
+
}
|
|
801
|
+
entry.secret = pre.to;
|
|
802
|
+
entry.decrypted = pre.plain;
|
|
803
|
+
rekeyed++;
|
|
804
|
+
}
|
|
805
|
+
this.#verifier = newVerifier;
|
|
806
|
+
});
|
|
807
|
+
return rekeyed;
|
|
808
|
+
}
|
|
571
809
|
};
|
|
810
|
+
function keystoreSummary(path) {
|
|
811
|
+
const absent = { protection: "absent", established: false, keyCount: 0, active: void 0 };
|
|
812
|
+
if (!(0, import_node_fs3.existsSync)(path)) return absent;
|
|
813
|
+
let parsed;
|
|
814
|
+
try {
|
|
815
|
+
parsed = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
|
|
816
|
+
} catch {
|
|
817
|
+
return absent;
|
|
818
|
+
}
|
|
819
|
+
const keys = parsed.keys && typeof parsed.keys === "object" ? parsed.keys : {};
|
|
820
|
+
const keyCount = Object.keys(keys).length;
|
|
821
|
+
const active = typeof parsed.active === "string" ? parsed.active : void 0;
|
|
822
|
+
if (parsed.protection === "none") {
|
|
823
|
+
return { protection: "dev", established: true, keyCount, active };
|
|
824
|
+
}
|
|
825
|
+
if (parsed.protection === "passphrase") {
|
|
826
|
+
return { protection: "encrypted", established: parsed.verifier !== void 0, keyCount, active };
|
|
827
|
+
}
|
|
828
|
+
return absent;
|
|
829
|
+
}
|
|
830
|
+
function keystoreProtection(path) {
|
|
831
|
+
return keystoreSummary(path).protection;
|
|
832
|
+
}
|
|
833
|
+
function initKeystore(path, options) {
|
|
834
|
+
ensureDir((0, import_node_path3.dirname)(path), 448);
|
|
835
|
+
let file;
|
|
836
|
+
if (options.protection === "none") {
|
|
837
|
+
file = { v: KEYSTORE_VERSION, protection: "none", keys: {} };
|
|
838
|
+
} else {
|
|
839
|
+
const passphrase = options.getPassphrase({ confirm: true });
|
|
840
|
+
const verifier = encryptSecret(VERIFIER_PLAINTEXT, passphrase, options.argonParams ?? DEFAULT_ARGON_PARAMS);
|
|
841
|
+
file = { v: KEYSTORE_VERSION, protection: "passphrase", verifier, keys: {} };
|
|
842
|
+
}
|
|
843
|
+
writeFileAtomic(path, `${JSON.stringify(file, null, 2)}
|
|
844
|
+
`, 384);
|
|
845
|
+
}
|
|
846
|
+
function changeKeystorePassphrase(path, oldPassphrase, newPassphrase, argonParams) {
|
|
847
|
+
const store = new FileKeyStore({
|
|
848
|
+
path,
|
|
849
|
+
...argonParams && { argonParams },
|
|
850
|
+
getPassphrase: () => {
|
|
851
|
+
throw new KeyStoreError(
|
|
852
|
+
`Unexpected passphrase prompt while changing the passphrase for ${path}.`,
|
|
853
|
+
"KEYSTORE_INTERNAL_ERROR",
|
|
854
|
+
{ path }
|
|
855
|
+
);
|
|
856
|
+
}
|
|
857
|
+
});
|
|
858
|
+
return store.changePassphrase(oldPassphrase, newPassphrase);
|
|
859
|
+
}
|
|
572
860
|
|
|
573
861
|
// src/keystore/file-backed-key-manager.ts
|
|
574
862
|
var FileBackedKeyManager = class {
|
|
@@ -629,10 +917,12 @@ var FileBackedKeyManager = class {
|
|
|
629
917
|
var import_node_fs4 = require("fs");
|
|
630
918
|
var ENV_KEYSTORE_PASSPHRASE = "BTCR2_KEYSTORE_PASSPHRASE";
|
|
631
919
|
function acquirePassphrase(options = {}) {
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
920
|
+
if (!options.forcePrompt) {
|
|
921
|
+
const fromEnv = process.env[ENV_KEYSTORE_PASSPHRASE];
|
|
922
|
+
if (fromEnv) return assertNonEmpty(fromEnv.replace(/\r?\n$/, ""));
|
|
923
|
+
if (options.passphraseFile) {
|
|
924
|
+
return assertNonEmpty((0, import_node_fs4.readFileSync)(options.passphraseFile, "utf-8").replace(/\r?\n$/, ""));
|
|
925
|
+
}
|
|
636
926
|
}
|
|
637
927
|
if (!process.stdin.isTTY) {
|
|
638
928
|
throw new KeyStoreError(
|
|
@@ -655,6 +945,12 @@ function assertNonEmpty(passphrase) {
|
|
|
655
945
|
}
|
|
656
946
|
return passphrase;
|
|
657
947
|
}
|
|
948
|
+
var IDLE_WAIT = new Int32Array(new SharedArrayBuffer(4));
|
|
949
|
+
var IDLE_POLL_MS = 20;
|
|
950
|
+
function dropLastUtf8Char(bytes) {
|
|
951
|
+
while (bytes.length > 0 && (bytes[bytes.length - 1] & 192) === 128) bytes.pop();
|
|
952
|
+
bytes.pop();
|
|
953
|
+
}
|
|
658
954
|
function promptHidden(label) {
|
|
659
955
|
process.stderr.write(label);
|
|
660
956
|
const stdin = process.stdin;
|
|
@@ -669,7 +965,10 @@ function promptHidden(label) {
|
|
|
669
965
|
read = (0, import_node_fs4.readSync)(stdin.fd, byte, 0, 1, null);
|
|
670
966
|
} catch (error) {
|
|
671
967
|
const code = error.code;
|
|
672
|
-
if (code === "EAGAIN")
|
|
968
|
+
if (code === "EAGAIN") {
|
|
969
|
+
Atomics.wait(IDLE_WAIT, 0, 0, IDLE_POLL_MS);
|
|
970
|
+
continue;
|
|
971
|
+
}
|
|
673
972
|
if (code === "EOF") break;
|
|
674
973
|
throw error;
|
|
675
974
|
}
|
|
@@ -680,7 +979,7 @@ function promptHidden(label) {
|
|
|
680
979
|
throw new KeyStoreError("Passphrase entry aborted.", "PASSPHRASE_REQUIRED_ERROR");
|
|
681
980
|
}
|
|
682
981
|
if (ch === 127 || ch === 8) {
|
|
683
|
-
bytes
|
|
982
|
+
dropLastUtf8Char(bytes);
|
|
684
983
|
continue;
|
|
685
984
|
}
|
|
686
985
|
bytes.push(ch);
|
|
@@ -702,6 +1001,16 @@ function writeConfigFile(path, mutate) {
|
|
|
702
1001
|
writeFileAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
703
1002
|
`, 384);
|
|
704
1003
|
}
|
|
1004
|
+
function writeDefaultConfigFile(path) {
|
|
1005
|
+
const scaffold = {
|
|
1006
|
+
schemaVersion: CONFIG_SCHEMA_VERSION,
|
|
1007
|
+
defaults: { output: "text" },
|
|
1008
|
+
profiles: Object.fromEntries(SUPPORTED_NETWORKS.map((n) => [n, {}]))
|
|
1009
|
+
};
|
|
1010
|
+
ensureDir((0, import_node_path4.dirname)(path), 448);
|
|
1011
|
+
writeFileAtomic(path, `${JSON.stringify(scaffold, null, 2)}
|
|
1012
|
+
`, 384);
|
|
1013
|
+
}
|
|
705
1014
|
function getConfigPath(config, path) {
|
|
706
1015
|
return path.split(".").reduce(
|
|
707
1016
|
(node, key) => node?.[key],
|
|
@@ -760,10 +1069,6 @@ function readEnvOverrides() {
|
|
|
760
1069
|
casRpcUrl: env(ENV_VARS.CAS_RPC_URL)
|
|
761
1070
|
};
|
|
762
1071
|
}
|
|
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
1072
|
function parseConfigFileRaw(path) {
|
|
768
1073
|
let content;
|
|
769
1074
|
try {
|
|
@@ -822,7 +1127,7 @@ function resolveActiveProfile(file, overrides) {
|
|
|
822
1127
|
return { name, network };
|
|
823
1128
|
}
|
|
824
1129
|
function resolveDefaultNetwork(overrides) {
|
|
825
|
-
const configPath = overrides?.config ?? defaultConfigPath();
|
|
1130
|
+
const configPath = overrides?.config ?? defaultConfigPath(overrides);
|
|
826
1131
|
const file = readConfigFile(configPath);
|
|
827
1132
|
const explicit = file?.defaults?.network;
|
|
828
1133
|
if (explicit && SUPPORTED_NETWORKS.includes(explicit)) return explicit;
|
|
@@ -833,7 +1138,7 @@ function resolveDefaultNetwork(overrides) {
|
|
|
833
1138
|
function profileNetworkMismatch(network, overrides) {
|
|
834
1139
|
let file;
|
|
835
1140
|
try {
|
|
836
|
-
file = readConfigFile(overrides?.config ?? defaultConfigPath());
|
|
1141
|
+
file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
837
1142
|
} catch {
|
|
838
1143
|
return void 0;
|
|
839
1144
|
}
|
|
@@ -847,7 +1152,7 @@ function resolveOutputFormat(options) {
|
|
|
847
1152
|
process.env.BTCR2_OUTPUT || void 0
|
|
848
1153
|
];
|
|
849
1154
|
try {
|
|
850
|
-
candidates.push(readConfigFile(options.config ?? defaultConfigPath())?.defaults?.output);
|
|
1155
|
+
candidates.push(readConfigFile(options.config ?? defaultConfigPath(options))?.defaults?.output);
|
|
851
1156
|
} catch {
|
|
852
1157
|
}
|
|
853
1158
|
for (const candidate of candidates) {
|
|
@@ -873,7 +1178,7 @@ function resolveRpcUnit(overrides, env, fileOverrides) {
|
|
|
873
1178
|
}
|
|
874
1179
|
function resolveConnectionConfig(network, overrides) {
|
|
875
1180
|
if (!network) return {};
|
|
876
|
-
const configPath = overrides?.config ?? defaultConfigPath();
|
|
1181
|
+
const configPath = overrides?.config ?? defaultConfigPath(overrides);
|
|
877
1182
|
const file = readConfigFile(configPath);
|
|
878
1183
|
const { name: activeProfile } = resolveActiveProfile(file, overrides);
|
|
879
1184
|
const profileName = activeProfile ?? network;
|
|
@@ -985,7 +1290,7 @@ function readRpcPassFile() {
|
|
|
985
1290
|
return readSecretFile(path, `file named by ${ENV_RPC_PASS_FILE}`);
|
|
986
1291
|
}
|
|
987
1292
|
function resolveBroadcastOptions(network, overrides, flags) {
|
|
988
|
-
const file = readConfigFile(overrides?.config ?? defaultConfigPath());
|
|
1293
|
+
const file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
989
1294
|
const { name: activeProfile } = resolveActiveProfile(file, overrides);
|
|
990
1295
|
const profileBtc = file?.profiles?.[activeProfile ?? network]?.btc;
|
|
991
1296
|
const options = {};
|
|
@@ -1007,7 +1312,7 @@ function parseFeeRate(raw) {
|
|
|
1007
1312
|
return rate;
|
|
1008
1313
|
}
|
|
1009
1314
|
function resolveEffectiveConfig(network, overrides) {
|
|
1010
|
-
const file = readConfigFile(overrides?.config ?? defaultConfigPath());
|
|
1315
|
+
const file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
1011
1316
|
const { name: activeProfile } = resolveActiveProfile(file, overrides);
|
|
1012
1317
|
const profileName = activeProfile ?? network;
|
|
1013
1318
|
const fileOv = file ? profileToOverrides(file, profileName) : {};
|
|
@@ -1096,14 +1401,38 @@ function defaultApiFactory(network, overrides) {
|
|
|
1096
1401
|
function buildKeystoreKms(overrides) {
|
|
1097
1402
|
return new FileBackedKeyManager({
|
|
1098
1403
|
path: resolveKeystorePath(overrides),
|
|
1099
|
-
|
|
1404
|
+
// The store decides when to confirm: it passes `confirm: true` only while
|
|
1405
|
+
// establishing a fresh keystore's passphrase, so a first-key typo is caught
|
|
1406
|
+
// by a second entry (ADR 080). confirm is a no-op for env/file sources.
|
|
1407
|
+
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: overrides?.passphraseFile, confirm: opts?.confirm })
|
|
1100
1408
|
});
|
|
1101
1409
|
}
|
|
1102
|
-
function
|
|
1103
|
-
return
|
|
1410
|
+
function resolveKeystoreProtection(overrides) {
|
|
1411
|
+
return keystoreProtection(resolveKeystorePath(overrides));
|
|
1412
|
+
}
|
|
1413
|
+
function assertKeystoreAllowedForNetwork(network, overrides) {
|
|
1414
|
+
if (network !== "bitcoin") return;
|
|
1415
|
+
if (resolveKeystoreProtection(overrides) !== "dev") return;
|
|
1416
|
+
const path = resolveKeystorePath(overrides);
|
|
1417
|
+
throw new CLIError(
|
|
1418
|
+
`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.`,
|
|
1419
|
+
"DEV_KEYSTORE_MAINNET_ERROR",
|
|
1420
|
+
{ path, network }
|
|
1421
|
+
);
|
|
1422
|
+
}
|
|
1423
|
+
function resolveKeystorePath(overrides, options) {
|
|
1424
|
+
const fromFlag = blankToUndef(overrides?.keystore);
|
|
1425
|
+
if (fromFlag) return fromFlag;
|
|
1426
|
+
let identity;
|
|
1427
|
+
try {
|
|
1428
|
+
identity = activeProfileIdentity(overrides);
|
|
1429
|
+
} catch (error) {
|
|
1430
|
+
if (!options?.lenient) throw error;
|
|
1431
|
+
}
|
|
1432
|
+
return blankToUndef(identity?.keystore) ?? defaultKeystorePath(overrides);
|
|
1104
1433
|
}
|
|
1105
1434
|
function activeProfileIdentity(overrides) {
|
|
1106
|
-
const file = readConfigFile(overrides?.config ?? defaultConfigPath());
|
|
1435
|
+
const file = readConfigFile(overrides?.config ?? defaultConfigPath(overrides));
|
|
1107
1436
|
const { name } = resolveActiveProfile(file, overrides);
|
|
1108
1437
|
return name ? file?.profiles?.[name]?.identity : void 0;
|
|
1109
1438
|
}
|
|
@@ -1128,6 +1457,85 @@ function deriveNetwork(did) {
|
|
|
1128
1457
|
return network;
|
|
1129
1458
|
}
|
|
1130
1459
|
|
|
1460
|
+
// src/output.ts
|
|
1461
|
+
var REDACTED = "********";
|
|
1462
|
+
var SECRET_KEY = /(pass|secret|token|auth|api[-_]?key|credential|bearer)/i;
|
|
1463
|
+
function isSecretKey(key) {
|
|
1464
|
+
return SECRET_KEY.test(key);
|
|
1465
|
+
}
|
|
1466
|
+
function scrubUrlUserinfo(value) {
|
|
1467
|
+
if (value === void 0) return void 0;
|
|
1468
|
+
return value.replace(/^([a-z][a-z0-9+.-]*:\/\/[^/@:]+):[^/@]*@/i, `$1:${REDACTED}@`);
|
|
1469
|
+
}
|
|
1470
|
+
function redactSecrets(value, keyName) {
|
|
1471
|
+
if (Array.isArray(value)) {
|
|
1472
|
+
return value.map((item) => redactSecrets(item));
|
|
1473
|
+
}
|
|
1474
|
+
if (value !== null && typeof value === "object") {
|
|
1475
|
+
const out = {};
|
|
1476
|
+
for (const [key, val] of Object.entries(value)) {
|
|
1477
|
+
out[key] = redactSecrets(val, key);
|
|
1478
|
+
}
|
|
1479
|
+
return out;
|
|
1480
|
+
}
|
|
1481
|
+
if (keyName !== void 0 && isSecretKey(keyName)) {
|
|
1482
|
+
return value === void 0 ? void 0 : REDACTED;
|
|
1483
|
+
}
|
|
1484
|
+
return typeof value === "string" ? scrubUrlUserinfo(value) : value;
|
|
1485
|
+
}
|
|
1486
|
+
function formatResult(result, options) {
|
|
1487
|
+
if (options.output === "json") {
|
|
1488
|
+
return JSON.stringify(result, null, 2);
|
|
1489
|
+
}
|
|
1490
|
+
const { data } = result;
|
|
1491
|
+
return typeof data === "string" ? data : JSON.stringify(data, null, 2);
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
// src/commands/init.ts
|
|
1495
|
+
function registerInitCommand(program, globals) {
|
|
1496
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
1497
|
+
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) => {
|
|
1498
|
+
const g = globals();
|
|
1499
|
+
const home = resolveHome(g);
|
|
1500
|
+
const configPath = g.config ?? defaultConfigPath(g);
|
|
1501
|
+
const keystorePath = resolveKeystorePath(g);
|
|
1502
|
+
ensureDir(home, 448);
|
|
1503
|
+
const created = [];
|
|
1504
|
+
if (!(0, import_node_fs6.existsSync)(configPath) || options.force) {
|
|
1505
|
+
writeDefaultConfigFile(configPath);
|
|
1506
|
+
created.push("config");
|
|
1507
|
+
}
|
|
1508
|
+
const keystoreExists = (0, import_node_fs6.existsSync)(keystorePath);
|
|
1509
|
+
if (keystoreExists && options.force && !g.quiet) {
|
|
1510
|
+
process.stderr.write(
|
|
1511
|
+
`note: a keystore already exists at ${keystorePath} and was left intact. To re-establish it (discarding its keys), run "btcr2 keystore init --force".
|
|
1512
|
+
`
|
|
1513
|
+
);
|
|
1514
|
+
}
|
|
1515
|
+
if (!keystoreExists) {
|
|
1516
|
+
if (options.dev && !g.quiet) {
|
|
1517
|
+
process.stderr.write(
|
|
1518
|
+
"warning: establishing an UNENCRYPTED dev keystore. Keys are stored in plaintext. Use it only for disposable testnet material; mainnet operations will be refused.\n"
|
|
1519
|
+
);
|
|
1520
|
+
}
|
|
1521
|
+
initKeystore(keystorePath, {
|
|
1522
|
+
protection: options.dev ? "none" : "passphrase",
|
|
1523
|
+
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
|
|
1524
|
+
});
|
|
1525
|
+
created.push("keystore");
|
|
1526
|
+
}
|
|
1527
|
+
const protection = keystoreSummary(keystorePath).protection;
|
|
1528
|
+
print({ action: "init", data: { home, config: configPath, keystore: keystorePath, created, protection } });
|
|
1529
|
+
if (!g.quiet && g.output !== "json") {
|
|
1530
|
+
process.stderr.write(`btcr2 home ready at ${home}. Next: btcr2 key generate --set-active
|
|
1531
|
+
`);
|
|
1532
|
+
}
|
|
1533
|
+
});
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1536
|
+
// src/commands/create.ts
|
|
1537
|
+
var import_utils3 = require("@noble/hashes/utils.js");
|
|
1538
|
+
|
|
1131
1539
|
// src/keystore/resolve-key-ref.ts
|
|
1132
1540
|
function fingerprintOf(id) {
|
|
1133
1541
|
return /^urn:kms:secp256k1:([0-9a-f]{32})$/.exec(id)?.[1];
|
|
@@ -1166,40 +1574,6 @@ function resolveKeyRef(kms, ref) {
|
|
|
1166
1574
|
throw new CLIError(`No key matches reference "${ref}".`, "KEY_NOT_FOUND_ERROR", { ref });
|
|
1167
1575
|
}
|
|
1168
1576
|
|
|
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
1577
|
// src/commands/create.ts
|
|
1204
1578
|
var EXPECTED_BYTES = {
|
|
1205
1579
|
k: { length: 33, label: "secp256k1 compressed public key (33 bytes)" },
|
|
@@ -1268,14 +1642,15 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
1268
1642
|
const publicKey2 = api2.kms.getPublicKey(keyId2);
|
|
1269
1643
|
const did2 = api2.createDid("deterministic", publicKey2, { network });
|
|
1270
1644
|
print(
|
|
1271
|
-
{ action: "create", data: did2, keyId: keyId2, publicKey: (0,
|
|
1645
|
+
{ action: "create", data: did2, keyId: keyId2, publicKey: (0, import_utils3.bytesToHex)(publicKey2) },
|
|
1272
1646
|
`Using stored key ${keyId2}.`
|
|
1273
1647
|
);
|
|
1274
1648
|
return;
|
|
1275
1649
|
}
|
|
1650
|
+
assertKeystoreAllowedForNetwork(network, overrides);
|
|
1276
1651
|
const api = keystoreFactory(void 0, overrides);
|
|
1277
1652
|
const { did, keyId } = api.generateDid({ network, setActive: true });
|
|
1278
|
-
const publicKey = (0,
|
|
1653
|
+
const publicKey = (0, import_utils3.bytesToHex)(api.kms.getPublicKey(keyId));
|
|
1279
1654
|
print(
|
|
1280
1655
|
{ action: "create", data: did, keyId, publicKey },
|
|
1281
1656
|
`Generated and stored key ${keyId} (now the active key).`
|
|
@@ -1284,6 +1659,7 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
1284
1659
|
}
|
|
1285
1660
|
function overridesFromGlobals(g) {
|
|
1286
1661
|
return {
|
|
1662
|
+
home: g.home,
|
|
1287
1663
|
config: g.config,
|
|
1288
1664
|
profile: g.profile,
|
|
1289
1665
|
keystore: g.keystore,
|
|
@@ -1305,7 +1681,7 @@ function parseGenesisBytes(hex, type) {
|
|
|
1305
1681
|
const expected = EXPECTED_BYTES[type];
|
|
1306
1682
|
let bytes;
|
|
1307
1683
|
try {
|
|
1308
|
-
bytes = (0,
|
|
1684
|
+
bytes = (0, import_utils3.hexToBytes)(hex.trim());
|
|
1309
1685
|
} catch {
|
|
1310
1686
|
throw new CLIError(
|
|
1311
1687
|
`Invalid bytes: not valid hex. Expected ${expected.label}.`,
|
|
@@ -1420,6 +1796,7 @@ function registerUpdateCommand(program, factory, globals) {
|
|
|
1420
1796
|
);
|
|
1421
1797
|
}
|
|
1422
1798
|
const network = deriveNetwork(did);
|
|
1799
|
+
assertKeystoreAllowedForNetwork(network, globals());
|
|
1423
1800
|
const api = factory(network, globals());
|
|
1424
1801
|
const keyId = resolveKeyRef(api.kms.kms, resolveSigningKeyRef(globals()));
|
|
1425
1802
|
const signer = new import_key_manager2.KeyManagerSigner(api.kms.kms, keyId);
|
|
@@ -1517,6 +1894,7 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
1517
1894
|
);
|
|
1518
1895
|
}
|
|
1519
1896
|
const network = deriveNetwork(did);
|
|
1897
|
+
assertKeystoreAllowedForNetwork(network, globals());
|
|
1520
1898
|
const api = factory(network, globals());
|
|
1521
1899
|
const keyId = resolveKeyRef(api.kms.kms, resolveSigningKeyRef(globals()));
|
|
1522
1900
|
const signer = new import_key_manager3.KeyManagerSigner(api.kms.kms, keyId);
|
|
@@ -1563,8 +1941,8 @@ function parseJsonArg2(flagName) {
|
|
|
1563
1941
|
|
|
1564
1942
|
// src/commands/key.ts
|
|
1565
1943
|
var import_keypair = require("@did-btcr2/keypair");
|
|
1566
|
-
var
|
|
1567
|
-
var
|
|
1944
|
+
var import_utils4 = require("@noble/hashes/utils.js");
|
|
1945
|
+
var import_node_fs7 = require("fs");
|
|
1568
1946
|
function registerKeyCommand(program, factory, globals) {
|
|
1569
1947
|
const key = program.command("key").description("Manage keypairs in the encrypted keystore.");
|
|
1570
1948
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
@@ -1573,7 +1951,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1573
1951
|
assertNameAvailable(api.kms.kms, options.name);
|
|
1574
1952
|
const setActive = options.setActive ?? false;
|
|
1575
1953
|
const id = api.kms.generateKey({ ...options.name && { tags: { name: options.name } }, setActive });
|
|
1576
|
-
print({ action: "key-generate", data: { keyId: id, publicKey: (0,
|
|
1954
|
+
print({ action: "key-generate", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(api.kms.getPublicKey(id)), active: setActive } });
|
|
1577
1955
|
});
|
|
1578
1956
|
key.command("list").alias("ls").description("List stored keys.").action(() => {
|
|
1579
1957
|
const kms = factory(void 0, globals()).kms.kms;
|
|
@@ -1593,7 +1971,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1593
1971
|
const kms = factory(void 0, globals()).kms.kms;
|
|
1594
1972
|
const id = resolveKeyRef(kms, ref);
|
|
1595
1973
|
const entry = kms.getEntry(id);
|
|
1596
|
-
print({ action: "key-show", data: { keyId: id, publicKey: (0,
|
|
1974
|
+
print({ action: "key-show", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(entry.publicKey), ...entry.tags && { tags: entry.tags } } });
|
|
1597
1975
|
});
|
|
1598
1976
|
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
1977
|
if (Boolean(options.secretFile) === Boolean(options.public)) {
|
|
@@ -1604,13 +1982,13 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1604
1982
|
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
1983
|
const setActive = options.setActive ?? false;
|
|
1606
1984
|
const id = api.kms.import(keyPair, { ...options.name && { tags: { name: options.name } }, setActive });
|
|
1607
|
-
print({ action: "key-import", data: { keyId: id, publicKey: (0,
|
|
1985
|
+
print({ action: "key-import", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(api.kms.getPublicKey(id)), watchOnly: !options.secretFile, active: setActive } });
|
|
1608
1986
|
});
|
|
1609
1987
|
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
1988
|
const api = factory(void 0, globals());
|
|
1611
1989
|
const id = resolveKeyRef(api.kms.kms, ref);
|
|
1612
1990
|
if (!options.secret) {
|
|
1613
|
-
print({ action: "key-export", data: { keyId: id, publicKey: (0,
|
|
1991
|
+
print({ action: "key-export", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(api.kms.getPublicKey(id)) } });
|
|
1614
1992
|
return;
|
|
1615
1993
|
}
|
|
1616
1994
|
if (!options.out) {
|
|
@@ -1621,7 +1999,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1621
1999
|
throw new CLIError(`Key ${id} is watch-only and has no secret to export.`, "INVALID_ARGUMENT_ERROR", { keyId: id });
|
|
1622
2000
|
}
|
|
1623
2001
|
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,
|
|
2002
|
+
writeSecretFile(options.out, (0, import_utils4.bytesToHex)(keyPair.secretKey.bytes));
|
|
1625
2003
|
print({ action: "key-export", data: { keyId: id, secretWrittenTo: options.out } });
|
|
1626
2004
|
});
|
|
1627
2005
|
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 +2024,7 @@ function assertNameAvailable(kms, name) {
|
|
|
1646
2024
|
function parseHex(hex, expectedBytes, label) {
|
|
1647
2025
|
let bytes;
|
|
1648
2026
|
try {
|
|
1649
|
-
bytes = (0,
|
|
2027
|
+
bytes = (0, import_utils4.hexToBytes)(hex.trim());
|
|
1650
2028
|
} catch {
|
|
1651
2029
|
throw new CLIError(`Invalid hex for ${label}.`, "INVALID_ARGUMENT_ERROR", { label });
|
|
1652
2030
|
}
|
|
@@ -1662,7 +2040,7 @@ function parseHex(hex, expectedBytes, label) {
|
|
|
1662
2040
|
function readHexFile(path, expectedBytes, label) {
|
|
1663
2041
|
let content;
|
|
1664
2042
|
try {
|
|
1665
|
-
content = (0,
|
|
2043
|
+
content = (0, import_node_fs7.readFileSync)(path, "utf-8");
|
|
1666
2044
|
} catch {
|
|
1667
2045
|
throw new CLIError(`Cannot read ${label} at ${path}.`, "INVALID_ARGUMENT_ERROR", { label, path });
|
|
1668
2046
|
}
|
|
@@ -1671,7 +2049,7 @@ function readHexFile(path, expectedBytes, label) {
|
|
|
1671
2049
|
function writeSecretFile(path, contents) {
|
|
1672
2050
|
let fd;
|
|
1673
2051
|
try {
|
|
1674
|
-
fd = (0,
|
|
2052
|
+
fd = (0, import_node_fs7.openSync)(path, "wx", 384);
|
|
1675
2053
|
} catch (error) {
|
|
1676
2054
|
if (error.code === "EEXIST") {
|
|
1677
2055
|
throw new CLIError(`Refusing to overwrite existing file ${path}. Choose a new --out path.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
@@ -1679,15 +2057,79 @@ function writeSecretFile(path, contents) {
|
|
|
1679
2057
|
throw error;
|
|
1680
2058
|
}
|
|
1681
2059
|
try {
|
|
1682
|
-
(0,
|
|
2060
|
+
(0, import_node_fs7.writeFileSync)(fd, contents);
|
|
1683
2061
|
} finally {
|
|
1684
|
-
(0,
|
|
2062
|
+
(0, import_node_fs7.closeSync)(fd);
|
|
1685
2063
|
}
|
|
1686
2064
|
}
|
|
1687
2065
|
|
|
2066
|
+
// src/commands/keystore.ts
|
|
2067
|
+
var import_node_fs8 = require("fs");
|
|
2068
|
+
function registerKeystoreCommand(program, globals) {
|
|
2069
|
+
const keystore = program.command("keystore").description("Establish, inspect, and re-key the keystore.");
|
|
2070
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
2071
|
+
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) => {
|
|
2072
|
+
const g = globals();
|
|
2073
|
+
const path = resolveKeystorePath(g);
|
|
2074
|
+
if ((0, import_node_fs8.existsSync)(path) && !options.force) {
|
|
2075
|
+
throw new CLIError(
|
|
2076
|
+
`A keystore already exists at ${path}. Use --force to re-establish it (this discards its keys).`,
|
|
2077
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2078
|
+
{ path }
|
|
2079
|
+
);
|
|
2080
|
+
}
|
|
2081
|
+
if ((0, import_node_fs8.existsSync)(path) && options.force && !g.quiet) {
|
|
2082
|
+
const { keyCount } = keystoreSummary(path);
|
|
2083
|
+
if (keyCount > 0) {
|
|
2084
|
+
process.stderr.write(
|
|
2085
|
+
`warning: re-establishing the keystore at ${path} permanently discards its ${keyCount} existing key(s).
|
|
2086
|
+
`
|
|
2087
|
+
);
|
|
2088
|
+
}
|
|
2089
|
+
}
|
|
2090
|
+
if (options.dev && !g.quiet) {
|
|
2091
|
+
process.stderr.write(
|
|
2092
|
+
"warning: creating an UNENCRYPTED dev keystore. Keys are stored in plaintext. Use it only for disposable testnet material; mainnet operations will be refused.\n"
|
|
2093
|
+
);
|
|
2094
|
+
}
|
|
2095
|
+
initKeystore(path, {
|
|
2096
|
+
protection: options.dev ? "none" : "passphrase",
|
|
2097
|
+
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
|
|
2098
|
+
});
|
|
2099
|
+
print({ action: "keystore-init", data: { path, protection: options.dev ? "dev" : "encrypted" } });
|
|
2100
|
+
});
|
|
2101
|
+
keystore.command("status").description("Show the keystore path, protection mode, and key count. Never decrypts or prompts.").action(() => {
|
|
2102
|
+
const g = globals();
|
|
2103
|
+
const path = resolveKeystorePath(g, { lenient: true });
|
|
2104
|
+
const summary = keystoreSummary(path);
|
|
2105
|
+
if (summary.protection === "dev" && !g.quiet && g.output !== "json") {
|
|
2106
|
+
process.stderr.write("warning: this is an UNENCRYPTED dev keystore; keys are stored in plaintext.\n");
|
|
2107
|
+
}
|
|
2108
|
+
print({ action: "keystore-status", data: { path, ...summary } });
|
|
2109
|
+
});
|
|
2110
|
+
keystore.command("change-passphrase").alias("passwd").description("Change the keystore passphrase, re-sealing every key under the new one. Encrypted keystores only.").action(() => {
|
|
2111
|
+
const g = globals();
|
|
2112
|
+
const path = resolveKeystorePath(g);
|
|
2113
|
+
const summary = keystoreSummary(path);
|
|
2114
|
+
if (summary.protection === "absent") {
|
|
2115
|
+
throw new CLIError(`No keystore at ${path}. Run "btcr2 keystore init" first.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
2116
|
+
}
|
|
2117
|
+
if (summary.protection === "dev") {
|
|
2118
|
+
throw new CLIError(
|
|
2119
|
+
`The keystore at ${path} is an unencrypted dev keystore; there is no passphrase to change.`,
|
|
2120
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2121
|
+
{ path }
|
|
2122
|
+
);
|
|
2123
|
+
}
|
|
2124
|
+
const oldPassphrase = acquirePassphrase({ passphraseFile: g.passphraseFile, prompt: "Current keystore passphrase: " });
|
|
2125
|
+
const newPassphrase = acquirePassphrase({ forcePrompt: true, confirm: true, prompt: "New keystore passphrase: " });
|
|
2126
|
+
const rekeyed = changeKeystorePassphrase(path, oldPassphrase, newPassphrase);
|
|
2127
|
+
print({ action: "keystore-change-passphrase", data: { path, rekeyed } });
|
|
2128
|
+
});
|
|
2129
|
+
}
|
|
2130
|
+
|
|
1688
2131
|
// src/commands/config.ts
|
|
1689
|
-
var
|
|
1690
|
-
var import_node_path5 = require("path");
|
|
2132
|
+
var import_node_fs9 = require("fs");
|
|
1691
2133
|
|
|
1692
2134
|
// src/config-schema.ts
|
|
1693
2135
|
var CONFIG_SCHEMA = {
|
|
@@ -1812,21 +2254,14 @@ function walk(obj, prefix, issues) {
|
|
|
1812
2254
|
// src/commands/config.ts
|
|
1813
2255
|
function registerConfigCommand(program, globals) {
|
|
1814
2256
|
const config = program.command("config").description("Read and write CLI configuration.");
|
|
1815
|
-
const path = () => globals().config ?? defaultConfigPath();
|
|
2257
|
+
const path = () => globals().config ?? defaultConfigPath(globals());
|
|
1816
2258
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
1817
2259
|
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
2260
|
const p = path();
|
|
1819
|
-
if ((0,
|
|
2261
|
+
if ((0, import_node_fs9.existsSync)(p) && !options.force) {
|
|
1820
2262
|
throw new CLIError(`Config already exists at ${p}. Use --force to overwrite.`, "INVALID_ARGUMENT_ERROR", { path: p });
|
|
1821
2263
|
}
|
|
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);
|
|
2264
|
+
writeDefaultConfigFile(p);
|
|
1830
2265
|
print({ action: "config-init", data: { path: p } });
|
|
1831
2266
|
});
|
|
1832
2267
|
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 +2306,14 @@ function registerConfigCommand(program, globals) {
|
|
|
1871
2306
|
}
|
|
1872
2307
|
print({ action: "config-effective", data });
|
|
1873
2308
|
});
|
|
1874
|
-
config.command("path").description("Print the resolved config-file and keystore paths.").action(() => {
|
|
2309
|
+
config.command("path").description("Print the resolved home directory, config-file, and keystore paths.").action(() => {
|
|
2310
|
+
const g = globals();
|
|
1875
2311
|
const data = {
|
|
1876
|
-
|
|
1877
|
-
|
|
2312
|
+
home: resolveHome(g),
|
|
2313
|
+
config: g.config ?? defaultConfigPath(g),
|
|
2314
|
+
// Diagnostic command: report the path even when the config is malformed
|
|
2315
|
+
// (this is a command you run to find and fix a broken config).
|
|
2316
|
+
keystore: resolveKeystorePath(g, { lenient: true })
|
|
1878
2317
|
};
|
|
1879
2318
|
print({ action: "config-path", data });
|
|
1880
2319
|
});
|
|
@@ -1927,7 +2366,7 @@ function isStringScalarPath(dotted) {
|
|
|
1927
2366
|
// src/commands/profile.ts
|
|
1928
2367
|
function registerProfileCommand(program, globals) {
|
|
1929
2368
|
const profile = program.command("profile").description("Manage configuration profiles.");
|
|
1930
|
-
const path = () => globals().config ?? defaultConfigPath();
|
|
2369
|
+
const path = () => globals().config ?? defaultConfigPath(globals());
|
|
1931
2370
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
1932
2371
|
profile.command("add <name>").description("Add an empty profile.").action((name) => {
|
|
1933
2372
|
writeConfigFile(path(), (raw) => {
|
|
@@ -2000,18 +2439,18 @@ function completionScript(shell) {
|
|
|
2000
2439
|
}
|
|
2001
2440
|
|
|
2002
2441
|
// src/version.ts
|
|
2003
|
-
var
|
|
2004
|
-
var
|
|
2442
|
+
var import_node_fs10 = require("fs");
|
|
2443
|
+
var import_node_path5 = require("path");
|
|
2005
2444
|
var import_node_url = require("url");
|
|
2006
2445
|
function readVersion() {
|
|
2007
|
-
let dir = (0,
|
|
2446
|
+
let dir = (0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
2008
2447
|
for (let i = 0; i < 5; i++) {
|
|
2009
2448
|
try {
|
|
2010
|
-
const pkg = JSON.parse((0,
|
|
2449
|
+
const pkg = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path5.join)(dir, "package.json"), "utf-8"));
|
|
2011
2450
|
if (pkg.name === "@did-btcr2/cli") return pkg.version;
|
|
2012
2451
|
} catch {
|
|
2013
2452
|
}
|
|
2014
|
-
dir = (0,
|
|
2453
|
+
dir = (0, import_node_path5.dirname)(dir);
|
|
2015
2454
|
}
|
|
2016
2455
|
return "0.0.0";
|
|
2017
2456
|
}
|
|
@@ -2035,17 +2474,19 @@ var DidBtcr2Cli = class {
|
|
|
2035
2474
|
* {@link keystoreApiFactory}.
|
|
2036
2475
|
*/
|
|
2037
2476
|
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:
|
|
2477
|
+
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
2478
|
const globals = () => this.program.opts();
|
|
2040
2479
|
this.program.hook("preAction", () => {
|
|
2041
2480
|
const opts = this.program.opts();
|
|
2042
|
-
opts.output = resolveOutputFormat({ output: opts.output, config: opts.config });
|
|
2481
|
+
opts.output = resolveOutputFormat({ output: opts.output, config: opts.config, home: opts.home });
|
|
2043
2482
|
});
|
|
2483
|
+
registerInitCommand(this.program, globals);
|
|
2044
2484
|
registerCreateCommand(this.program, factory, keystoreFactory, globals);
|
|
2045
2485
|
registerResolveCommand(this.program, factory, globals);
|
|
2046
2486
|
registerUpdateCommand(this.program, keystoreFactory, globals);
|
|
2047
2487
|
registerDeactivateCommand(this.program, keystoreFactory, globals);
|
|
2048
2488
|
registerKeyCommand(this.program, keystoreFactory, globals);
|
|
2489
|
+
registerKeystoreCommand(this.program, globals);
|
|
2049
2490
|
registerConfigCommand(this.program, globals);
|
|
2050
2491
|
registerProfileCommand(this.program, globals);
|
|
2051
2492
|
registerCompletionCommand(this.program, globals);
|
|
@@ -2095,6 +2536,7 @@ function handleError(error, verbose) {
|
|
|
2095
2536
|
REDACTED,
|
|
2096
2537
|
SUPPORTED_NETWORKS,
|
|
2097
2538
|
VERSION,
|
|
2539
|
+
assertKeystoreAllowedForNetwork,
|
|
2098
2540
|
blankToUndef,
|
|
2099
2541
|
defaultApiFactory,
|
|
2100
2542
|
defaultConfigPath,
|
|
@@ -2114,7 +2556,9 @@ function handleError(error, verbose) {
|
|
|
2114
2556
|
registerConfigCommand,
|
|
2115
2557
|
registerCreateCommand,
|
|
2116
2558
|
registerDeactivateCommand,
|
|
2559
|
+
registerInitCommand,
|
|
2117
2560
|
registerKeyCommand,
|
|
2561
|
+
registerKeystoreCommand,
|
|
2118
2562
|
registerProfileCommand,
|
|
2119
2563
|
registerResolveCommand,
|
|
2120
2564
|
registerUpdateCommand,
|
|
@@ -2124,6 +2568,7 @@ function handleError(error, verbose) {
|
|
|
2124
2568
|
resolveDefaultNetwork,
|
|
2125
2569
|
resolveEffectiveConfig,
|
|
2126
2570
|
resolveKeystorePath,
|
|
2571
|
+
resolveKeystoreProtection,
|
|
2127
2572
|
resolveOutputFormat,
|
|
2128
2573
|
resolveSecretRef,
|
|
2129
2574
|
resolveSigningKeyRef,
|
|
@@ -2131,5 +2576,6 @@ function handleError(error, verbose) {
|
|
|
2131
2576
|
scrubUrlUserinfo,
|
|
2132
2577
|
setConfigPath,
|
|
2133
2578
|
unsetConfigPath,
|
|
2134
|
-
writeConfigFile
|
|
2579
|
+
writeConfigFile,
|
|
2580
|
+
writeDefaultConfigFile
|
|
2135
2581
|
});
|