@did-btcr2/cli 0.21.0 → 0.23.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 +29 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/cjs/index.js +494 -64
- package/dist/esm/src/cli.js +4 -2
- package/dist/esm/src/cli.js.map +1 -1
- package/dist/esm/src/commands/completion.js +1 -1
- package/dist/esm/src/commands/completion.js.map +1 -1
- package/dist/esm/src/commands/create.js +33 -41
- package/dist/esm/src/commands/create.js.map +1 -1
- package/dist/esm/src/commands/genesis.js +119 -0
- package/dist/esm/src/commands/genesis.js.map +1 -0
- package/dist/esm/src/commands/identifier.js +96 -0
- package/dist/esm/src/commands/identifier.js.map +1 -0
- package/dist/esm/src/commands/index.js +2 -0
- package/dist/esm/src/commands/index.js.map +1 -1
- package/dist/esm/src/commands/resolve.js +15 -1
- package/dist/esm/src/commands/resolve.js.map +1 -1
- package/dist/esm/src/commands/write.js +10 -7
- package/dist/esm/src/commands/write.js.map +1 -1
- package/dist/esm/src/genesis-document-file.js +26 -0
- package/dist/esm/src/genesis-document-file.js.map +1 -0
- package/dist/esm/src/genesis-spec.js +93 -0
- package/dist/esm/src/genesis-spec.js.map +1 -0
- package/dist/esm/src/genesis-wizard.js +132 -0
- package/dist/esm/src/genesis-wizard.js.map +1 -0
- package/dist/esm/src/hints.js +14 -3
- package/dist/esm/src/hints.js.map +1 -1
- package/dist/esm/src/network-option.js +39 -0
- package/dist/esm/src/network-option.js.map +1 -0
- package/dist/esm/src/resolution-options.js +19 -2
- package/dist/esm/src/resolution-options.js.map +1 -1
- package/dist/types/src/cli.d.ts +1 -1
- package/dist/types/src/cli.d.ts.map +1 -1
- package/dist/types/src/commands/create.d.ts +5 -4
- package/dist/types/src/commands/create.d.ts.map +1 -1
- package/dist/types/src/commands/genesis.d.ts +20 -0
- package/dist/types/src/commands/genesis.d.ts.map +1 -0
- package/dist/types/src/commands/identifier.d.ts +12 -0
- package/dist/types/src/commands/identifier.d.ts.map +1 -0
- package/dist/types/src/commands/index.d.ts +2 -0
- package/dist/types/src/commands/index.d.ts.map +1 -1
- package/dist/types/src/commands/resolve.d.ts +7 -0
- package/dist/types/src/commands/resolve.d.ts.map +1 -1
- package/dist/types/src/commands/write.d.ts +2 -1
- package/dist/types/src/commands/write.d.ts.map +1 -1
- package/dist/types/src/genesis-document-file.d.ts +11 -0
- package/dist/types/src/genesis-document-file.d.ts.map +1 -0
- package/dist/types/src/genesis-spec.d.ts +70 -0
- package/dist/types/src/genesis-spec.d.ts.map +1 -0
- package/dist/types/src/genesis-wizard.d.ts +30 -0
- package/dist/types/src/genesis-wizard.d.ts.map +1 -0
- package/dist/types/src/hints.d.ts +7 -0
- package/dist/types/src/hints.d.ts.map +1 -1
- package/dist/types/src/network-option.d.ts +15 -0
- package/dist/types/src/network-option.d.ts.map +1 -0
- package/dist/types/src/resolution-options.d.ts +6 -2
- package/dist/types/src/resolution-options.d.ts.map +1 -1
- package/dist/types/src/types.d.ts +38 -1
- package/dist/types/src/types.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/cli.ts +5 -1
- package/src/commands/completion.ts +1 -1
- package/src/commands/create.ts +39 -55
- package/src/commands/genesis.ts +149 -0
- package/src/commands/identifier.ts +134 -0
- package/src/commands/index.ts +2 -0
- package/src/commands/resolve.ts +20 -1
- package/src/commands/write.ts +10 -7
- package/src/genesis-document-file.ts +35 -0
- package/src/genesis-spec.ts +149 -0
- package/src/genesis-wizard.ts +163 -0
- package/src/hints.ts +13 -2
- package/src/network-option.ts +50 -0
- package/src/resolution-options.ts +21 -2
- package/src/types.ts +34 -2
package/dist/cjs/index.js
CHANGED
|
@@ -51,6 +51,8 @@ __export(index_exports, {
|
|
|
51
51
|
registerConfigCommand: () => registerConfigCommand,
|
|
52
52
|
registerCreateCommand: () => registerCreateCommand,
|
|
53
53
|
registerDeactivateCommand: () => registerDeactivateCommand,
|
|
54
|
+
registerGenesisCommand: () => registerGenesisCommand,
|
|
55
|
+
registerIdentifierCommand: () => registerIdentifierCommand,
|
|
54
56
|
registerInitCommand: () => registerInitCommand,
|
|
55
57
|
registerKeyCommand: () => registerKeyCommand,
|
|
56
58
|
registerKeystoreCommand: () => registerKeystoreCommand,
|
|
@@ -2096,13 +2098,35 @@ function printNextSteps(init, unlocked, session, doctor) {
|
|
|
2096
2098
|
// src/commands/create.ts
|
|
2097
2099
|
var import_utils4 = require("@noble/hashes/utils.js");
|
|
2098
2100
|
|
|
2101
|
+
// src/genesis-document-file.ts
|
|
2102
|
+
var import_promises = require("fs/promises");
|
|
2103
|
+
var GENESIS_DOCUMENT_HELP = "Path to the JSON genesis document of an external identifier (x). Fills sidecar.genesisDocument of the resolution options";
|
|
2104
|
+
async function readGenesisDocumentFile(path) {
|
|
2105
|
+
let parsed;
|
|
2106
|
+
try {
|
|
2107
|
+
parsed = JSON.parse(await (0, import_promises.readFile)(path, "utf-8"));
|
|
2108
|
+
} catch {
|
|
2109
|
+
throw new CLIError(
|
|
2110
|
+
"Invalid genesis document path. Must be a valid path to a JSON file.",
|
|
2111
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2112
|
+
{ genesisDocument: path }
|
|
2113
|
+
);
|
|
2114
|
+
}
|
|
2115
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
2116
|
+
throw new CLIError(
|
|
2117
|
+
"Invalid genesis document. The file must contain a JSON object.",
|
|
2118
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2119
|
+
{ genesisDocument: path }
|
|
2120
|
+
);
|
|
2121
|
+
}
|
|
2122
|
+
return parsed;
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2099
2125
|
// src/hints.ts
|
|
2100
2126
|
var import_api3 = require("@did-btcr2/api");
|
|
2101
2127
|
var import_method2 = require("@did-btcr2/method");
|
|
2102
2128
|
function printCreateFundingHint(g, network, did) {
|
|
2103
2129
|
if (g.quiet || g.output === "json") return;
|
|
2104
|
-
const faucet = (0, import_api3.faucetUrl)(network);
|
|
2105
|
-
if (!faucet) return;
|
|
2106
2130
|
let beaconAddress;
|
|
2107
2131
|
try {
|
|
2108
2132
|
const { serviceEndpoint } = import_method2.BeaconUtils.createBeaconService(did, "p2wpkh", "SingletonBeacon");
|
|
@@ -2110,6 +2134,12 @@ function printCreateFundingHint(g, network, did) {
|
|
|
2110
2134
|
} catch {
|
|
2111
2135
|
return;
|
|
2112
2136
|
}
|
|
2137
|
+
printBeaconFundingHint(g, network, beaconAddress);
|
|
2138
|
+
}
|
|
2139
|
+
function printBeaconFundingHint(g, network, beaconAddress) {
|
|
2140
|
+
if (g.quiet || g.output === "json") return;
|
|
2141
|
+
const faucet = (0, import_api3.faucetUrl)(network);
|
|
2142
|
+
if (!faucet) return;
|
|
2113
2143
|
const explorer = (0, import_api3.explorerAddressUrl)(network, beaconAddress);
|
|
2114
2144
|
const lines = [
|
|
2115
2145
|
"Fund the initial beacon to anchor updates:",
|
|
@@ -2165,33 +2195,59 @@ function resolveKeyRef(kms, ref) {
|
|
|
2165
2195
|
throw new CLIError(`No key matches reference "${ref}".`, "KEY_NOT_FOUND_ERROR", { ref });
|
|
2166
2196
|
}
|
|
2167
2197
|
|
|
2198
|
+
// src/network-option.ts
|
|
2199
|
+
var NETWORK_OPTION_HELP = "Identifier bitcoin network <bitcoin|testnet3|testnet4|signet|mutinynet|regtest> (default: config defaults.network, else regtest)";
|
|
2200
|
+
function overridesFromGlobals(g) {
|
|
2201
|
+
return {
|
|
2202
|
+
home: g.home,
|
|
2203
|
+
config: g.config,
|
|
2204
|
+
profile: g.profile,
|
|
2205
|
+
keystore: g.keystore,
|
|
2206
|
+
passphraseFile: g.passphraseFile
|
|
2207
|
+
};
|
|
2208
|
+
}
|
|
2209
|
+
function resolveNetworkOption(explicit, overrides) {
|
|
2210
|
+
if (!explicit) return resolveDefaultNetwork(overrides);
|
|
2211
|
+
if (!SUPPORTED_NETWORKS.includes(explicit)) {
|
|
2212
|
+
throw new CLIError(
|
|
2213
|
+
'Invalid network. Must be one of "bitcoin", "testnet3", "testnet4", "signet", "mutinynet", or "regtest".',
|
|
2214
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2215
|
+
{ network: explicit }
|
|
2216
|
+
);
|
|
2217
|
+
}
|
|
2218
|
+
return explicit;
|
|
2219
|
+
}
|
|
2220
|
+
function warnProfileNetworkMismatch(g, network, overrides) {
|
|
2221
|
+
const mismatch = profileNetworkMismatch(network, overrides);
|
|
2222
|
+
if (mismatch && !g.quiet) {
|
|
2223
|
+
process.stderr.write(
|
|
2224
|
+
`Warning: creating a "${network}" identifier while the active profile "${mismatch.profile}" declares network "${mismatch.declared}". The identifier's network and the profile's endpoints may not match.
|
|
2225
|
+
`
|
|
2226
|
+
);
|
|
2227
|
+
}
|
|
2228
|
+
}
|
|
2229
|
+
|
|
2168
2230
|
// src/commands/create.ts
|
|
2169
2231
|
var EXPECTED_BYTES = {
|
|
2170
2232
|
k: { length: 33, label: "secp256k1 compressed public key (33 bytes)" },
|
|
2171
2233
|
x: { length: 32, label: "SHA-256 hash (32 bytes)" }
|
|
2172
2234
|
};
|
|
2173
2235
|
function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
2174
|
-
program.command("create").description("Create an identifier and initial DID document").option("-t, --type <type>", "Identifier type <k|x>", "k").option(
|
|
2175
|
-
"-n, --network <network>",
|
|
2176
|
-
"Identifier bitcoin network <bitcoin|testnet3|testnet4|signet|mutinynet|regtest> (default: config defaults.network, else regtest)"
|
|
2177
|
-
).option(
|
|
2236
|
+
program.command("create").description("Create an identifier and initial DID document").option("-t, --type <type>", "Identifier type <k|x>", "k").option("-n, --network <network>", NETWORK_OPTION_HELP).option(
|
|
2178
2237
|
"-b, --bytes <bytes>",
|
|
2179
2238
|
"Genesis bytes as a hex string. For type=k, a 33-byte secp256k1 public key (omit to generate a key). For type=x, the 32-byte SHA-256 hash of a genesis document."
|
|
2239
|
+
).option(
|
|
2240
|
+
"--document <path>",
|
|
2241
|
+
'For type=x, the path of the JSON genesis document to hash (see "btcr2 genesis build"). Exclusive with --bytes.'
|
|
2180
2242
|
).action(async (options) => {
|
|
2181
2243
|
const g = globals();
|
|
2182
2244
|
if (options.type !== "k" && options.type !== "x") {
|
|
2183
2245
|
throw new CLIError('Invalid type. Must be "k" or "x".', "INVALID_ARGUMENT_ERROR", options);
|
|
2184
2246
|
}
|
|
2185
2247
|
const overrides = overridesFromGlobals(g);
|
|
2186
|
-
const network =
|
|
2248
|
+
const network = resolveNetworkOption(options.network, overrides);
|
|
2187
2249
|
const signingKey = g.signingKey;
|
|
2188
|
-
|
|
2189
|
-
if (mismatch && !g.quiet) {
|
|
2190
|
-
process.stderr.write(
|
|
2191
|
-
`Warning: creating a "${network}" identifier while the active profile "${mismatch.profile}" declares network "${mismatch.declared}". The identifier's network and the profile's endpoints may not match.
|
|
2192
|
-
`
|
|
2193
|
-
);
|
|
2194
|
-
}
|
|
2250
|
+
warnProfileNetworkMismatch(g, network, overrides);
|
|
2195
2251
|
const print = (result, note) => {
|
|
2196
2252
|
console.log(formatResult(result, g));
|
|
2197
2253
|
if (note && g.output !== "json") process.stderr.write(`${note}
|
|
@@ -2204,9 +2260,21 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
2204
2260
|
"INVALID_ARGUMENT_ERROR"
|
|
2205
2261
|
);
|
|
2206
2262
|
}
|
|
2263
|
+
if (options.bytes !== void 0 && options.document !== void 0) {
|
|
2264
|
+
throw new CLIError("Provide at most one of --bytes or --document.", "INVALID_ARGUMENT_ERROR");
|
|
2265
|
+
}
|
|
2266
|
+
if (options.document !== void 0) {
|
|
2267
|
+
const genesisDocument = await readGenesisDocumentFile(options.document);
|
|
2268
|
+
const api2 = factory();
|
|
2269
|
+
const { did: did3, genesisBytes: genesisBytes2, didDocument } = api2.btcr2.createExternalFromDocument(genesisDocument, { network });
|
|
2270
|
+
print({ action: "create", data: did3, genesisBytes: (0, import_utils4.bytesToHex)(genesisBytes2) });
|
|
2271
|
+
const beacons = api2.btcr2.getBeacons(didDocument);
|
|
2272
|
+
if (beacons.length > 0) printBeaconFundingHint(g, network, beacons[0].address);
|
|
2273
|
+
return;
|
|
2274
|
+
}
|
|
2207
2275
|
if (options.bytes === void 0) {
|
|
2208
2276
|
throw new CLIError(
|
|
2209
|
-
"External identifiers (-t x) require --bytes <hex>,
|
|
2277
|
+
"External identifiers (-t x) require --document <path>, the genesis document, or --bytes <hex>, its 32-byte hash. Key generation is only available for -t k.",
|
|
2210
2278
|
"INVALID_ARGUMENT_ERROR"
|
|
2211
2279
|
);
|
|
2212
2280
|
}
|
|
@@ -2215,6 +2283,9 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
2215
2283
|
print({ action: "create", data: did2 });
|
|
2216
2284
|
return;
|
|
2217
2285
|
}
|
|
2286
|
+
if (options.document !== void 0) {
|
|
2287
|
+
throw new CLIError("--document applies only to external identifiers (-t x).", "INVALID_ARGUMENT_ERROR");
|
|
2288
|
+
}
|
|
2218
2289
|
if (options.bytes !== void 0 && signingKey) {
|
|
2219
2290
|
throw new CLIError(
|
|
2220
2291
|
"Provide at most one of --bytes or --signing-key.",
|
|
@@ -2251,26 +2322,6 @@ function registerCreateCommand(program, factory, keystoreFactory, globals) {
|
|
|
2251
2322
|
printCreateFundingHint(g, network, did);
|
|
2252
2323
|
});
|
|
2253
2324
|
}
|
|
2254
|
-
function overridesFromGlobals(g) {
|
|
2255
|
-
return {
|
|
2256
|
-
home: g.home,
|
|
2257
|
-
config: g.config,
|
|
2258
|
-
profile: g.profile,
|
|
2259
|
-
keystore: g.keystore,
|
|
2260
|
-
passphraseFile: g.passphraseFile
|
|
2261
|
-
};
|
|
2262
|
-
}
|
|
2263
|
-
function resolveNetwork(explicit, overrides) {
|
|
2264
|
-
if (!explicit) return resolveDefaultNetwork(overrides);
|
|
2265
|
-
if (!SUPPORTED_NETWORKS.includes(explicit)) {
|
|
2266
|
-
throw new CLIError(
|
|
2267
|
-
'Invalid network. Must be one of "bitcoin", "testnet3", "testnet4", "signet", "mutinynet", or "regtest".',
|
|
2268
|
-
"INVALID_ARGUMENT_ERROR",
|
|
2269
|
-
{ network: explicit }
|
|
2270
|
-
);
|
|
2271
|
-
}
|
|
2272
|
-
return explicit;
|
|
2273
|
-
}
|
|
2274
2325
|
function parseGenesisBytes(hex, type) {
|
|
2275
2326
|
const expected = EXPECTED_BYTES[type];
|
|
2276
2327
|
let bytes;
|
|
@@ -2298,7 +2349,10 @@ var import_api5 = require("@did-btcr2/api");
|
|
|
2298
2349
|
|
|
2299
2350
|
// src/resolution-options.ts
|
|
2300
2351
|
var import_api4 = require("@did-btcr2/api");
|
|
2301
|
-
var
|
|
2352
|
+
var import_promises2 = require("fs/promises");
|
|
2353
|
+
function hasResolutionFlags(flags) {
|
|
2354
|
+
return flags.resolutionOptions !== void 0 || flags.resolutionOptionsPath !== void 0 || flags.minConf !== void 0 || flags.genesisDocument !== void 0;
|
|
2355
|
+
}
|
|
2302
2356
|
var MIN_CONF_HELP = `Minimum block confirmations a beacon signal needs before resolution applies it (positive integer, default: ${import_api4.DEFAULT_MIN_CONF}). Overrides a minConf inside the JSON resolution options`;
|
|
2303
2357
|
async function readResolutionOptions(flags) {
|
|
2304
2358
|
let resolutionOptions;
|
|
@@ -2314,7 +2368,7 @@ async function readResolutionOptions(flags) {
|
|
|
2314
2368
|
}
|
|
2315
2369
|
} else if (flags.resolutionOptionsPath) {
|
|
2316
2370
|
try {
|
|
2317
|
-
const content = await (0,
|
|
2371
|
+
const content = await (0, import_promises2.readFile)(flags.resolutionOptionsPath, "utf-8");
|
|
2318
2372
|
resolutionOptions = JSON.parse(content);
|
|
2319
2373
|
} catch {
|
|
2320
2374
|
throw new CLIError(
|
|
@@ -2327,6 +2381,13 @@ async function readResolutionOptions(flags) {
|
|
|
2327
2381
|
if (flags.minConf !== void 0) {
|
|
2328
2382
|
resolutionOptions = { ...resolutionOptions ?? {}, minConf: flags.minConf };
|
|
2329
2383
|
}
|
|
2384
|
+
if (flags.genesisDocument !== void 0) {
|
|
2385
|
+
const genesisDocument = await readGenesisDocumentFile(flags.genesisDocument);
|
|
2386
|
+
resolutionOptions = {
|
|
2387
|
+
...resolutionOptions ?? {},
|
|
2388
|
+
sidecar: { ...resolutionOptions?.sidecar ?? {}, genesisDocument }
|
|
2389
|
+
};
|
|
2390
|
+
}
|
|
2330
2391
|
return resolutionOptions;
|
|
2331
2392
|
}
|
|
2332
2393
|
function parseMinConf(value) {
|
|
@@ -2342,7 +2403,7 @@ function parseMinConf(value) {
|
|
|
2342
2403
|
|
|
2343
2404
|
// src/commands/resolve.ts
|
|
2344
2405
|
function registerResolveCommand(program, factory, globals) {
|
|
2345
|
-
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").option("--min-conf <n>", MIN_CONF_HELP, parseMinConf).action(async (options) => {
|
|
2406
|
+
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").option("--min-conf <n>", MIN_CONF_HELP, parseMinConf).option("--genesis-document <path>", GENESIS_DOCUMENT_HELP).action(async (options) => {
|
|
2346
2407
|
const parsed = await validateResolveOptions(options);
|
|
2347
2408
|
const network = deriveNetwork(parsed.identifier);
|
|
2348
2409
|
const api = factory(network, globals());
|
|
@@ -2352,13 +2413,24 @@ function registerResolveCommand(program, factory, globals) {
|
|
|
2352
2413
|
});
|
|
2353
2414
|
}
|
|
2354
2415
|
async function validateResolveOptions(options) {
|
|
2355
|
-
import_api5.Identifier.decode(options.identifier);
|
|
2416
|
+
const components = import_api5.Identifier.decode(options.identifier);
|
|
2417
|
+
assertGenesisDocumentApplies(options, components.hrp);
|
|
2356
2418
|
const resolutionOptions = await readResolutionOptions(options);
|
|
2357
2419
|
return { identifier: options.identifier, options: resolutionOptions };
|
|
2358
2420
|
}
|
|
2421
|
+
function assertGenesisDocumentApplies(flags, hrp) {
|
|
2422
|
+
if (flags.genesisDocument !== void 0 && hrp === "k") {
|
|
2423
|
+
throw new CLIError(
|
|
2424
|
+
"--genesis-document applies only to external identifiers (x).",
|
|
2425
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2426
|
+
{ genesisDocument: flags.genesisDocument }
|
|
2427
|
+
);
|
|
2428
|
+
}
|
|
2429
|
+
}
|
|
2359
2430
|
|
|
2360
2431
|
// src/commands/write.ts
|
|
2361
2432
|
var import_key_manager2 = require("@did-btcr2/key-manager");
|
|
2433
|
+
var import_api6 = require("@did-btcr2/api");
|
|
2362
2434
|
function registerWriteOptions(command) {
|
|
2363
2435
|
return command.option(
|
|
2364
2436
|
"-s, --source-document <json>",
|
|
@@ -2380,7 +2452,7 @@ function registerWriteOptions(command) {
|
|
|
2380
2452
|
).option(
|
|
2381
2453
|
"--resolution-options-path <path>",
|
|
2382
2454
|
"Path to a JSON file with resolution options, for the resolution of the source document"
|
|
2383
|
-
).option("--min-conf <n>", MIN_CONF_HELP, parseMinConf).option(
|
|
2455
|
+
).option("--min-conf <n>", MIN_CONF_HELP, parseMinConf).option("--genesis-document <path>", `${GENESIS_DOCUMENT_HELP}, for the resolution of the source document`).option(
|
|
2384
2456
|
"--publish-to-cas <mode>",
|
|
2385
2457
|
"Publish update artifacts to a writable CAS before broadcast: auto|always|never. CAS publication is optional; the default distributes the returned artifacts via sidecar.",
|
|
2386
2458
|
parsePublishToCasMode,
|
|
@@ -2405,14 +2477,14 @@ async function prepareWrite(options, factory, g) {
|
|
|
2405
2477
|
{ did }
|
|
2406
2478
|
);
|
|
2407
2479
|
}
|
|
2408
|
-
|
|
2409
|
-
if (hasDocument && hasResolutionFlags) {
|
|
2480
|
+
if (hasDocument && hasResolutionFlags(options)) {
|
|
2410
2481
|
throw new CLIError(
|
|
2411
|
-
"--resolution-options, --resolution-options-path,
|
|
2482
|
+
"--resolution-options, --resolution-options-path, --min-conf, and --genesis-document apply only when --source-document and --source-version-id are omitted. A supplied source pair skips resolution.",
|
|
2412
2483
|
"INVALID_ARGUMENT_ERROR",
|
|
2413
2484
|
{ did }
|
|
2414
2485
|
);
|
|
2415
2486
|
}
|
|
2487
|
+
assertGenesisDocumentApplies(options, import_api6.Identifier.decode(did).hrp);
|
|
2416
2488
|
assertKeystoreAllowedForNetwork(network, g);
|
|
2417
2489
|
const resolutionOptions = await readResolutionOptions(options);
|
|
2418
2490
|
const api = factory(network, g);
|
|
@@ -2500,10 +2572,364 @@ function registerDeactivateCommand(program, factory, globals) {
|
|
|
2500
2572
|
});
|
|
2501
2573
|
}
|
|
2502
2574
|
|
|
2503
|
-
// src/commands/
|
|
2504
|
-
var
|
|
2575
|
+
// src/commands/identifier.ts
|
|
2576
|
+
var import_api7 = require("@did-btcr2/api");
|
|
2505
2577
|
var import_utils5 = require("@noble/hashes/utils.js");
|
|
2578
|
+
function registerIdentifierCommand(program, factory, globals) {
|
|
2579
|
+
const identifier = program.command("identifier").description("Decode and validate did:btcr2 identifiers (offline).");
|
|
2580
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
2581
|
+
identifier.command("decode <did>").description("Print the components of a did:btcr2 identifier.").option(
|
|
2582
|
+
"--initial-document",
|
|
2583
|
+
"Add the initial DID document. An external identifier (x) needs --genesis-document.",
|
|
2584
|
+
false
|
|
2585
|
+
).option(
|
|
2586
|
+
"--genesis-document <path>",
|
|
2587
|
+
"Path to the JSON genesis document of an external identifier (x). Requires --initial-document."
|
|
2588
|
+
).action(async (did, options) => {
|
|
2589
|
+
if (options.genesisDocument !== void 0 && !options.initialDocument) {
|
|
2590
|
+
throw new CLIError("--genesis-document requires --initial-document.", "INVALID_ARGUMENT_ERROR");
|
|
2591
|
+
}
|
|
2592
|
+
assertValidIdentifier(did);
|
|
2593
|
+
const api = factory();
|
|
2594
|
+
const components = api.did.decode(did);
|
|
2595
|
+
if (options.genesisDocument !== void 0 && components.hrp === "k") {
|
|
2596
|
+
throw new CLIError(
|
|
2597
|
+
"--genesis-document applies only to external identifiers (x).",
|
|
2598
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2599
|
+
{ did }
|
|
2600
|
+
);
|
|
2601
|
+
}
|
|
2602
|
+
const data = {
|
|
2603
|
+
did,
|
|
2604
|
+
idType: components.idType,
|
|
2605
|
+
hrp: components.hrp,
|
|
2606
|
+
version: components.version,
|
|
2607
|
+
network: components.network,
|
|
2608
|
+
genesisBytes: (0, import_utils5.bytesToHex)(components.genesisBytes)
|
|
2609
|
+
};
|
|
2610
|
+
if (options.initialDocument) {
|
|
2611
|
+
if (components.hrp === "x" && options.genesisDocument === void 0) {
|
|
2612
|
+
throw new CLIError(
|
|
2613
|
+
"An external identifier (x) needs --genesis-document <path> for --initial-document.",
|
|
2614
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2615
|
+
{ did }
|
|
2616
|
+
);
|
|
2617
|
+
}
|
|
2618
|
+
const genesisDocument = options.genesisDocument === void 0 ? void 0 : await readGenesisDocumentFile(options.genesisDocument);
|
|
2619
|
+
data.initialDocument = api.btcr2.getInitialDocument(did, genesisDocument);
|
|
2620
|
+
}
|
|
2621
|
+
print({ action: "identifier-decode", data });
|
|
2622
|
+
});
|
|
2623
|
+
identifier.command("validate <did>").description("Check that a did:btcr2 identifier conforms to the specification. Exit code 1 if it does not.").option(
|
|
2624
|
+
"-b, --bytes <hex>",
|
|
2625
|
+
"Genesis bytes as a hex string that the identifier must encode: the 33-byte public key (k) or the 32-byte genesis document hash (x). Adds the genesisBytesMatch check."
|
|
2626
|
+
).option(
|
|
2627
|
+
"--genesis-document <path>",
|
|
2628
|
+
"Path to the JSON genesis document of an external identifier (x). Adds the genesisDocument check."
|
|
2629
|
+
).action(async (did, options) => {
|
|
2630
|
+
const genesisBytes = options.bytes === void 0 ? void 0 : parseHexBytes(options.bytes);
|
|
2631
|
+
let genesisDocument;
|
|
2632
|
+
if (options.genesisDocument !== void 0) {
|
|
2633
|
+
if (import_api7.Identifier.isValid(did) && import_api7.Identifier.decode(did).hrp === "k") {
|
|
2634
|
+
throw new CLIError(
|
|
2635
|
+
"--genesis-document applies only to external identifiers (x).",
|
|
2636
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2637
|
+
{ did }
|
|
2638
|
+
);
|
|
2639
|
+
}
|
|
2640
|
+
genesisDocument = await readGenesisDocumentFile(options.genesisDocument);
|
|
2641
|
+
}
|
|
2642
|
+
const report = factory().did.validate(did, { genesisBytes, genesisDocument });
|
|
2643
|
+
print({ action: "identifier-validate", data: report });
|
|
2644
|
+
if (!report.valid) process.exitCode = 1;
|
|
2645
|
+
});
|
|
2646
|
+
}
|
|
2647
|
+
function assertValidIdentifier(did) {
|
|
2648
|
+
const report = import_api7.Identifier.validate(did);
|
|
2649
|
+
if (report.valid) return;
|
|
2650
|
+
const failed = report.checks[report.checks.length - 1];
|
|
2651
|
+
throw new CLIError(
|
|
2652
|
+
`Invalid identifier (${failed.name} check): ${failed.detail ?? "failed"}`,
|
|
2653
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2654
|
+
{ did, check: failed.name }
|
|
2655
|
+
);
|
|
2656
|
+
}
|
|
2657
|
+
function parseHexBytes(value) {
|
|
2658
|
+
try {
|
|
2659
|
+
return (0, import_utils5.hexToBytes)(value.trim());
|
|
2660
|
+
} catch {
|
|
2661
|
+
throw new CLIError("Invalid bytes: not valid hex.", "INVALID_ARGUMENT_ERROR", { bytes: value });
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2664
|
+
|
|
2665
|
+
// src/commands/genesis.ts
|
|
2666
|
+
var import_utils7 = require("@noble/hashes/utils.js");
|
|
2506
2667
|
var import_node_fs9 = require("fs");
|
|
2668
|
+
var import_promises3 = require("fs/promises");
|
|
2669
|
+
var import_node_path6 = require("path");
|
|
2670
|
+
var import_promises4 = require("readline/promises");
|
|
2671
|
+
|
|
2672
|
+
// src/genesis-spec.ts
|
|
2673
|
+
var import_utils6 = require("@noble/hashes/utils.js");
|
|
2674
|
+
function parseGenesisSpec(value, source) {
|
|
2675
|
+
const fail = (message) => {
|
|
2676
|
+
throw new CLIError(`Invalid genesis spec: ${message}`, "INVALID_ARGUMENT_ERROR", { spec: source });
|
|
2677
|
+
};
|
|
2678
|
+
if (value === null || typeof value !== "object" || Array.isArray(value)) {
|
|
2679
|
+
return fail("the file must contain a JSON object.");
|
|
2680
|
+
}
|
|
2681
|
+
const spec = value;
|
|
2682
|
+
if (!Array.isArray(spec.verificationMethods) || spec.verificationMethods.length === 0) {
|
|
2683
|
+
return fail('"verificationMethods" must be a non-empty array.');
|
|
2684
|
+
}
|
|
2685
|
+
spec.verificationMethods.forEach((vm, index) => {
|
|
2686
|
+
if (vm === null || typeof vm !== "object") return fail(`verificationMethods[${index}] must be an object.`);
|
|
2687
|
+
const hasKey = typeof vm.key === "string" && vm.key.length > 0;
|
|
2688
|
+
const hasPublicKey = typeof vm.publicKey === "string" && vm.publicKey.length > 0;
|
|
2689
|
+
if (hasKey === hasPublicKey) {
|
|
2690
|
+
return fail(`verificationMethods[${index}] needs exactly one of "key" (a keystore reference) or "publicKey" (hex).`);
|
|
2691
|
+
}
|
|
2692
|
+
});
|
|
2693
|
+
if (spec.beacons !== void 0) {
|
|
2694
|
+
if (!Array.isArray(spec.beacons)) return fail('"beacons" must be an array.');
|
|
2695
|
+
spec.beacons.forEach((beacon, index) => {
|
|
2696
|
+
if (beacon === null || typeof beacon !== "object") return fail(`beacons[${index}] must be an object.`);
|
|
2697
|
+
const sources = ["key", "publicKey", "address"].filter((name) => beacon[name] !== void 0);
|
|
2698
|
+
if (sources.length > 1) {
|
|
2699
|
+
return fail(`beacons[${index}] names ${sources.join(" and ")}. Give only one of "key", "publicKey", or "address".`);
|
|
2700
|
+
}
|
|
2701
|
+
});
|
|
2702
|
+
}
|
|
2703
|
+
if (spec.services !== void 0 && !Array.isArray(spec.services)) {
|
|
2704
|
+
return fail('"services" must be an array.');
|
|
2705
|
+
}
|
|
2706
|
+
return spec;
|
|
2707
|
+
}
|
|
2708
|
+
function toApiSpec(spec, network, resolvePublicKey) {
|
|
2709
|
+
const keyBytes = (entry, label) => entry.key !== void 0 ? resolvePublicKey(entry.key) : parseHexPublicKey(entry.publicKey, label);
|
|
2710
|
+
const verificationMethods = spec.verificationMethods.map((vm, index) => ({
|
|
2711
|
+
publicKey: keyBytes(vm, `verificationMethods[${index}].publicKey`),
|
|
2712
|
+
relationships: vm.relationships,
|
|
2713
|
+
fragment: vm.fragment
|
|
2714
|
+
}));
|
|
2715
|
+
const beacons = spec.beacons?.map((beacon, index) => ({
|
|
2716
|
+
type: beacon.type,
|
|
2717
|
+
publicKey: beacon.key !== void 0 || beacon.publicKey !== void 0 ? keyBytes(beacon, `beacons[${index}].publicKey`) : void 0,
|
|
2718
|
+
addressType: beacon.addressType,
|
|
2719
|
+
address: beacon.address,
|
|
2720
|
+
fragment: beacon.fragment
|
|
2721
|
+
}));
|
|
2722
|
+
return {
|
|
2723
|
+
network,
|
|
2724
|
+
verificationMethods,
|
|
2725
|
+
beacons,
|
|
2726
|
+
services: spec.services
|
|
2727
|
+
};
|
|
2728
|
+
}
|
|
2729
|
+
function parseHexPublicKey(hex, label) {
|
|
2730
|
+
try {
|
|
2731
|
+
return (0, import_utils6.hexToBytes)(hex.trim());
|
|
2732
|
+
} catch {
|
|
2733
|
+
throw new CLIError(`Invalid ${label}: not valid hex.`, "INVALID_ARGUMENT_ERROR", { publicKey: hex });
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
|
|
2737
|
+
// src/genesis-wizard.ts
|
|
2738
|
+
var import_api8 = require("@did-btcr2/api");
|
|
2739
|
+
var PUBLIC_KEY_HEX = /^0[23][0-9a-fA-F]{64}$/;
|
|
2740
|
+
async function collectGenesisSpec(ask, ctx) {
|
|
2741
|
+
const verificationMethods = await collectVerificationMethods(ask, ctx);
|
|
2742
|
+
const beacons = await collectBeacons(ask, ctx, verificationMethods[0]);
|
|
2743
|
+
const services = await collectServices(ask, ctx);
|
|
2744
|
+
return { verificationMethods, beacons, ...services.length > 0 && { services } };
|
|
2745
|
+
}
|
|
2746
|
+
async function collectVerificationMethods(ask, ctx) {
|
|
2747
|
+
if (ctx.keys.length === 0) {
|
|
2748
|
+
ctx.say("The keystore has no keys. Enter each key as a 33-byte compressed public key in hex.");
|
|
2749
|
+
} else {
|
|
2750
|
+
ctx.say("Keys in the keystore:");
|
|
2751
|
+
for (const key of ctx.keys) {
|
|
2752
|
+
ctx.say(` ${key.fingerprint}${key.name ? ` ${key.name}` : ""}${key.active ? " (active)" : ""}`);
|
|
2753
|
+
}
|
|
2754
|
+
}
|
|
2755
|
+
const active = ctx.keys.find((key) => key.active);
|
|
2756
|
+
const methods = [];
|
|
2757
|
+
for (let n = 1; ; n++) {
|
|
2758
|
+
const defaultRef = n === 1 && active ? active.name ?? active.fingerprint : void 0;
|
|
2759
|
+
const source = await askKeySource(ask, ctx, `Verification method ${n}: key reference or public key hex`, defaultRef);
|
|
2760
|
+
const relationships = await askRelationships(ask, ctx, n);
|
|
2761
|
+
methods.push({ ...source, ...relationships && { relationships } });
|
|
2762
|
+
if (!await askYesNo(ask, "Add another verification method?")) break;
|
|
2763
|
+
}
|
|
2764
|
+
return methods;
|
|
2765
|
+
}
|
|
2766
|
+
async function collectBeacons(ask, ctx, firstMethod) {
|
|
2767
|
+
const beacons = [];
|
|
2768
|
+
for (let n = 1; ; n++) {
|
|
2769
|
+
const type = await askChoice(ask, ctx, `Beacon ${n} type`, import_api8.BEACON_TYPES, "SingletonBeacon");
|
|
2770
|
+
const address = (await ask(`Beacon ${n} address (blank: derive the address from the key of method 1): `)).trim();
|
|
2771
|
+
if (address.length === 0) {
|
|
2772
|
+
const addressType = await askChoice(ask, ctx, `Beacon ${n} address type`, import_api8.BEACON_ADDRESS_TYPES, import_api8.DEFAULT_BEACON_ADDRESS_TYPE);
|
|
2773
|
+
const keySource = firstMethod.key !== void 0 ? { key: firstMethod.key } : { publicKey: firstMethod.publicKey };
|
|
2774
|
+
beacons.push({ type, ...keySource, addressType });
|
|
2775
|
+
} else {
|
|
2776
|
+
beacons.push({ type, address });
|
|
2777
|
+
}
|
|
2778
|
+
if (!await askYesNo(ask, "Add another beacon?")) break;
|
|
2779
|
+
}
|
|
2780
|
+
return beacons;
|
|
2781
|
+
}
|
|
2782
|
+
async function collectServices(ask, ctx) {
|
|
2783
|
+
const services = [];
|
|
2784
|
+
while (await askYesNo(ask, "Add a service?")) {
|
|
2785
|
+
const n = services.length + 1;
|
|
2786
|
+
const fragment = (await ask(`Service ${n} id fragment (blank: service-<position>): `)).trim();
|
|
2787
|
+
const type = await askRequired(ask, ctx, `Service ${n} type`);
|
|
2788
|
+
const serviceEndpoint = await askRequired(ask, ctx, `Service ${n} endpoint`);
|
|
2789
|
+
services.push({ ...fragment.length > 0 && { id: `#${fragment.replace(/^#/, "")}` }, type, serviceEndpoint });
|
|
2790
|
+
}
|
|
2791
|
+
return services;
|
|
2792
|
+
}
|
|
2793
|
+
async function askKeySource(ask, ctx, question, defaultRef) {
|
|
2794
|
+
for (; ; ) {
|
|
2795
|
+
const raw = (await ask(`${question}${defaultRef ? ` [${defaultRef}]` : ""}: `)).trim();
|
|
2796
|
+
const answer = raw.length > 0 ? raw : defaultRef;
|
|
2797
|
+
if (answer === void 0) {
|
|
2798
|
+
ctx.say("A key is required.");
|
|
2799
|
+
continue;
|
|
2800
|
+
}
|
|
2801
|
+
if (PUBLIC_KEY_HEX.test(answer)) return { publicKey: answer.toLowerCase() };
|
|
2802
|
+
try {
|
|
2803
|
+
ctx.resolveKey(answer);
|
|
2804
|
+
return { key: answer };
|
|
2805
|
+
} catch (error) {
|
|
2806
|
+
ctx.say(error.message);
|
|
2807
|
+
}
|
|
2808
|
+
}
|
|
2809
|
+
}
|
|
2810
|
+
async function askRelationships(ask, ctx, n) {
|
|
2811
|
+
const names = import_api8.VERIFICATION_RELATIONSHIPS.join(", ");
|
|
2812
|
+
for (; ; ) {
|
|
2813
|
+
const raw = (await ask(`Relationships of method ${n} (comma-separated: ${names}) [all]: `)).trim();
|
|
2814
|
+
if (raw.length === 0) return void 0;
|
|
2815
|
+
const chosen = raw.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
|
|
2816
|
+
const unknown = chosen.filter((name) => !import_api8.VERIFICATION_RELATIONSHIPS.includes(name));
|
|
2817
|
+
if (unknown.length === 0) return chosen;
|
|
2818
|
+
ctx.say(`Unknown relationship: ${unknown.join(", ")}. Expected one of ${names}.`);
|
|
2819
|
+
}
|
|
2820
|
+
}
|
|
2821
|
+
async function askChoice(ask, ctx, question, choices, defaultValue) {
|
|
2822
|
+
for (; ; ) {
|
|
2823
|
+
const raw = (await ask(`${question} (${choices.join(", ")}) [${defaultValue}]: `)).trim();
|
|
2824
|
+
if (raw.length === 0) return defaultValue;
|
|
2825
|
+
if (choices.includes(raw)) return raw;
|
|
2826
|
+
ctx.say(`Unknown value "${raw}". Expected one of ${choices.join(", ")}.`);
|
|
2827
|
+
}
|
|
2828
|
+
}
|
|
2829
|
+
async function askRequired(ask, ctx, question) {
|
|
2830
|
+
for (; ; ) {
|
|
2831
|
+
const raw = (await ask(`${question}: `)).trim();
|
|
2832
|
+
if (raw.length > 0) return raw;
|
|
2833
|
+
ctx.say("A value is required.");
|
|
2834
|
+
}
|
|
2835
|
+
}
|
|
2836
|
+
async function askYesNo(ask, question) {
|
|
2837
|
+
const raw = (await ask(`${question} [y/N]: `)).trim();
|
|
2838
|
+
return /^y(es)?$/i.test(raw);
|
|
2839
|
+
}
|
|
2840
|
+
|
|
2841
|
+
// src/commands/genesis.ts
|
|
2842
|
+
var DEFAULT_GENESIS_PATH = "genesis.json";
|
|
2843
|
+
function registerGenesisCommand(program, factory, keystoreFactory, globals) {
|
|
2844
|
+
const genesis = program.command("genesis").description("Build the genesis document of an external identifier (offline).");
|
|
2845
|
+
const print = (result) => console.log(formatResult(result, globals()));
|
|
2846
|
+
genesis.command("build").description("Build a genesis document, write it to a file, and print the external identifier.").option("-n, --network <network>", NETWORK_OPTION_HELP).option("--spec <path>", "Path to a JSON spec file with the keys, beacons, and services. Runs without prompts.").option("--out <path>", "Path of the genesis document to write.", DEFAULT_GENESIS_PATH).option("--force", "Overwrite an existing --out file.", false).action(async (options) => {
|
|
2847
|
+
const g = globals();
|
|
2848
|
+
const overrides = overridesFromGlobals(g);
|
|
2849
|
+
const network = resolveNetworkOption(options.network, overrides);
|
|
2850
|
+
warnProfileNetworkMismatch(g, network, overrides);
|
|
2851
|
+
const outPath = (0, import_node_path6.resolve)(options.out);
|
|
2852
|
+
if (!options.force && (0, import_node_fs9.existsSync)(outPath)) {
|
|
2853
|
+
throw new CLIError(
|
|
2854
|
+
`The file ${outPath} exists. Pass --force to overwrite it, or --out <path> for another file.`,
|
|
2855
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2856
|
+
{ out: outPath }
|
|
2857
|
+
);
|
|
2858
|
+
}
|
|
2859
|
+
let keystoreApi;
|
|
2860
|
+
const kms = () => (keystoreApi ??= keystoreFactory(void 0, overrides)).kms.kms;
|
|
2861
|
+
const resolvePublicKey = (ref) => kms().getPublicKey(resolveKeyRef(kms(), ref));
|
|
2862
|
+
const spec = options.spec !== void 0 ? await readSpecFile(options.spec) : await runWizard(kms);
|
|
2863
|
+
const api = factory();
|
|
2864
|
+
const document2 = api.btcr2.buildGenesisDocument(toApiSpec(spec, network, resolvePublicKey));
|
|
2865
|
+
const json = `${JSON.stringify(document2, null, 2)}
|
|
2866
|
+
`;
|
|
2867
|
+
const { did, genesisBytes, didDocument } = api.btcr2.createExternalFromDocument(JSON.parse(json), { network });
|
|
2868
|
+
await (0, import_promises3.writeFile)(outPath, json, { encoding: "utf-8", flag: options.force ? "w" : "wx" });
|
|
2869
|
+
const beacons = api.btcr2.getBeacons(didDocument);
|
|
2870
|
+
print({
|
|
2871
|
+
action: "genesis-build",
|
|
2872
|
+
data: { did, network, genesisBytes: (0, import_utils7.bytesToHex)(genesisBytes), path: outPath, beacons }
|
|
2873
|
+
});
|
|
2874
|
+
if (!g.quiet && g.output !== "json") {
|
|
2875
|
+
process.stderr.write(
|
|
2876
|
+
`Wrote the genesis document to ${outPath}. Keep it: the identifier resolves only with it. Pass --genesis-document <path> to resolve, update, and deactivate, or publish it to a CAS.
|
|
2877
|
+
`
|
|
2878
|
+
);
|
|
2879
|
+
if (beacons.length > 0) printBeaconFundingHint(g, network, beacons[0].address);
|
|
2880
|
+
}
|
|
2881
|
+
});
|
|
2882
|
+
}
|
|
2883
|
+
async function readSpecFile(path) {
|
|
2884
|
+
let parsed;
|
|
2885
|
+
try {
|
|
2886
|
+
parsed = JSON.parse(await (0, import_promises3.readFile)(path, "utf-8"));
|
|
2887
|
+
} catch {
|
|
2888
|
+
throw new CLIError(
|
|
2889
|
+
"Invalid genesis spec path. Must be a valid path to a JSON file.",
|
|
2890
|
+
"INVALID_ARGUMENT_ERROR",
|
|
2891
|
+
{ spec: path }
|
|
2892
|
+
);
|
|
2893
|
+
}
|
|
2894
|
+
return parseGenesisSpec(parsed, path);
|
|
2895
|
+
}
|
|
2896
|
+
async function runWizard(kms) {
|
|
2897
|
+
if (!process.stdin.isTTY) {
|
|
2898
|
+
throw new CLIError(
|
|
2899
|
+
"No terminal is attached. Pass --spec <path> to build the genesis document without prompts.",
|
|
2900
|
+
"INVALID_ARGUMENT_ERROR"
|
|
2901
|
+
);
|
|
2902
|
+
}
|
|
2903
|
+
const store = kms();
|
|
2904
|
+
const rl = (0, import_promises4.createInterface)({ input: process.stdin, output: process.stderr });
|
|
2905
|
+
try {
|
|
2906
|
+
return await collectGenesisSpec((question) => rl.question(question), {
|
|
2907
|
+
say: (line) => process.stderr.write(`${line}
|
|
2908
|
+
`),
|
|
2909
|
+
keys: listKeys(store),
|
|
2910
|
+
resolveKey: (ref) => resolveKeyRef(store, ref)
|
|
2911
|
+
});
|
|
2912
|
+
} finally {
|
|
2913
|
+
rl.close();
|
|
2914
|
+
}
|
|
2915
|
+
}
|
|
2916
|
+
function listKeys(kms) {
|
|
2917
|
+
const active = kms.activeKeyId;
|
|
2918
|
+
return kms.listKeys().map((id) => {
|
|
2919
|
+
const entry = kms.getEntry(id);
|
|
2920
|
+
return {
|
|
2921
|
+
keyId: id,
|
|
2922
|
+
fingerprint: id.split(":").pop() ?? id,
|
|
2923
|
+
...entry.tags?.name && { name: entry.tags.name },
|
|
2924
|
+
active: id === active
|
|
2925
|
+
};
|
|
2926
|
+
});
|
|
2927
|
+
}
|
|
2928
|
+
|
|
2929
|
+
// src/commands/key.ts
|
|
2930
|
+
var import_keypair = require("@did-btcr2/keypair");
|
|
2931
|
+
var import_utils8 = require("@noble/hashes/utils.js");
|
|
2932
|
+
var import_node_fs10 = require("fs");
|
|
2507
2933
|
function registerKeyCommand(program, factory, globals) {
|
|
2508
2934
|
const key = program.command("key").description("Manage keypairs in the encrypted keystore.");
|
|
2509
2935
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
@@ -2512,7 +2938,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
2512
2938
|
assertNameAvailable(api.kms.kms, options.name);
|
|
2513
2939
|
const setActive = options.setActive ?? false;
|
|
2514
2940
|
const id = api.kms.generateKey({ ...options.name && { tags: { name: options.name } }, setActive });
|
|
2515
|
-
print({ action: "key-generate", data: { keyId: id, publicKey: (0,
|
|
2941
|
+
print({ action: "key-generate", data: { keyId: id, publicKey: (0, import_utils8.bytesToHex)(api.kms.getPublicKey(id)), active: setActive } });
|
|
2516
2942
|
});
|
|
2517
2943
|
key.command("list").alias("ls").description("List stored keys.").action(() => {
|
|
2518
2944
|
const kms = factory(void 0, globals()).kms.kms;
|
|
@@ -2532,7 +2958,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
2532
2958
|
const kms = factory(void 0, globals()).kms.kms;
|
|
2533
2959
|
const id = resolveKeyRef(kms, ref);
|
|
2534
2960
|
const entry = kms.getEntry(id);
|
|
2535
|
-
print({ action: "key-show", data: { keyId: id, publicKey: (0,
|
|
2961
|
+
print({ action: "key-show", data: { keyId: id, publicKey: (0, import_utils8.bytesToHex)(entry.publicKey), ...entry.tags && { tags: entry.tags } } });
|
|
2536
2962
|
});
|
|
2537
2963
|
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) => {
|
|
2538
2964
|
if (Boolean(options.secretFile) === Boolean(options.public)) {
|
|
@@ -2543,13 +2969,13 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
2543
2969
|
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") });
|
|
2544
2970
|
const setActive = options.setActive ?? false;
|
|
2545
2971
|
const id = api.kms.import(keyPair, { ...options.name && { tags: { name: options.name } }, setActive });
|
|
2546
|
-
print({ action: "key-import", data: { keyId: id, publicKey: (0,
|
|
2972
|
+
print({ action: "key-import", data: { keyId: id, publicKey: (0, import_utils8.bytesToHex)(api.kms.getPublicKey(id)), watchOnly: !options.secretFile, active: setActive } });
|
|
2547
2973
|
});
|
|
2548
2974
|
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) => {
|
|
2549
2975
|
const api = factory(void 0, globals());
|
|
2550
2976
|
const id = resolveKeyRef(api.kms.kms, ref);
|
|
2551
2977
|
if (!options.secret) {
|
|
2552
|
-
print({ action: "key-export", data: { keyId: id, publicKey: (0,
|
|
2978
|
+
print({ action: "key-export", data: { keyId: id, publicKey: (0, import_utils8.bytesToHex)(api.kms.getPublicKey(id)) } });
|
|
2553
2979
|
return;
|
|
2554
2980
|
}
|
|
2555
2981
|
if (!options.out) {
|
|
@@ -2560,7 +2986,7 @@ function registerKeyCommand(program, factory, globals) {
|
|
|
2560
2986
|
throw new CLIError(`Key ${id} is watch-only and has no secret to export.`, "INVALID_ARGUMENT_ERROR", { keyId: id });
|
|
2561
2987
|
}
|
|
2562
2988
|
process.stderr.write("warning: writing an unencrypted secret key to disk. Protect this file and delete it when done.\n");
|
|
2563
|
-
writeSecretFile(options.out, (0,
|
|
2989
|
+
writeSecretFile(options.out, (0, import_utils8.bytesToHex)(keyPair.secretKey.bytes));
|
|
2564
2990
|
print({ action: "key-export", data: { keyId: id, secretWrittenTo: options.out } });
|
|
2565
2991
|
});
|
|
2566
2992
|
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) => {
|
|
@@ -2585,7 +3011,7 @@ function assertNameAvailable(kms, name) {
|
|
|
2585
3011
|
function parseHex(hex, expectedBytes, label) {
|
|
2586
3012
|
let bytes;
|
|
2587
3013
|
try {
|
|
2588
|
-
bytes = (0,
|
|
3014
|
+
bytes = (0, import_utils8.hexToBytes)(hex.trim());
|
|
2589
3015
|
} catch {
|
|
2590
3016
|
throw new CLIError(`Invalid hex for ${label}.`, "INVALID_ARGUMENT_ERROR", { label });
|
|
2591
3017
|
}
|
|
@@ -2601,7 +3027,7 @@ function parseHex(hex, expectedBytes, label) {
|
|
|
2601
3027
|
function readHexFile(path, expectedBytes, label) {
|
|
2602
3028
|
let content;
|
|
2603
3029
|
try {
|
|
2604
|
-
content = (0,
|
|
3030
|
+
content = (0, import_node_fs10.readFileSync)(path, "utf-8");
|
|
2605
3031
|
} catch {
|
|
2606
3032
|
throw new CLIError(`Cannot read ${label} at ${path}.`, "INVALID_ARGUMENT_ERROR", { label, path });
|
|
2607
3033
|
}
|
|
@@ -2610,7 +3036,7 @@ function readHexFile(path, expectedBytes, label) {
|
|
|
2610
3036
|
function writeSecretFile(path, contents) {
|
|
2611
3037
|
let fd;
|
|
2612
3038
|
try {
|
|
2613
|
-
fd = (0,
|
|
3039
|
+
fd = (0, import_node_fs10.openSync)(path, "wx", 384);
|
|
2614
3040
|
} catch (error) {
|
|
2615
3041
|
if (error.code === "EEXIST") {
|
|
2616
3042
|
throw new CLIError(`Refusing to overwrite existing file ${path}. Choose a new --out path.`, "INVALID_ARGUMENT_ERROR", { path });
|
|
@@ -2618,14 +3044,14 @@ function writeSecretFile(path, contents) {
|
|
|
2618
3044
|
throw error;
|
|
2619
3045
|
}
|
|
2620
3046
|
try {
|
|
2621
|
-
(0,
|
|
3047
|
+
(0, import_node_fs10.writeFileSync)(fd, contents);
|
|
2622
3048
|
} finally {
|
|
2623
|
-
(0,
|
|
3049
|
+
(0, import_node_fs10.closeSync)(fd);
|
|
2624
3050
|
}
|
|
2625
3051
|
}
|
|
2626
3052
|
|
|
2627
3053
|
// src/commands/config.ts
|
|
2628
|
-
var
|
|
3054
|
+
var import_node_fs11 = require("fs");
|
|
2629
3055
|
|
|
2630
3056
|
// src/config-schema.ts
|
|
2631
3057
|
var CONFIG_SCHEMA = {
|
|
@@ -2762,7 +3188,7 @@ function registerConfigCommand(program, globals) {
|
|
|
2762
3188
|
const print = (result) => console.log(formatResult(result, globals()));
|
|
2763
3189
|
config.command("init").description("Create a default config file with one profile per network.").option("--force", "Overwrite an existing config file.", false).action((options) => {
|
|
2764
3190
|
const p = path();
|
|
2765
|
-
if ((0,
|
|
3191
|
+
if ((0, import_node_fs11.existsSync)(p) && !options.force) {
|
|
2766
3192
|
throw new CLIError(`Config already exists at ${p}. Use --force to overwrite.`, "INVALID_ARGUMENT_ERROR", { path: p });
|
|
2767
3193
|
}
|
|
2768
3194
|
writeDefaultConfigFile(p);
|
|
@@ -2912,7 +3338,7 @@ function registerProfileCommand(program, globals) {
|
|
|
2912
3338
|
}
|
|
2913
3339
|
|
|
2914
3340
|
// src/commands/completion.ts
|
|
2915
|
-
var COMMANDS = "create resolve read update deactivate delete key config profile completion";
|
|
3341
|
+
var COMMANDS = "create resolve read update deactivate delete identifier genesis key config profile completion";
|
|
2916
3342
|
function registerCompletionCommand(program, _globals) {
|
|
2917
3343
|
program.command("completion [shell]").description("Print a shell completion script (bash, zsh, or fish) to stdout.").action((shell = "bash") => {
|
|
2918
3344
|
console.log(completionScript(shell));
|
|
@@ -2943,18 +3369,18 @@ function completionScript(shell) {
|
|
|
2943
3369
|
}
|
|
2944
3370
|
|
|
2945
3371
|
// src/version.ts
|
|
2946
|
-
var
|
|
2947
|
-
var
|
|
3372
|
+
var import_node_fs12 = require("fs");
|
|
3373
|
+
var import_node_path7 = require("path");
|
|
2948
3374
|
var import_node_url = require("url");
|
|
2949
3375
|
function readVersion() {
|
|
2950
|
-
let dir = (0,
|
|
3376
|
+
let dir = (0, import_node_path7.dirname)((0, import_node_url.fileURLToPath)(importMetaUrl));
|
|
2951
3377
|
for (let i = 0; i < 5; i++) {
|
|
2952
3378
|
try {
|
|
2953
|
-
const pkg = JSON.parse((0,
|
|
3379
|
+
const pkg = JSON.parse((0, import_node_fs12.readFileSync)((0, import_node_path7.join)(dir, "package.json"), "utf-8"));
|
|
2954
3380
|
if (pkg.name === "@did-btcr2/cli") return pkg.version;
|
|
2955
3381
|
} catch {
|
|
2956
3382
|
}
|
|
2957
|
-
dir = (0,
|
|
3383
|
+
dir = (0, import_node_path7.dirname)(dir);
|
|
2958
3384
|
}
|
|
2959
3385
|
return "0.0.0";
|
|
2960
3386
|
}
|
|
@@ -2972,7 +3398,7 @@ var DidBtcr2Cli = class {
|
|
|
2972
3398
|
* for known networks and localhost Polar for regtest.
|
|
2973
3399
|
*
|
|
2974
3400
|
* @param factory - Optional API factory for keystore-free commands (create,
|
|
2975
|
-
* resolve). Defaults to {@link defaultApiFactory}.
|
|
3401
|
+
* resolve, identifier). Defaults to {@link defaultApiFactory}.
|
|
2976
3402
|
* @param keystoreFactory - Optional keystore-aware API factory for commands
|
|
2977
3403
|
* that need a signing identity (key, update, deactivate). Defaults to
|
|
2978
3404
|
* {@link keystoreApiFactory}.
|
|
@@ -2990,6 +3416,8 @@ var DidBtcr2Cli = class {
|
|
|
2990
3416
|
registerResolveCommand(this.program, factory, globals);
|
|
2991
3417
|
registerUpdateCommand(this.program, keystoreFactory, globals);
|
|
2992
3418
|
registerDeactivateCommand(this.program, keystoreFactory, globals);
|
|
3419
|
+
registerIdentifierCommand(this.program, factory, globals);
|
|
3420
|
+
registerGenesisCommand(this.program, factory, keystoreFactory, globals);
|
|
2993
3421
|
registerKeyCommand(this.program, keystoreFactory, globals);
|
|
2994
3422
|
registerKeystoreCommand(this.program, globals);
|
|
2995
3423
|
registerConfigCommand(this.program, globals);
|
|
@@ -3064,6 +3492,8 @@ function handleError(error, verbose) {
|
|
|
3064
3492
|
registerConfigCommand,
|
|
3065
3493
|
registerCreateCommand,
|
|
3066
3494
|
registerDeactivateCommand,
|
|
3495
|
+
registerGenesisCommand,
|
|
3496
|
+
registerIdentifierCommand,
|
|
3067
3497
|
registerInitCommand,
|
|
3068
3498
|
registerKeyCommand,
|
|
3069
3499
|
registerKeystoreCommand,
|