@did-btcr2/method 0.19.0 → 0.20.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.
Files changed (119) hide show
  1. package/dist/browser.js +715 -1445
  2. package/dist/browser.mjs +715 -1445
  3. package/dist/cjs/core/beacon/beacon.js +25 -0
  4. package/dist/cjs/core/beacon/beacon.js.map +1 -0
  5. package/dist/cjs/core/beacon/cas-beacon.js +20 -36
  6. package/dist/cjs/core/beacon/cas-beacon.js.map +1 -1
  7. package/dist/cjs/core/beacon/error.js +4 -4
  8. package/dist/cjs/core/beacon/error.js.map +1 -1
  9. package/dist/cjs/core/beacon/factory.js +5 -7
  10. package/dist/cjs/core/beacon/factory.js.map +1 -1
  11. package/dist/cjs/core/beacon/interfaces.js +1 -31
  12. package/dist/cjs/core/beacon/interfaces.js.map +1 -1
  13. package/dist/cjs/core/beacon/signal-discovery.js +183 -0
  14. package/dist/cjs/core/beacon/signal-discovery.js.map +1 -0
  15. package/dist/cjs/core/beacon/singleton.js +54 -81
  16. package/dist/cjs/core/beacon/singleton.js.map +1 -1
  17. package/dist/cjs/core/beacon/smt-beacon.js +22 -39
  18. package/dist/cjs/core/beacon/smt-beacon.js.map +1 -1
  19. package/dist/cjs/core/beacon/utils.js +4 -9
  20. package/dist/cjs/core/beacon/utils.js.map +1 -1
  21. package/dist/cjs/core/resolve.js +87 -277
  22. package/dist/cjs/core/resolve.js.map +1 -1
  23. package/dist/cjs/core/update.js +62 -153
  24. package/dist/cjs/core/update.js.map +1 -1
  25. package/dist/cjs/did-btcr2.js +80 -75
  26. package/dist/cjs/did-btcr2.js.map +1 -1
  27. package/dist/cjs/index.js +3 -1
  28. package/dist/cjs/index.js.map +1 -1
  29. package/dist/cjs/utils/appendix.js +6 -15
  30. package/dist/cjs/utils/appendix.js.map +1 -1
  31. package/dist/cjs/utils/did-document-builder.js +5 -0
  32. package/dist/cjs/utils/did-document-builder.js.map +1 -1
  33. package/dist/cjs/utils/did-document.js +11 -14
  34. package/dist/cjs/utils/did-document.js.map +1 -1
  35. package/dist/esm/core/beacon/beacon.js +25 -0
  36. package/dist/esm/core/beacon/beacon.js.map +1 -0
  37. package/dist/esm/core/beacon/cas-beacon.js +20 -36
  38. package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
  39. package/dist/esm/core/beacon/error.js +4 -4
  40. package/dist/esm/core/beacon/error.js.map +1 -1
  41. package/dist/esm/core/beacon/factory.js +5 -7
  42. package/dist/esm/core/beacon/factory.js.map +1 -1
  43. package/dist/esm/core/beacon/interfaces.js +1 -31
  44. package/dist/esm/core/beacon/interfaces.js.map +1 -1
  45. package/dist/esm/core/beacon/signal-discovery.js +183 -0
  46. package/dist/esm/core/beacon/signal-discovery.js.map +1 -0
  47. package/dist/esm/core/beacon/singleton.js +54 -81
  48. package/dist/esm/core/beacon/singleton.js.map +1 -1
  49. package/dist/esm/core/beacon/smt-beacon.js +22 -39
  50. package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
  51. package/dist/esm/core/beacon/utils.js +4 -9
  52. package/dist/esm/core/beacon/utils.js.map +1 -1
  53. package/dist/esm/core/resolve.js +87 -277
  54. package/dist/esm/core/resolve.js.map +1 -1
  55. package/dist/esm/core/update.js +62 -153
  56. package/dist/esm/core/update.js.map +1 -1
  57. package/dist/esm/did-btcr2.js +80 -75
  58. package/dist/esm/did-btcr2.js.map +1 -1
  59. package/dist/esm/index.js +3 -1
  60. package/dist/esm/index.js.map +1 -1
  61. package/dist/esm/utils/appendix.js +6 -15
  62. package/dist/esm/utils/appendix.js.map +1 -1
  63. package/dist/esm/utils/did-document-builder.js +5 -0
  64. package/dist/esm/utils/did-document-builder.js.map +1 -1
  65. package/dist/esm/utils/did-document.js +11 -14
  66. package/dist/esm/utils/did-document.js.map +1 -1
  67. package/dist/types/core/beacon/beacon.d.ts +44 -0
  68. package/dist/types/core/beacon/beacon.d.ts.map +1 -0
  69. package/dist/types/core/beacon/cas-beacon.d.ts +19 -30
  70. package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
  71. package/dist/types/core/beacon/error.d.ts +2 -2
  72. package/dist/types/core/beacon/error.d.ts.map +1 -1
  73. package/dist/types/core/beacon/factory.d.ts +4 -6
  74. package/dist/types/core/beacon/factory.d.ts.map +1 -1
  75. package/dist/types/core/beacon/interfaces.d.ts +7 -46
  76. package/dist/types/core/beacon/interfaces.d.ts.map +1 -1
  77. package/dist/types/core/beacon/signal-discovery.d.ts +25 -0
  78. package/dist/types/core/beacon/signal-discovery.d.ts.map +1 -0
  79. package/dist/types/core/beacon/singleton.d.ts +16 -29
  80. package/dist/types/core/beacon/singleton.d.ts.map +1 -1
  81. package/dist/types/core/beacon/smt-beacon.d.ts +21 -33
  82. package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
  83. package/dist/types/core/beacon/utils.d.ts.map +1 -1
  84. package/dist/types/core/interfaces.d.ts +1 -8
  85. package/dist/types/core/interfaces.d.ts.map +1 -1
  86. package/dist/types/core/resolve.d.ts +27 -43
  87. package/dist/types/core/resolve.d.ts.map +1 -1
  88. package/dist/types/core/types.d.ts +21 -8
  89. package/dist/types/core/types.d.ts.map +1 -1
  90. package/dist/types/core/update.d.ts +30 -73
  91. package/dist/types/core/update.d.ts.map +1 -1
  92. package/dist/types/did-btcr2.d.ts +37 -44
  93. package/dist/types/did-btcr2.d.ts.map +1 -1
  94. package/dist/types/index.d.ts +3 -1
  95. package/dist/types/index.d.ts.map +1 -1
  96. package/dist/types/utils/appendix.d.ts.map +1 -1
  97. package/dist/types/utils/did-document-builder.d.ts +5 -0
  98. package/dist/types/utils/did-document-builder.d.ts.map +1 -1
  99. package/dist/types/utils/did-document.d.ts +16 -14
  100. package/dist/types/utils/did-document.d.ts.map +1 -1
  101. package/package.json +4 -4
  102. package/src/core/beacon/beacon.ts +58 -0
  103. package/src/core/beacon/cas-beacon.ts +30 -44
  104. package/src/core/beacon/error.ts +5 -6
  105. package/src/core/beacon/factory.ts +7 -9
  106. package/src/core/beacon/interfaces.ts +7 -64
  107. package/src/core/beacon/signal-discovery.ts +237 -0
  108. package/src/core/beacon/singleton.ts +74 -94
  109. package/src/core/beacon/smt-beacon.ts +32 -49
  110. package/src/core/beacon/utils.ts +16 -13
  111. package/src/core/interfaces.ts +1 -9
  112. package/src/core/resolve.ts +116 -360
  113. package/src/core/types.ts +25 -8
  114. package/src/core/update.ts +90 -235
  115. package/src/did-btcr2.ts +131 -95
  116. package/src/index.ts +8 -1
  117. package/src/utils/appendix.ts +8 -22
  118. package/src/utils/did-document-builder.ts +5 -0
  119. package/src/utils/did-document.ts +41 -45
@@ -1,10 +1,6 @@
1
1
  import {
2
2
  BitcoinNetworkConnection,
3
- BlockV3,
4
- GENESIS_TX_ID,
5
- getNetwork,
6
- RawTransactionV2,
7
- TXIN_WITNESS_COINBASE
3
+ getNetwork
8
4
  } from '@did-btcr2/bitcoin';
9
5
  import {
10
6
  DateUtils,
@@ -15,16 +11,15 @@ import {
15
11
  JSONPatch,
16
12
  JSONUtils,
17
13
  LATE_PUBLISHING_ERROR,
18
- MethodError,
19
14
  MISSING_UPDATE_DATA,
20
15
  ResolveError
21
16
  } from '@did-btcr2/common';
22
17
  import {
23
18
  BIP340Cryptosuite,
24
19
  BIP340DataIntegrityProof,
25
- BTCR2SignedUpdate,
26
- BTCR2UnsignedUpdate,
27
- SchnorrMultikey
20
+ SchnorrMultikey,
21
+ SignedBTCR2Update,
22
+ UnsignedBTCR2Update
28
23
  } from '@did-btcr2/cryptosuite';
29
24
  import { CompressedSecp256k1PublicKey } from '@did-btcr2/keypair';
30
25
  import { bytesToHex } from '@noble/hashes/utils';
@@ -32,7 +27,8 @@ import { canonicalization, DidBtcr2 } from '../did-btcr2.js';
32
27
  import { Appendix } from '../utils/appendix.js';
33
28
  import { DidDocument, ID_PLACEHOLDER_VALUE } from '../utils/did-document.js';
34
29
  import { BeaconFactory } from './beacon/factory.js';
35
- import { BeaconService, BeaconSignal, BlockMetadata } from './beacon/interfaces.js';
30
+ import { BeaconService, BlockMetadata } from './beacon/interfaces.js';
31
+ import { BeaconSignalDiscovery } from './beacon/signal-discovery.js';
36
32
  import { BeaconUtils } from './beacon/utils.js';
37
33
  import { DidComponents, Identifier } from './identifier.js';
38
34
  import { SMTProof } from './interfaces.js';
@@ -58,79 +54,6 @@ export interface DidResolutionResponse {
58
54
  * @type {Resolve}
59
55
  */
60
56
  export class Resolve {
61
- /**
62
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-sidecar-data | Process Sidecar Data}
63
- * @param {Sidecar} sidecar The sidecar data to process.
64
- * @returns {SidecarData} The processed sidecar data containing maps of updates, CAS announcements, and SMT proofs.
65
- */
66
- static processSidecarData(sidecar: Sidecar = {} as Sidecar): SidecarData {
67
- // BTCR2 Signed Updates map
68
- const updateMap = new Map<string, BTCR2SignedUpdate>();
69
- if(sidecar.updates?.length)
70
- for(const update of sidecar.updates) {
71
- updateMap.set(canonicalization.process(update, { encoding: 'hex' }), update);
72
- }
73
-
74
- // CAS Announcements map
75
- const casMap = new Map<string, CASAnnouncement>();
76
- if(sidecar.casUpdates?.length)
77
- for(const update of sidecar.casUpdates) {
78
- casMap.set(canonicalization.process(update, { encoding: 'hex' }), update);
79
- }
80
-
81
- // SMT Proofs map
82
- const smtMap = new Map<string, SMTProof>();
83
- if(sidecar.smtProofs?.length)
84
- for(const proof of sidecar.smtProofs) {
85
- smtMap.set(proof.id, proof);
86
- }
87
-
88
- return { updateMap, casMap, smtMap };
89
- }
90
-
91
- /**
92
- * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#establish-current-document | 7.2.d Establish current_document}.
93
- * Resolution begins by creating an Initial Did Document called current_document (Current DID Document).
94
- * The current_document is iteratively patched with BTCR2 Signed Updates announced by Authorized Beacon Signals.
95
- * @param {DidComponents} didComponents The decoded components of the did.
96
- * @param {GenesisDocument} genesisDocument The genesis document for resolving the DID Document.
97
- * @returns {Promise<DidDocument>} The resolved DID Document object.
98
- * @throws {DidError} if the DID hrp is invalid, no sidecarData passed and hrp = "x".
99
- */
100
- static async establishCurrentDocument(
101
- didComponents: DidComponents,
102
- genesisDocument?: object,
103
- ): Promise<DidDocument> {
104
- // Deconstruct the hrp from the components
105
- const { hrp, genesisBytes } = didComponents;
106
-
107
- // If hrp `x`, perform external resolution
108
- if (hrp === IdentifierHrp.x) {
109
- if(!genesisDocument)
110
- throw new ResolveError(
111
- 'External resolution requires genesisDocument',
112
- MISSING_UPDATE_DATA, { didComponents }
113
- );
114
- return await this.external(didComponents, genesisDocument);
115
- }
116
-
117
- // Check for hrp `k`
118
- if(hrp === IdentifierHrp.k){
119
- // Validate genesis bytes as a compressed secp256k1 public key
120
- if(!CompressedSecp256k1PublicKey.isValid(genesisBytes)) {
121
- throw new ResolveError(
122
- 'Deterministic resolution requires valid secp256k1 public key',
123
- INVALID_DID, { genesisBytes }
124
- );
125
- }
126
- // Perform deterministic resolution
127
- return this.deterministic(didComponents);
128
- }
129
-
130
- // Else, throw an error for unsupported hrp
131
- throw new ResolveError(`Unsupported DID hrp ${hrp}`, INVALID_DID, { hrp });
132
- }
133
-
134
57
  /**
135
58
  * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#if-genesis_bytes-is-a-secp256k1-public-key | 7.2.d.1 if genesis bytes is a secp256k1 Public Key}.
136
59
  * @param {DidComponents} didComponents The decoded components of the did.
@@ -172,7 +95,7 @@ export class Resolve {
172
95
  * @param {DidComponents} didComponents BTCR2 DID components used to resolve the DID Document
173
96
  * @param {GenesisDocument} genesisDocument The genesis document for resolving the DID Document.
174
97
  * @returns {Promise<DidDocument>} The resolved DID Document object
175
- * @throws {MethodError} InvalidDidDocument if not conformant to DID Core v1.1
98
+ * @throws {ResolveError} InvalidDidDocument if not conformant to DID Core v1.1
176
99
  */
177
100
  static async external(
178
101
  didComponents: DidComponents,
@@ -186,7 +109,7 @@ export class Resolve {
186
109
 
187
110
  // If the genesisBytes do not match the hashBytes, throw an error
188
111
  if (genesisBytes !== hashBytes) {
189
- throw new MethodError(
112
+ throw new ResolveError(
190
113
  `Initial document mismatch: genesisBytes ${genesisBytes} !== hashBytes ${hashBytes}`,
191
114
  INVALID_DID_DOCUMENT, { genesisBytes, hashBytes }
192
115
  );
@@ -204,54 +127,122 @@ export class Resolve {
204
127
  return new DidDocument(currentDocument);
205
128
  }
206
129
 
130
+ /**
131
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-sidecar-data | Process Sidecar Data}
132
+ * @param {Sidecar} sidecar The sidecar data to process.
133
+ * @returns {ProcessedSidecar} The processed sidecar data containing maps of updates, CAS announcements, and SMT proofs.
134
+ */
135
+ static sidecarData(sidecar: Sidecar = {} as Sidecar): SidecarData {
136
+ // BTCR2 Signed Updates map
137
+ const updateMap = new Map<string, SignedBTCR2Update>();
138
+ if(sidecar.updates?.length)
139
+ for(const update of sidecar.updates) {
140
+ updateMap.set(canonicalization.process(update, { encoding: 'hex' }), update);
141
+ }
142
+
143
+ // CAS Announcements map
144
+ const casMap = new Map<string, CASAnnouncement>();
145
+ if(sidecar.casUpdates?.length)
146
+ for(const update of sidecar.casUpdates) {
147
+ casMap.set(canonicalization.process(update, { encoding: 'hex' }), update);
148
+ }
149
+
150
+ // SMT Proofs map
151
+ const smtMap = new Map<string, SMTProof>();
152
+ if(sidecar.smtProofs?.length)
153
+ for(const proof of sidecar.smtProofs) {
154
+ smtMap.set(proof.id, proof);
155
+ }
156
+
157
+ return { updateMap, casMap, smtMap };
158
+ }
159
+
160
+ /**
161
+ * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#establish-current-document | 7.2.d Establish current_document}.
162
+ * Resolution begins by creating an Initial Did Document called current_document (Current DID Document).
163
+ * The current_document is iteratively patched with BTCR2 Signed Updates announced by Authorized Beacon Signals.
164
+ * @param {DidComponents} didComponents The decoded components of the did.
165
+ * @param {GenesisDocument} genesisDocument The genesis document for resolving the DID Document.
166
+ * @returns {Promise<DidDocument>} The resolved DID Document object.
167
+ * @throws {ResolveError} if the DID hrp is invalid, no sidecarData passed and hrp = "x".
168
+ */
169
+
170
+ static async currentDocument(
171
+ didComponents: DidComponents,
172
+ genesisDocument?: object,
173
+ ): Promise<DidDocument> {
174
+ // Deconstruct the hrp from the components
175
+ const { hrp, genesisBytes } = didComponents;
176
+
177
+ // If hrp `x`, perform external resolution
178
+ if (hrp === IdentifierHrp.x) {
179
+ if(!genesisDocument)
180
+ throw new ResolveError(
181
+ 'External resolution requires genesisDocument',
182
+ MISSING_UPDATE_DATA, { didComponents }
183
+ );
184
+ return await this.external(didComponents, genesisDocument);
185
+ }
186
+
187
+ // Check for hrp `k`
188
+ if(hrp === IdentifierHrp.k){
189
+ // Validate genesis bytes as a compressed secp256k1 public key
190
+ if(!CompressedSecp256k1PublicKey.isValid(genesisBytes)) {
191
+ throw new ResolveError(
192
+ 'Deterministic resolution requires valid secp256k1 public key',
193
+ INVALID_DID, { genesisBytes }
194
+ );
195
+ }
196
+ // Perform deterministic resolution
197
+ return this.deterministic(didComponents);
198
+ }
199
+
200
+ // Else, throw an error for unsupported hrp
201
+ throw new ResolveError(`Unsupported DID hrp ${hrp}`, INVALID_DID, { hrp });
202
+ }
203
+
207
204
  /**
208
205
  * Finds uses the beacon services in the currentDocument to scan for onchain Beacon Signals (transactions) containing
209
206
  * Signal Bytes (last output in OP_RETURN transaction).
210
207
  * @param {Array<BeaconService>} beaconServices The array of BeaconService objects to search for signals.
211
208
  * @param {SidecarData} sidecarData The sidecar data containing maps of updates, CAS announcements, and SMT proofs.
212
209
  * @param {BitcoinNetworkConnection} bitcoin The bitcoin network connection used to fetch beacon signals
213
- * @param {boolean} [fullBlockchainTraversal=false] Whether to perform a full blockchain traversal or use an indexer
214
- * @returns {Promise<Array<[BTCR2SignedUpdate, BlockMetadata]>>} The array of BTCR2 Signed Updates announced by the Beacon Signals.
210
+ * @returns {Promise<Array<[SignedBTCR2Update, BlockMetadata]>>} The array of BTCR2 Signed Updates announced by the Beacon Signals.
215
211
  */
216
- static async processBeaconSignals(
212
+ static async beaconSignals(
217
213
  beaconServices: Array<BeaconService>,
218
214
  sidecarData: SidecarData,
219
- bitcoin: BitcoinNetworkConnection,
220
- fullBlockchainTraversal?: boolean
221
- ): Promise<Array<[BTCR2SignedUpdate, BlockMetadata]>> {
222
- // Query indexer or perform a full blockchain traversal for Beacon Signals
223
- const beaconServicesSignals = !fullBlockchainTraversal
224
- ? await this.queryBlockchainIndexer(beaconServices, bitcoin)
225
- : await this.traverseFullBlockchain(beaconServices, bitcoin);
226
-
227
-
228
- // Set updates to an empty array
229
- const unsortedUpdates = new Array<[BTCR2SignedUpdate, BlockMetadata]>();
230
-
231
- // Iterate over each beacon service and its signals
232
- for(const [service, signals] of beaconServicesSignals) {
233
- // Establish a beacon object
234
- const beacon = BeaconFactory.establish(service, signals, sidecarData);
235
- // Process its signals
236
- const processed = await beacon.processSignals();
237
- // Append the processed updates to the updates array
238
- unsortedUpdates.push(...processed);
239
- }
215
+ bitcoin: BitcoinNetworkConnection
216
+ ): Promise<Array<[SignedBTCR2Update, BlockMetadata]>> {
217
+ // Discover Beacon Signals via indexer server (esplora/electrs) or full node
218
+ const beaconServicesSignals = bitcoin.network.rest
219
+ ? await BeaconSignalDiscovery.indexer(beaconServices, bitcoin)
220
+ : await BeaconSignalDiscovery.fullnode(beaconServices, bitcoin);
221
+
222
+ // Process each beacon's signals in parallel
223
+ const promises = Array.from(beaconServicesSignals.entries()).map(
224
+ async ([service, signals]) => {
225
+ // Skip beacons with no signals
226
+ if (!signals.length) return [];
227
+ // Establish a typed beacon and process its signals
228
+ return BeaconFactory.establish(service).processSignals(signals, sidecarData);
229
+ }
230
+ );
240
231
 
241
- return unsortedUpdates;
232
+ return (await Promise.all(promises)).flat();
242
233
  }
243
234
 
244
235
  /**
245
236
  * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-updates | 7.2.f Process updates Array}.
246
237
  * @param {DidDocument} currentDocument The current DID Document to apply the updates to.
247
- * @param {Array<[BTCR2SignedUpdate, BlockMetadata]>} unsortedUpdates The unsorted array of BTCR2 Signed Updates and their associated Block Metadata.
238
+ * @param {Array<[SignedBTCR2Update, BlockMetadata]>} unsortedUpdates The unsorted array of BTCR2 Signed Updates and their associated Block Metadata.
248
239
  * @param {string} [versionTime] The optional version time to limit updates to.
249
240
  * @param {string} [versionId] The optional version id to limit updates to.
250
241
  * @returns {Promise<DidResolutionResponse>} The updated DID Document, number of confirmations, and version id.
251
242
  */
252
- static async processUpdatesArray(
243
+ static async updates(
253
244
  currentDocument: DidDocument,
254
- unsortedUpdates: Array<[BTCR2SignedUpdate, BlockMetadata]>,
245
+ unsortedUpdates: Array<[SignedBTCR2Update, BlockMetadata]>,
255
246
  versionTime?: string,
256
247
  versionId?: string
257
248
  ): Promise<DidResolutionResponse> {
@@ -298,7 +289,7 @@ export class Resolve {
298
289
  // If update.targetVersionId <= currentVersionId, confirm duplicate update
299
290
  if(update.targetVersionId <= currentVersionId) {
300
291
  updateHashHistory.push(currentDocumentHash);
301
- this.confirmDuplicateUpdate(update, updateHashHistory);
292
+ this.confirmDuplicate(update, updateHashHistory);
302
293
  }
303
294
 
304
295
  // If update.targetVersionId == currentVersionId + 1, apply the update
@@ -315,9 +306,9 @@ export class Resolve {
315
306
  );
316
307
  }
317
308
  // Apply the update to the currentDocument and set it in the response
318
- response.currentDocument = await this.applyDidUpdate(response.currentDocument, update);
309
+ response.currentDocument = await this.applyUpdate(response.currentDocument, update);
319
310
  // Create unsigned_update by removing the proof property from update.
320
- const unsignedUpdate = JSONUtils.deleteKeys(update, ['proof']) as BTCR2UnsignedUpdate;
311
+ const unsignedUpdate = JSONUtils.deleteKeys(update, ['proof']) as UnsignedBTCR2Update;
321
312
  // Push the canonicalized unsigned update hash to the updateHashHistory
322
313
  updateHashHistory.push(canonicalization.process(unsignedUpdate, { encoding: 'base58' }));
323
314
  }
@@ -353,245 +344,15 @@ export class Resolve {
353
344
  return response;
354
345
  }
355
346
 
356
- /**
357
- * Retrieves the beacon signals for the given array of BeaconService objects
358
- * using a esplora/electrs REST API connection via a bitcoin I/O driver.
359
- * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to retrieve signals for
360
- * @param {BitcoinNetworkConnection} bitcoin Bitcoin network connection to use for REST calls
361
- * @returns {Promise<Array<BeaconSignal>>} Promise resolving to an array of BeaconSignal objects
362
- */
363
- static async queryBlockchainIndexer(
364
- beaconServices: Array<BeaconService>,
365
- bitcoin: BitcoinNetworkConnection
366
- ): Promise<Map<BeaconService, Array<BeaconSignal>>> {
367
- // Empty array of beaconSignals
368
- const beaconServiceSignals = new Map<BeaconService, Array<BeaconSignal>>();
369
-
370
- // Iterate over each beacon
371
- for (const beaconService of beaconServices) {
372
- beaconServiceSignals.set(beaconService, []);
373
- // Get the transactions for the beacon address via REST
374
- const beaconSignals = await bitcoin.network.rest.address.getTxs(
375
- beaconService.serviceEndpoint as string
376
- );
377
-
378
- // If no signals are found, continue
379
- if (!beaconSignals || !beaconSignals.length) {
380
- continue;
381
- }
382
-
383
- // Iterate over each signal
384
- for (const beaconSignal of beaconSignals) {
385
- // Get the last vout in the transaction
386
- const signalVout = beaconSignal.vout.slice(-1)[0];
387
-
388
- /**
389
- * Look for OP_RETURN in last vout scriptpubkey_asm
390
- * Vout (rest) format:
391
- * {
392
- * scriptpubkey: '6a20570f177c65e64fb5cf61180b664cdddf09ab76153c2b192e22006e5b22a3917a',
393
- * scriptpubkey_asm: 'OP_RETURN OP_PUSHBYTES_32 570f177c65e64fb5cf61180b664cdddf09ab76153c2b192e22006e5b22a3917a',
394
- * scriptpubkey_type: 'op_return',
395
- * value: 0
396
- * }
397
- */
398
- if(!signalVout || !signalVout.scriptpubkey_asm.includes('OP_RETURN')) {
399
- // If not found, continue to next signal
400
- continue;
401
- }
402
-
403
- // Construct output map for easier access
404
- const outputMap = new Map<string, string | number>(Object.entries(signalVout));
405
-
406
- // Grab the signal vout scriptpubkey
407
- const signalVoutScriptPubkey = outputMap.get('scriptpubkey_asm') as string;
408
-
409
- // If the signal vout scriptpubkey does not exist, continue to next signal
410
- if(!signalVoutScriptPubkey){
411
- continue;
412
- }
413
-
414
- // Extract hex string hash of the signal bytes from the scriptpubkey
415
- const updateHash = signalVoutScriptPubkey.split(' ').slice(-1)[0];
416
- if(!updateHash) {
417
- continue;
418
- }
419
-
420
- const confirmations = await bitcoin.network.rest.block.count() - beaconSignal.status.block_height + 1;
421
- // Push the beacon signal object to the signals array for the beacon service
422
- beaconServiceSignals.get(beaconService)?.push({
423
- tx : beaconSignal,
424
- signalBytes : updateHash,
425
- blockMetadata : {
426
- confirmations,
427
- height : beaconSignal.status.block_height,
428
- time : beaconSignal.status.block_time,
429
- }
430
- });
431
- }
432
- }
433
-
434
-
435
- // Return the beaconSignals
436
- return beaconServiceSignals;
437
- }
438
-
439
- /**
440
- * Traverse the full blockchain from genesis to chain top looking for beacon signals.
441
- * @param {Array<BeaconService>} beaconServices Array of BeaconService objects to search for signals.
442
- * @param {BitcoinNetworkConnection} bitcoin Bitcoin network connection to use for RPC calls.
443
- * @returns {Promise<Array<BeaconSignal>>} Promise resolving to an array of BeaconSignal objects.
444
- */
445
- static async traverseFullBlockchain(
446
- beaconServices: Array<BeaconService>,
447
- bitcoin: BitcoinNetworkConnection
448
- ): Promise<Map<BeaconService, Array<BeaconSignal>>> {
449
- const beaconServiceSignals = new Map<BeaconService, Array<BeaconSignal>>();
450
-
451
- for(const beaconService of beaconServices) {
452
- beaconServiceSignals.set(beaconService, []);
453
- }
454
-
455
- // Get the RPC connection from the bitcoin network
456
- const rpc = bitcoin.network.rpc;
457
-
458
- // Ensure that the RPC connection is available
459
- if(!rpc) {
460
- throw new ResolveError('RPC connection is not available', 'RPC_CONNECTION_ERROR', bitcoin);
461
- }
462
-
463
- // Get the current block height
464
- const targetHeight = await rpc.getBlockCount();
465
-
466
- // Set genesis height
467
- let height = 0;
468
-
469
- // Opt into rpc connection to get the block data at the blockhash
470
- let block = await bitcoin.network.rpc!.getBlock({ height }) as BlockV3;
471
-
472
- console.info(`Searching for beacon signals, please wait ...`);
473
- while (block.height <= targetHeight) {
474
- // Iterate over each transaction in the block
475
- for (const tx of block.tx) {
476
- // If the txid is a coinbase, continue ...
477
- if (tx.txid === GENESIS_TX_ID) {
478
- continue;
479
- }
480
-
481
- // Iterate over each input in the transaction
482
- for (const vin of tx.vin) {
483
-
484
- // If the vin is a coinbase transaction, continue ...
485
- if (vin.coinbase) {
486
- continue;
487
- }
488
-
489
- // If the vin txinwitness contains a coinbase did, continue ...
490
- if (vin.txinwitness && vin.txinwitness.length === 1 && vin.txinwitness[0] === TXIN_WITNESS_COINBASE) {
491
- continue;
492
- }
493
-
494
- // If the txid from the vin is undefined, continue ...
495
- if (!vin.txid) {
496
- continue;
497
- }
498
-
499
- // If the vout from the vin is undefined, continue ...
500
- if (vin.vout === undefined) {
501
- continue;
502
- }
503
-
504
- // Get the previous output transaction data
505
- const prevout = await rpc.getRawTransaction(vin.txid, 2) as RawTransactionV2;
506
-
507
- // If the previous output vout at the vin.vout index is undefined, continue ...
508
- if (!prevout.vout[vin.vout]) {
509
- continue;
510
- }
511
-
512
- // Get the address from the scriptPubKey from the prevvout (previous output's input at the vout index)
513
- const scriptPubKey = prevout.vout[vin.vout].scriptPubKey;
514
-
515
- // If the scriptPubKey.address is undefined, continue ...
516
- if (!scriptPubKey.address) {
517
- continue;
518
- }
519
-
520
- // If the beaconAddress from prevvout scriptPubKey is not a beacon service endpoint address, continue ...
521
- const beaconService = BeaconUtils.getBeaconServicesMap(beaconServices).get(scriptPubKey.address);
522
- if (!beaconService) {
523
- continue;
524
- }
525
-
526
- /**
527
- * Look for 'OP_RETURN' in prevout.vout[vin.vout].scriptPubKey.asm, else continue ...
528
- *
529
- * TxOut (rpc) format:
530
- * {
531
- * value: 0,
532
- * n: 1,
533
- * scriptPubKey: {
534
- * asm: 'OP_RETURN 570f177c65e64fb5cf61180b664cdddf09ab76153c2b192e22006e5b22a3917a',
535
- * desc: 'raw(6a20570f177c65e64fb5cf61180b664cdddf09ab76153c2b192e22006e5b22a3917a)#cdgj3pm4',
536
- * hex: '6a20570f177c65e64fb5cf61180b664cdddf09ab76153c2b192e22006e5b22a3917a',
537
- * type: 'nulldata'
538
- * }
539
- * }
540
- */
541
- const txVoutScriptPubkeyAsm = prevout.vout[vin.vout].scriptPubKey.asm;
542
- if(!txVoutScriptPubkeyAsm.includes('OP_RETURN')) {
543
- continue;
544
- }
545
-
546
- // Log the found txid and beacon
547
- console.info(`Tx ${tx.txid} contains beacon service address ${scriptPubKey.address} and OP_RETURN!`, tx);
548
-
549
- // Extract hex string hash of the signal bytes from the scriptpubkey
550
- const updateHash = txVoutScriptPubkeyAsm.split(' ').slice(-1)[0];
551
- if(!updateHash) {
552
- continue;
553
- }
554
-
555
- // Push the beacon signal object to the beacon signals array for that beacon service
556
- beaconServiceSignals.get(beaconService)?.push({
557
- tx,
558
- signalBytes : updateHash,
559
- blockMetadata : {
560
- height : block.height,
561
- time : block.time,
562
- confirmations : block.confirmations
563
- }
564
- });
565
- };
566
- }
567
-
568
- // Increment the height
569
- height += 1;
570
-
571
- // Check if we've reached the chain tip
572
- const tip = await rpc.getBlockCount();
573
- if(height > tip) {
574
- // If so, break the loop
575
- console.info(`Chain tip reached ${height}, breaking ...`);
576
- break;
577
- }
578
-
579
- // Reset the block var to the next block data
580
- block = await rpc.getBlock({ height }) as BlockV3;
581
- }
582
-
583
- return beaconServiceSignals;
584
- }
585
-
586
347
  /**
587
348
  * Implements subsection {@link https://dcdpr.github.io/did-btcr2/#confirm-duplicate-update | 7.2.f.1 Confirm Duplicate Update}.
588
349
  * This step confirms that an update with a lower-than-expected targetVersionId is a true duplicate.
589
- * @param {BTCR2SignedUpdate} update The BTCR2 Signed Update to confirm as a duplicate.
350
+ * @param {SignedBTCR2Update} update The BTCR2 Signed Update to confirm as a duplicate.
590
351
  * @returns {void} Does not return a value, but throws an error if the update is not a valid duplicate.
591
352
  */
592
- static confirmDuplicateUpdate(update: BTCR2SignedUpdate, updateHashHistory: string[]): void {
353
+ static confirmDuplicate(update: SignedBTCR2Update, updateHashHistory: string[]): void {
593
354
  // Create unsigned_update by removing the proof property from update.
594
- const unsignedUpdate = JSONUtils.deleteKeys(update, ['proof']) as BTCR2UnsignedUpdate;
355
+ const unsignedUpdate = JSONUtils.deleteKeys(update, ['proof']) as UnsignedBTCR2Update;
595
356
 
596
357
  // Hash unsignedUpdate with JSON Document Hashing algorithm
597
358
  const unsignedUpdateHash = canonicalization.process(unsignedUpdate);
@@ -611,13 +372,13 @@ export class Resolve {
611
372
  /**
612
373
  * Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#apply-update | 7.2.f.3 Apply Update}.
613
374
  * @param {DidDocument} currentDocument The current DID Document to apply the update to.
614
- * @param {BTCR2SignedUpdate} update The BTCR2 Signed Update to apply.
375
+ * @param {SignedBTCR2Update} update The BTCR2 Signed Update to apply.
615
376
  * @returns {Promise<DidDocument>} The updated DID Document after applying the update.
616
377
  * @throws {ResolveError} If the update is invalid or cannot be applied.
617
378
  */
618
- static async applyDidUpdate(
379
+ static async applyUpdate(
619
380
  currentDocument: DidDocument,
620
- update: BTCR2SignedUpdate
381
+ update: SignedBTCR2Update
621
382
  ): Promise<DidDocument> {
622
383
  // Get the capability id from the to update proof.
623
384
  const capabilityId = update.proof?.capability;
@@ -652,13 +413,8 @@ export class Resolve {
652
413
  // Get the verificationMethod from the DID Document using the verificationMethodId.
653
414
  const vm = DidBtcr2.getSigningMethod(currentDocument, verificationMethodId);
654
415
 
655
- // Split the vmId by the `#` to get the id and controller.
656
- const [vmController, vmId] = vm.id.split('#');
657
-
658
416
  // Construct a new SchnorrMultikey.
659
- const multikey = SchnorrMultikey.fromPublicKeyMultibase(
660
- `#${vmId}`, vmController, vm.publicKeyMultibase
661
- );
417
+ const multikey = SchnorrMultikey.fromVerificationMethod(vm);
662
418
 
663
419
  // Construct a new BIP340Cryptosuite with the SchnorrMultikey.
664
420
  const cryptosuite = new BIP340Cryptosuite(multikey);
@@ -674,7 +430,7 @@ export class Resolve {
674
430
 
675
431
  // If the result is not verified, throw INVALID_DID_UPDATE error
676
432
  if (!verificationResult.verified) {
677
- throw new MethodError(
433
+ throw new ResolveError(
678
434
  'Invalid update: proof not verified',
679
435
  INVALID_DID_UPDATE, verificationResult
680
436
  );
@@ -695,7 +451,7 @@ export class Resolve {
695
451
  // Make sure the update.targetHash equals currentDocumentHash.
696
452
  if (updateTargetHash !== currentDocumentHash) {
697
453
  // If they do not match, throw INVALID_DID_UPDATE error.
698
- throw new MethodError(
454
+ throw new ResolveError(
699
455
  `Invalid update: updateTargetHash !== currentDocumentHash`,
700
456
  INVALID_DID_UPDATE, { updateTargetHash, currentDocumentHash }
701
457
  );
package/src/core/types.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { HexString } from '@did-btcr2/common';
2
- import { BTCR2SignedUpdate } from '@did-btcr2/cryptosuite';
2
+ import { SignedBTCR2Update } from '@did-btcr2/cryptosuite';
3
3
  import { SMTProof } from './interfaces.js';
4
4
 
5
5
  /**
@@ -41,7 +41,7 @@ export type Sidecar = {
41
41
  * Optional array of BTCR2 Signed Updates. Required if the DID being resolved
42
42
  * has ever had a published BTCR2 Update.
43
43
  */
44
- updates?: Array<BTCR2SignedUpdate>
44
+ updates?: Array<SignedBTCR2Update>
45
45
 
46
46
  /**
47
47
  * Optional array of CAS Announcements. Required if the DID being reslved has
@@ -56,23 +56,40 @@ export type Sidecar = {
56
56
  smtProofs?: Array<SMTProof>;
57
57
  };
58
58
 
59
+ /**
60
+ * The Sidecar data structure used for Singleton Beacons.
61
+ */
62
+ export type SingletonBeaconSidecarData = Map<HexString, SignedBTCR2Update>;
63
+ /**
64
+ * The Sidecar data structure used for CAS Beacons.
65
+ */
66
+ export type CASBeaconSidecarData = Map<HexString, CASAnnouncement>;
67
+ /**
68
+ * The Sidecar data structure used for SMT Beacons.
69
+ */
70
+ export type SMTBeaconSidecarData = Map<string, SMTProof>;
71
+
59
72
  /**
60
73
  * The Sidecar data structure post-processing used for resolution.
61
74
  */
62
75
  export type SidecarData = {
63
76
  /**
64
77
  * Map of BTCR2 Signed Updates by their hash bytes.
65
- * @type {Map<HexString, BTCR2SignedUpdate>}
66
78
  */
67
- updateMap: Map<HexString, BTCR2SignedUpdate>;
79
+ updateMap: SingletonBeaconSidecarData;
80
+
68
81
  /**
69
82
  * Map of CAS Announcements by their hash bytes.
70
- * @type {Map<HexString, CASAnnouncement>}
71
83
  */
72
- casMap: Map<HexString, CASAnnouncement>;
84
+ casMap: CASBeaconSidecarData;
85
+
73
86
  /**
74
87
  * Map of SMT Proofs by their ID.
75
- * @type {Map<string, SMTProof>}
76
88
  */
77
- smtMap: Map<string, SMTProof>;
89
+ smtMap: SMTBeaconSidecarData;
78
90
  }
91
+
92
+ /**
93
+ * Union type for all Beacon Sidecar data structures.
94
+ */
95
+ export type BeaconSidecarData = SingletonBeaconSidecarData | CASBeaconSidecarData | SMTBeaconSidecarData;