@did-btcr2/cli 0.17.0 → 0.18.1
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 +421 -177
- 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 +87 -44
- package/dist/esm/src/commands/init.js.map +1 -1
- package/dist/esm/src/commands/keystore.js +61 -42
- 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 +66 -0
- 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/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 +37 -1
- 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/types.d.ts +17 -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 +146 -54
- package/src/commands/keystore.ts +95 -55
- package/src/commands/quickstart.ts +209 -0
- package/src/commands/update.ts +2 -0
- package/src/config.ts +75 -0
- package/src/hints.ts +51 -0
- package/src/types.ts +12 -1
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,
|
|
@@ -1310,6 +1314,45 @@ function resolveDefaultNetwork(overrides) {
|
|
|
1310
1314
|
if (network) return network;
|
|
1311
1315
|
return "regtest";
|
|
1312
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
|
+
}
|
|
1313
1356
|
function profileNetworkMismatch(network, overrides) {
|
|
1314
1357
|
let file;
|
|
1315
1358
|
try {
|
|
@@ -1682,51 +1725,384 @@ function formatResult(result, options) {
|
|
|
1682
1725
|
}
|
|
1683
1726
|
|
|
1684
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
|
+
}
|
|
1685
1775
|
function registerInitCommand(program, globals) {
|
|
1686
1776
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
1687
|
-
program.command("init").description("Set up the btcr2 home: create the directory, a default config, and establish the keystore.").option(
|
|
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) => {
|
|
1688
1781
|
const g = globals();
|
|
1689
|
-
const
|
|
1690
|
-
const
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
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 }
|
|
1703
1818
|
);
|
|
1704
1819
|
}
|
|
1705
|
-
if (!
|
|
1706
|
-
|
|
1820
|
+
if ((0, import_node_fs8.existsSync)(path) && options.force && !g.quiet) {
|
|
1821
|
+
const { keyCount } = keystoreSummary(path);
|
|
1822
|
+
if (keyCount > 0) {
|
|
1707
1823
|
process.stderr.write(
|
|
1708
|
-
|
|
1824
|
+
`warning: re-establishing the keystore at ${path} permanently discards its ${keyCount} existing key(s).
|
|
1825
|
+
`
|
|
1709
1826
|
);
|
|
1710
1827
|
}
|
|
1711
|
-
initKeystore(keystorePath, {
|
|
1712
|
-
protection: options.dev ? "none" : "passphrase",
|
|
1713
|
-
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
|
|
1714
|
-
});
|
|
1715
|
-
clearSession(defaultSessionPath(g));
|
|
1716
|
-
created.push("keystore");
|
|
1717
1828
|
}
|
|
1718
|
-
|
|
1719
|
-
|
|
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
|
+
});
|
|
1720
2016
|
if (!g.quiet && g.output !== "json") {
|
|
1721
|
-
|
|
1722
|
-
`);
|
|
2017
|
+
printNextSteps(init, unlocked, session, doctor);
|
|
1723
2018
|
}
|
|
1724
2019
|
});
|
|
1725
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
|
+
}
|
|
1726
2071
|
|
|
1727
2072
|
// src/commands/create.ts
|
|
1728
2073
|
var import_utils4 = require("@noble/hashes/utils.js");
|
|
1729
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
|
+
}
|
|
2105
|
+
|
|
1730
2106
|
// src/keystore/resolve-key-ref.ts
|
|
1731
2107
|
function fingerprintOf(id) {
|
|
1732
2108
|
return /^urn:kms:secp256k1:([0-9a-f]{32})$/.exec(id)?.[1];
|
|
@@ -1825,6 +2201,7 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
1825
2201
|
const genesisBytes = parseGenesisBytes(options.bytes, "k");
|
|
1826
2202
|
const did2 = factory().createDid("deterministic", genesisBytes, { network });
|
|
1827
2203
|
print({ action: "create", data: did2 });
|
|
2204
|
+
printCreateFundingHint(g, network, did2);
|
|
1828
2205
|
return;
|
|
1829
2206
|
}
|
|
1830
2207
|
if (signingKey) {
|
|
@@ -1836,6 +2213,7 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
1836
2213
|
{ action: "create", data: did2, keyId: keyId2, publicKey: (0, import_utils4.bytesToHex)(publicKey2) },
|
|
1837
2214
|
`Using stored key ${keyId2}.`
|
|
1838
2215
|
);
|
|
2216
|
+
printCreateFundingHint(g, network, did2);
|
|
1839
2217
|
return;
|
|
1840
2218
|
}
|
|
1841
2219
|
assertKeystoreAllowedForNetwork(network, overrides);
|
|
@@ -1846,6 +2224,7 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
1846
2224
|
{ action: "create", data: did, keyId, publicKey },
|
|
1847
2225
|
`Generated and stored key ${keyId} (now the active key).`
|
|
1848
2226
|
);
|
|
2227
|
+
printCreateFundingHint(g, network, did);
|
|
1849
2228
|
});
|
|
1850
2229
|
}
|
|
1851
2230
|
function overridesFromGlobals(g) {
|
|
@@ -1891,7 +2270,7 @@ function parseGenesisBytes(hex, type) {
|
|
|
1891
2270
|
}
|
|
1892
2271
|
|
|
1893
2272
|
// src/commands/resolve.ts
|
|
1894
|
-
var
|
|
2273
|
+
var import_api4 = require("@did-btcr2/api");
|
|
1895
2274
|
var import_promises = require("fs/promises");
|
|
1896
2275
|
function registerResolveCommand(program, factory, globals) {
|
|
1897
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) => {
|
|
@@ -1904,7 +2283,7 @@ function registerResolveCommand(program, factory, globals) {
|
|
|
1904
2283
|
});
|
|
1905
2284
|
}
|
|
1906
2285
|
async function validateResolveOptions(options) {
|
|
1907
|
-
|
|
2286
|
+
import_api4.Identifier.decode(options.identifier);
|
|
1908
2287
|
let resolutionOptions = void 0;
|
|
1909
2288
|
if (options.resolutionOptions) {
|
|
1910
2289
|
try {
|
|
@@ -2006,6 +2385,7 @@ function registerUpdateCommand(program, factory, globals) {
|
|
|
2006
2385
|
...broadcastOptions ? { broadcastOptions } : {}
|
|
2007
2386
|
});
|
|
2008
2387
|
console.log(formatResult({ action: "update", data }, globals()));
|
|
2388
|
+
printWatchHint(globals(), network, data.txid);
|
|
2009
2389
|
});
|
|
2010
2390
|
}
|
|
2011
2391
|
function parsePublishToCasMode(value) {
|
|
@@ -2104,6 +2484,7 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
2104
2484
|
...broadcastOptions ? { broadcastOptions } : {}
|
|
2105
2485
|
});
|
|
2106
2486
|
console.log(formatResult({ action: "deactivate", data }, globals()));
|
|
2487
|
+
printWatchHint(globals(), network, data.txid);
|
|
2107
2488
|
});
|
|
2108
2489
|
}
|
|
2109
2490
|
function parsePublishToCasMode2(value) {
|
|
@@ -2133,7 +2514,7 @@ function parseJsonArg2(flagName) {
|
|
|
2133
2514
|
// src/commands/key.ts
|
|
2134
2515
|
var import_keypair = require("@did-btcr2/keypair");
|
|
2135
2516
|
var import_utils5 = require("@noble/hashes/utils.js");
|
|
2136
|
-
var
|
|
2517
|
+
var import_node_fs9 = require("fs");
|
|
2137
2518
|
function registerKeyCommand(program, factory, globals) {
|
|
2138
2519
|
const key = program.command("key").description("Manage keypairs in the encrypted keystore.");
|
|
2139
2520
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
@@ -2231,7 +2612,7 @@ function parseHex(hex, expectedBytes, label) {
|
|
|
2231
2612
|
function readHexFile(path, expectedBytes, label) {
|
|
2232
2613
|
let content;
|
|
2233
2614
|
try {
|
|
2234
|
-
content = (0,
|
|
2615
|
+
content = (0, import_node_fs9.readFileSync)(path, "utf-8");
|
|
2235
2616
|
} catch {
|
|
2236
2617
|
throw new CLIError(`Cannot read ${label} at ${path}.`, "INVALID_ARGUMENT_ERROR", { label, path });
|
|
2237
2618
|
}
|
|
@@ -2240,7 +2621,7 @@ function readHexFile(path, expectedBytes, label) {
|
|
|
2240
2621
|
function writeSecretFile(path, contents) {
|
|
2241
2622
|
let fd;
|
|
2242
2623
|
try {
|
|
2243
|
-
fd = (0,
|
|
2624
|
+
fd = (0, import_node_fs9.openSync)(path, "wx", 384);
|
|
2244
2625
|
} catch (error) {
|
|
2245
2626
|
if (error.code === "EEXIST") {
|
|
2246
2627
|
throw new CLIError(`Refusing to overwrite existing file ${path}. Choose a new --out path.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
@@ -2248,152 +2629,10 @@ function writeSecretFile(path, contents) {
|
|
|
2248
2629
|
throw error;
|
|
2249
2630
|
}
|
|
2250
2631
|
try {
|
|
2251
|
-
(0,
|
|
2632
|
+
(0, import_node_fs9.writeFileSync)(fd, contents);
|
|
2252
2633
|
} finally {
|
|
2253
|
-
(0,
|
|
2254
|
-
}
|
|
2255
|
-
}
|
|
2256
|
-
|
|
2257
|
-
// src/commands/keystore.ts
|
|
2258
|
-
var import_node_fs9 = require("fs");
|
|
2259
|
-
function registerKeystoreCommand(program, globals) {
|
|
2260
|
-
const keystore = program.command("keystore").description("Establish, inspect, re-key, and unlock the keystore.");
|
|
2261
|
-
const print = (result) => console.log(formatResult(result, globals()));
|
|
2262
|
-
keystore.command("init").description("Establish the keystore (encrypted by default). Prompts for a passphrase and confirms it.").option("--dev", "Create an UNENCRYPTED dev keystore: plaintext keys, no passphrase. Testnet only; mainnet operations are refused.", false).option("--force", "Re-establish even if a keystore already exists (discards its keys).", false).action((options) => {
|
|
2263
|
-
const g = globals();
|
|
2264
|
-
const path = resolveKeystorePath(g);
|
|
2265
|
-
if ((0, import_node_fs9.existsSync)(path) && !options.force) {
|
|
2266
|
-
throw new CLIError(
|
|
2267
|
-
`A keystore already exists at ${path}. Use --force to re-establish it (this discards its keys).`,
|
|
2268
|
-
"INVALID_ARGUMENT_ERROR",
|
|
2269
|
-
{ path }
|
|
2270
|
-
);
|
|
2271
|
-
}
|
|
2272
|
-
if ((0, import_node_fs9.existsSync)(path) && options.force && !g.quiet) {
|
|
2273
|
-
const { keyCount } = keystoreSummary(path);
|
|
2274
|
-
if (keyCount > 0) {
|
|
2275
|
-
process.stderr.write(
|
|
2276
|
-
`warning: re-establishing the keystore at ${path} permanently discards its ${keyCount} existing key(s).
|
|
2277
|
-
`
|
|
2278
|
-
);
|
|
2279
|
-
}
|
|
2280
|
-
}
|
|
2281
|
-
if (options.dev && !g.quiet) {
|
|
2282
|
-
process.stderr.write(
|
|
2283
|
-
"warning: creating an UNENCRYPTED dev keystore. Keys are stored in plaintext. Use it only for disposable testnet material; mainnet operations will be refused.\n"
|
|
2284
|
-
);
|
|
2285
|
-
}
|
|
2286
|
-
initKeystore(path, {
|
|
2287
|
-
protection: options.dev ? "none" : "passphrase",
|
|
2288
|
-
getPassphrase: (opts) => acquirePassphrase({ passphraseFile: g.passphraseFile, confirm: opts?.confirm, prompt: "New keystore passphrase: " })
|
|
2289
|
-
});
|
|
2290
|
-
clearSession(defaultSessionPath(g));
|
|
2291
|
-
print({ action: "keystore-init", data: { path, protection: options.dev ? "dev" : "encrypted" } });
|
|
2292
|
-
});
|
|
2293
|
-
keystore.command("status").description("Show the keystore path, protection mode, key count, and session state. Never decrypts or prompts.").action(() => {
|
|
2294
|
-
const g = globals();
|
|
2295
|
-
const path = resolveKeystorePath(g, { lenient: true });
|
|
2296
|
-
const summary = keystoreSummary(path);
|
|
2297
|
-
const session = readSessionStatus(defaultSessionPath(g), path, keystoreVerifierId(path));
|
|
2298
|
-
if (summary.protection === "dev" && !g.quiet && g.output !== "json") {
|
|
2299
|
-
process.stderr.write("warning: this is an UNENCRYPTED dev keystore; keys are stored in plaintext.\n");
|
|
2300
|
-
}
|
|
2301
|
-
print({ action: "keystore-status", data: { path, ...summary, session } });
|
|
2302
|
-
});
|
|
2303
|
-
keystore.command("change-passphrase").alias("passwd").description("Change the keystore passphrase, re-sealing every key under the new one. Encrypted keystores only.").action(() => {
|
|
2304
|
-
const g = globals();
|
|
2305
|
-
const path = resolveKeystorePath(g);
|
|
2306
|
-
const summary = keystoreSummary(path);
|
|
2307
|
-
if (summary.protection === "absent") {
|
|
2308
|
-
throw new CLIError(`No keystore at ${path}. Run "btcr2 keystore init" first.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
2309
|
-
}
|
|
2310
|
-
if (summary.protection === "dev") {
|
|
2311
|
-
throw new CLIError(
|
|
2312
|
-
`The keystore at ${path} is an unencrypted dev keystore; there is no passphrase to change.`,
|
|
2313
|
-
"INVALID_ARGUMENT_ERROR",
|
|
2314
|
-
{ path }
|
|
2315
|
-
);
|
|
2316
|
-
}
|
|
2317
|
-
const oldPassphrase = acquirePassphrase({ passphraseFile: g.passphraseFile, prompt: "Current keystore passphrase: " });
|
|
2318
|
-
const newPassphrase = acquirePassphrase({ forcePrompt: true, confirm: true, prompt: "New keystore passphrase: " });
|
|
2319
|
-
const rekeyed = changeKeystorePassphrase(path, oldPassphrase, newPassphrase);
|
|
2320
|
-
clearSession(defaultSessionPath(g));
|
|
2321
|
-
print({ action: "keystore-change-passphrase", data: { path, rekeyed } });
|
|
2322
|
-
});
|
|
2323
|
-
keystore.command("unlock").description("Cache the keystore passphrase for a session so later commands do not re-prompt (ADR 081).").option("--ttl <duration>", `Session lifetime: bare seconds or an s/m/h suffix (default 1h, max 24h). Also $${ENV_KEYSTORE_TTL}.`).option("--allow-mainnet", "Permit unlocking when the active network is mainnet (bitcoin); this suspends per-use passphrase auth for the session.", false).action((options) => {
|
|
2324
|
-
const g = globals();
|
|
2325
|
-
const path = resolveKeystorePath(g);
|
|
2326
|
-
const summary = keystoreSummary(path);
|
|
2327
|
-
if (summary.protection === "absent") {
|
|
2328
|
-
throw new CLIError(`No keystore at ${path}. Run "btcr2 init" or "btcr2 keystore init" first.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
2329
|
-
}
|
|
2330
|
-
if (summary.protection === "dev") {
|
|
2331
|
-
throw new CLIError(
|
|
2332
|
-
`The keystore at ${path} is an unencrypted dev keystore; it has no passphrase to cache, so no unlock is needed.`,
|
|
2333
|
-
"INVALID_ARGUMENT_ERROR",
|
|
2334
|
-
{ path }
|
|
2335
|
-
);
|
|
2336
|
-
}
|
|
2337
|
-
if (!summary.established) {
|
|
2338
|
-
throw new CLIError(
|
|
2339
|
-
`The keystore at ${path} has no passphrase established yet. Establish one with "btcr2 keystore init" or the first "btcr2 key generate".`,
|
|
2340
|
-
"INVALID_ARGUMENT_ERROR",
|
|
2341
|
-
{ path }
|
|
2342
|
-
);
|
|
2343
|
-
}
|
|
2344
|
-
if (!options.allowMainnet && resolveDefaultNetwork(g) === "bitcoin") {
|
|
2345
|
-
throw new CLIError(
|
|
2346
|
-
`Refusing to unlock for a mainnet (bitcoin) context: caching the passphrase suspends per-use authentication for the session. Pass --allow-mainnet to override, or keep signing mainnet updates with a per-use passphrase prompt.`,
|
|
2347
|
-
"MAINNET_UNLOCK_REFUSED_ERROR",
|
|
2348
|
-
{ path }
|
|
2349
|
-
);
|
|
2350
|
-
}
|
|
2351
|
-
const ttlMs = resolveSessionTtl(options.ttl);
|
|
2352
|
-
const passphrase = acquirePassphrase({ passphraseFile: g.passphraseFile, prompt: "Keystore passphrase: " });
|
|
2353
|
-
if (!verifyKeystorePassphrase(path, passphrase)) {
|
|
2354
|
-
throw new CLIError(`Incorrect passphrase for the keystore at ${path}; no session was created.`, "DECRYPT_ERROR", { path });
|
|
2355
|
-
}
|
|
2356
|
-
const verifierId = keystoreVerifierId(path);
|
|
2357
|
-
if (!verifierId) {
|
|
2358
|
-
throw new CLIError(`The keystore at ${path} has no verifier to bind a session to.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
2359
|
-
}
|
|
2360
|
-
const session = writeSession(defaultSessionPath(g), {
|
|
2361
|
-
keystorePath: path,
|
|
2362
|
-
verifierId,
|
|
2363
|
-
passphrase,
|
|
2364
|
-
ttlMs,
|
|
2365
|
-
allowMainnet: !!options.allowMainnet
|
|
2366
|
-
});
|
|
2367
|
-
print({ action: "keystore-unlock", data: { keystore: path, expiresAt: session.expiresAt, ttlSeconds: session.ttlSeconds } });
|
|
2368
|
-
});
|
|
2369
|
-
keystore.command("lock").description("Revoke the cached session so later commands prompt for the passphrase again (ADR 081).").action(() => {
|
|
2370
|
-
const g = globals();
|
|
2371
|
-
const sessionPath = defaultSessionPath(g);
|
|
2372
|
-
const cleared = clearSession(sessionPath);
|
|
2373
|
-
print({ action: "keystore-lock", data: { path: sessionPath, cleared } });
|
|
2374
|
-
});
|
|
2375
|
-
}
|
|
2376
|
-
function resolveSessionTtl(flag) {
|
|
2377
|
-
const fromFlag = blankToUndef(flag);
|
|
2378
|
-
const raw = fromFlag ?? blankToUndef(process.env[ENV_KEYSTORE_TTL]);
|
|
2379
|
-
if (raw === void 0) return DEFAULT_SESSION_TTL_MS;
|
|
2380
|
-
const source = fromFlag !== void 0 ? "--ttl" : `$${ENV_KEYSTORE_TTL}`;
|
|
2381
|
-
const ms = parseTtlToMs(raw);
|
|
2382
|
-
if (ms === void 0 || ms <= 0) {
|
|
2383
|
-
throw new CLIError(
|
|
2384
|
-
`Invalid ${source} "${raw}": expected seconds or a value with an s/m/h suffix, e.g. 3600, 45m, or 2h.`,
|
|
2385
|
-
"INVALID_ARGUMENT_ERROR",
|
|
2386
|
-
{ value: raw, source }
|
|
2387
|
-
);
|
|
2388
|
-
}
|
|
2389
|
-
if (ms > MAX_SESSION_TTL_MS) {
|
|
2390
|
-
throw new CLIError(
|
|
2391
|
-
`${source} "${raw}" exceeds the 24h maximum for a cached passphrase.`,
|
|
2392
|
-
"INVALID_ARGUMENT_ERROR",
|
|
2393
|
-
{ value: raw, source }
|
|
2394
|
-
);
|
|
2634
|
+
(0, import_node_fs9.closeSync)(fd);
|
|
2395
2635
|
}
|
|
2396
|
-
return ms;
|
|
2397
2636
|
}
|
|
2398
2637
|
|
|
2399
2638
|
// src/commands/config.ts
|
|
@@ -2749,6 +2988,7 @@ var DidBtcr2Cli = class {
|
|
|
2749
2988
|
opts.output = resolveOutputFormat({ output: opts.output, config: opts.config, home: opts.home });
|
|
2750
2989
|
});
|
|
2751
2990
|
registerInitCommand(this.program, globals);
|
|
2991
|
+
registerQuickstartCommand(this.program, globals);
|
|
2752
2992
|
registerCreateCommand(this.program, factory, keystoreFactory, globals);
|
|
2753
2993
|
registerResolveCommand(this.program, factory, globals);
|
|
2754
2994
|
registerUpdateCommand(this.program, keystoreFactory, globals);
|
|
@@ -2805,6 +3045,7 @@ function handleError(error, verbose) {
|
|
|
2805
3045
|
SUPPORTED_NETWORKS,
|
|
2806
3046
|
VERSION,
|
|
2807
3047
|
assertKeystoreAllowedForNetwork,
|
|
3048
|
+
assertSupportedNetwork,
|
|
2808
3049
|
blankToUndef,
|
|
2809
3050
|
defaultApiFactory,
|
|
2810
3051
|
defaultConfigPath,
|
|
@@ -2815,9 +3056,11 @@ function handleError(error, verbose) {
|
|
|
2815
3056
|
keystoreApiFactory,
|
|
2816
3057
|
parseConfigFileRaw,
|
|
2817
3058
|
parseHeaderList,
|
|
3059
|
+
persistDefaultNetwork,
|
|
2818
3060
|
profileNetworkMismatch,
|
|
2819
3061
|
profileToOverrides,
|
|
2820
3062
|
readConfigFile,
|
|
3063
|
+
readConfiguredDefaultNetwork,
|
|
2821
3064
|
readEnvOverrides,
|
|
2822
3065
|
redactSecrets,
|
|
2823
3066
|
registerCompletionCommand,
|
|
@@ -2828,6 +3071,7 @@ function handleError(error, verbose) {
|
|
|
2828
3071
|
registerKeyCommand,
|
|
2829
3072
|
registerKeystoreCommand,
|
|
2830
3073
|
registerProfileCommand,
|
|
3074
|
+
registerQuickstartCommand,
|
|
2831
3075
|
registerResolveCommand,
|
|
2832
3076
|
registerUpdateCommand,
|
|
2833
3077
|
resolveActiveProfile,
|