@did-btcr2/cli 0.16.0 → 0.18.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 +33 -8
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +640 -128
- package/dist/esm/src/cli.js +2 -1
- package/dist/esm/src/cli.js.map +1 -1
- package/dist/esm/src/commands/create.js +4 -0
- package/dist/esm/src/commands/create.js.map +1 -1
- package/dist/esm/src/commands/deactivate.js +2 -0
- package/dist/esm/src/commands/deactivate.js.map +1 -1
- package/dist/esm/src/commands/index.js +1 -0
- package/dist/esm/src/commands/index.js.map +1 -1
- package/dist/esm/src/commands/init.js +89 -40
- package/dist/esm/src/commands/init.js.map +1 -1
- package/dist/esm/src/commands/keystore.js +126 -8
- package/dist/esm/src/commands/keystore.js.map +1 -1
- package/dist/esm/src/commands/quickstart.js +158 -0
- package/dist/esm/src/commands/quickstart.js.map +1 -0
- package/dist/esm/src/commands/update.js +2 -0
- package/dist/esm/src/commands/update.js.map +1 -1
- package/dist/esm/src/config.js +93 -6
- package/dist/esm/src/config.js.map +1 -1
- package/dist/esm/src/hints.js +54 -0
- package/dist/esm/src/hints.js.map +1 -0
- package/dist/esm/src/keystore/file-key-store.js +48 -0
- package/dist/esm/src/keystore/file-key-store.js.map +1 -1
- package/dist/esm/src/keystore/passphrase.js +13 -0
- package/dist/esm/src/keystore/passphrase.js.map +1 -1
- package/dist/esm/src/keystore/session.js +250 -0
- package/dist/esm/src/keystore/session.js.map +1 -0
- package/dist/esm/src/paths.js +12 -0
- package/dist/esm/src/paths.js.map +1 -1
- package/dist/esm/src/types.js.map +1 -1
- package/dist/types/src/cli.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 +1 -0
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/commands/init.d.ts +52 -5
- package/dist/types/src/commands/init.d.ts.map +1 -1
- package/dist/types/src/commands/keystore.d.ts +41 -4
- package/dist/types/src/commands/keystore.d.ts.map +1 -1
- package/dist/types/src/commands/quickstart.d.ts +12 -0
- package/dist/types/src/commands/quickstart.d.ts.map +1 -0
- package/dist/types/src/commands/update.d.ts.map +1 -1
- package/dist/types/src/config.d.ts +35 -0
- package/dist/types/src/config.d.ts.map +1 -1
- package/dist/types/src/hints.d.ts +24 -0
- package/dist/types/src/hints.d.ts.map +1 -0
- package/dist/types/src/keystore/file-key-store.d.ts +16 -0
- package/dist/types/src/keystore/file-key-store.d.ts.map +1 -1
- package/dist/types/src/keystore/passphrase.d.ts +10 -0
- package/dist/types/src/keystore/passphrase.d.ts.map +1 -1
- package/dist/types/src/keystore/session.d.ts +105 -0
- package/dist/types/src/keystore/session.d.ts.map +1 -0
- package/dist/types/src/paths.d.ts +10 -0
- package/dist/types/src/paths.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +32 -0
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/cli.ts +2 -0
- package/src/commands/create.ts +4 -0
- package/src/commands/deactivate.ts +2 -0
- package/src/commands/index.ts +1 -0
- package/src/commands/init.ts +148 -50
- package/src/commands/keystore.ts +183 -9
- package/src/commands/quickstart.ts +209 -0
- package/src/commands/update.ts +2 -0
- package/src/config.ts +103 -6
- package/src/hints.ts +51 -0
- package/src/keystore/file-key-store.ts +43 -0
- package/src/keystore/passphrase.ts +23 -0
- package/src/keystore/session.ts +303 -0
- package/src/paths.ts +13 -0
- package/src/types.ts +16 -2
package/dist/cjs/index.js
CHANGED
|
@@ -29,6 +29,7 @@ __export(index_exports, {
|
|
|
29
29
|
SUPPORTED_NETWORKS: () => SUPPORTED_NETWORKS,
|
|
30
30
|
VERSION: () => VERSION,
|
|
31
31
|
assertKeystoreAllowedForNetwork: () => assertKeystoreAllowedForNetwork,
|
|
32
|
+
assertSupportedNetwork: () => assertSupportedNetwork,
|
|
32
33
|
blankToUndef: () => blankToUndef,
|
|
33
34
|
defaultApiFactory: () => defaultApiFactory,
|
|
34
35
|
defaultConfigPath: () => defaultConfigPath,
|
|
@@ -39,9 +40,11 @@ __export(index_exports, {
|
|
|
39
40
|
keystoreApiFactory: () => keystoreApiFactory,
|
|
40
41
|
parseConfigFileRaw: () => parseConfigFileRaw,
|
|
41
42
|
parseHeaderList: () => parseHeaderList,
|
|
43
|
+
persistDefaultNetwork: () => persistDefaultNetwork,
|
|
42
44
|
profileNetworkMismatch: () => profileNetworkMismatch,
|
|
43
45
|
profileToOverrides: () => profileToOverrides,
|
|
44
46
|
readConfigFile: () => readConfigFile,
|
|
47
|
+
readConfiguredDefaultNetwork: () => readConfiguredDefaultNetwork,
|
|
45
48
|
readEnvOverrides: () => readEnvOverrides,
|
|
46
49
|
redactSecrets: () => redactSecrets,
|
|
47
50
|
registerCompletionCommand: () => registerCompletionCommand,
|
|
@@ -52,6 +55,7 @@ __export(index_exports, {
|
|
|
52
55
|
registerKeyCommand: () => registerKeyCommand,
|
|
53
56
|
registerKeystoreCommand: () => registerKeystoreCommand,
|
|
54
57
|
registerProfileCommand: () => registerProfileCommand,
|
|
58
|
+
registerQuickstartCommand: () => registerQuickstartCommand,
|
|
55
59
|
registerResolveCommand: () => registerResolveCommand,
|
|
56
60
|
registerUpdateCommand: () => registerUpdateCommand,
|
|
57
61
|
resolveActiveProfile: () => resolveActiveProfile,
|
|
@@ -82,13 +86,13 @@ var import_common3 = require("@did-btcr2/common");
|
|
|
82
86
|
var import_commander = require("commander");
|
|
83
87
|
|
|
84
88
|
// src/commands/init.ts
|
|
85
|
-
var
|
|
89
|
+
var import_node_fs7 = require("fs");
|
|
86
90
|
|
|
87
91
|
// src/config.ts
|
|
88
92
|
var import_api = require("@did-btcr2/api");
|
|
89
93
|
var import_method = require("@did-btcr2/method");
|
|
90
|
-
var
|
|
91
|
-
var
|
|
94
|
+
var import_node_fs6 = require("fs");
|
|
95
|
+
var import_node_path5 = require("path");
|
|
92
96
|
|
|
93
97
|
// src/error.ts
|
|
94
98
|
var import_common = require("@did-btcr2/common");
|
|
@@ -167,6 +171,7 @@ var import_key_manager = require("@did-btcr2/key-manager");
|
|
|
167
171
|
// src/keystore/file-key-store.ts
|
|
168
172
|
var import_node_fs3 = require("fs");
|
|
169
173
|
var import_node_path3 = require("path");
|
|
174
|
+
var import_sha2 = require("@noble/hashes/sha2.js");
|
|
170
175
|
var import_utils2 = require("@noble/hashes/utils.js");
|
|
171
176
|
var import_base2 = require("@scure/base");
|
|
172
177
|
|
|
@@ -360,6 +365,7 @@ function blankToUndef(value) {
|
|
|
360
365
|
var ENV_HOME = "BTCR2_HOME";
|
|
361
366
|
var CONFIG_FILENAME = "config.json";
|
|
362
367
|
var KEYSTORE_FILENAME = "keystore.json";
|
|
368
|
+
var SESSION_FILENAME = "session.json";
|
|
363
369
|
function resolveHome(overrides) {
|
|
364
370
|
return blankToUndef(overrides?.home) ?? blankToUndef(process.env[ENV_HOME]) ?? platformDefaultHome();
|
|
365
371
|
}
|
|
@@ -376,6 +382,9 @@ function defaultConfigPath(overrides) {
|
|
|
376
382
|
function defaultKeystorePath(overrides) {
|
|
377
383
|
return (0, import_node_path2.join)(resolveHome(overrides), KEYSTORE_FILENAME);
|
|
378
384
|
}
|
|
385
|
+
function defaultSessionPath(overrides) {
|
|
386
|
+
return (0, import_node_path2.join)(resolveHome(overrides), SESSION_FILENAME);
|
|
387
|
+
}
|
|
379
388
|
|
|
380
389
|
// src/keystore/file-key-store.ts
|
|
381
390
|
var KEYSTORE_VERSION = 1;
|
|
@@ -830,6 +839,32 @@ function keystoreSummary(path) {
|
|
|
830
839
|
function keystoreProtection(path) {
|
|
831
840
|
return keystoreSummary(path).protection;
|
|
832
841
|
}
|
|
842
|
+
function keystoreVerifierId(path) {
|
|
843
|
+
if (!(0, import_node_fs3.existsSync)(path)) return void 0;
|
|
844
|
+
let parsed;
|
|
845
|
+
try {
|
|
846
|
+
parsed = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
|
|
847
|
+
} catch {
|
|
848
|
+
return void 0;
|
|
849
|
+
}
|
|
850
|
+
if (!parsed.verifier) return void 0;
|
|
851
|
+
return import_base2.base64urlnopad.encode((0, import_sha2.sha256)((0, import_utils2.utf8ToBytes)(JSON.stringify(parsed.verifier))));
|
|
852
|
+
}
|
|
853
|
+
function verifyKeystorePassphrase(path, passphrase) {
|
|
854
|
+
if (!(0, import_node_fs3.existsSync)(path)) return false;
|
|
855
|
+
let parsed;
|
|
856
|
+
try {
|
|
857
|
+
parsed = JSON.parse((0, import_node_fs3.readFileSync)(path, "utf-8"));
|
|
858
|
+
} catch {
|
|
859
|
+
return false;
|
|
860
|
+
}
|
|
861
|
+
if (parsed.protection !== "passphrase" || !parsed.verifier) return false;
|
|
862
|
+
try {
|
|
863
|
+
return bytesEqual(decryptSecret(parsed.verifier, passphrase), VERIFIER_PLAINTEXT);
|
|
864
|
+
} catch {
|
|
865
|
+
return false;
|
|
866
|
+
}
|
|
867
|
+
}
|
|
833
868
|
function initKeystore(path, options) {
|
|
834
869
|
ensureDir((0, import_node_path3.dirname)(path), 448);
|
|
835
870
|
let file;
|
|
@@ -923,6 +958,8 @@ function acquirePassphrase(options = {}) {
|
|
|
923
958
|
if (options.passphraseFile) {
|
|
924
959
|
return assertNonEmpty((0, import_node_fs4.readFileSync)(options.passphraseFile, "utf-8").replace(/\r?\n$/, ""));
|
|
925
960
|
}
|
|
961
|
+
const fromSession = options.beforePrompt?.();
|
|
962
|
+
if (fromSession) return assertNonEmpty(fromSession);
|
|
926
963
|
}
|
|
927
964
|
if (!process.stdin.isTTY) {
|
|
928
965
|
throw new KeyStoreError(
|
|
@@ -991,13 +1028,155 @@ function promptHidden(label) {
|
|
|
991
1028
|
return Buffer.from(bytes).toString("utf-8");
|
|
992
1029
|
}
|
|
993
1030
|
|
|
1031
|
+
// src/keystore/session.ts
|
|
1032
|
+
var import_node_fs5 = require("fs");
|
|
1033
|
+
var import_node_path4 = require("path");
|
|
1034
|
+
var import_utils3 = require("@noble/hashes/utils.js");
|
|
1035
|
+
var import_base3 = require("@scure/base");
|
|
1036
|
+
var SESSION_VERSION = 1;
|
|
1037
|
+
var DEFAULT_SESSION_TTL_MS = 60 * 60 * 1e3;
|
|
1038
|
+
var MAX_SESSION_TTL_MS = 24 * 60 * 60 * 1e3;
|
|
1039
|
+
var ENV_KEYSTORE_TTL = "BTCR2_KEYSTORE_TTL";
|
|
1040
|
+
function writeSession(sessionPath, input) {
|
|
1041
|
+
const createdAt = Date.now();
|
|
1042
|
+
const session = {
|
|
1043
|
+
v: SESSION_VERSION,
|
|
1044
|
+
keystore: (0, import_node_path4.resolve)(input.keystorePath),
|
|
1045
|
+
verifierId: input.verifierId,
|
|
1046
|
+
passphrase: import_base3.base64urlnopad.encode((0, import_utils3.utf8ToBytes)(input.passphrase)),
|
|
1047
|
+
allowMainnet: input.allowMainnet ?? false,
|
|
1048
|
+
createdAt,
|
|
1049
|
+
expiresAt: createdAt + input.ttlMs,
|
|
1050
|
+
ttlSeconds: Math.round(input.ttlMs / 1e3)
|
|
1051
|
+
};
|
|
1052
|
+
ensureDir((0, import_node_path4.dirname)(sessionPath), 448);
|
|
1053
|
+
writeFileAtomic(sessionPath, `${JSON.stringify(session, null, 2)}
|
|
1054
|
+
`, 384);
|
|
1055
|
+
return session;
|
|
1056
|
+
}
|
|
1057
|
+
function clearSession(sessionPath) {
|
|
1058
|
+
let existed = false;
|
|
1059
|
+
try {
|
|
1060
|
+
existed = (0, import_node_fs5.existsSync)(sessionPath);
|
|
1061
|
+
if (existed) (0, import_node_fs5.rmSync)(sessionPath, { force: true });
|
|
1062
|
+
} catch {
|
|
1063
|
+
existed = false;
|
|
1064
|
+
}
|
|
1065
|
+
sweepSessionTemps(sessionPath);
|
|
1066
|
+
return existed;
|
|
1067
|
+
}
|
|
1068
|
+
function sweepSessionTemps(sessionPath) {
|
|
1069
|
+
const dir = (0, import_node_path4.dirname)(sessionPath);
|
|
1070
|
+
const prefix = `.${(0, import_node_path4.basename)(sessionPath)}.`;
|
|
1071
|
+
try {
|
|
1072
|
+
for (const name of (0, import_node_fs5.readdirSync)(dir)) {
|
|
1073
|
+
if (name.startsWith(prefix) && name.endsWith(".tmp")) {
|
|
1074
|
+
try {
|
|
1075
|
+
(0, import_node_fs5.rmSync)((0, import_node_path4.join)(dir, name), { force: true });
|
|
1076
|
+
} catch {
|
|
1077
|
+
}
|
|
1078
|
+
}
|
|
1079
|
+
}
|
|
1080
|
+
} catch {
|
|
1081
|
+
}
|
|
1082
|
+
}
|
|
1083
|
+
function readLiveSessionPassphrase(sessionPath, keystorePath, currentVerifierId, isMainnetOperation = false) {
|
|
1084
|
+
const verdict = evaluateSession(sessionPath, keystorePath, currentVerifierId, Date.now());
|
|
1085
|
+
if (verdict.status === "live") {
|
|
1086
|
+
if (isMainnetOperation && !verdict.session.allowMainnet) return void 0;
|
|
1087
|
+
try {
|
|
1088
|
+
return Buffer.from(import_base3.base64urlnopad.decode(verdict.session.passphrase)).toString("utf-8");
|
|
1089
|
+
} catch {
|
|
1090
|
+
clearSession(sessionPath);
|
|
1091
|
+
return void 0;
|
|
1092
|
+
}
|
|
1093
|
+
}
|
|
1094
|
+
if (verdict.status === "expired" || verdict.status === "stale" || verdict.status === "future" || verdict.status === "malformed") {
|
|
1095
|
+
clearSession(sessionPath);
|
|
1096
|
+
}
|
|
1097
|
+
return void 0;
|
|
1098
|
+
}
|
|
1099
|
+
function readSessionStatus(sessionPath, keystorePath, currentVerifierId) {
|
|
1100
|
+
const now = Date.now();
|
|
1101
|
+
const verdict = evaluateSession(sessionPath, keystorePath, currentVerifierId, now);
|
|
1102
|
+
if (verdict.status !== "live") return { active: false };
|
|
1103
|
+
return {
|
|
1104
|
+
active: true,
|
|
1105
|
+
expiresAt: verdict.session.expiresAt,
|
|
1106
|
+
secondsRemaining: Math.max(0, Math.round((verdict.session.expiresAt - now) / 1e3)),
|
|
1107
|
+
allowMainnet: verdict.session.allowMainnet
|
|
1108
|
+
};
|
|
1109
|
+
}
|
|
1110
|
+
function parseTtlToMs(raw) {
|
|
1111
|
+
const match = /^(\d+)([smh]?)$/.exec(raw.trim());
|
|
1112
|
+
if (!match) return void 0;
|
|
1113
|
+
const n = Number(match[1]);
|
|
1114
|
+
if (!Number.isFinite(n)) return void 0;
|
|
1115
|
+
const unitMs = match[2] === "h" ? 36e5 : match[2] === "m" ? 6e4 : 1e3;
|
|
1116
|
+
return n * unitMs;
|
|
1117
|
+
}
|
|
1118
|
+
function evaluateSession(sessionPath, keystorePath, currentVerifierId, now) {
|
|
1119
|
+
const session = secureReadSession(sessionPath);
|
|
1120
|
+
if (!session) return { status: "none" };
|
|
1121
|
+
if (session.v !== SESSION_VERSION) return { status: "malformed" };
|
|
1122
|
+
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") {
|
|
1123
|
+
return { status: "malformed" };
|
|
1124
|
+
}
|
|
1125
|
+
if (session.createdAt > now) return { status: "future" };
|
|
1126
|
+
if (now >= session.expiresAt) return { status: "expired" };
|
|
1127
|
+
if ((0, import_node_path4.resolve)(session.keystore) !== (0, import_node_path4.resolve)(keystorePath)) return { status: "foreign" };
|
|
1128
|
+
if (currentVerifierId === void 0 || session.verifierId !== currentVerifierId) return { status: "stale" };
|
|
1129
|
+
return { status: "live", session };
|
|
1130
|
+
}
|
|
1131
|
+
function secureReadSession(sessionPath) {
|
|
1132
|
+
let raw;
|
|
1133
|
+
if (process.platform === "win32") {
|
|
1134
|
+
try {
|
|
1135
|
+
raw = (0, import_node_fs5.readFileSync)(sessionPath, "utf-8");
|
|
1136
|
+
} catch {
|
|
1137
|
+
return void 0;
|
|
1138
|
+
}
|
|
1139
|
+
} else {
|
|
1140
|
+
let fd;
|
|
1141
|
+
try {
|
|
1142
|
+
fd = (0, import_node_fs5.openSync)(sessionPath, import_node_fs5.constants.O_RDONLY | import_node_fs5.constants.O_NOFOLLOW);
|
|
1143
|
+
} catch {
|
|
1144
|
+
return void 0;
|
|
1145
|
+
}
|
|
1146
|
+
try {
|
|
1147
|
+
const st = (0, import_node_fs5.fstatSync)(fd);
|
|
1148
|
+
const myUid = typeof process.getuid === "function" ? process.getuid() : void 0;
|
|
1149
|
+
if (!st.isFile() || myUid !== void 0 && st.uid !== myUid || (st.mode & 63) !== 0) {
|
|
1150
|
+
try {
|
|
1151
|
+
(0, import_node_fs5.rmSync)(sessionPath, { force: true });
|
|
1152
|
+
} catch {
|
|
1153
|
+
}
|
|
1154
|
+
return void 0;
|
|
1155
|
+
}
|
|
1156
|
+
raw = (0, import_node_fs5.readFileSync)(fd, "utf-8");
|
|
1157
|
+
} catch {
|
|
1158
|
+
return void 0;
|
|
1159
|
+
} finally {
|
|
1160
|
+
try {
|
|
1161
|
+
(0, import_node_fs5.closeSync)(fd);
|
|
1162
|
+
} catch {
|
|
1163
|
+
}
|
|
1164
|
+
}
|
|
1165
|
+
}
|
|
1166
|
+
try {
|
|
1167
|
+
return JSON.parse(raw);
|
|
1168
|
+
} catch {
|
|
1169
|
+
return void 0;
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
|
|
994
1173
|
// src/config.ts
|
|
995
1174
|
var CONFIG_SCHEMA_VERSION = 1;
|
|
996
1175
|
function writeConfigFile(path, mutate) {
|
|
997
1176
|
const raw = readConfigFile(path) ?? {};
|
|
998
1177
|
mutate(raw);
|
|
999
1178
|
raw.schemaVersion = CONFIG_SCHEMA_VERSION;
|
|
1000
|
-
ensureDir((0,
|
|
1179
|
+
ensureDir((0, import_node_path5.dirname)(path), 448);
|
|
1001
1180
|
writeFileAtomic(path, `${JSON.stringify(raw, null, 2)}
|
|
1002
1181
|
`, 384);
|
|
1003
1182
|
}
|
|
@@ -1007,7 +1186,7 @@ function writeDefaultConfigFile(path) {
|
|
|
1007
1186
|
defaults: { output: "text" },
|
|
1008
1187
|
profiles: Object.fromEntries(SUPPORTED_NETWORKS.map((n) => [n, {}]))
|
|
1009
1188
|
};
|
|
1010
|
-
ensureDir((0,
|
|
1189
|
+
ensureDir((0, import_node_path5.dirname)(path), 448);
|
|
1011
1190
|
writeFileAtomic(path, `${JSON.stringify(scaffold, null, 2)}
|
|
1012
1191
|
`, 384);
|
|
1013
1192
|
}
|
|
@@ -1072,7 +1251,7 @@ function readEnvOverrides() {
|
|
|
1072
1251
|
function parseConfigFileRaw(path) {
|
|
1073
1252
|
let content;
|
|
1074
1253
|
try {
|
|
1075
|
-
content = (0,
|
|
1254
|
+
content = (0, import_node_fs6.readFileSync)(path, "utf-8");
|
|
1076
1255
|
} catch (error) {
|
|
1077
1256
|
if (error.code === "ENOENT") return void 0;
|
|
1078
1257
|
throw new CLIError(
|
|
@@ -1135,6 +1314,45 @@ function resolveDefaultNetwork(overrides) {
|
|
|
1135
1314
|
if (network) return network;
|
|
1136
1315
|
return "regtest";
|
|
1137
1316
|
}
|
|
1317
|
+
function readConfiguredDefaultNetwork(overrides) {
|
|
1318
|
+
const configPath = overrides?.config ?? defaultConfigPath(overrides);
|
|
1319
|
+
let raw;
|
|
1320
|
+
try {
|
|
1321
|
+
raw = parseConfigFileRaw(configPath);
|
|
1322
|
+
} catch {
|
|
1323
|
+
return void 0;
|
|
1324
|
+
}
|
|
1325
|
+
const value = raw ? getConfigPath(raw, "defaults.network") : void 0;
|
|
1326
|
+
return typeof value === "string" && SUPPORTED_NETWORKS.includes(value) ? value : void 0;
|
|
1327
|
+
}
|
|
1328
|
+
function persistDefaultNetwork(configPath, opts) {
|
|
1329
|
+
const raw = parseConfigFileRaw(configPath);
|
|
1330
|
+
const rawValue = raw ? getConfigPath(raw, "defaults.network") : void 0;
|
|
1331
|
+
const rawNetwork = typeof rawValue === "string" && SUPPORTED_NETWORKS.includes(rawValue) ? rawValue : void 0;
|
|
1332
|
+
if (opts.explicit) {
|
|
1333
|
+
if (opts.explicit !== rawNetwork) {
|
|
1334
|
+
writeConfigFile(configPath, (r) => setConfigPath(r, "defaults.network", opts.explicit));
|
|
1335
|
+
return { network: opts.explicit, wrote: true };
|
|
1336
|
+
}
|
|
1337
|
+
return { network: opts.explicit, wrote: false };
|
|
1338
|
+
}
|
|
1339
|
+
if (rawNetwork) return { network: rawNetwork, wrote: false };
|
|
1340
|
+
if (opts.fallback) {
|
|
1341
|
+
writeConfigFile(configPath, (r) => setConfigPath(r, "defaults.network", opts.fallback));
|
|
1342
|
+
return { network: opts.fallback, wrote: true };
|
|
1343
|
+
}
|
|
1344
|
+
return { network: resolveDefaultNetwork(opts.overrides), wrote: false };
|
|
1345
|
+
}
|
|
1346
|
+
function assertSupportedNetwork(value) {
|
|
1347
|
+
if (!SUPPORTED_NETWORKS.includes(value)) {
|
|
1348
|
+
throw new CLIError(
|
|
1349
|
+
`Invalid network "${value}". Must be one of ${SUPPORTED_NETWORKS.join(", ")}.`,
|
|
1350
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1351
|
+
{ network: value }
|
|
1352
|
+
);
|
|
1353
|
+
}
|
|
1354
|
+
return value;
|
|
1355
|
+
}
|
|
1138
1356
|
function profileNetworkMismatch(network, overrides) {
|
|
1139
1357
|
let file;
|
|
1140
1358
|
try {
|
|
@@ -1264,7 +1482,7 @@ function trimTrailingNewline(value) {
|
|
|
1264
1482
|
}
|
|
1265
1483
|
function readSecretFile(path, source) {
|
|
1266
1484
|
try {
|
|
1267
|
-
return trimTrailingNewline((0,
|
|
1485
|
+
return trimTrailingNewline((0, import_node_fs6.readFileSync)(path, "utf-8"));
|
|
1268
1486
|
} catch (error) {
|
|
1269
1487
|
throw new CLIError(
|
|
1270
1488
|
`Could not read the RPC password ${source} at ${path}: ${error.message}`,
|
|
@@ -1398,13 +1616,28 @@ async function runDoctor(network, overrides) {
|
|
|
1398
1616
|
function defaultApiFactory(network, overrides) {
|
|
1399
1617
|
return (0, import_api.createApi)(resolveConnectionConfig(network, overrides));
|
|
1400
1618
|
}
|
|
1401
|
-
function buildKeystoreKms(overrides) {
|
|
1619
|
+
function buildKeystoreKms(overrides, network) {
|
|
1620
|
+
const keystorePath = resolveKeystorePath(overrides);
|
|
1621
|
+
const sessionPath = defaultSessionPath(overrides);
|
|
1622
|
+
const isMainnetOperation = network === "bitcoin";
|
|
1402
1623
|
return new FileBackedKeyManager({
|
|
1403
|
-
path:
|
|
1624
|
+
path: keystorePath,
|
|
1404
1625
|
// The store decides when to confirm: it passes `confirm: true` only while
|
|
1405
1626
|
// establishing a fresh keystore's passphrase, so a first-key typo is caught
|
|
1406
1627
|
// by a second entry (ADR 080). confirm is a no-op for env/file sources.
|
|
1407
|
-
|
|
1628
|
+
//
|
|
1629
|
+
// On the non-establishing path, a cached session (ADR 081) is consulted below
|
|
1630
|
+
// the env var / --passphrase-file and above the interactive prompt. It is
|
|
1631
|
+
// wired ONLY when not confirming, so establishment never consults the session
|
|
1632
|
+
// and a first passphrase is always entered fresh and twice. The session is
|
|
1633
|
+
// bound to this keystore's verifier, so a rotated passphrase invalidates it.
|
|
1634
|
+
getPassphrase: (opts) => acquirePassphrase({
|
|
1635
|
+
passphraseFile: overrides?.passphraseFile,
|
|
1636
|
+
confirm: opts?.confirm,
|
|
1637
|
+
...opts?.confirm ? {} : {
|
|
1638
|
+
beforePrompt: () => readLiveSessionPassphrase(sessionPath, keystorePath, keystoreVerifierId(keystorePath), isMainnetOperation)
|
|
1639
|
+
}
|
|
1640
|
+
})
|
|
1408
1641
|
});
|
|
1409
1642
|
}
|
|
1410
1643
|
function resolveKeystoreProtection(overrides) {
|
|
@@ -1442,7 +1675,7 @@ function resolveSigningKeyRef(overrides) {
|
|
|
1442
1675
|
function keystoreApiFactory(network, overrides) {
|
|
1443
1676
|
return (0, import_api.createApi)({
|
|
1444
1677
|
...resolveConnectionConfig(network, overrides),
|
|
1445
|
-
kms: buildKeystoreKms(overrides)
|
|
1678
|
+
kms: buildKeystoreKms(overrides, network)
|
|
1446
1679
|
});
|
|
1447
1680
|
}
|
|
1448
1681
|
function deriveNetwork(did) {
|
|
@@ -1492,49 +1725,383 @@ function formatResult(result, options) {
|
|
|
1492
1725
|
}
|
|
1493
1726
|
|
|
1494
1727
|
// src/commands/init.ts
|
|
1728
|
+
function runInit(g, options = {}) {
|
|
1729
|
+
const home = resolveHome(g);
|
|
1730
|
+
const configPath = g.config ?? defaultConfigPath(g);
|
|
1731
|
+
const keystorePath = resolveKeystorePath(g);
|
|
1732
|
+
ensureDir(home, 448);
|
|
1733
|
+
const created = [];
|
|
1734
|
+
if (!(0, import_node_fs7.existsSync)(configPath) || options.force) {
|
|
1735
|
+
writeDefaultConfigFile(configPath);
|
|
1736
|
+
created.push("config");
|
|
1737
|
+
}
|
|
1738
|
+
const keystoreExists = (0, import_node_fs7.existsSync)(keystorePath);
|
|
1739
|
+
if (keystoreExists && options.force && !g.quiet) {
|
|
1740
|
+
process.stderr.write(
|
|
1741
|
+
`note: a keystore already exists at ${keystorePath} and was left intact. To re-establish it (discarding its keys), run "btcr2 keystore init --force".
|
|
1742
|
+
`
|
|
1743
|
+
);
|
|
1744
|
+
}
|
|
1745
|
+
let establishedPassphrase;
|
|
1746
|
+
if (!keystoreExists) {
|
|
1747
|
+
if (options.dev && !g.quiet) {
|
|
1748
|
+
process.stderr.write(
|
|
1749
|
+
"warning: establishing an UNENCRYPTED dev keystore. Keys are stored in plaintext. Use it only for disposable testnet material; mainnet operations will be refused.\n"
|
|
1750
|
+
);
|
|
1751
|
+
}
|
|
1752
|
+
initKeystore(keystorePath, {
|
|
1753
|
+
protection: options.dev ? "none" : "passphrase",
|
|
1754
|
+
getPassphrase: (opts) => {
|
|
1755
|
+
const passphrase = acquirePassphrase({
|
|
1756
|
+
passphraseFile: g.passphraseFile,
|
|
1757
|
+
confirm: opts?.confirm,
|
|
1758
|
+
prompt: "New keystore passphrase: "
|
|
1759
|
+
});
|
|
1760
|
+
if (options.captureEstablishedPassphrase && !options.dev) establishedPassphrase = passphrase;
|
|
1761
|
+
return passphrase;
|
|
1762
|
+
}
|
|
1763
|
+
});
|
|
1764
|
+
clearSession(defaultSessionPath(g));
|
|
1765
|
+
created.push("keystore");
|
|
1766
|
+
}
|
|
1767
|
+
const { network } = persistDefaultNetwork(configPath, {
|
|
1768
|
+
explicit: options.network,
|
|
1769
|
+
fallback: options.fallbackNetwork,
|
|
1770
|
+
overrides: g
|
|
1771
|
+
});
|
|
1772
|
+
const protection = keystoreSummary(keystorePath).protection;
|
|
1773
|
+
return { home, config: configPath, keystore: keystorePath, network, created, protection, establishedPassphrase };
|
|
1774
|
+
}
|
|
1495
1775
|
function registerInitCommand(program, globals) {
|
|
1496
1776
|
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(
|
|
1777
|
+
program.command("init").description("Set up the btcr2 home: create the directory, a default config, and establish the keystore.").option(
|
|
1778
|
+
"-n, --network <network>",
|
|
1779
|
+
"Bitcoin network to record as defaults.network <bitcoin|testnet3|testnet4|signet|mutinynet|regtest>"
|
|
1780
|
+
).option("--dev", "Establish an UNENCRYPTED dev keystore: plaintext keys, no passphrase. Testnet only.", false).option("--force", "Re-create the config even if it already exists (never the keystore).", false).action((options) => {
|
|
1498
1781
|
const g = globals();
|
|
1499
|
-
const
|
|
1500
|
-
const
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1782
|
+
const network = options.network ? assertSupportedNetwork(options.network) : void 0;
|
|
1783
|
+
const result = runInit(g, { dev: options.dev, force: options.force, network });
|
|
1784
|
+
print({
|
|
1785
|
+
action: "init",
|
|
1786
|
+
data: {
|
|
1787
|
+
home: result.home,
|
|
1788
|
+
config: result.config,
|
|
1789
|
+
keystore: result.keystore,
|
|
1790
|
+
network: result.network,
|
|
1791
|
+
created: result.created,
|
|
1792
|
+
protection: result.protection
|
|
1793
|
+
}
|
|
1794
|
+
});
|
|
1795
|
+
if (!g.quiet && g.output !== "json") {
|
|
1796
|
+
process.stderr.write(`btcr2 home ready at ${result.home} on ${result.network}. Next: btcr2 key generate --set-active
|
|
1797
|
+
`);
|
|
1798
|
+
}
|
|
1799
|
+
});
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
// src/commands/quickstart.ts
|
|
1803
|
+
var import_api2 = require("@did-btcr2/api");
|
|
1804
|
+
|
|
1805
|
+
// src/commands/keystore.ts
|
|
1806
|
+
var import_node_fs8 = require("fs");
|
|
1807
|
+
function registerKeystoreCommand(program, globals) {
|
|
1808
|
+
const keystore = program.command("keystore").description("Establish, inspect, re-key, and unlock the keystore.");
|
|
1809
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
1810
|
+
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) => {
|
|
1811
|
+
const g = globals();
|
|
1812
|
+
const path = resolveKeystorePath(g);
|
|
1813
|
+
if ((0, import_node_fs8.existsSync)(path) && !options.force) {
|
|
1814
|
+
throw new CLIError(
|
|
1815
|
+
`A keystore already exists at ${path}. Use --force to re-establish it (this discards its keys).`,
|
|
1816
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1817
|
+
{ path }
|
|
1513
1818
|
);
|
|
1514
1819
|
}
|
|
1515
|
-
if (!
|
|
1516
|
-
|
|
1820
|
+
if ((0, import_node_fs8.existsSync)(path) && options.force && !g.quiet) {
|
|
1821
|
+
const { keyCount } = keystoreSummary(path);
|
|
1822
|
+
if (keyCount > 0) {
|
|
1517
1823
|
process.stderr.write(
|
|
1518
|
-
|
|
1824
|
+
`warning: re-establishing the keystore at ${path} permanently discards its ${keyCount} existing key(s).
|
|
1825
|
+
`
|
|
1519
1826
|
);
|
|
1520
1827
|
}
|
|
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
1828
|
}
|
|
1527
|
-
|
|
1528
|
-
|
|
1829
|
+
if (options.dev && !g.quiet) {
|
|
1830
|
+
process.stderr.write(
|
|
1831
|
+
"warning: creating an UNENCRYPTED dev keystore. Keys are stored in plaintext. Use it only for disposable testnet material; mainnet operations will be refused.\n"
|
|
1832
|
+
);
|
|
1833
|
+
}
|
|
1834
|
+
initKeystore(path, {
|
|
1835
|
+
protection: options.dev ? "none" : "passphrase",
|
|
1836
|
+
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
|
|
1837
|
+
});
|
|
1838
|
+
clearSession(defaultSessionPath(g));
|
|
1839
|
+
print({ action: "keystore-init", data: { path, protection: options.dev ? "dev" : "encrypted" } });
|
|
1840
|
+
});
|
|
1841
|
+
keystore.command("status").description("Show the keystore path, protection mode, key count, and session state. Never decrypts or prompts.").action(() => {
|
|
1842
|
+
const g = globals();
|
|
1843
|
+
const path = resolveKeystorePath(g, { lenient: true });
|
|
1844
|
+
const summary = keystoreSummary(path);
|
|
1845
|
+
const session = readSessionStatus(defaultSessionPath(g), path, keystoreVerifierId(path));
|
|
1846
|
+
if (summary.protection === "dev" && !g.quiet && g.output !== "json") {
|
|
1847
|
+
process.stderr.write("warning: this is an UNENCRYPTED dev keystore; keys are stored in plaintext.\n");
|
|
1848
|
+
}
|
|
1849
|
+
print({ action: "keystore-status", data: { path, ...summary, session } });
|
|
1850
|
+
});
|
|
1851
|
+
keystore.command("change-passphrase").alias("passwd").description("Change the keystore passphrase, re-sealing every key under the new one. Encrypted keystores only.").action(() => {
|
|
1852
|
+
const g = globals();
|
|
1853
|
+
const path = resolveKeystorePath(g);
|
|
1854
|
+
const summary = keystoreSummary(path);
|
|
1855
|
+
if (summary.protection === "absent") {
|
|
1856
|
+
throw new CLIError(`No keystore at ${path}. Run "btcr2 keystore init" first.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
1857
|
+
}
|
|
1858
|
+
if (summary.protection === "dev") {
|
|
1859
|
+
throw new CLIError(
|
|
1860
|
+
`The keystore at ${path} is an unencrypted dev keystore; there is no passphrase to change.`,
|
|
1861
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1862
|
+
{ path }
|
|
1863
|
+
);
|
|
1864
|
+
}
|
|
1865
|
+
const oldPassphrase = acquirePassphrase({ passphraseFile: g.passphraseFile, prompt: "Current keystore passphrase: " });
|
|
1866
|
+
const newPassphrase = acquirePassphrase({ forcePrompt: true, confirm: true, prompt: "New keystore passphrase: " });
|
|
1867
|
+
const rekeyed = changeKeystorePassphrase(path, oldPassphrase, newPassphrase);
|
|
1868
|
+
clearSession(defaultSessionPath(g));
|
|
1869
|
+
print({ action: "keystore-change-passphrase", data: { path, rekeyed } });
|
|
1870
|
+
});
|
|
1871
|
+
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) => {
|
|
1872
|
+
const g = globals();
|
|
1873
|
+
const path = resolveKeystorePath(g);
|
|
1874
|
+
const ttlMs = resolveSessionTtl(options.ttl);
|
|
1875
|
+
const session = unlockSession({
|
|
1876
|
+
g,
|
|
1877
|
+
keystorePath: path,
|
|
1878
|
+
network: resolveDefaultNetwork(g),
|
|
1879
|
+
allowMainnet: !!options.allowMainnet,
|
|
1880
|
+
ttlMs
|
|
1881
|
+
});
|
|
1882
|
+
print({ action: "keystore-unlock", data: { keystore: path, expiresAt: session.expiresAt, ttlSeconds: session.ttlSeconds } });
|
|
1883
|
+
});
|
|
1884
|
+
keystore.command("lock").description("Revoke the cached session so later commands prompt for the passphrase again (ADR 081).").action(() => {
|
|
1885
|
+
const g = globals();
|
|
1886
|
+
const sessionPath = defaultSessionPath(g);
|
|
1887
|
+
const cleared = clearSession(sessionPath);
|
|
1888
|
+
print({ action: "keystore-lock", data: { path: sessionPath, cleared } });
|
|
1889
|
+
});
|
|
1890
|
+
}
|
|
1891
|
+
function unlockSession(input) {
|
|
1892
|
+
const { g, keystorePath: path, network, allowMainnet, ttlMs } = input;
|
|
1893
|
+
const summary = keystoreSummary(path);
|
|
1894
|
+
if (summary.protection === "absent") {
|
|
1895
|
+
throw new CLIError(`No keystore at ${path}. Run "btcr2 init" or "btcr2 keystore init" first.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
1896
|
+
}
|
|
1897
|
+
if (summary.protection === "dev") {
|
|
1898
|
+
throw new CLIError(
|
|
1899
|
+
`The keystore at ${path} is an unencrypted dev keystore; it has no passphrase to cache, so no unlock is needed.`,
|
|
1900
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1901
|
+
{ path }
|
|
1902
|
+
);
|
|
1903
|
+
}
|
|
1904
|
+
if (!summary.established) {
|
|
1905
|
+
throw new CLIError(
|
|
1906
|
+
`The keystore at ${path} has no passphrase established yet. Establish one with "btcr2 keystore init" or the first "btcr2 key generate".`,
|
|
1907
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1908
|
+
{ path }
|
|
1909
|
+
);
|
|
1910
|
+
}
|
|
1911
|
+
if (!allowMainnet && network === "bitcoin") {
|
|
1912
|
+
throw new CLIError(
|
|
1913
|
+
"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.",
|
|
1914
|
+
"MAINNET_UNLOCK_REFUSED_ERROR",
|
|
1915
|
+
{ path }
|
|
1916
|
+
);
|
|
1917
|
+
}
|
|
1918
|
+
const passphrase = input.passphrase ?? acquirePassphrase({ passphraseFile: g.passphraseFile, prompt: "Keystore passphrase: " });
|
|
1919
|
+
if (!verifyKeystorePassphrase(path, passphrase)) {
|
|
1920
|
+
throw new CLIError(`Incorrect passphrase for the keystore at ${path}; no session was created.`, "DECRYPT_ERROR", { path });
|
|
1921
|
+
}
|
|
1922
|
+
const verifierId = keystoreVerifierId(path);
|
|
1923
|
+
if (!verifierId) {
|
|
1924
|
+
throw new CLIError(`The keystore at ${path} has no verifier to bind a session to.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
1925
|
+
}
|
|
1926
|
+
return writeSession(defaultSessionPath(g), {
|
|
1927
|
+
keystorePath: path,
|
|
1928
|
+
verifierId,
|
|
1929
|
+
passphrase,
|
|
1930
|
+
ttlMs,
|
|
1931
|
+
allowMainnet
|
|
1932
|
+
});
|
|
1933
|
+
}
|
|
1934
|
+
function resolveSessionTtl(flag) {
|
|
1935
|
+
const fromFlag = blankToUndef(flag);
|
|
1936
|
+
const raw = fromFlag ?? blankToUndef(process.env[ENV_KEYSTORE_TTL]);
|
|
1937
|
+
if (raw === void 0) return DEFAULT_SESSION_TTL_MS;
|
|
1938
|
+
const source = fromFlag !== void 0 ? "--ttl" : `$${ENV_KEYSTORE_TTL}`;
|
|
1939
|
+
const ms = parseTtlToMs(raw);
|
|
1940
|
+
if (ms === void 0 || ms <= 0) {
|
|
1941
|
+
throw new CLIError(
|
|
1942
|
+
`Invalid ${source} "${raw}": expected seconds or a value with an s/m/h suffix, e.g. 3600, 45m, or 2h.`,
|
|
1943
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1944
|
+
{ value: raw, source }
|
|
1945
|
+
);
|
|
1946
|
+
}
|
|
1947
|
+
if (ms > MAX_SESSION_TTL_MS) {
|
|
1948
|
+
throw new CLIError(
|
|
1949
|
+
`${source} "${raw}" exceeds the 24h maximum for a cached passphrase.`,
|
|
1950
|
+
"INVALID_ARGUMENT_ERROR",
|
|
1951
|
+
{ value: raw, source }
|
|
1952
|
+
);
|
|
1953
|
+
}
|
|
1954
|
+
return ms;
|
|
1955
|
+
}
|
|
1956
|
+
|
|
1957
|
+
// src/commands/quickstart.ts
|
|
1958
|
+
var QUICKSTART_DEFAULT_NETWORK = "mutinynet";
|
|
1959
|
+
function registerQuickstartCommand(program, globals) {
|
|
1960
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
1961
|
+
program.command("quickstart").description("One-command onboarding: create the home + config + keystore, record the network, and (optionally) cache the session and probe endpoints.").option(
|
|
1962
|
+
"-n, --network <network>",
|
|
1963
|
+
"Bitcoin network to set up <bitcoin|testnet3|testnet4|signet|mutinynet|regtest> (default: mutinynet)"
|
|
1964
|
+
).option("--dev", "Establish an UNENCRYPTED dev keystore: plaintext keys, no passphrase. Testnet only.", false).option("--unlock", "Cache the passphrase for the session so later commands do not re-prompt (ADR 081).", false).option("--ttl <duration>", `Session lifetime with --unlock: bare seconds or an s/m/h suffix (default 1h, max 24h). Also $${ENV_KEYSTORE_TTL}.`).option("--no-doctor", "Skip the endpoint reachability probe.").option("--allow-mainnet", "Permit -n bitcoin (records mainnet as the default; dev keystores are still refused).", false).option("--force", "Re-create the config even if it already exists (never the keystore).", false).action(async (options) => {
|
|
1965
|
+
const g = globals();
|
|
1966
|
+
const explicit = options.network ? assertSupportedNetwork(options.network) : void 0;
|
|
1967
|
+
const network = explicit ?? readConfiguredDefaultNetwork(g) ?? QUICKSTART_DEFAULT_NETWORK;
|
|
1968
|
+
if (network === "bitcoin") {
|
|
1969
|
+
if (options.dev) {
|
|
1970
|
+
throw new CLIError(
|
|
1971
|
+
"Refusing to quickstart a mainnet (bitcoin) dev keystore: dev keystores store keys in plaintext and never operate on mainnet. Drop --dev, or choose a testnet with -n.",
|
|
1972
|
+
"MAINNET_QUICKSTART_REFUSED_ERROR",
|
|
1973
|
+
{ network }
|
|
1974
|
+
);
|
|
1975
|
+
}
|
|
1976
|
+
if (!options.allowMainnet) {
|
|
1977
|
+
throw new CLIError(
|
|
1978
|
+
"Refusing to quickstart on mainnet (bitcoin) without --allow-mainnet. Pass --allow-mainnet to record mainnet as the default, or choose a testnet with -n (the default is mutinynet).",
|
|
1979
|
+
"MAINNET_QUICKSTART_REFUSED_ERROR",
|
|
1980
|
+
{ network }
|
|
1981
|
+
);
|
|
1982
|
+
}
|
|
1983
|
+
}
|
|
1984
|
+
const init = runInit(g, {
|
|
1985
|
+
dev: options.dev,
|
|
1986
|
+
force: options.force,
|
|
1987
|
+
network: explicit,
|
|
1988
|
+
fallbackNetwork: explicit ? void 0 : QUICKSTART_DEFAULT_NETWORK,
|
|
1989
|
+
captureEstablishedPassphrase: !!options.unlock && !options.dev
|
|
1990
|
+
});
|
|
1991
|
+
let unlocked = false;
|
|
1992
|
+
let session;
|
|
1993
|
+
if (options.unlock && !options.dev) {
|
|
1994
|
+
const outcome = cacheSession(g, init, options.ttl, !!options.allowMainnet);
|
|
1995
|
+
unlocked = outcome.unlocked;
|
|
1996
|
+
session = outcome.session;
|
|
1997
|
+
}
|
|
1998
|
+
let doctor;
|
|
1999
|
+
if (options.doctor) {
|
|
2000
|
+
doctor = await runDoctor(init.network, g);
|
|
2001
|
+
}
|
|
2002
|
+
print({
|
|
2003
|
+
action: "quickstart",
|
|
2004
|
+
data: {
|
|
2005
|
+
home: init.home,
|
|
2006
|
+
config: init.config,
|
|
2007
|
+
keystore: init.keystore,
|
|
2008
|
+
network: init.network,
|
|
2009
|
+
created: init.created,
|
|
2010
|
+
protection: init.protection,
|
|
2011
|
+
unlocked,
|
|
2012
|
+
...session ? { session } : {},
|
|
2013
|
+
...doctor ? { doctor } : {}
|
|
2014
|
+
}
|
|
2015
|
+
});
|
|
1529
2016
|
if (!g.quiet && g.output !== "json") {
|
|
1530
|
-
|
|
1531
|
-
`);
|
|
2017
|
+
printNextSteps(init, unlocked, session, doctor);
|
|
1532
2018
|
}
|
|
1533
2019
|
});
|
|
1534
2020
|
}
|
|
2021
|
+
function cacheSession(g, init, ttlFlag, allowMainnet) {
|
|
2022
|
+
const ttlMs = resolveSessionTtl(ttlFlag);
|
|
2023
|
+
const freshlyEstablished = init.created.includes("keystore");
|
|
2024
|
+
if (!freshlyEstablished) {
|
|
2025
|
+
const status = readSessionStatus(defaultSessionPath(g), init.keystore, keystoreVerifierId(init.keystore));
|
|
2026
|
+
if (status.active && status.expiresAt !== void 0) {
|
|
2027
|
+
return { unlocked: true, session: { expiresAt: status.expiresAt, ttlSeconds: status.secondsRemaining ?? 0 } };
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
try {
|
|
2031
|
+
const written = unlockSession({
|
|
2032
|
+
g,
|
|
2033
|
+
keystorePath: init.keystore,
|
|
2034
|
+
network: init.network,
|
|
2035
|
+
allowMainnet,
|
|
2036
|
+
ttlMs,
|
|
2037
|
+
// Reuse the establish-time passphrase on a fresh keystore: no second prompt.
|
|
2038
|
+
passphrase: freshlyEstablished ? init.establishedPassphrase : void 0
|
|
2039
|
+
});
|
|
2040
|
+
return { unlocked: true, session: { expiresAt: written.expiresAt, ttlSeconds: written.ttlSeconds } };
|
|
2041
|
+
} catch (error) {
|
|
2042
|
+
const type = error.type;
|
|
2043
|
+
if (!freshlyEstablished && !process.stdin.isTTY && type === "PASSPHRASE_REQUIRED_ERROR") {
|
|
2044
|
+
if (!g.quiet) {
|
|
2045
|
+
process.stderr.write(
|
|
2046
|
+
'note: no passphrase source and no terminal; skipped caching the session. Run "btcr2 keystore unlock" later to cache it.\n'
|
|
2047
|
+
);
|
|
2048
|
+
}
|
|
2049
|
+
return { unlocked: false };
|
|
2050
|
+
}
|
|
2051
|
+
throw error;
|
|
2052
|
+
}
|
|
2053
|
+
}
|
|
2054
|
+
function printNextSteps(init, unlocked, session, doctor) {
|
|
2055
|
+
const lines = [`btcr2 home ready at ${init.home} on ${init.network}.`];
|
|
2056
|
+
if (unlocked && session) {
|
|
2057
|
+
lines.push(`Session cached until ${new Date(session.expiresAt).toISOString()}; signing will not re-prompt until it expires.`);
|
|
2058
|
+
}
|
|
2059
|
+
if (init.protection === "dev") {
|
|
2060
|
+
lines.push("Dev keystore: keys are stored in plaintext; mainnet operations are refused.");
|
|
2061
|
+
}
|
|
2062
|
+
if (doctor && doctor.checks.some((c) => !c.ok)) {
|
|
2063
|
+
lines.push('Warning: one or more endpoints were unreachable (see the doctor report). Re-run "btcr2 config doctor" for detail.');
|
|
2064
|
+
}
|
|
2065
|
+
lines.push("Next: btcr2 key generate --name demo --set-active");
|
|
2066
|
+
const faucet = (0, import_api2.faucetUrl)(init.network);
|
|
2067
|
+
if (faucet) lines.push(`Faucet (fund your beacon after "btcr2 create"): ${faucet}`);
|
|
2068
|
+
process.stderr.write(`${lines.join("\n")}
|
|
2069
|
+
`);
|
|
2070
|
+
}
|
|
1535
2071
|
|
|
1536
2072
|
// src/commands/create.ts
|
|
1537
|
-
var
|
|
2073
|
+
var import_utils4 = require("@noble/hashes/utils.js");
|
|
2074
|
+
|
|
2075
|
+
// src/hints.ts
|
|
2076
|
+
var import_api3 = require("@did-btcr2/api");
|
|
2077
|
+
var import_method2 = require("@did-btcr2/method");
|
|
2078
|
+
function printCreateFundingHint(g, network, did) {
|
|
2079
|
+
if (g.quiet || g.output === "json") return;
|
|
2080
|
+
const faucet = (0, import_api3.faucetUrl)(network);
|
|
2081
|
+
if (!faucet) return;
|
|
2082
|
+
let beaconAddress;
|
|
2083
|
+
try {
|
|
2084
|
+
const { serviceEndpoint } = import_method2.BeaconUtils.createBeaconService(did, "p2wpkh", "SingletonBeacon");
|
|
2085
|
+
beaconAddress = serviceEndpoint.replace(/^bitcoin:/, "");
|
|
2086
|
+
} catch {
|
|
2087
|
+
return;
|
|
2088
|
+
}
|
|
2089
|
+
const explorer = (0, import_api3.explorerAddressUrl)(network, beaconAddress);
|
|
2090
|
+
const lines = [
|
|
2091
|
+
"Fund the initial beacon to anchor updates:",
|
|
2092
|
+
` Beacon: ${beaconAddress}`,
|
|
2093
|
+
` Faucet: ${faucet}`
|
|
2094
|
+
];
|
|
2095
|
+
if (explorer) lines.push(` Explorer: ${explorer}`);
|
|
2096
|
+
process.stderr.write(`${lines.join("\n")}
|
|
2097
|
+
`);
|
|
2098
|
+
}
|
|
2099
|
+
function printWatchHint(g, network, txid) {
|
|
2100
|
+
if (g.quiet || g.output === "json") return;
|
|
2101
|
+
const url = (0, import_api3.explorerTxUrl)(network, txid);
|
|
2102
|
+
if (url) process.stderr.write(`Watch: ${url}
|
|
2103
|
+
`);
|
|
2104
|
+
}
|
|
1538
2105
|
|
|
1539
2106
|
// src/keystore/resolve-key-ref.ts
|
|
1540
2107
|
function fingerprintOf(id) {
|
|
@@ -1634,6 +2201,7 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
1634
2201
|
const genesisBytes = parseGenesisBytes(options.bytes, "k");
|
|
1635
2202
|
const did2 = factory().createDid("deterministic", genesisBytes, { network });
|
|
1636
2203
|
print({ action: "create", data: did2 });
|
|
2204
|
+
printCreateFundingHint(g, network, did2);
|
|
1637
2205
|
return;
|
|
1638
2206
|
}
|
|
1639
2207
|
if (signingKey) {
|
|
@@ -1642,19 +2210,21 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
1642
2210
|
const publicKey2 = api2.kms.getPublicKey(keyId2);
|
|
1643
2211
|
const did2 = api2.createDid("deterministic", publicKey2, { network });
|
|
1644
2212
|
print(
|
|
1645
|
-
{ action: "create", data: did2, keyId: keyId2, publicKey: (0,
|
|
2213
|
+
{ action: "create", data: did2, keyId: keyId2, publicKey: (0, import_utils4.bytesToHex)(publicKey2) },
|
|
1646
2214
|
`Using stored key ${keyId2}.`
|
|
1647
2215
|
);
|
|
2216
|
+
printCreateFundingHint(g, network, did2);
|
|
1648
2217
|
return;
|
|
1649
2218
|
}
|
|
1650
2219
|
assertKeystoreAllowedForNetwork(network, overrides);
|
|
1651
2220
|
const api = keystoreFactory(void 0, overrides);
|
|
1652
2221
|
const { did, keyId } = api.generateDid({ network, setActive: true });
|
|
1653
|
-
const publicKey = (0,
|
|
2222
|
+
const publicKey = (0, import_utils4.bytesToHex)(api.kms.getPublicKey(keyId));
|
|
1654
2223
|
print(
|
|
1655
2224
|
{ action: "create", data: did, keyId, publicKey },
|
|
1656
2225
|
`Generated and stored key ${keyId} (now the active key).`
|
|
1657
2226
|
);
|
|
2227
|
+
printCreateFundingHint(g, network, did);
|
|
1658
2228
|
});
|
|
1659
2229
|
}
|
|
1660
2230
|
function overridesFromGlobals(g) {
|
|
@@ -1681,7 +2251,7 @@ function parseGenesisBytes(hex, type) {
|
|
|
1681
2251
|
const expected = EXPECTED_BYTES[type];
|
|
1682
2252
|
let bytes;
|
|
1683
2253
|
try {
|
|
1684
|
-
bytes = (0,
|
|
2254
|
+
bytes = (0, import_utils4.hexToBytes)(hex.trim());
|
|
1685
2255
|
} catch {
|
|
1686
2256
|
throw new CLIError(
|
|
1687
2257
|
`Invalid bytes: not valid hex. Expected ${expected.label}.`,
|
|
@@ -1700,7 +2270,7 @@ function parseGenesisBytes(hex, type) {
|
|
|
1700
2270
|
}
|
|
1701
2271
|
|
|
1702
2272
|
// src/commands/resolve.ts
|
|
1703
|
-
var
|
|
2273
|
+
var import_api4 = require("@did-btcr2/api");
|
|
1704
2274
|
var import_promises = require("fs/promises");
|
|
1705
2275
|
function registerResolveCommand(program, factory, globals) {
|
|
1706
2276
|
program.command("resolve").alias("read").description("Resolve the DID document of the identifier.").requiredOption("-i, --identifier <identifier>", "did:btcr2 identifier").option("-r, --resolution-options <json>", "JSON string containing resolution options").option("-p, --resolution-options-path <path>", "Path to a JSON file containing resolution options").action(async (options) => {
|
|
@@ -1713,7 +2283,7 @@ function registerResolveCommand(program, factory, globals) {
|
|
|
1713
2283
|
});
|
|
1714
2284
|
}
|
|
1715
2285
|
async function validateResolveOptions(options) {
|
|
1716
|
-
|
|
2286
|
+
import_api4.Identifier.decode(options.identifier);
|
|
1717
2287
|
let resolutionOptions = void 0;
|
|
1718
2288
|
if (options.resolutionOptions) {
|
|
1719
2289
|
try {
|
|
@@ -1815,6 +2385,7 @@ function registerUpdateCommand(program, factory, globals) {
|
|
|
1815
2385
|
...broadcastOptions ? { broadcastOptions } : {}
|
|
1816
2386
|
});
|
|
1817
2387
|
console.log(formatResult({ action: "update", data }, globals()));
|
|
2388
|
+
printWatchHint(globals(), network, data.txid);
|
|
1818
2389
|
});
|
|
1819
2390
|
}
|
|
1820
2391
|
function parsePublishToCasMode(value) {
|
|
@@ -1913,6 +2484,7 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
1913
2484
|
...broadcastOptions ? { broadcastOptions } : {}
|
|
1914
2485
|
});
|
|
1915
2486
|
console.log(formatResult({ action: "deactivate", data }, globals()));
|
|
2487
|
+
printWatchHint(globals(), network, data.txid);
|
|
1916
2488
|
});
|
|
1917
2489
|
}
|
|
1918
2490
|
function parsePublishToCasMode2(value) {
|
|
@@ -1941,8 +2513,8 @@ function parseJsonArg2(flagName) {
|
|
|
1941
2513
|
|
|
1942
2514
|
// src/commands/key.ts
|
|
1943
2515
|
var import_keypair = require("@did-btcr2/keypair");
|
|
1944
|
-
var
|
|
1945
|
-
var
|
|
2516
|
+
var import_utils5 = require("@noble/hashes/utils.js");
|
|
2517
|
+
var import_node_fs9 = require("fs");
|
|
1946
2518
|
function registerKeyCommand(program, factory, globals) {
|
|
1947
2519
|
const key = program.command("key").description("Manage keypairs in the encrypted keystore.");
|
|
1948
2520
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
@@ -1951,7 +2523,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1951
2523
|
assertNameAvailable(api.kms.kms, options.name);
|
|
1952
2524
|
const setActive = options.setActive ?? false;
|
|
1953
2525
|
const id = api.kms.generateKey({ ...options.name && { tags: { name: options.name } }, setActive });
|
|
1954
|
-
print({ action: "key-generate", data: { keyId: id, publicKey: (0,
|
|
2526
|
+
print({ action: "key-generate", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(api.kms.getPublicKey(id)), active: setActive } });
|
|
1955
2527
|
});
|
|
1956
2528
|
key.command("list").alias("ls").description("List stored keys.").action(() => {
|
|
1957
2529
|
const kms = factory(void 0, globals()).kms.kms;
|
|
@@ -1971,7 +2543,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1971
2543
|
const kms = factory(void 0, globals()).kms.kms;
|
|
1972
2544
|
const id = resolveKeyRef(kms, ref);
|
|
1973
2545
|
const entry = kms.getEntry(id);
|
|
1974
|
-
print({ action: "key-show", data: { keyId: id, publicKey: (0,
|
|
2546
|
+
print({ action: "key-show", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(entry.publicKey), ...entry.tags && { tags: entry.tags } } });
|
|
1975
2547
|
});
|
|
1976
2548
|
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
2549
|
if (Boolean(options.secretFile) === Boolean(options.public)) {
|
|
@@ -1982,13 +2554,13 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1982
2554
|
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
2555
|
const setActive = options.setActive ?? false;
|
|
1984
2556
|
const id = api.kms.import(keyPair, { ...options.name && { tags: { name: options.name } }, setActive });
|
|
1985
|
-
print({ action: "key-import", data: { keyId: id, publicKey: (0,
|
|
2557
|
+
print({ action: "key-import", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(api.kms.getPublicKey(id)), watchOnly: !options.secretFile, active: setActive } });
|
|
1986
2558
|
});
|
|
1987
2559
|
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
2560
|
const api = factory(void 0, globals());
|
|
1989
2561
|
const id = resolveKeyRef(api.kms.kms, ref);
|
|
1990
2562
|
if (!options.secret) {
|
|
1991
|
-
print({ action: "key-export", data: { keyId: id, publicKey: (0,
|
|
2563
|
+
print({ action: "key-export", data: { keyId: id, publicKey: (0, import_utils5.bytesToHex)(api.kms.getPublicKey(id)) } });
|
|
1992
2564
|
return;
|
|
1993
2565
|
}
|
|
1994
2566
|
if (!options.out) {
|
|
@@ -1999,7 +2571,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
1999
2571
|
throw new CLIError(`Key ${id} is watch-only and has no secret to export.`, "INVALID_ARGUMENT_ERROR", { keyId: id });
|
|
2000
2572
|
}
|
|
2001
2573
|
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,
|
|
2574
|
+
writeSecretFile(options.out, (0, import_utils5.bytesToHex)(keyPair.secretKey.bytes));
|
|
2003
2575
|
print({ action: "key-export", data: { keyId: id, secretWrittenTo: options.out } });
|
|
2004
2576
|
});
|
|
2005
2577
|
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 +2596,7 @@ function assertNameAvailable(kms, name) {
|
|
|
2024
2596
|
function parseHex(hex, expectedBytes, label) {
|
|
2025
2597
|
let bytes;
|
|
2026
2598
|
try {
|
|
2027
|
-
bytes = (0,
|
|
2599
|
+
bytes = (0, import_utils5.hexToBytes)(hex.trim());
|
|
2028
2600
|
} catch {
|
|
2029
2601
|
throw new CLIError(`Invalid hex for ${label}.`, "INVALID_ARGUMENT_ERROR", { label });
|
|
2030
2602
|
}
|
|
@@ -2040,7 +2612,7 @@ function parseHex(hex, expectedBytes, label) {
|
|
|
2040
2612
|
function readHexFile(path, expectedBytes, label) {
|
|
2041
2613
|
let content;
|
|
2042
2614
|
try {
|
|
2043
|
-
content = (0,
|
|
2615
|
+
content = (0, import_node_fs9.readFileSync)(path, "utf-8");
|
|
2044
2616
|
} catch {
|
|
2045
2617
|
throw new CLIError(`Cannot read ${label} at ${path}.`, "INVALID_ARGUMENT_ERROR", { label, path });
|
|
2046
2618
|
}
|
|
@@ -2049,7 +2621,7 @@ function readHexFile(path, expectedBytes, label) {
|
|
|
2049
2621
|
function writeSecretFile(path, contents) {
|
|
2050
2622
|
let fd;
|
|
2051
2623
|
try {
|
|
2052
|
-
fd = (0,
|
|
2624
|
+
fd = (0, import_node_fs9.openSync)(path, "wx", 384);
|
|
2053
2625
|
} catch (error) {
|
|
2054
2626
|
if (error.code === "EEXIST") {
|
|
2055
2627
|
throw new CLIError(`Refusing to overwrite existing file ${path}. Choose a new --out path.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
@@ -2057,79 +2629,14 @@ function writeSecretFile(path, contents) {
|
|
|
2057
2629
|
throw error;
|
|
2058
2630
|
}
|
|
2059
2631
|
try {
|
|
2060
|
-
(0,
|
|
2632
|
+
(0, import_node_fs9.writeFileSync)(fd, contents);
|
|
2061
2633
|
} finally {
|
|
2062
|
-
(0,
|
|
2634
|
+
(0, import_node_fs9.closeSync)(fd);
|
|
2063
2635
|
}
|
|
2064
2636
|
}
|
|
2065
2637
|
|
|
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
|
-
|
|
2131
2638
|
// src/commands/config.ts
|
|
2132
|
-
var
|
|
2639
|
+
var import_node_fs10 = require("fs");
|
|
2133
2640
|
|
|
2134
2641
|
// src/config-schema.ts
|
|
2135
2642
|
var CONFIG_SCHEMA = {
|
|
@@ -2258,7 +2765,7 @@ function registerConfigCommand(program, globals) {
|
|
|
2258
2765
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
2259
2766
|
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
2767
|
const p = path();
|
|
2261
|
-
if ((0,
|
|
2768
|
+
if ((0, import_node_fs10.existsSync)(p) && !options.force) {
|
|
2262
2769
|
throw new CLIError(`Config already exists at ${p}. Use --force to overwrite.`, "INVALID_ARGUMENT_ERROR", { path: p });
|
|
2263
2770
|
}
|
|
2264
2771
|
writeDefaultConfigFile(p);
|
|
@@ -2439,18 +2946,18 @@ function completionScript(shell) {
|
|
|
2439
2946
|
}
|
|
2440
2947
|
|
|
2441
2948
|
// src/version.ts
|
|
2442
|
-
var
|
|
2443
|
-
var
|
|
2949
|
+
var import_node_fs11 = require("fs");
|
|
2950
|
+
var import_node_path6 = require("path");
|
|
2444
2951
|
var import_node_url = require("url");
|
|
2445
2952
|
function readVersion() {
|
|
2446
|
-
let dir = (0,
|
|
2953
|
+
let dir = (0, import_node_path6.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
2447
2954
|
for (let i = 0; i < 5; i++) {
|
|
2448
2955
|
try {
|
|
2449
|
-
const pkg = JSON.parse((0,
|
|
2956
|
+
const pkg = JSON.parse((0, import_node_fs11.readFileSync)((0, import_node_path6.join)(dir, "package.json"), "utf-8"));
|
|
2450
2957
|
if (pkg.name === "@did-btcr2/cli") return pkg.version;
|
|
2451
2958
|
} catch {
|
|
2452
2959
|
}
|
|
2453
|
-
dir = (0,
|
|
2960
|
+
dir = (0, import_node_path6.dirname)(dir);
|
|
2454
2961
|
}
|
|
2455
2962
|
return "0.0.0";
|
|
2456
2963
|
}
|
|
@@ -2481,6 +2988,7 @@ var DidBtcr2Cli = class {
|
|
|
2481
2988
|
opts.output = resolveOutputFormat({ output: opts.output, config: opts.config, home: opts.home });
|
|
2482
2989
|
});
|
|
2483
2990
|
registerInitCommand(this.program, globals);
|
|
2991
|
+
registerQuickstartCommand(this.program, globals);
|
|
2484
2992
|
registerCreateCommand(this.program, factory, keystoreFactory, globals);
|
|
2485
2993
|
registerResolveCommand(this.program, factory, globals);
|
|
2486
2994
|
registerUpdateCommand(this.program, keystoreFactory, globals);
|
|
@@ -2537,6 +3045,7 @@ function handleError(error, verbose) {
|
|
|
2537
3045
|
SUPPORTED_NETWORKS,
|
|
2538
3046
|
VERSION,
|
|
2539
3047
|
assertKeystoreAllowedForNetwork,
|
|
3048
|
+
assertSupportedNetwork,
|
|
2540
3049
|
blankToUndef,
|
|
2541
3050
|
defaultApiFactory,
|
|
2542
3051
|
defaultConfigPath,
|
|
@@ -2547,9 +3056,11 @@ function handleError(error, verbose) {
|
|
|
2547
3056
|
keystoreApiFactory,
|
|
2548
3057
|
parseConfigFileRaw,
|
|
2549
3058
|
parseHeaderList,
|
|
3059
|
+
persistDefaultNetwork,
|
|
2550
3060
|
profileNetworkMismatch,
|
|
2551
3061
|
profileToOverrides,
|
|
2552
3062
|
readConfigFile,
|
|
3063
|
+
readConfiguredDefaultNetwork,
|
|
2553
3064
|
readEnvOverrides,
|
|
2554
3065
|
redactSecrets,
|
|
2555
3066
|
registerCompletionCommand,
|
|
@@ -2560,6 +3071,7 @@ function handleError(error, verbose) {
|
|
|
2560
3071
|
registerKeyCommand,
|
|
2561
3072
|
registerKeystoreCommand,
|
|
2562
3073
|
registerProfileCommand,
|
|
3074
|
+
registerQuickstartCommand,
|
|
2563
3075
|
registerResolveCommand,
|
|
2564
3076
|
registerUpdateCommand,
|
|
2565
3077
|
resolveActiveProfile,
|