@did-btcr2/cli 0.16.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.
Files changed (40) hide show
  1. package/dist/.tsbuildinfo +1 -1
  2. package/dist/cjs/index.js +310 -42
  3. package/dist/esm/src/commands/init.js +7 -1
  4. package/dist/esm/src/commands/init.js.map +1 -1
  5. package/dist/esm/src/commands/keystore.js +107 -8
  6. package/dist/esm/src/commands/keystore.js.map +1 -1
  7. package/dist/esm/src/config.js +27 -6
  8. package/dist/esm/src/config.js.map +1 -1
  9. package/dist/esm/src/keystore/file-key-store.js +48 -0
  10. package/dist/esm/src/keystore/file-key-store.js.map +1 -1
  11. package/dist/esm/src/keystore/passphrase.js +13 -0
  12. package/dist/esm/src/keystore/passphrase.js.map +1 -1
  13. package/dist/esm/src/keystore/session.js +250 -0
  14. package/dist/esm/src/keystore/session.js.map +1 -0
  15. package/dist/esm/src/paths.js +12 -0
  16. package/dist/esm/src/paths.js.map +1 -1
  17. package/dist/esm/src/types.js.map +1 -1
  18. package/dist/types/src/commands/init.d.ts.map +1 -1
  19. package/dist/types/src/commands/keystore.d.ts +5 -4
  20. package/dist/types/src/commands/keystore.d.ts.map +1 -1
  21. package/dist/types/src/config.d.ts.map +1 -1
  22. package/dist/types/src/keystore/file-key-store.d.ts +16 -0
  23. package/dist/types/src/keystore/file-key-store.d.ts.map +1 -1
  24. package/dist/types/src/keystore/passphrase.d.ts +10 -0
  25. package/dist/types/src/keystore/passphrase.d.ts.map +1 -1
  26. package/dist/types/src/keystore/session.d.ts +105 -0
  27. package/dist/types/src/keystore/session.d.ts.map +1 -0
  28. package/dist/types/src/paths.d.ts +10 -0
  29. package/dist/types/src/paths.d.ts.map +1 -1
  30. package/dist/types/src/types.d.ts +15 -0
  31. package/dist/types/src/types.d.ts.map +1 -1
  32. package/package.json +4 -4
  33. package/src/commands/init.ts +7 -1
  34. package/src/commands/keystore.ts +143 -9
  35. package/src/config.ts +28 -6
  36. package/src/keystore/file-key-store.ts +43 -0
  37. package/src/keystore/passphrase.ts +23 -0
  38. package/src/keystore/session.ts +303 -0
  39. package/src/paths.ts +13 -0
  40. package/src/types.ts +4 -1
package/dist/cjs/index.js CHANGED
@@ -82,13 +82,13 @@ var import_common3 = require("@did-btcr2/common");
82
82
  var import_commander = require("commander");
83
83
 
84
84
  // src/commands/init.ts
85
- var import_node_fs6 = require("fs");
85
+ var import_node_fs7 = require("fs");
86
86
 
87
87
  // src/config.ts
88
88
  var import_api = require("@did-btcr2/api");
89
89
  var import_method = require("@did-btcr2/method");
90
- var import_node_fs5 = require("fs");
91
- var import_node_path4 = require("path");
90
+ var import_node_fs6 = require("fs");
91
+ var import_node_path5 = require("path");
92
92
 
93
93
  // src/error.ts
94
94
  var import_common = require("@did-btcr2/common");
@@ -167,6 +167,7 @@ var import_key_manager = require("@did-btcr2/key-manager");
167
167
  // src/keystore/file-key-store.ts
168
168
  var import_node_fs3 = require("fs");
169
169
  var import_node_path3 = require("path");
170
+ var import_sha2 = require("@noble/hashes/sha2.js");
170
171
  var import_utils2 = require("@noble/hashes/utils.js");
171
172
  var import_base2 = require("@scure/base");
172
173
 
@@ -360,6 +361,7 @@ function blankToUndef(value) {
360
361
  var ENV_HOME = "BTCR2_HOME";
361
362
  var CONFIG_FILENAME = "config.json";
362
363
  var KEYSTORE_FILENAME = "keystore.json";
364
+ var SESSION_FILENAME = "session.json";
363
365
  function resolveHome(overrides) {
364
366
  return blankToUndef(overrides?.home) ?? blankToUndef(process.env[ENV_HOME]) ?? platformDefaultHome();
365
367
  }
@@ -376,6 +378,9 @@ function defaultConfigPath(overrides) {
376
378
  function defaultKeystorePath(overrides) {
377
379
  return (0, import_node_path2.join)(resolveHome(overrides), KEYSTORE_FILENAME);
378
380
  }
381
+ function defaultSessionPath(overrides) {
382
+ return (0, import_node_path2.join)(resolveHome(overrides), SESSION_FILENAME);
383
+ }
379
384
 
380
385
  // src/keystore/file-key-store.ts
381
386
  var KEYSTORE_VERSION = 1;
@@ -830,6 +835,32 @@ function keystoreSummary(path) {
830
835
  function keystoreProtection(path) {
831
836
  return keystoreSummary(path).protection;
832
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
+ }
833
864
  function initKeystore(path, options) {
834
865
  ensureDir((0, import_node_path3.dirname)(path), 448);
835
866
  let file;
@@ -923,6 +954,8 @@ function acquirePassphrase(options = {}) {
923
954
  if (options.passphraseFile) {
924
955
  return assertNonEmpty((0, import_node_fs4.readFileSync)(options.passphraseFile, "utf-8").replace(/\r?\n$/, ""));
925
956
  }
957
+ const fromSession = options.beforePrompt?.();
958
+ if (fromSession) return assertNonEmpty(fromSession);
926
959
  }
927
960
  if (!process.stdin.isTTY) {
928
961
  throw new KeyStoreError(
@@ -991,13 +1024,155 @@ function promptHidden(label) {
991
1024
  return Buffer.from(bytes).toString("utf-8");
992
1025
  }
993
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
+
994
1169
  // src/config.ts
995
1170
  var CONFIG_SCHEMA_VERSION = 1;
996
1171
  function writeConfigFile(path, mutate) {
997
1172
  const raw = readConfigFile(path) ?? {};
998
1173
  mutate(raw);
999
1174
  raw.schemaVersion = CONFIG_SCHEMA_VERSION;
1000
- ensureDir((0, import_node_path4.dirname)(path), 448);
1175
+ ensureDir((0, import_node_path5.dirname)(path), 448);
1001
1176
  writeFileAtomic(path, `${JSON.stringify(raw, null, 2)}
1002
1177
  `, 384);
1003
1178
  }
@@ -1007,7 +1182,7 @@ function writeDefaultConfigFile(path) {
1007
1182
  defaults: { output: "text" },
1008
1183
  profiles: Object.fromEntries(SUPPORTED_NETWORKS.map((n) => [n, {}]))
1009
1184
  };
1010
- ensureDir((0, import_node_path4.dirname)(path), 448);
1185
+ ensureDir((0, import_node_path5.dirname)(path), 448);
1011
1186
  writeFileAtomic(path, `${JSON.stringify(scaffold, null, 2)}
1012
1187
  `, 384);
1013
1188
  }
@@ -1072,7 +1247,7 @@ function readEnvOverrides() {
1072
1247
  function parseConfigFileRaw(path) {
1073
1248
  let content;
1074
1249
  try {
1075
- content = (0, import_node_fs5.readFileSync)(path, "utf-8");
1250
+ content = (0, import_node_fs6.readFileSync)(path, "utf-8");
1076
1251
  } catch (error) {
1077
1252
  if (error.code === "ENOENT") return void 0;
1078
1253
  throw new CLIError(
@@ -1264,7 +1439,7 @@ function trimTrailingNewline(value) {
1264
1439
  }
1265
1440
  function readSecretFile(path, source) {
1266
1441
  try {
1267
- return trimTrailingNewline((0, import_node_fs5.readFileSync)(path, "utf-8"));
1442
+ return trimTrailingNewline((0, import_node_fs6.readFileSync)(path, "utf-8"));
1268
1443
  } catch (error) {
1269
1444
  throw new CLIError(
1270
1445
  `Could not read the RPC password ${source} at ${path}: ${error.message}`,
@@ -1398,13 +1573,28 @@ async function runDoctor(network, overrides) {
1398
1573
  function defaultApiFactory(network, overrides) {
1399
1574
  return (0, import_api.createApi)(resolveConnectionConfig(network, overrides));
1400
1575
  }
1401
- function buildKeystoreKms(overrides) {
1576
+ function buildKeystoreKms(overrides, network) {
1577
+ const keystorePath = resolveKeystorePath(overrides);
1578
+ const sessionPath = defaultSessionPath(overrides);
1579
+ const isMainnetOperation = network === "bitcoin";
1402
1580
  return new FileBackedKeyManager({
1403
- path: resolveKeystorePath(overrides),
1581
+ path: keystorePath,
1404
1582
  // The store decides when to confirm: it passes `confirm: true` only while
1405
1583
  // establishing a fresh keystore's passphrase, so a first-key typo is caught
1406
1584
  // 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 })
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
+ })
1408
1598
  });
1409
1599
  }
1410
1600
  function resolveKeystoreProtection(overrides) {
@@ -1442,7 +1632,7 @@ function resolveSigningKeyRef(overrides) {
1442
1632
  function keystoreApiFactory(network, overrides) {
1443
1633
  return (0, import_api.createApi)({
1444
1634
  ...resolveConnectionConfig(network, overrides),
1445
- kms: buildKeystoreKms(overrides)
1635
+ kms: buildKeystoreKms(overrides, network)
1446
1636
  });
1447
1637
  }
1448
1638
  function deriveNetwork(did) {
@@ -1501,11 +1691,11 @@ function registerInitCommand(program, globals) {
1501
1691
  const keystorePath = resolveKeystorePath(g);
1502
1692
  ensureDir(home, 448);
1503
1693
  const created = [];
1504
- if (!(0, import_node_fs6.existsSync)(configPath) || options.force) {
1694
+ if (!(0, import_node_fs7.existsSync)(configPath) || options.force) {
1505
1695
  writeDefaultConfigFile(configPath);
1506
1696
  created.push("config");
1507
1697
  }
1508
- const keystoreExists = (0, import_node_fs6.existsSync)(keystorePath);
1698
+ const keystoreExists = (0, import_node_fs7.existsSync)(keystorePath);
1509
1699
  if (keystoreExists && options.force && !g.quiet) {
1510
1700
  process.stderr.write(
1511
1701
  `note: a keystore already exists at ${keystorePath} and was left intact. To re-establish it (discarding its keys), run "btcr2 keystore init --force".
@@ -1522,6 +1712,7 @@ function registerInitCommand(program, globals) {
1522
1712
  protection: options.dev ? "none" : "passphrase",
1523
1713
  getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
1524
1714
  });
1715
+ clearSession(defaultSessionPath(g));
1525
1716
  created.push("keystore");
1526
1717
  }
1527
1718
  const protection = keystoreSummary(keystorePath).protection;
@@ -1534,7 +1725,7 @@ function registerInitCommand(program, globals) {
1534
1725
  }
1535
1726
 
1536
1727
  // src/commands/create.ts
1537
- var import_utils3 = require("@noble/hashes/utils.js");
1728
+ var import_utils4 = require("@noble/hashes/utils.js");
1538
1729
 
1539
1730
  // src/keystore/resolve-key-ref.ts
1540
1731
  function fingerprintOf(id) {
@@ -1642,7 +1833,7 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
1642
1833
  const publicKey2 = api2.kms.getPublicKey(keyId2);
1643
1834
  const did2 = api2.createDid("deterministic", publicKey2, { network });
1644
1835
  print(
1645
- { action: "create", data: did2, keyId: keyId2, publicKey: (0, import_utils3.bytesToHex)(publicKey2) },
1836
+ { action: "create", data: did2, keyId: keyId2, publicKey: (0, import_utils4.bytesToHex)(publicKey2) },
1646
1837
  `Using stored key ${keyId2}.`
1647
1838
  );
1648
1839
  return;
@@ -1650,7 +1841,7 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
1650
1841
  assertKeystoreAllowedForNetwork(network, overrides);
1651
1842
  const api = keystoreFactory(void 0, overrides);
1652
1843
  const { did, keyId } = api.generateDid({ network, setActive: true });
1653
- const publicKey = (0, import_utils3.bytesToHex)(api.kms.getPublicKey(keyId));
1844
+ const publicKey = (0, import_utils4.bytesToHex)(api.kms.getPublicKey(keyId));
1654
1845
  print(
1655
1846
  { action: "create", data: did, keyId, publicKey },
1656
1847
  `Generated and stored key ${keyId} (now the active key).`
@@ -1681,7 +1872,7 @@ function parseGenesisBytes(hex, type) {
1681
1872
  const expected = EXPECTED_BYTES[type];
1682
1873
  let bytes;
1683
1874
  try {
1684
- bytes = (0, import_utils3.hexToBytes)(hex.trim());
1875
+ bytes = (0, import_utils4.hexToBytes)(hex.trim());
1685
1876
  } catch {
1686
1877
  throw new CLIError(
1687
1878
  `Invalid bytes: not valid hex. Expected ${expected.label}.`,
@@ -1941,8 +2132,8 @@ function parseJsonArg2(flagName) {
1941
2132
 
1942
2133
  // src/commands/key.ts
1943
2134
  var import_keypair = require("@did-btcr2/keypair");
1944
- var import_utils4 = require("@noble/hashes/utils.js");
1945
- var import_node_fs7 = require("fs");
2135
+ var import_utils5 = require("@noble/hashes/utils.js");
2136
+ var import_node_fs8 = require("fs");
1946
2137
  function registerKeyCommand(program, factory, globals) {
1947
2138
  const key = program.command("key").description("Manage keypairs in the encrypted keystore.");
1948
2139
  const print = (result) => console.log(formatResult(result, globals()));
@@ -1951,7 +2142,7 @@ function registerKeyCommand(program, factory, globals) {
1951
2142
  assertNameAvailable(api.kms.kms, options.name);
1952
2143
  const setActive = options.setActive ?? false;
1953
2144
  const id = api.kms.generateKey({ ...options.name && { tags: { name: options.name } }, setActive });
1954
- print({ action: "key-generate", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(api.kms.getPublicKey(id)), active: setActive } });
2145
+ print({ action: "key-generate", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(api.kms.getPublicKey(id)), active: setActive } });
1955
2146
  });
1956
2147
  key.command("list").alias("ls").description("List stored keys.").action(() => {
1957
2148
  const kms = factory(void 0, globals()).kms.kms;
@@ -1971,7 +2162,7 @@ function registerKeyCommand(program, factory, globals) {
1971
2162
  const kms = factory(void 0, globals()).kms.kms;
1972
2163
  const id = resolveKeyRef(kms, ref);
1973
2164
  const entry = kms.getEntry(id);
1974
- print({ action: "key-show", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(entry.publicKey), ...entry.tags && { tags: entry.tags } } });
2165
+ print({ action: "key-show", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(entry.publicKey), ...entry.tags && { tags: entry.tags } } });
1975
2166
  });
1976
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) => {
1977
2168
  if (Boolean(options.secretFile) === Boolean(options.public)) {
@@ -1982,13 +2173,13 @@ function registerKeyCommand(program, factory, globals) {
1982
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") });
1983
2174
  const setActive = options.setActive ?? false;
1984
2175
  const id = api.kms.import(keyPair, { ...options.name && { tags: { name: options.name } }, setActive });
1985
- print({ action: "key-import", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(api.kms.getPublicKey(id)), watchOnly: !options.secretFile, active: setActive } });
2176
+ print({ action: "key-import", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(api.kms.getPublicKey(id)), watchOnly: !options.secretFile, active: setActive } });
1986
2177
  });
1987
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) => {
1988
2179
  const api = factory(void 0, globals());
1989
2180
  const id = resolveKeyRef(api.kms.kms, ref);
1990
2181
  if (!options.secret) {
1991
- print({ action: "key-export", data: { keyId: id, publicKey: (0, import_utils4.bytesToHex)(api.kms.getPublicKey(id)) } });
2182
+ print({ action: "key-export", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(api.kms.getPublicKey(id)) } });
1992
2183
  return;
1993
2184
  }
1994
2185
  if (!options.out) {
@@ -1999,7 +2190,7 @@ function registerKeyCommand(program, factory, globals) {
1999
2190
  throw new CLIError(`Key ${id} is watch-only and has no secret to export.`, "INVALID_ARGUMENT_ERROR", { keyId: id });
2000
2191
  }
2001
2192
  process.stderr.write("warning: writing an unencrypted secret key to disk. Protect this file and delete it when done.\n");
2002
- writeSecretFile(options.out, (0, import_utils4.bytesToHex)(keyPair.secretKey.bytes));
2193
+ writeSecretFile(options.out, (0, import_utils5.bytesToHex)(keyPair.secretKey.bytes));
2003
2194
  print({ action: "key-export", data: { keyId: id, secretWrittenTo: options.out } });
2004
2195
  });
2005
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) => {
@@ -2024,7 +2215,7 @@ function assertNameAvailable(kms, name) {
2024
2215
  function parseHex(hex, expectedBytes, label) {
2025
2216
  let bytes;
2026
2217
  try {
2027
- bytes = (0, import_utils4.hexToBytes)(hex.trim());
2218
+ bytes = (0, import_utils5.hexToBytes)(hex.trim());
2028
2219
  } catch {
2029
2220
  throw new CLIError(`Invalid hex for ${label}.`, "INVALID_ARGUMENT_ERROR", { label });
2030
2221
  }
@@ -2040,7 +2231,7 @@ function parseHex(hex, expectedBytes, label) {
2040
2231
  function readHexFile(path, expectedBytes, label) {
2041
2232
  let content;
2042
2233
  try {
2043
- content = (0, import_node_fs7.readFileSync)(path, "utf-8");
2234
+ content = (0, import_node_fs8.readFileSync)(path, "utf-8");
2044
2235
  } catch {
2045
2236
  throw new CLIError(`Cannot read ${label} at ${path}.`, "INVALID_ARGUMENT_ERROR", { label, path });
2046
2237
  }
@@ -2049,7 +2240,7 @@ function readHexFile(path, expectedBytes, label) {
2049
2240
  function writeSecretFile(path, contents) {
2050
2241
  let fd;
2051
2242
  try {
2052
- fd = (0, import_node_fs7.openSync)(path, "wx", 384);
2243
+ fd = (0, import_node_fs8.openSync)(path, "wx", 384);
2053
2244
  } catch (error) {
2054
2245
  if (error.code === "EEXIST") {
2055
2246
  throw new CLIError(`Refusing to overwrite existing file ${path}. Choose a new --out path.`, "INVALID_ARGUMENT_ERROR", { path });
@@ -2057,28 +2248,28 @@ function writeSecretFile(path, contents) {
2057
2248
  throw error;
2058
2249
  }
2059
2250
  try {
2060
- (0, import_node_fs7.writeFileSync)(fd, contents);
2251
+ (0, import_node_fs8.writeFileSync)(fd, contents);
2061
2252
  } finally {
2062
- (0, import_node_fs7.closeSync)(fd);
2253
+ (0, import_node_fs8.closeSync)(fd);
2063
2254
  }
2064
2255
  }
2065
2256
 
2066
2257
  // src/commands/keystore.ts
2067
- var import_node_fs8 = require("fs");
2258
+ var import_node_fs9 = require("fs");
2068
2259
  function registerKeystoreCommand(program, globals) {
2069
- const keystore = program.command("keystore").description("Establish, inspect, and re-key the keystore.");
2260
+ const keystore = program.command("keystore").description("Establish, inspect, re-key, and unlock the keystore.");
2070
2261
  const print = (result) => console.log(formatResult(result, globals()));
2071
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) => {
2072
2263
  const g = globals();
2073
2264
  const path = resolveKeystorePath(g);
2074
- if ((0, import_node_fs8.existsSync)(path) && !options.force) {
2265
+ if ((0, import_node_fs9.existsSync)(path) && !options.force) {
2075
2266
  throw new CLIError(
2076
2267
  `A keystore already exists at ${path}. Use --force to re-establish it (this discards its keys).`,
2077
2268
  "INVALID_ARGUMENT_ERROR",
2078
2269
  { path }
2079
2270
  );
2080
2271
  }
2081
- if ((0, import_node_fs8.existsSync)(path) && options.force && !g.quiet) {
2272
+ if ((0, import_node_fs9.existsSync)(path) && options.force && !g.quiet) {
2082
2273
  const { keyCount } = keystoreSummary(path);
2083
2274
  if (keyCount > 0) {
2084
2275
  process.stderr.write(
@@ -2096,16 +2287,18 @@ function registerKeystoreCommand(program, globals) {
2096
2287
  protection: options.dev ? "none" : "passphrase",
2097
2288
  getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
2098
2289
  });
2290
+ clearSession(defaultSessionPath(g));
2099
2291
  print({ action: "keystore-init", data: { path, protection: options.dev ? "dev" : "encrypted" } });
2100
2292
  });
2101
- keystore.command("status").description("Show the keystore path, protection mode, and key count. Never decrypts or prompts.").action(() => {
2293
+ keystore.command("status").description("Show the keystore path, protection mode, key count, and session state. Never decrypts or prompts.").action(() => {
2102
2294
  const g = globals();
2103
2295
  const path = resolveKeystorePath(g, { lenient: true });
2104
2296
  const summary = keystoreSummary(path);
2297
+ const session = readSessionStatus(defaultSessionPath(g), path, keystoreVerifierId(path));
2105
2298
  if (summary.protection === "dev" && !g.quiet && g.output !== "json") {
2106
2299
  process.stderr.write("warning: this is an UNENCRYPTED dev keystore; keys are stored in plaintext.\n");
2107
2300
  }
2108
- print({ action: "keystore-status", data: { path, ...summary } });
2301
+ print({ action: "keystore-status", data: { path, ...summary, session } });
2109
2302
  });
2110
2303
  keystore.command("change-passphrase").alias("passwd").description("Change the keystore passphrase, re-sealing every key under the new one. Encrypted keystores only.").action(() => {
2111
2304
  const g = globals();
@@ -2124,12 +2317,87 @@ function registerKeystoreCommand(program, globals) {
2124
2317
  const oldPassphrase = acquirePassphrase({ passphraseFile: g.passphraseFile, prompt: "Current keystore passphrase: " });
2125
2318
  const newPassphrase = acquirePassphrase({ forcePrompt: true, confirm: true, prompt: "New keystore passphrase: " });
2126
2319
  const rekeyed = changeKeystorePassphrase(path, oldPassphrase, newPassphrase);
2320
+ clearSession(defaultSessionPath(g));
2127
2321
  print({ action: "keystore-change-passphrase", data: { path, rekeyed } });
2128
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;
2129
2397
  }
2130
2398
 
2131
2399
  // src/commands/config.ts
2132
- var import_node_fs9 = require("fs");
2400
+ var import_node_fs10 = require("fs");
2133
2401
 
2134
2402
  // src/config-schema.ts
2135
2403
  var CONFIG_SCHEMA = {
@@ -2258,7 +2526,7 @@ function registerConfigCommand(program, globals) {
2258
2526
  const print = (result) => console.log(formatResult(result, globals()));
2259
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) => {
2260
2528
  const p = path();
2261
- if ((0, import_node_fs9.existsSync)(p) && !options.force) {
2529
+ if ((0, import_node_fs10.existsSync)(p) && !options.force) {
2262
2530
  throw new CLIError(`Config already exists at ${p}. Use --force to overwrite.`, "INVALID_ARGUMENT_ERROR", { path: p });
2263
2531
  }
2264
2532
  writeDefaultConfigFile(p);
@@ -2439,18 +2707,18 @@ function completionScript(shell) {
2439
2707
  }
2440
2708
 
2441
2709
  // src/version.ts
2442
- var import_node_fs10 = require("fs");
2443
- var import_node_path5 = require("path");
2710
+ var import_node_fs11 = require("fs");
2711
+ var import_node_path6 = require("path");
2444
2712
  var import_node_url = require("url");
2445
2713
  function readVersion() {
2446
- let dir = (0, import_node_path5.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
2714
+ let dir = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
2447
2715
  for (let i = 0; i < 5; i++) {
2448
2716
  try {
2449
- const pkg = JSON.parse((0, import_node_fs10.readFileSync)((0, import_node_path5.join)(dir, "package.json"), "utf-8"));
2717
+ const pkg = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(dir, "package.json"), "utf-8"));
2450
2718
  if (pkg.name === "@did-btcr2/cli") return pkg.version;
2451
2719
  } catch {
2452
2720
  }
2453
- dir = (0, import_node_path5.dirname)(dir);
2721
+ dir = (0, import_node_path6.dirname)(dir);
2454
2722
  }
2455
2723
  return "0.0.0";
2456
2724
  }
@@ -3,8 +3,9 @@ import { defaultConfigPath, resolveKeystorePath, writeDefaultConfigFile } from '
3
3
  import { ensureDir } from '../keystore/atomic.js';
4
4
  import { initKeystore, keystoreSummary } from '../keystore/file-key-store.js';
5
5
  import { acquirePassphrase } from '../keystore/passphrase.js';
6
+ import { clearSession } from '../keystore/session.js';
6
7
  import { formatResult } from '../output.js';
7
- import { resolveHome } from '../paths.js';
8
+ import { defaultSessionPath, resolveHome } from '../paths.js';
8
9
  /**
9
10
  * Registers the top-level `btcr2 init`: the one-command entry point that creates
10
11
  * the btcr2 home (ADR 079), writes a default config if none exists, and
@@ -51,6 +52,11 @@ export function registerInitCommand(program, globals) {
51
52
  protection: options.dev ? 'none' : 'passphrase',
52
53
  getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: 'New keystore passphrase: ' }),
53
54
  });
55
+ // A freshly established keystore mints a new verifier (or none, for --dev),
56
+ // so any cached session holds a passphrase for a keystore that no longer
57
+ // exists. Drop it, matching `keystore init` / `change-passphrase`, rather
58
+ // than leave a stale plaintext passphrase behind (ADR 081).
59
+ clearSession(defaultSessionPath(g));
54
60
  created.push('keystore');
55
61
  }
56
62
  const protection = keystoreSummary(keystorePath).protection;
@@ -1 +1 @@
1
- {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../../src/commands/init.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"init.js","sourceRoot":"","sources":["../../../../src/commands/init.ts"],"names":[],"mappings":"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"}