@didcid/keymaster 0.4.1 → 0.4.3

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.
@@ -4,6 +4,7 @@ Object.defineProperty(exports, '__esModule', { value: true });
4
4
 
5
5
  var imageSize = require('image-size');
6
6
  var fileType = require('file-type');
7
+ var lightBolt11Decoder = require('light-bolt11-decoder');
7
8
  var db_typeGuards = require('./db/typeGuards.cjs');
8
9
  require('crypto');
9
10
 
@@ -466,6 +467,18 @@ class UnknownIDError extends ArchonError {
466
467
  super(UnknownIDError.type, detail);
467
468
  }
468
469
  }
470
+ class LightningNotConfiguredError extends ArchonError {
471
+ static type = 'Lightning not configured';
472
+ constructor(detail) {
473
+ super(LightningNotConfiguredError.type, detail);
474
+ }
475
+ }
476
+ class LightningUnavailableError extends ArchonError {
477
+ static type = 'Lightning service unavailable';
478
+ constructor(detail) {
479
+ super(LightningUnavailableError.type, detail);
480
+ }
481
+ }
469
482
 
470
483
  const base32 = rfc4648({
471
484
  prefix: 'b',
@@ -3924,6 +3937,275 @@ class Keymaster {
3924
3937
  });
3925
3938
  return true;
3926
3939
  }
3940
+ async addNostr(name) {
3941
+ const keypair = await this.fetchKeyPair(name);
3942
+ if (!keypair) {
3943
+ throw new InvalidParameterError('id');
3944
+ }
3945
+ const nostr = this.cipher.jwkToNostr(keypair.publicJwk);
3946
+ const id = await this.fetchIdInfo(name);
3947
+ await this.mergeData(id.did, { nostr });
3948
+ return nostr;
3949
+ }
3950
+ async removeNostr(name) {
3951
+ const id = await this.fetchIdInfo(name);
3952
+ return this.mergeData(id.did, { nostr: null });
3953
+ }
3954
+ async exportNsec(name) {
3955
+ const keypair = await this.fetchKeyPair(name);
3956
+ if (!keypair) {
3957
+ throw new InvalidParameterError('id');
3958
+ }
3959
+ return this.cipher.jwkToNsec(keypair.privateJwk);
3960
+ }
3961
+ async signNostrEvent(event) {
3962
+ const keypair = await this.fetchKeyPair();
3963
+ if (!keypair) {
3964
+ throw new InvalidParameterError('id');
3965
+ }
3966
+ const nostr = this.cipher.jwkToNostr(keypair.publicJwk);
3967
+ const serialized = JSON.stringify([
3968
+ 0,
3969
+ nostr.pubkey,
3970
+ event.created_at,
3971
+ event.kind,
3972
+ event.tags,
3973
+ event.content,
3974
+ ]);
3975
+ const id = this.cipher.hashMessage(serialized);
3976
+ const sig = this.cipher.signSchnorr(id, keypair.privateJwk);
3977
+ return {
3978
+ ...event,
3979
+ id,
3980
+ pubkey: nostr.pubkey,
3981
+ sig,
3982
+ };
3983
+ }
3984
+ // Lightning helpers
3985
+ requireDrawbridge() {
3986
+ const drawbridge = this.gatekeeper;
3987
+ if (typeof drawbridge.createLightningWallet !== 'function') {
3988
+ throw new LightningUnavailableError('Gateway does not support Lightning');
3989
+ }
3990
+ return drawbridge;
3991
+ }
3992
+ async getLightningConfig(name) {
3993
+ const drawbridge = this.requireDrawbridge();
3994
+ const url = drawbridge.url;
3995
+ let wallet = await this.loadWallet();
3996
+ let idInfo = await this.fetchIdInfo(name, wallet);
3997
+ if (!idInfo.lightning) {
3998
+ throw new LightningNotConfiguredError(`No Lightning wallet configured for ${idInfo.did}`);
3999
+ }
4000
+ // Migrate old flat format to per-URL dictionary
4001
+ if ('walletId' in idInfo.lightning) {
4002
+ await this.mutateWallet(async (wallet) => {
4003
+ const idInfo = await this.fetchIdInfo(name, wallet);
4004
+ if (idInfo.lightning && 'walletId' in idInfo.lightning) {
4005
+ idInfo.lightning = { [url]: idInfo.lightning };
4006
+ }
4007
+ });
4008
+ wallet = await this.loadWallet();
4009
+ idInfo = await this.fetchIdInfo(name, wallet);
4010
+ }
4011
+ const config = idInfo.lightning[url];
4012
+ if (!config) {
4013
+ throw new LightningNotConfiguredError(`No Lightning wallet configured for ${idInfo.did} on ${url}`);
4014
+ }
4015
+ return config;
4016
+ }
4017
+ // Lightning methods
4018
+ async addLightning(name) {
4019
+ try {
4020
+ return await this.getLightningConfig(name);
4021
+ }
4022
+ catch (e) {
4023
+ if (e.type !== LightningNotConfiguredError.type)
4024
+ throw e;
4025
+ }
4026
+ const drawbridge = this.requireDrawbridge();
4027
+ const url = drawbridge.url;
4028
+ let result;
4029
+ await this.mutateWallet(async (wallet) => {
4030
+ const idInfo = await this.fetchIdInfo(name, wallet);
4031
+ const store = (idInfo.lightning || {});
4032
+ const walletName = `archon-${idInfo.did.split(':').pop()?.substring(0, 12)}`;
4033
+ const created = await drawbridge.createLightningWallet(walletName);
4034
+ store[url] = {
4035
+ walletId: created.walletId,
4036
+ adminKey: created.adminKey,
4037
+ invoiceKey: created.invoiceKey,
4038
+ };
4039
+ idInfo.lightning = store;
4040
+ result = store[url];
4041
+ });
4042
+ return result;
4043
+ }
4044
+ async removeLightning(name) {
4045
+ // Migrate old format if possible (ignore errors)
4046
+ try {
4047
+ await this.getLightningConfig(name);
4048
+ }
4049
+ catch { /* ok */ }
4050
+ // Try to get URL for targeted removal; fall back to removing all
4051
+ const gk = this.gatekeeper;
4052
+ const url = gk.url ? new URL(gk.url).origin : null;
4053
+ await this.mutateWallet(async (wallet) => {
4054
+ const idInfo = await this.fetchIdInfo(name, wallet);
4055
+ if (!idInfo.lightning)
4056
+ return;
4057
+ if (url) {
4058
+ const store = idInfo.lightning;
4059
+ delete store[url];
4060
+ if (Object.keys(store).length === 0) {
4061
+ delete idInfo.lightning;
4062
+ }
4063
+ }
4064
+ else {
4065
+ delete idInfo.lightning;
4066
+ }
4067
+ });
4068
+ return true;
4069
+ }
4070
+ async getLightningBalance(name) {
4071
+ const drawbridge = this.requireDrawbridge();
4072
+ const config = await this.getLightningConfig(name);
4073
+ return drawbridge.getLightningBalance(config.invoiceKey);
4074
+ }
4075
+ async createLightningInvoice(amount, memo = '', name) {
4076
+ if (!amount || amount <= 0) {
4077
+ throw new InvalidParameterError('amount');
4078
+ }
4079
+ const drawbridge = this.requireDrawbridge();
4080
+ const config = await this.getLightningConfig(name);
4081
+ return drawbridge.createLightningInvoice(config.invoiceKey, amount, memo);
4082
+ }
4083
+ async payLightningInvoice(bolt11, name) {
4084
+ if (!bolt11) {
4085
+ throw new InvalidParameterError('bolt11');
4086
+ }
4087
+ const drawbridge = this.requireDrawbridge();
4088
+ const config = await this.getLightningConfig(name);
4089
+ return drawbridge.payLightningInvoice(config.adminKey, bolt11);
4090
+ }
4091
+ async checkLightningPayment(paymentHash, name) {
4092
+ if (!paymentHash) {
4093
+ throw new InvalidParameterError('paymentHash');
4094
+ }
4095
+ const drawbridge = this.requireDrawbridge();
4096
+ const config = await this.getLightningConfig(name);
4097
+ const data = await drawbridge.checkLightningPayment(config.invoiceKey, paymentHash);
4098
+ return {
4099
+ paid: data.paid,
4100
+ preimage: data.preimage,
4101
+ paymentHash,
4102
+ };
4103
+ }
4104
+ async decodeLightningInvoice(bolt11) {
4105
+ if (!bolt11) {
4106
+ throw new InvalidParameterError('bolt11');
4107
+ }
4108
+ const decoded = lightBolt11Decoder.decode(bolt11);
4109
+ const info = {};
4110
+ let timestamp;
4111
+ for (const section of decoded.sections) {
4112
+ switch (section.name) {
4113
+ case 'amount':
4114
+ info.amount = `${parseInt(section.value) / 1000} sats`;
4115
+ break;
4116
+ case 'description':
4117
+ info.description = section.value;
4118
+ break;
4119
+ case 'timestamp':
4120
+ timestamp = section.value;
4121
+ info.created = new Date(section.value * 1000).toISOString();
4122
+ break;
4123
+ case 'expiry':
4124
+ info.expiry = `${section.value} seconds`;
4125
+ break;
4126
+ case 'payment_hash':
4127
+ info.payment_hash = section.value;
4128
+ break;
4129
+ case 'coin_network':
4130
+ info.network = section.value?.bech32;
4131
+ break;
4132
+ }
4133
+ }
4134
+ if (timestamp !== undefined && decoded.expiry !== undefined) {
4135
+ info.expires = new Date((timestamp + decoded.expiry) * 1000).toISOString();
4136
+ }
4137
+ return info;
4138
+ }
4139
+ async publishLightning(name) {
4140
+ const drawbridge = this.requireDrawbridge();
4141
+ const config = await this.getLightningConfig(name);
4142
+ const id = await this.fetchIdInfo(name);
4143
+ const did = id.did;
4144
+ // Register invoiceKey on Drawbridge using DID suffix as key
4145
+ const didSuffix = did.split(':').pop();
4146
+ const result = await drawbridge.publishLightning(didSuffix, config.invoiceKey);
4147
+ // Add service endpoint to DID document
4148
+ const doc = await this.resolveDID(did);
4149
+ const services = doc.didDocument?.service || [];
4150
+ const serviceId = `${did}#lightning`;
4151
+ // Remove existing lightning service if present
4152
+ const filtered = services.filter(s => s.id !== serviceId);
4153
+ const publicHost = result.publicHost || drawbridge.url;
4154
+ filtered.push({
4155
+ id: serviceId,
4156
+ type: 'Lightning',
4157
+ serviceEndpoint: `${publicHost}/invoice/${didSuffix}`,
4158
+ });
4159
+ doc.didDocument.service = filtered;
4160
+ await this.updateDID(did, doc);
4161
+ return true;
4162
+ }
4163
+ async unpublishLightning(name) {
4164
+ const drawbridge = this.requireDrawbridge();
4165
+ const id = await this.fetchIdInfo(name);
4166
+ const did = id.did;
4167
+ // Unregister from Drawbridge using DID suffix as key
4168
+ const didSuffix = did.split(':').pop();
4169
+ await drawbridge.unpublishLightning(didSuffix);
4170
+ // Remove service endpoint from DID document
4171
+ const doc = await this.resolveDID(did);
4172
+ const services = doc.didDocument?.service || [];
4173
+ const serviceId = `${did}#lightning`;
4174
+ const filtered = services.filter(s => s.id !== serviceId);
4175
+ if (filtered.length > 0) {
4176
+ doc.didDocument.service = filtered;
4177
+ }
4178
+ else {
4179
+ delete doc.didDocument.service;
4180
+ }
4181
+ await this.updateDID(did, doc);
4182
+ return true;
4183
+ }
4184
+ async zapLightning(id, amount, memo, name) {
4185
+ const isLud16 = id.includes('@') && !id.startsWith('did:');
4186
+ let recipient;
4187
+ if (isLud16) {
4188
+ recipient = id;
4189
+ }
4190
+ else {
4191
+ const did = await this.lookupDID(id);
4192
+ if (!did) {
4193
+ throw new InvalidParameterError('did');
4194
+ }
4195
+ recipient = did;
4196
+ }
4197
+ if (!amount || amount <= 0) {
4198
+ throw new InvalidParameterError('amount');
4199
+ }
4200
+ const drawbridge = this.requireDrawbridge();
4201
+ const config = await this.getLightningConfig(name);
4202
+ return drawbridge.zapLightning(config.adminKey, recipient, amount, memo);
4203
+ }
4204
+ async getLightningPayments(name) {
4205
+ const drawbridge = this.requireDrawbridge();
4206
+ const config = await this.getLightningConfig(name);
4207
+ return drawbridge.getLightningPayments(config.adminKey);
4208
+ }
3927
4209
  async testAgent(id) {
3928
4210
  const doc = await this.resolveDID(id);
3929
4211
  return doc.didDocumentRegistration?.type === 'agent';
@@ -4348,26 +4630,27 @@ class Keymaster {
4348
4630
  }
4349
4631
  async createGroup(name, options = {}) {
4350
4632
  const group = {
4351
- name: name,
4352
- members: []
4633
+ version: 2,
4634
+ members: [],
4353
4635
  };
4354
- return this.createAsset({ group }, options);
4636
+ return this.createAsset({ name, group }, options);
4355
4637
  }
4356
4638
  async getGroup(id) {
4357
4639
  const asset = await this.resolveAsset(id);
4358
4640
  if (!asset) {
4359
4641
  return null;
4360
4642
  }
4361
- // TEMP during did:cid, return old version groups
4362
- const castOldAsset = asset;
4363
- if (castOldAsset.members) {
4364
- return castOldAsset;
4643
+ // V2: { name, group: { version: 2, members } }
4644
+ if (asset.group?.version === 2) {
4645
+ return { name: asset.name, members: asset.group.members };
4365
4646
  }
4366
- const castAsset = asset;
4367
- if (!castAsset.group) {
4368
- return null;
4647
+ // V1: { group: { name, members } }
4648
+ if (asset.group?.name && Array.isArray(asset.group?.members)) {
4649
+ const group = asset.group;
4650
+ await this.mergeData(id, { name: group.name, group: { version: 2, members: group.members } });
4651
+ return group;
4369
4652
  }
4370
- return castAsset.group;
4653
+ return null;
4371
4654
  }
4372
4655
  async addGroupMember(groupId, memberId) {
4373
4656
  const groupDID = await this.lookupDID(groupId);
@@ -4398,8 +4681,8 @@ class Keymaster {
4398
4681
  }
4399
4682
  const members = new Set(group.members);
4400
4683
  members.add(memberDID);
4401
- group.members = Array.from(members);
4402
- return this.mergeData(groupDID, { group });
4684
+ const updatedMembers = Array.from(members);
4685
+ return this.mergeData(groupDID, { group: { version: 2, members: updatedMembers } });
4403
4686
  }
4404
4687
  async removeGroupMember(groupId, memberId) {
4405
4688
  const groupDID = await this.lookupDID(groupId);
@@ -4421,8 +4704,8 @@ class Keymaster {
4421
4704
  }
4422
4705
  const members = new Set(group.members);
4423
4706
  members.delete(memberDID);
4424
- group.members = Array.from(members);
4425
- return this.mergeData(groupDID, { group });
4707
+ const updatedMembers = Array.from(members);
4708
+ return this.mergeData(groupDID, { group: { version: 2, members: updatedMembers } });
4426
4709
  }
4427
4710
  async testGroup(groupId, memberId) {
4428
4711
  try {
package/dist/cjs/node.cjs CHANGED
@@ -16,6 +16,7 @@ var db_mongo = require('./db/mongo.cjs');
16
16
  var db_sqlite = require('./db/sqlite.cjs');
17
17
  require('image-size');
18
18
  require('file-type');
19
+ require('light-bolt11-decoder');
19
20
  require('crypto');
20
21
  require('buffer');
21
22
  require('axios');
package/dist/esm/cli.js CHANGED
@@ -5,7 +5,7 @@ import path from 'path';
5
5
  import { fileURLToPath } from 'url';
6
6
  import dotenv from 'dotenv';
7
7
  import Keymaster from './keymaster.js';
8
- import GatekeeperClient from '@didcid/gatekeeper/client';
8
+ import DrawbridgeClient from '@didcid/gatekeeper/drawbridge';
9
9
  import CipherNode from '@didcid/cipher/node';
10
10
  import WalletJson from './db/json.js';
11
11
  import WalletSQLite from './db/sqlite.js';
@@ -701,6 +701,175 @@ program
701
701
  console.error(error.error || error.message || error);
702
702
  }
703
703
  });
704
+ // Nostr commands
705
+ program
706
+ .command('add-nostr [id]')
707
+ .description('Derive and add nostr keys to an agent DID')
708
+ .action(async (id) => {
709
+ try {
710
+ const nostr = await keymaster.addNostr(id);
711
+ console.log(JSON.stringify(nostr, null, 4));
712
+ }
713
+ catch (error) {
714
+ console.error(error.error || error.message || error);
715
+ }
716
+ });
717
+ program
718
+ .command('remove-nostr [id]')
719
+ .description('Remove nostr keys from an agent DID')
720
+ .action(async (id) => {
721
+ try {
722
+ await keymaster.removeNostr(id);
723
+ console.log(UPDATE_OK);
724
+ }
725
+ catch (error) {
726
+ console.error(error.error || error.message || error);
727
+ }
728
+ });
729
+ // Lightning commands
730
+ program
731
+ .command('add-lightning [id]')
732
+ .description('Create a Lightning wallet for a DID')
733
+ .action(async (id) => {
734
+ try {
735
+ const config = await keymaster.addLightning(id);
736
+ console.log(JSON.stringify(config, null, 4));
737
+ }
738
+ catch (error) {
739
+ console.error(error.error || error.message || error);
740
+ }
741
+ });
742
+ program
743
+ .command('remove-lightning [id]')
744
+ .description('Remove Lightning wallet from a DID')
745
+ .action(async (id) => {
746
+ try {
747
+ await keymaster.removeLightning(id);
748
+ console.log(UPDATE_OK);
749
+ }
750
+ catch (error) {
751
+ console.error(error.error || error.message || error);
752
+ }
753
+ });
754
+ program
755
+ .command('lightning-balance [id]')
756
+ .description('Check Lightning wallet balance')
757
+ .action(async (id) => {
758
+ try {
759
+ const balance = await keymaster.getLightningBalance(id);
760
+ console.log(`${balance.balance} sats`);
761
+ }
762
+ catch (error) {
763
+ console.error(error.error || error.message || error);
764
+ }
765
+ });
766
+ program
767
+ .command('lightning-decode <bolt11>')
768
+ .description('Decode a Lightning BOLT11 invoice')
769
+ .action(async (bolt11) => {
770
+ try {
771
+ const info = await keymaster.decodeLightningInvoice(bolt11);
772
+ console.log(JSON.stringify(info, null, 4));
773
+ }
774
+ catch (error) {
775
+ console.error(error.error || error.message || error);
776
+ }
777
+ });
778
+ program
779
+ .command('lightning-invoice <amount> <memo> [id]')
780
+ .description('Create a Lightning invoice to receive sats')
781
+ .action(async (amount, memo, id) => {
782
+ try {
783
+ const invoice = await keymaster.createLightningInvoice(parseInt(amount), memo, id);
784
+ console.log(JSON.stringify(invoice, null, 4));
785
+ }
786
+ catch (error) {
787
+ console.error(error.error || error.message || error);
788
+ }
789
+ });
790
+ program
791
+ .command('lightning-pay <bolt11> [id]')
792
+ .description('Pay a Lightning invoice')
793
+ .action(async (bolt11, id) => {
794
+ try {
795
+ const payment = await keymaster.payLightningInvoice(bolt11, id);
796
+ console.log(JSON.stringify(payment, null, 4));
797
+ }
798
+ catch (error) {
799
+ console.error(error.error || error.message || error);
800
+ }
801
+ });
802
+ program
803
+ .command('lightning-check <paymentHash> [id]')
804
+ .description('Check status of a Lightning payment')
805
+ .action(async (paymentHash, id) => {
806
+ try {
807
+ const status = await keymaster.checkLightningPayment(paymentHash, id);
808
+ console.log(JSON.stringify(status, null, 4));
809
+ }
810
+ catch (error) {
811
+ console.error(error.error || error.message || error);
812
+ }
813
+ });
814
+ program
815
+ .command('publish-lightning [id]')
816
+ .description('Publish Lightning service endpoint for a DID')
817
+ .action(async (id) => {
818
+ try {
819
+ await keymaster.publishLightning(id);
820
+ console.log(UPDATE_OK);
821
+ }
822
+ catch (error) {
823
+ console.error(error.error || error.message || error);
824
+ }
825
+ });
826
+ program
827
+ .command('unpublish-lightning [id]')
828
+ .description('Remove Lightning service endpoint from a DID')
829
+ .action(async (id) => {
830
+ try {
831
+ await keymaster.unpublishLightning(id);
832
+ console.log(UPDATE_OK);
833
+ }
834
+ catch (error) {
835
+ console.error(error.error || error.message || error);
836
+ }
837
+ });
838
+ program
839
+ .command('lightning-zap <recipient> <amount> [memo]')
840
+ .description('Send sats via Lightning (DID, alias, or Lightning Address)')
841
+ .action(async (recipient, amount, memo) => {
842
+ try {
843
+ const result = await keymaster.zapLightning(recipient, parseInt(amount), memo);
844
+ console.log(JSON.stringify(result, null, 4));
845
+ }
846
+ catch (error) {
847
+ console.error(error.error || error.message || error);
848
+ }
849
+ });
850
+ program
851
+ .command('lightning-payments [id]')
852
+ .description('Show Lightning payment history')
853
+ .action(async (id) => {
854
+ try {
855
+ const payments = await keymaster.getLightningPayments(id);
856
+ if (payments.length === 0) {
857
+ console.log('No payments found.');
858
+ return;
859
+ }
860
+ for (const p of payments) {
861
+ const d = p.time ? new Date(p.time) : null;
862
+ const date = d ? `${d.getFullYear()}/${String(d.getMonth() + 1).padStart(2, '0')}/${String(d.getDate()).padStart(2, '0')} ${String(d.getHours()).padStart(2, '0')}:${String(d.getMinutes()).padStart(2, '0')}:${String(d.getSeconds()).padStart(2, '0')}` : '—';
863
+ const fee = p.fee > 0 ? ` (fee: ${p.fee})` : '';
864
+ const memo = p.memo ? ` "${p.memo}"` : '';
865
+ const status = p.pending ? ' [pending]' : '';
866
+ console.log(`${date} ${p.amount} sats${fee}${memo}${status}`);
867
+ }
868
+ }
869
+ catch (error) {
870
+ console.error(error.error || error.message || error);
871
+ }
872
+ });
704
873
  // Group commands
705
874
  program
706
875
  .command('create-group <groupName>')
@@ -1557,7 +1726,7 @@ async function run() {
1557
1726
  }
1558
1727
  try {
1559
1728
  // Initialize gatekeeper client
1560
- const gatekeeper = new GatekeeperClient();
1729
+ const gatekeeper = new DrawbridgeClient();
1561
1730
  await gatekeeper.connect({
1562
1731
  url: gatekeeperURL,
1563
1732
  waitUntilReady: true,