bsv-bap 0.2.0-alpha.2 → 0.2.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/src/cli.ts CHANGED
@@ -1,268 +1,282 @@
1
1
  #!/usr/bin/env bun
2
- /**
3
- * BAP CLI - Bitcoin Attestation Protocol Command Line Interface
4
- *
5
- * Usage:
6
- * bap create [--name <name>] [--wif <wif>] Create new BAP identity
7
- * bap sign <message> Sign a message
8
- * bap verify <message> <sig> <address> Verify a signature
9
- * bap friend-pubkey <friendBapId> Get friend public key
10
- * bap encrypt <data> <friendBapId> Encrypt for friend
11
- * bap decrypt <ciphertext> <friendBapId> Decrypt from friend
12
- * bap export Export identity backup
13
- * bap import <backup> Import identity from backup
14
- * bap info Show current identity info
15
- */
16
-
17
- import { readFileSync, writeFileSync, existsSync } from "node:fs";
2
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
18
3
  import { homedir } from "node:os";
19
4
  import { join } from "node:path";
20
- import { PrivateKey, Utils } from "@bsv/sdk";
21
- import { BAP } from "bsv-bap";
5
+ import { PrivateKey } from "@bsv/sdk";
6
+ import { Command } from "commander";
7
+ import { BAP, bapIdFromAddress, bapIdFromPubkey } from "bsv-bap";
22
8
 
23
- const { toHex, toArray } = Utils;
24
-
25
- // Default config path
9
+ // Storage paths
26
10
  const CONFIG_DIR = join(homedir(), ".bap");
27
11
  const CONFIG_FILE = join(CONFIG_DIR, "identity.json");
12
+ const ACTIVE_FILE = join(CONFIG_DIR, "active");
28
13
 
29
- interface StoredIdentity {
30
- wif: string;
14
+ // Stored config shape
15
+ interface StoredConfig {
16
+ rootPk: string;
31
17
  ids: string;
32
- label?: string;
18
+ labels: Record<string, string>;
33
19
  createdAt: string;
34
20
  }
35
21
 
36
22
  function ensureConfigDir(): void {
37
- const { mkdirSync } = require("node:fs");
38
23
  if (!existsSync(CONFIG_DIR)) {
39
24
  mkdirSync(CONFIG_DIR, { recursive: true });
40
25
  }
41
26
  }
42
27
 
43
- function loadIdentity(): BAP | null {
44
- if (!existsSync(CONFIG_FILE)) {
45
- return null;
28
+ function loadConfig(): StoredConfig | null {
29
+ if (!existsSync(CONFIG_FILE)) return null;
30
+ return JSON.parse(readFileSync(CONFIG_FILE, "utf-8")) as StoredConfig;
31
+ }
32
+
33
+ function createBAP(key: string): BAP {
34
+ // xprv keys start with "xprv" — use BIP32 mode; otherwise Type42
35
+ if (key.startsWith("xprv")) {
36
+ return new BAP(key);
46
37
  }
38
+ return new BAP({ rootPk: key });
39
+ }
47
40
 
48
- try {
49
- const data = JSON.parse(readFileSync(CONFIG_FILE, "utf-8")) as StoredIdentity;
50
- const bap = new BAP({ rootPk: data.wif });
51
- if (data.ids) {
52
- bap.importIds(data.ids);
53
- }
54
- return bap;
55
- } catch (error) {
56
- console.error("Failed to load identity:", error);
57
- return null;
41
+ function loadBAP(): { bap: BAP; config: StoredConfig } {
42
+ const config = loadConfig();
43
+ if (!config) {
44
+ console.error("No identity found. Run 'bap create' first.");
45
+ process.exit(1);
46
+ }
47
+ const bap = createBAP(config.rootPk);
48
+ if (config.ids) {
49
+ bap.importIds(config.ids);
58
50
  }
51
+ return { bap, config };
59
52
  }
60
53
 
61
- function saveIdentity(bap: BAP, wif: string, label?: string): void {
54
+ function saveConfig(
55
+ bap: BAP,
56
+ rootPk: string,
57
+ labels: Record<string, string>,
58
+ createdAt?: string,
59
+ ): void {
62
60
  ensureConfigDir();
63
- const backup: StoredIdentity = {
64
- wif,
61
+ const config: StoredConfig = {
62
+ rootPk,
65
63
  ids: bap.exportIds(),
66
- ...(label && { label }),
67
- createdAt: new Date().toISOString(),
64
+ labels,
65
+ createdAt: createdAt ?? new Date().toISOString(),
68
66
  };
69
- writeFileSync(CONFIG_FILE, JSON.stringify(backup, null, 2));
67
+ writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
70
68
  }
71
69
 
72
- function printUsage(): void {
73
- console.log(`
74
- BAP CLI - Bitcoin Attestation Protocol
75
-
76
- Commands:
77
- create [--name <name>] [--wif <wif>] Create new BAP identity
78
- sign <message> Sign a message with identity
79
- verify <message> <sig> <address> Verify a BSM signature
80
- friend-pubkey <friendBapId> Get encryption pubkey for friend
81
- encrypt <data> <friendBapId> Encrypt data for friend
82
- decrypt <ciphertext> <friendBapId> Decrypt data from friend
83
- export Export identity backup (JSON)
84
- import <file> Import identity from backup file
85
- info Show current identity info
86
- help Show this help message
87
-
88
- Options:
89
- --name <name> Identity name (for create)
90
- --wif <wif> Use existing WIF key (for create)
91
-
92
- Examples:
93
- bap create --name "My Identity"
94
- bap sign "Hello World"
95
- bap verify "Hello World" <signature> <address>
96
- bap friend-pubkey "abc123..."
97
- bap encrypt "secret message" "abc123..."
98
- bap decrypt "<ciphertext>" "abc123..."
99
- bap export > backup.json
100
- bap import backup.json
101
- `);
70
+ function getActiveBapId(): string | null {
71
+ if (!existsSync(ACTIVE_FILE)) return null;
72
+ return readFileSync(ACTIVE_FILE, "utf-8").trim();
102
73
  }
103
74
 
104
- function createIdentity(args: string[]): void {
105
- let name = "Default Identity";
106
- let wif: string | undefined;
107
-
108
- // Parse arguments
109
- for (let i = 0; i < args.length; i++) {
110
- if (args[i] === "--name" && args[i + 1]) {
111
- name = args[i + 1];
112
- i++;
113
- } else if (args[i] === "--wif" && args[i + 1]) {
114
- wif = args[i + 1];
115
- i++;
116
- }
117
- }
118
-
119
- // Generate or use provided WIF
120
- if (!wif) {
121
- const privateKey = PrivateKey.fromRandom();
122
- wif = privateKey.toWif();
123
- }
124
-
125
- // Create BAP instance with Type42
126
- const bap = new BAP({ rootPk: wif });
127
- const identity = bap.newId(name);
128
-
129
- // Save to config
130
- saveIdentity(bap, wif);
131
-
132
- console.log("Identity created successfully!");
133
- console.log(` Name: ${name}`);
134
- console.log(` Identity Key: ${identity.getIdentityKey()}`);
135
- console.log(` Root Address: ${identity.rootAddress}`);
136
- console.log(` Signing Address: ${identity.getCurrentAddress()}`);
137
- console.log(`\nStored at: ${CONFIG_FILE}`);
75
+ function setActiveBapId(bapId: string): void {
76
+ ensureConfigDir();
77
+ writeFileSync(ACTIVE_FILE, bapId);
138
78
  }
139
79
 
140
- function signMessage(message: string): void {
141
- const bap = loadIdentity();
142
- if (!bap) {
143
- console.error("No identity found. Run 'bap create' first.");
144
- process.exit(1);
145
- }
146
-
80
+ function getActiveIdentity(bap: BAP, config: StoredConfig) {
81
+ const activeBapId = getActiveBapId();
147
82
  const ids = bap.listIds();
83
+
148
84
  if (ids.length === 0) {
149
- console.error("No identities in BAP instance.");
85
+ console.error("No identities found. Run 'bap create' first.");
150
86
  process.exit(1);
151
87
  }
152
88
 
153
- const identity = bap.getId(ids[0]);
89
+ const bapId = activeBapId && ids.includes(activeBapId) ? activeBapId : ids[0];
90
+ const identity = bap.getId(bapId);
154
91
  if (!identity) {
155
- console.error("Failed to get identity.");
92
+ console.error(`Identity ${bapId} not found.`);
156
93
  process.exit(1);
157
94
  }
158
-
159
- const { address, signature } = identity.signMessage(toArray(message, "utf8"));
160
-
161
- console.log(JSON.stringify({ message, address, signature }, null, 2));
95
+ return { identity, bapId, label: config.labels[bapId] };
162
96
  }
163
97
 
164
- function verifySignature(message: string, signature: string, address: string): void {
165
- const bap = new BAP({ rootPk: PrivateKey.fromRandom().toWif() }); // Temporary instance
166
- const isValid = bap.verifySignature(message, address, signature);
98
+ // --- CLI ---
167
99
 
168
- console.log(JSON.stringify({ valid: isValid, message, address, signature }, null, 2));
169
- }
170
-
171
- function getFriendPubkey(friendBapId: string): void {
172
- const bap = loadIdentity();
173
- if (!bap) {
174
- console.error("No identity found. Run 'bap create' first.");
175
- process.exit(1);
176
- }
177
-
178
- const ids = bap.listIds();
179
- if (ids.length === 0) {
180
- console.error("No identities in BAP instance.");
181
- process.exit(1);
182
- }
100
+ const program = new Command();
183
101
 
184
- const identity = bap.getId(ids[0]);
185
- if (!identity) {
186
- console.error("Failed to get identity.");
187
- process.exit(1);
188
- }
102
+ program
103
+ .name("bap")
104
+ .description("BAP - Bitcoin Attestation Protocol CLI")
105
+ .version("0.2.0");
189
106
 
190
- const publicKey = identity.getEncryptionPublicKeyWithSeed(friendBapId);
191
- console.log(JSON.stringify({ friendBapId, publicKey }, null, 2));
192
- }
107
+ // Identity Management
193
108
 
194
- function encryptForFriend(data: string, friendBapId: string): void {
195
- const bap = loadIdentity();
196
- if (!bap) {
197
- console.error("No identity found. Run 'bap create' first.");
198
- process.exit(1);
199
- }
109
+ program
110
+ .command("create")
111
+ .description("Create a new identity")
112
+ .option("--name <name>", "Human-readable label for the identity")
113
+ .option("--wif <wif>", "Use an existing WIF key as the master key")
114
+ .action((opts) => {
115
+ let config = loadConfig();
116
+ let rootPk: string;
117
+ let labels: Record<string, string>;
118
+ let createdAt: string | undefined;
119
+ let bap: BAP;
200
120
 
201
- const ids = bap.listIds();
202
- if (ids.length === 0) {
203
- console.error("No identities in BAP instance.");
204
- process.exit(1);
205
- }
121
+ if (config) {
122
+ // Existing master add a new identity
123
+ rootPk = config.rootPk;
124
+ labels = config.labels;
125
+ createdAt = config.createdAt;
126
+ bap = createBAP(rootPk);
127
+ bap.importIds(config.ids);
128
+ } else {
129
+ // New master
130
+ rootPk = opts.wif ?? PrivateKey.fromRandom().toWif();
131
+ labels = {};
132
+ bap = createBAP(rootPk);
133
+ }
206
134
 
207
- const identity = bap.getId(ids[0]);
208
- if (!identity) {
209
- console.error("Failed to get identity.");
210
- process.exit(1);
211
- }
135
+ const identity = bap.newId();
136
+ const bapId = identity.bapId;
212
137
 
213
- const ciphertext = identity.encryptWithSeed(data, friendBapId);
214
- console.log(JSON.stringify({ ciphertext, friendBapId }, null, 2));
215
- }
138
+ if (opts.name) {
139
+ labels[bapId] = opts.name;
140
+ }
216
141
 
217
- function decryptFromFriend(ciphertext: string, friendBapId: string): void {
218
- const bap = loadIdentity();
219
- if (!bap) {
220
- console.error("No identity found. Run 'bap create' first.");
221
- process.exit(1);
222
- }
142
+ saveConfig(bap, rootPk, labels, createdAt);
143
+ setActiveBapId(bapId);
144
+
145
+ console.log("Identity created:");
146
+ console.log(` BAP ID: ${bapId}`);
147
+ if (opts.name) console.log(` Label: ${opts.name}`);
148
+ console.log(` Root Address: ${identity.rootAddress}`);
149
+ console.log(` Root Path: ${identity.rootPath}`);
150
+ console.log(` Stored at: ${CONFIG_FILE}`);
151
+ });
152
+
153
+ program
154
+ .command("list")
155
+ .description("List all identities (* = active)")
156
+ .action(() => {
157
+ const { bap, config } = loadBAP();
158
+ const ids = bap.listIds();
159
+ const active = getActiveBapId();
223
160
 
224
- const ids = bap.listIds();
225
- if (ids.length === 0) {
226
- console.error("No identities in BAP instance.");
227
- process.exit(1);
228
- }
161
+ if (ids.length === 0) {
162
+ console.log("No identities. Run 'bap create' to get started.");
163
+ return;
164
+ }
229
165
 
230
- const identity = bap.getId(ids[0]);
231
- if (!identity) {
232
- console.error("Failed to get identity.");
233
- process.exit(1);
234
- }
166
+ for (const bapId of ids) {
167
+ const marker = bapId === active ? " *" : " ";
168
+ const label = config.labels[bapId];
169
+ const suffix = label ? ` (${label})` : "";
170
+ console.log(`${marker} ${bapId}${suffix}`);
171
+ }
172
+ });
173
+
174
+ program
175
+ .command("use")
176
+ .description("Set active identity")
177
+ .argument("<bapId>", "BAP ID to activate")
178
+ .action((bapId: string) => {
179
+ const { bap } = loadBAP();
180
+ const ids = bap.listIds();
235
181
 
236
- try {
237
- const data = identity.decryptWithSeed(ciphertext, friendBapId);
238
- console.log(JSON.stringify({ data, friendBapId }, null, 2));
239
- } catch (error) {
240
- console.error("Decryption failed:", error instanceof Error ? error.message : error);
241
- process.exit(1);
242
- }
243
- }
182
+ if (!ids.includes(bapId)) {
183
+ console.error(`Identity ${bapId} not found.`);
184
+ process.exit(1);
185
+ }
244
186
 
245
- function exportIdentity(): void {
246
- const bap = loadIdentity();
247
- if (!bap) {
248
- console.error("No identity found. Run 'bap create' first.");
249
- process.exit(1);
250
- }
187
+ setActiveBapId(bapId);
188
+ console.log(`Active identity: ${bapId}`);
189
+ });
190
+
191
+ program
192
+ .command("info")
193
+ .description("Show active identity details")
194
+ .action(() => {
195
+ const { bap, config } = loadBAP();
196
+ const { identity, bapId, label } = getActiveIdentity(bap, config);
197
+
198
+ console.log("Active Identity:");
199
+ console.log(` BAP ID: ${bapId}`);
200
+ if (label) console.log(` Label: ${label}`);
201
+ console.log(` Root Address: ${identity.rootAddress}`);
202
+ console.log(` Root Path: ${identity.rootPath}`);
203
+ console.log(` Current Path: ${identity.currentPath}`);
204
+ console.log(` Previous Path: ${identity.previousPath}`);
205
+ console.log(` Account Key: ${identity.getAccountKey().toPublicKey().toString()}`);
206
+ });
207
+
208
+ program
209
+ .command("remove")
210
+ .description("Remove an identity")
211
+ .argument("<bapId>", "BAP ID to remove")
212
+ .action((bapId: string) => {
213
+ const { bap, config } = loadBAP();
214
+
215
+ if (!bap.getId(bapId)) {
216
+ console.error(`Identity ${bapId} not found.`);
217
+ process.exit(1);
218
+ }
251
219
 
252
- // Read stored WIF
253
- const stored = JSON.parse(readFileSync(CONFIG_FILE, "utf-8")) as StoredIdentity;
254
- const backup = bap.exportForBackup(stored.label);
220
+ bap.removeId(bapId);
221
+ delete config.labels[bapId];
222
+ saveConfig(bap, config.rootPk, config.labels, config.createdAt);
223
+
224
+ // Clear active if it was this one
225
+ if (getActiveBapId() === bapId) {
226
+ const remaining = bap.listIds();
227
+ if (remaining.length > 0) {
228
+ setActiveBapId(remaining[0]);
229
+ } else {
230
+ writeFileSync(ACTIVE_FILE, "");
231
+ }
232
+ }
255
233
 
256
- console.log(JSON.stringify(backup, null, 2));
257
- }
234
+ console.log(`Removed identity: ${bapId}`);
235
+ });
236
+
237
+ // Backup
238
+
239
+ program
240
+ .command("export")
241
+ .description("Export master backup (JSON to stdout)")
242
+ .action(() => {
243
+ const { bap } = loadBAP();
244
+ const backup = bap.exportForBackup();
245
+ console.log(JSON.stringify(backup, null, 2));
246
+ });
247
+
248
+ program
249
+ .command("export-account")
250
+ .description("Export account backup for active or specified identity")
251
+ .option("--id <bapId>", "Specific BAP ID to export")
252
+ .action((opts) => {
253
+ const { bap, config } = loadBAP();
254
+
255
+ let identity;
256
+ if (opts.id) {
257
+ identity = bap.getId(opts.id);
258
+ if (!identity) {
259
+ console.error(`Identity ${opts.id} not found.`);
260
+ process.exit(1);
261
+ }
262
+ } else {
263
+ ({ identity } = getActiveIdentity(bap, config));
264
+ }
258
265
 
259
- function importIdentity(file: string): void {
260
- if (!existsSync(file)) {
261
- console.error(`File not found: ${file}`);
262
- process.exit(1);
263
- }
266
+ const backup = identity.exportAccountBackup();
267
+ console.log(JSON.stringify(backup, null, 2));
268
+ });
269
+
270
+ program
271
+ .command("import")
272
+ .description("Import from backup file")
273
+ .argument("<file>", "Path to backup JSON file")
274
+ .action((file: string) => {
275
+ if (!existsSync(file)) {
276
+ console.error(`File not found: ${file}`);
277
+ process.exit(1);
278
+ }
264
279
 
265
- try {
266
280
  const backup = JSON.parse(readFileSync(file, "utf-8"));
267
281
 
268
282
  if (!backup.rootPk && !backup.xprv) {
@@ -271,136 +285,127 @@ function importIdentity(file: string): void {
271
285
  }
272
286
 
273
287
  let bap: BAP;
274
- let wif: string;
288
+ let rootPk: string;
275
289
 
276
290
  if (backup.rootPk) {
277
- // Type42 format
278
291
  bap = new BAP({ rootPk: backup.rootPk });
279
- wif = backup.rootPk;
292
+ rootPk = backup.rootPk;
280
293
  } else {
281
- // BIP32 format (legacy)
282
294
  bap = new BAP(backup.xprv);
283
- wif = backup.xprv;
295
+ rootPk = backup.xprv;
284
296
  }
285
297
 
286
298
  if (backup.ids) {
287
299
  bap.importIds(backup.ids);
288
300
  }
289
301
 
290
- saveIdentity(bap, wif, backup.label);
291
-
292
- const ids = bap.listIds();
293
- console.log("Identity imported successfully!");
294
- console.log(` Identities: ${ids.length}`);
302
+ const labels: Record<string, string> = {};
295
303
  if (backup.label) {
296
- console.log(` Label: ${backup.label}`);
304
+ // Apply label to first identity as a default
305
+ const ids = bap.listIds();
306
+ if (ids.length > 0) {
307
+ labels[ids[0]] = backup.label;
308
+ }
297
309
  }
298
- console.log(`\nStored at: ${CONFIG_FILE}`);
299
- } catch (error) {
300
- console.error("Failed to import identity:", error);
301
- process.exit(1);
302
- }
303
- }
304
310
 
305
- function showInfo(): void {
306
- const bap = loadIdentity();
307
- if (!bap) {
308
- console.error("No identity found. Run 'bap create' first.");
309
- process.exit(1);
310
- }
311
-
312
- const ids = bap.listIds();
313
- console.log("BAP Identity Info");
314
- console.log(` Config: ${CONFIG_FILE}`);
315
- console.log(` Identities: ${ids.length}`);
316
-
317
- for (const idKey of ids) {
318
- const identity = bap.getId(idKey);
319
- if (identity) {
320
- console.log(`\n Identity: ${identity.idName}`);
321
- console.log(` Key: ${idKey}`);
322
- console.log(` Root Address: ${identity.rootAddress}`);
323
- console.log(` Current Address: ${identity.getCurrentAddress()}`);
324
- console.log(` Encryption Pubkey: ${identity.getEncryptionPublicKey()}`);
325
- }
326
- }
327
- }
311
+ saveConfig(bap, rootPk, labels);
328
312
 
329
- // Main CLI entry point
330
- const args = process.argv.slice(2);
331
- const command = args[0];
332
-
333
- switch (command) {
334
- case "create":
335
- createIdentity(args.slice(1));
336
- break;
337
-
338
- case "sign":
339
- if (!args[1]) {
340
- console.error("Usage: bap sign <message>");
341
- process.exit(1);
342
- }
343
- signMessage(args[1]);
344
- break;
345
-
346
- case "verify":
347
- if (!args[1] || !args[2] || !args[3]) {
348
- console.error("Usage: bap verify <message> <signature> <address>");
349
- process.exit(1);
350
- }
351
- verifySignature(args[1], args[2], args[3]);
352
- break;
353
-
354
- case "friend-pubkey":
355
- if (!args[1]) {
356
- console.error("Usage: bap friend-pubkey <friendBapId>");
357
- process.exit(1);
358
- }
359
- getFriendPubkey(args[1]);
360
- break;
361
-
362
- case "encrypt":
363
- if (!args[1] || !args[2]) {
364
- console.error("Usage: bap encrypt <data> <friendBapId>");
365
- process.exit(1);
366
- }
367
- encryptForFriend(args[1], args[2]);
368
- break;
369
-
370
- case "decrypt":
371
- if (!args[1] || !args[2]) {
372
- console.error("Usage: bap decrypt <ciphertext> <friendBapId>");
373
- process.exit(1);
313
+ const ids = bap.listIds();
314
+ if (ids.length > 0) {
315
+ setActiveBapId(ids[0]);
374
316
  }
375
- decryptFromFriend(args[1], args[2]);
376
- break;
377
317
 
378
- case "export":
379
- exportIdentity();
380
- break;
381
-
382
- case "import":
383
- if (!args[1]) {
384
- console.error("Usage: bap import <backup-file>");
385
- process.exit(1);
386
- }
387
- importIdentity(args[1]);
388
- break;
389
-
390
- case "info":
391
- showInfo();
392
- break;
393
-
394
- case "help":
395
- case "--help":
396
- case "-h":
397
- printUsage();
398
- break;
399
-
400
- default:
401
- if (command) {
402
- console.error(`Unknown command: ${command}`);
318
+ console.log("Backup imported:");
319
+ console.log(` Identities: ${ids.length}`);
320
+ if (backup.label) console.log(` Label: ${backup.label}`);
321
+ console.log(` Stored at: ${CONFIG_FILE}`);
322
+ });
323
+
324
+ // Crypto
325
+
326
+ program
327
+ .command("encrypt")
328
+ .description("Encrypt data with master key (ECIES)")
329
+ .argument("<data>", "Data to encrypt")
330
+ .action((data: string) => {
331
+ const { bap } = loadBAP();
332
+ console.log(bap.encrypt(data));
333
+ });
334
+
335
+ program
336
+ .command("decrypt")
337
+ .description("Decrypt ciphertext with master key")
338
+ .argument("<ciphertext>", "Base64 ciphertext to decrypt")
339
+ .action((ciphertext: string) => {
340
+ const { bap } = loadBAP();
341
+ console.log(bap.decrypt(ciphertext));
342
+ });
343
+
344
+ program
345
+ .command("verify")
346
+ .description("Verify a BSM signature")
347
+ .argument("<message>", "Original message")
348
+ .argument("<signature>", "Base64 signature")
349
+ .argument("<address>", "Signing address")
350
+ .action((message: string, signature: string, address: string) => {
351
+ const bap = new BAP({ rootPk: PrivateKey.fromRandom().toWif() });
352
+ let valid = false;
353
+ try {
354
+ valid = bap.verifySignature(message, address, signature);
355
+ } catch {
356
+ // Invalid signature format — treat as not valid
403
357
  }
404
- printUsage();
405
- process.exit(command ? 1 : 0);
406
- }
358
+ console.log(JSON.stringify({ valid, message, address, signature }, null, 2));
359
+ });
360
+
361
+ // API Lookups
362
+
363
+ program
364
+ .command("lookup")
365
+ .description("Lookup identity on the BAP overlay")
366
+ .argument("<bapId>", "BAP ID to lookup")
367
+ .action(async (bapId: string) => {
368
+ const bap = new BAP({ rootPk: PrivateKey.fromRandom().toWif() });
369
+ const result = await bap.getIdentity(bapId);
370
+ console.log(JSON.stringify(result, null, 2));
371
+ });
372
+
373
+ program
374
+ .command("lookup-address")
375
+ .description("Lookup identity by Bitcoin address")
376
+ .argument("<address>", "Bitcoin address to lookup")
377
+ .action(async (address: string) => {
378
+ const bap = new BAP({ rootPk: PrivateKey.fromRandom().toWif() });
379
+ const result = await bap.getIdentityFromAddress(address);
380
+ console.log(JSON.stringify(result, null, 2));
381
+ });
382
+
383
+ program
384
+ .command("attestations")
385
+ .description("Get attestations for an attribute hash")
386
+ .argument("<hash>", "Attribute hash to lookup")
387
+ .action(async (hash: string) => {
388
+ const bap = new BAP({ rootPk: PrivateKey.fromRandom().toWif() });
389
+ const result = await bap.getAttestationsForHash(hash);
390
+ console.log(JSON.stringify(result, null, 2));
391
+ });
392
+
393
+ // Utilities
394
+
395
+ program
396
+ .command("id-from-address")
397
+ .description("Derive BAP ID from a Bitcoin address")
398
+ .argument("<address>", "Bitcoin address (must be the root/member address)")
399
+ .action((address: string) => {
400
+ console.log(bapIdFromAddress(address));
401
+ });
402
+
403
+ program
404
+ .command("id-from-pubkey")
405
+ .description("Derive BAP ID from a compressed public key")
406
+ .argument("<pubkey>", "Compressed public key hex (must be the member key)")
407
+ .action((pubkey: string) => {
408
+ console.log(bapIdFromPubkey(pubkey));
409
+ });
410
+
411
+ program.parse();