@did-btcr2/method 0.61.0 → 0.63.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 +6 -0
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +3 -3
- package/dist/browser.mjs +3 -3
- package/dist/cjs/index.js +213 -151
- package/dist/esm/core/beacon/signal-discovery.js +34 -1
- package/dist/esm/core/beacon/signal-discovery.js.map +1 -1
- package/dist/esm/core/identifier.js +12 -2
- package/dist/esm/core/identifier.js.map +1 -1
- package/dist/esm/core/resolver.js +209 -197
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/types/core/beacon/interfaces.d.ts +9 -1
- package/dist/types/core/beacon/interfaces.d.ts.map +1 -1
- package/dist/types/core/beacon/signal-discovery.d.ts +12 -0
- package/dist/types/core/beacon/signal-discovery.d.ts.map +1 -1
- package/dist/types/core/identifier.d.ts.map +1 -1
- package/dist/types/core/interfaces.d.ts +16 -5
- package/dist/types/core/interfaces.d.ts.map +1 -1
- package/dist/types/core/resolver.d.ts +19 -27
- package/dist/types/core/resolver.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/core/beacon/interfaces.ts +10 -1
- package/src/core/beacon/signal-discovery.ts +44 -1
- package/src/core/identifier.ts +14 -2
- package/src/core/interfaces.ts +16 -5
- package/src/core/resolver.ts +268 -249
package/dist/cjs/index.js
CHANGED
|
@@ -1370,7 +1370,18 @@ var Identifier = class _Identifier {
|
|
|
1370
1370
|
if (encoded !== encoded.toLowerCase()) {
|
|
1371
1371
|
throw new import_common8.IdentifierError(`Invalid method-specific id (must be lowercase): ${identifier}`, import_common8.INVALID_DID, { identifier });
|
|
1372
1372
|
}
|
|
1373
|
-
|
|
1373
|
+
let hrp;
|
|
1374
|
+
let dataBytes;
|
|
1375
|
+
try {
|
|
1376
|
+
({ prefix: hrp, bytes: dataBytes } = import_base.bech32m.decodeToBytes(encoded));
|
|
1377
|
+
} catch (error) {
|
|
1378
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
1379
|
+
throw new import_common8.IdentifierError(
|
|
1380
|
+
`Invalid method-specific id (Bech32m decoding failed: ${reason}): ${identifier}`,
|
|
1381
|
+
import_common8.INVALID_DID,
|
|
1382
|
+
{ identifier, reason }
|
|
1383
|
+
);
|
|
1384
|
+
}
|
|
1374
1385
|
if (!["x", "k"].includes(hrp)) {
|
|
1375
1386
|
throw new import_common8.IdentifierError(`Invalid hrp: ${hrp}`, import_common8.INVALID_DID, { identifier });
|
|
1376
1387
|
}
|
|
@@ -1803,6 +1814,7 @@ var BeaconSignalDiscovery = class _BeaconSignalDiscovery {
|
|
|
1803
1814
|
static async indexer(beaconServices, bitcoin) {
|
|
1804
1815
|
const beaconServiceSignals = /* @__PURE__ */ new Map();
|
|
1805
1816
|
const currentBlockCount = await bitcoin.rest.block.count();
|
|
1817
|
+
const mediantimes = /* @__PURE__ */ new Map();
|
|
1806
1818
|
for (const beaconService of beaconServices) {
|
|
1807
1819
|
beaconServiceSignals.set(beaconService, []);
|
|
1808
1820
|
const beaconAddress = BeaconUtils.parseBitcoinAddress(beaconService.serviceEndpoint);
|
|
@@ -1827,19 +1839,49 @@ var BeaconSignalDiscovery = class _BeaconSignalDiscovery {
|
|
|
1827
1839
|
continue;
|
|
1828
1840
|
}
|
|
1829
1841
|
const confirmations = currentBlockCount - status.block_height + 1;
|
|
1842
|
+
const mediantime = await _BeaconSignalDiscovery.mediantime(status.block_hash, bitcoin, mediantimes);
|
|
1830
1843
|
beaconServiceSignals.get(beaconService)?.push({
|
|
1831
1844
|
tx: beaconSignal,
|
|
1832
1845
|
signalBytes: updateHash,
|
|
1833
1846
|
blockMetadata: {
|
|
1834
1847
|
confirmations,
|
|
1835
1848
|
height: status.block_height,
|
|
1836
|
-
time: status.block_time
|
|
1849
|
+
time: status.block_time,
|
|
1850
|
+
mediantime
|
|
1837
1851
|
}
|
|
1838
1852
|
});
|
|
1839
1853
|
}
|
|
1840
1854
|
}
|
|
1841
1855
|
return beaconServiceSignals;
|
|
1842
1856
|
}
|
|
1857
|
+
/**
|
|
1858
|
+
* Return the median time past of a block, from the per-run cache or from the
|
|
1859
|
+
* Esplora block record (`GET /block/:hash`). The specification compares
|
|
1860
|
+
* `versionTime` with the block `mediantime`, and the address listing does not
|
|
1861
|
+
* carry it. One block record serves every signal in that block.
|
|
1862
|
+
* @param {string} blockhash The hash of the block that contains a signal.
|
|
1863
|
+
* @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls.
|
|
1864
|
+
* @param {Map<string, number>} cache The median time past values fetched so far, keyed by block hash.
|
|
1865
|
+
* @returns {Promise<number>} The median time past of the block, in Unix seconds.
|
|
1866
|
+
* @throws {ResolveError} `INTERNAL_ERROR` if the backend returns no block record or no `mediantime` for the hash.
|
|
1867
|
+
*/
|
|
1868
|
+
static async mediantime(blockhash, bitcoin, cache) {
|
|
1869
|
+
const cached = cache.get(blockhash);
|
|
1870
|
+
if (cached !== void 0) {
|
|
1871
|
+
return cached;
|
|
1872
|
+
}
|
|
1873
|
+
const block = await bitcoin.rest.block.get({ blockhash });
|
|
1874
|
+
const mediantime = block?.mediantime;
|
|
1875
|
+
if (typeof mediantime !== "number" || !Number.isFinite(mediantime)) {
|
|
1876
|
+
throw new import_common10.ResolveError(
|
|
1877
|
+
`Block ${blockhash} has no mediantime in the block record of the Bitcoin REST backend.`,
|
|
1878
|
+
import_common10.INTERNAL_ERROR,
|
|
1879
|
+
{ blockhash, block }
|
|
1880
|
+
);
|
|
1881
|
+
}
|
|
1882
|
+
cache.set(blockhash, mediantime);
|
|
1883
|
+
return mediantime;
|
|
1884
|
+
}
|
|
1843
1885
|
/**
|
|
1844
1886
|
* Traverse the full blockchain from genesis to chain top looking for beacon signals.
|
|
1845
1887
|
* @param {Array<BeaconService>} beaconServices Array of BeaconService objects to search for signals.
|
|
@@ -1909,6 +1951,7 @@ var BeaconSignalDiscovery = class _BeaconSignalDiscovery {
|
|
|
1909
1951
|
blockMetadata: {
|
|
1910
1952
|
height: block.height,
|
|
1911
1953
|
time: block.time,
|
|
1954
|
+
mediantime: block.mediantime,
|
|
1912
1955
|
confirmations: block.confirmations
|
|
1913
1956
|
}
|
|
1914
1957
|
});
|
|
@@ -2428,17 +2471,46 @@ function isSMTProof(value) {
|
|
|
2428
2471
|
function validateMinConf(value) {
|
|
2429
2472
|
if (value === void 0) return DEFAULT_MIN_CONF;
|
|
2430
2473
|
if (typeof value === "number" && Number.isInteger(value) && value >= 1) return value;
|
|
2431
|
-
const shown = typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2432
2474
|
throw new import_common13.ResolveError(
|
|
2433
|
-
`Invalid resolution option minConf: expected a positive integer (minimum 1), got ${shown}.`,
|
|
2475
|
+
`Invalid resolution option minConf: expected a positive integer (minimum 1), got ${shown(value)}.`,
|
|
2434
2476
|
import_common13.INVALID_OPTIONS,
|
|
2435
2477
|
{ minConf: value }
|
|
2436
2478
|
);
|
|
2437
2479
|
}
|
|
2480
|
+
function shown(value) {
|
|
2481
|
+
return typeof value === "string" ? JSON.stringify(value) : String(value);
|
|
2482
|
+
}
|
|
2483
|
+
var ASCII_INTEGER = /^-?[0-9]+$/;
|
|
2484
|
+
function validateVersionId(value) {
|
|
2485
|
+
if (value === void 0) return void 0;
|
|
2486
|
+
if (typeof value === "string" && ASCII_INTEGER.test(value) && Number.isSafeInteger(Number(value))) {
|
|
2487
|
+
return Number(value);
|
|
2488
|
+
}
|
|
2489
|
+
throw new import_common13.ResolveError(
|
|
2490
|
+
`Invalid resolution option versionId: expected an ASCII string of an integer, got ${shown(value)}.`,
|
|
2491
|
+
import_common13.INVALID_OPTIONS,
|
|
2492
|
+
{ versionId: value }
|
|
2493
|
+
);
|
|
2494
|
+
}
|
|
2495
|
+
var UTC_XSD_DATETIME = /^-?\d{4,}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}Z$/;
|
|
2496
|
+
function validateVersionTime(value) {
|
|
2497
|
+
if (value === void 0) return void 0;
|
|
2498
|
+
if (typeof value === "string" && UTC_XSD_DATETIME.test(value) && import_common13.DateUtils.isValidXsdDateTime(value)) {
|
|
2499
|
+
const ms = Date.parse(value);
|
|
2500
|
+
if (Number.isFinite(ms)) return ms;
|
|
2501
|
+
}
|
|
2502
|
+
throw new import_common13.ResolveError(
|
|
2503
|
+
`Invalid resolution option versionTime: expected an XML Datetime in UTC without a fraction (for example "2026-07-01T00:00:00Z"), got ${shown(value)}.`,
|
|
2504
|
+
import_common13.INVALID_OPTIONS,
|
|
2505
|
+
{ versionTime: value }
|
|
2506
|
+
);
|
|
2507
|
+
}
|
|
2438
2508
|
var Resolver = class _Resolver {
|
|
2439
2509
|
// --- Immutable inputs ---
|
|
2440
2510
|
#didComponents;
|
|
2511
|
+
/** The parsed `ResolutionOptions.versionId`, or `undefined` when the option is absent. */
|
|
2441
2512
|
#versionId;
|
|
2513
|
+
/** The parsed `ResolutionOptions.versionTime` in milliseconds since the Unix epoch, or `undefined`. */
|
|
2442
2514
|
#versionTime;
|
|
2443
2515
|
/**
|
|
2444
2516
|
* The specific phase the Resolver is current in.
|
|
@@ -2449,22 +2521,28 @@ var Resolver = class _Resolver {
|
|
|
2449
2521
|
#providedGenesisDocument = null;
|
|
2450
2522
|
#beaconServicesSignals = /* @__PURE__ */ new Map();
|
|
2451
2523
|
#processedServices = /* @__PURE__ */ new Set();
|
|
2524
|
+
/** The beacon addresses the resolver requested signals for: `scanned_beacons` of the specification. */
|
|
2452
2525
|
#requestCache = /* @__PURE__ */ new Set();
|
|
2526
|
+
/**
|
|
2527
|
+
* The tuples of the specification's `updates` list: a signed update and the metadata of
|
|
2528
|
+
* the block that announced it. BeaconProcess appends; ProcessUpdate sorts the list and
|
|
2529
|
+
* removes one tuple per step. A tuple that one pass does not reach waits for the next.
|
|
2530
|
+
*/
|
|
2453
2531
|
#unsortedUpdates = [];
|
|
2454
2532
|
#resolvedResponse = null;
|
|
2455
2533
|
/**
|
|
2456
|
-
*
|
|
2457
|
-
*
|
|
2458
|
-
*
|
|
2459
|
-
*
|
|
2460
|
-
*
|
|
2461
|
-
*
|
|
2462
|
-
*
|
|
2463
|
-
* announced on beacons that earlier updates added: round two would forget it had
|
|
2464
|
-
* already reached version two, see version three, and raise a late-publishing error.
|
|
2534
|
+
* The state of the specification loop, carried across every pass: the version counter
|
|
2535
|
+
* (`current_version_id`), the update-hash history that backs duplicate confirmation
|
|
2536
|
+
* (`update_hash_history`), the confirmations of the block that contains the most
|
|
2537
|
+
* recently applied unique update (`block_confirmations`), and the header time of that
|
|
2538
|
+
* block as `updated`. A pass that finds a new beacon address returns to discovery, so
|
|
2539
|
+
* the state must not restart: a restart would reject a linear history whose later
|
|
2540
|
+
* updates are announced on beacons that earlier updates added.
|
|
2465
2541
|
*/
|
|
2466
2542
|
#currentVersionId = 1;
|
|
2467
2543
|
#updateHashHistory = [];
|
|
2544
|
+
#blockConfirmations = 0;
|
|
2545
|
+
#updated;
|
|
2468
2546
|
/**
|
|
2469
2547
|
* Opt-in upper bound on multi-round beacon-discovery passes. `Infinity` (the
|
|
2470
2548
|
* default) leaves discovery unbounded; termination is already guaranteed by
|
|
@@ -2489,8 +2567,15 @@ var Resolver = class _Resolver {
|
|
|
2489
2567
|
this.#didComponents = didComponents;
|
|
2490
2568
|
this.#sidecarData = sidecarData;
|
|
2491
2569
|
this.#currentDocument = currentDocument;
|
|
2492
|
-
|
|
2493
|
-
|
|
2570
|
+
if (options?.versionId !== void 0 && options?.versionTime !== void 0) {
|
|
2571
|
+
throw new import_common13.ResolveError(
|
|
2572
|
+
"Invalid resolution options: versionId and versionTime are mutually exclusive. Pass one of them.",
|
|
2573
|
+
import_common13.INVALID_OPTIONS,
|
|
2574
|
+
{ versionId: options.versionId, versionTime: options.versionTime }
|
|
2575
|
+
);
|
|
2576
|
+
}
|
|
2577
|
+
this.#versionId = validateVersionId(options?.versionId);
|
|
2578
|
+
this.#versionTime = validateVersionTime(options?.versionTime);
|
|
2494
2579
|
const rounds = options?.maxDiscoveryRounds;
|
|
2495
2580
|
this.#maxDiscoveryRounds = typeof rounds === "number" && rounds > 0 ? rounds : Infinity;
|
|
2496
2581
|
this.#minConf = validateMinConf(options?.minConf);
|
|
@@ -2530,14 +2615,14 @@ var Resolver = class _Resolver {
|
|
|
2530
2615
|
* @param {DidComponents} didComponents BTCR2 DID components used to resolve the DID Document
|
|
2531
2616
|
* @param {object} genesisDocument The genesis document for resolving the DID Document.
|
|
2532
2617
|
* @returns {DidDocument} The resolved DID Document object
|
|
2533
|
-
* @throws {ResolveError}
|
|
2618
|
+
* @throws {ResolveError} `INVALID_DID` if the hash of the genesis document is not the genesis bytes of the identifier
|
|
2534
2619
|
*/
|
|
2535
2620
|
static external(didComponents, genesisDocument) {
|
|
2536
2621
|
const genesisDocumentHash = (0, import_common13.canonicalHashBytes)(genesisDocument);
|
|
2537
2622
|
if (!(0, import_utils7.equalBytes)(didComponents.genesisBytes, genesisDocumentHash)) {
|
|
2538
2623
|
throw new import_common13.ResolveError(
|
|
2539
2624
|
`Initial document mismatch: genesisBytes !== genesisDocumentHash`,
|
|
2540
|
-
import_common13.
|
|
2625
|
+
import_common13.INVALID_DID,
|
|
2541
2626
|
{
|
|
2542
2627
|
genesisBytes: (0, import_common13.encode)(didComponents.genesisBytes, "hex"),
|
|
2543
2628
|
genesisDocumentHash: (0, import_common13.encode)(genesisDocumentHash, "hex")
|
|
@@ -2574,89 +2659,7 @@ var Resolver = class _Resolver {
|
|
|
2574
2659
|
return { updateMap, casMap, smtMap };
|
|
2575
2660
|
}
|
|
2576
2661
|
/**
|
|
2577
|
-
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#
|
|
2578
|
-
* @param {DidDocument} currentDocument The current DID Document to apply the updates to.
|
|
2579
|
-
* @param {Array<[SignedBTCR2Update, BlockMetadata]>} unsortedUpdates The unsorted array of BTCR2 Signed Updates and their associated Block Metadata.
|
|
2580
|
-
* @param {string} [versionTime] The optional version time to limit updates to.
|
|
2581
|
-
* @param {string} [versionId] The optional version id to limit updates to.
|
|
2582
|
-
* @param {{ currentVersionId: number; updateHashHistory: HashBytes[] }} [resolutionState]
|
|
2583
|
-
* Version counter and update-hash history carried from earlier discovery rounds.
|
|
2584
|
-
* Standalone callers omit it and start fresh at version 1 with an empty history.
|
|
2585
|
-
* @returns {DidResolutionResponse} The updated DID Document, number of confirmations, and version id.
|
|
2586
|
-
*
|
|
2587
|
-
* Confirmation depth is not checked here. The BeaconProcess phase excludes a
|
|
2588
|
-
* signal below `ResolutionOptions.minConf` before its update reaches this method,
|
|
2589
|
-
* so every tuple here comes from a block at or above the threshold.
|
|
2590
|
-
*/
|
|
2591
|
-
static updates(currentDocument, unsortedUpdates, versionTime, versionId, resolutionState = { currentVersionId: 1, updateHashHistory: [] }) {
|
|
2592
|
-
let currentVersionId = resolutionState.currentVersionId;
|
|
2593
|
-
const updateHashHistory = resolutionState.updateHashHistory;
|
|
2594
|
-
const updates = unsortedUpdates.sort(
|
|
2595
|
-
([upd0, blk0], [upd1, blk1]) => upd0.targetVersionId - upd1.targetVersionId || blk0.height - blk1.height
|
|
2596
|
-
);
|
|
2597
|
-
const response = {
|
|
2598
|
-
didDocument: currentDocument,
|
|
2599
|
-
metadata: {
|
|
2600
|
-
versionId: `${currentVersionId}`,
|
|
2601
|
-
confirmations: 0,
|
|
2602
|
-
updated: "",
|
|
2603
|
-
deactivated: currentDocument.deactivated || false
|
|
2604
|
-
}
|
|
2605
|
-
};
|
|
2606
|
-
for (const [update, block] of updates) {
|
|
2607
|
-
const currentDocumentHash = (0, import_common13.canonicalHashBytes)(response.didDocument);
|
|
2608
|
-
const blocktime = import_common13.DateUtils.blocktimeToTimestamp(block.time);
|
|
2609
|
-
response.metadata.updated = import_common13.DateUtils.toISOStringNonFractional(blocktime);
|
|
2610
|
-
response.metadata.confirmations = block.confirmations;
|
|
2611
|
-
if (update.targetVersionId <= currentVersionId) {
|
|
2612
|
-
this.confirmDuplicate(update, updateHashHistory);
|
|
2613
|
-
continue;
|
|
2614
|
-
}
|
|
2615
|
-
if (versionTime) {
|
|
2616
|
-
if (blocktime > import_common13.DateUtils.dateStringToTimestamp(versionTime)) {
|
|
2617
|
-
return response;
|
|
2618
|
-
}
|
|
2619
|
-
}
|
|
2620
|
-
if (update.targetVersionId === currentVersionId + 1) {
|
|
2621
|
-
const sourceHashBytes = (0, import_common13.decode)(update.sourceHash, "base64urlnopad");
|
|
2622
|
-
if (!(0, import_utils7.equalBytes)(sourceHashBytes, currentDocumentHash)) {
|
|
2623
|
-
throw new import_common13.ResolveError(
|
|
2624
|
-
`Hash mismatch: update.sourceHash !== currentDocumentHash`,
|
|
2625
|
-
import_common13.INVALID_DID_UPDATE,
|
|
2626
|
-
{
|
|
2627
|
-
sourceHash: update.sourceHash,
|
|
2628
|
-
currentDocumentHash: (0, import_common13.encode)(currentDocumentHash, "hex")
|
|
2629
|
-
}
|
|
2630
|
-
);
|
|
2631
|
-
}
|
|
2632
|
-
response.didDocument = this.applyUpdate(response.didDocument, update);
|
|
2633
|
-
const unsignedUpdate = import_common13.JSONUtils.deleteKeys(update, ["proof"]);
|
|
2634
|
-
updateHashHistory.push((0, import_common13.canonicalHashBytes)(unsignedUpdate));
|
|
2635
|
-
} else {
|
|
2636
|
-
throw new import_common13.ResolveError(
|
|
2637
|
-
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
2638
|
-
import_common13.LATE_PUBLISHING_ERROR,
|
|
2639
|
-
{
|
|
2640
|
-
targetVersionId: update.targetVersionId,
|
|
2641
|
-
currentVersionId: currentVersionId + 1
|
|
2642
|
-
}
|
|
2643
|
-
);
|
|
2644
|
-
}
|
|
2645
|
-
currentVersionId++;
|
|
2646
|
-
response.metadata.versionId = `${currentVersionId}`;
|
|
2647
|
-
const versionIdNumber = Number(versionId);
|
|
2648
|
-
if (!isNaN(versionIdNumber) && versionIdNumber <= currentVersionId) {
|
|
2649
|
-
return response;
|
|
2650
|
-
}
|
|
2651
|
-
if (response.didDocument.deactivated) {
|
|
2652
|
-
response.metadata.deactivated = response.didDocument.deactivated;
|
|
2653
|
-
return response;
|
|
2654
|
-
}
|
|
2655
|
-
}
|
|
2656
|
-
return response;
|
|
2657
|
-
}
|
|
2658
|
-
/**
|
|
2659
|
-
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/#confirm-duplicate-update | 7.2.f.1 Confirm Duplicate Update}.
|
|
2662
|
+
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#confirm-duplicate-update | Confirm Duplicate Update}.
|
|
2660
2663
|
* This step confirms that an update with a lower-than-expected targetVersionId is a true duplicate.
|
|
2661
2664
|
* @param {SignedBTCR2Update} update The BTCR2 Signed Update to confirm as a duplicate.
|
|
2662
2665
|
* @param {HashBytes[]} updateHashHistory The accumulated hash history for comparison.
|
|
@@ -2695,14 +2698,26 @@ var Resolver = class _Resolver {
|
|
|
2695
2698
|
}
|
|
2696
2699
|
}
|
|
2697
2700
|
/**
|
|
2698
|
-
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#apply-update |
|
|
2701
|
+
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#apply-update | Apply update}
|
|
2699
2702
|
* and its step {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#check-update-proof | Check update.proof}.
|
|
2700
2703
|
* @param {DidDocument} currentDocument The current DID Document to apply the update to.
|
|
2701
2704
|
* @param {SignedBTCR2Update} update The BTCR2 Signed Update to apply.
|
|
2702
2705
|
* @returns {DidDocument} The updated DID Document after applying the update.
|
|
2703
|
-
* @throws {ResolveError}
|
|
2706
|
+
* @throws {ResolveError} `INVALID_DID_UPDATE` if the update is invalid or cannot be applied.
|
|
2704
2707
|
*/
|
|
2705
2708
|
static applyUpdate(currentDocument, update) {
|
|
2709
|
+
const currentDocumentHash = (0, import_common13.canonicalHashBytes)(currentDocument);
|
|
2710
|
+
const sourceHashBytes = (0, import_common13.decode)(update.sourceHash, "base64urlnopad");
|
|
2711
|
+
if (!(0, import_utils7.equalBytes)(sourceHashBytes, currentDocumentHash)) {
|
|
2712
|
+
throw new import_common13.ResolveError(
|
|
2713
|
+
`Hash mismatch: update.sourceHash !== currentDocumentHash`,
|
|
2714
|
+
import_common13.INVALID_DID_UPDATE,
|
|
2715
|
+
{
|
|
2716
|
+
sourceHash: update.sourceHash,
|
|
2717
|
+
currentDocumentHash: (0, import_common13.encode)(currentDocumentHash, "hex")
|
|
2718
|
+
}
|
|
2719
|
+
);
|
|
2720
|
+
}
|
|
2706
2721
|
if (!isBtcr2UpdateContext(update["@context"])) {
|
|
2707
2722
|
throw new import_common13.ResolveError(
|
|
2708
2723
|
"Invalid update: @context is not the array the specification pins for a BTCR2 Update",
|
|
@@ -2763,13 +2778,13 @@ var Resolver = class _Resolver {
|
|
|
2763
2778
|
}
|
|
2764
2779
|
const updatedDocument = import_common13.JSONPatch.apply(currentDocument, update.patch);
|
|
2765
2780
|
DidDocument.validate(updatedDocument);
|
|
2766
|
-
const
|
|
2781
|
+
const updatedDocumentHash = (0, import_common13.canonicalHashBytes)(updatedDocument);
|
|
2767
2782
|
const updateTargetHash = (0, import_common13.decode)(update.targetHash);
|
|
2768
|
-
if (!(0, import_utils7.equalBytes)(updateTargetHash,
|
|
2783
|
+
if (!(0, import_utils7.equalBytes)(updateTargetHash, updatedDocumentHash)) {
|
|
2769
2784
|
throw new import_common13.ResolveError(
|
|
2770
|
-
`Invalid update: update.targetHash !==
|
|
2785
|
+
`Invalid update: update.targetHash !== updatedDocumentHash`,
|
|
2771
2786
|
import_common13.INVALID_DID_UPDATE,
|
|
2772
|
-
{ updateTargetHash,
|
|
2787
|
+
{ updateTargetHash, updatedDocumentHash }
|
|
2773
2788
|
);
|
|
2774
2789
|
}
|
|
2775
2790
|
return updatedDocument;
|
|
@@ -2843,59 +2858,99 @@ var Resolver = class _Resolver {
|
|
|
2843
2858
|
if (allNeeds.length > 0) {
|
|
2844
2859
|
return { status: "action-required", needs: allNeeds };
|
|
2845
2860
|
}
|
|
2846
|
-
this.#phase = "
|
|
2861
|
+
this.#phase = "ProcessUpdate" /* ProcessUpdate */;
|
|
2847
2862
|
continue;
|
|
2848
2863
|
}
|
|
2849
|
-
// Phase:
|
|
2850
|
-
//
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
2854
|
-
|
|
2855
|
-
|
|
2856
|
-
|
|
2857
|
-
|
|
2858
|
-
|
|
2859
|
-
|
|
2860
|
-
|
|
2861
|
-
|
|
2862
|
-
|
|
2863
|
-
|
|
2864
|
-
|
|
2865
|
-
|
|
2866
|
-
|
|
2867
|
-
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2864
|
+
// Phase: ProcessUpdate
|
|
2865
|
+
// Spec "Process Next Update": one tuple per step. The phase repeats until
|
|
2866
|
+
// the document resolves, or until an applied update adds a beacon address
|
|
2867
|
+
// that the resolver did not scan (then the pass returns to BeaconDiscovery).
|
|
2868
|
+
case "ProcessUpdate" /* ProcessUpdate */: {
|
|
2869
|
+
const document = this.#currentDocument;
|
|
2870
|
+
if (this.#versionId !== void 0 && this.#currentVersionId === this.#versionId) {
|
|
2871
|
+
this.#phase = "Complete" /* Complete */;
|
|
2872
|
+
continue;
|
|
2873
|
+
}
|
|
2874
|
+
if (this.#unsortedUpdates.length === 0 || document.deactivated) {
|
|
2875
|
+
if (this.#versionId !== void 0) {
|
|
2876
|
+
throw new import_common13.ResolveError(
|
|
2877
|
+
`Version ${this.#versionId} of the DID does not exist: the history ` + (document.deactivated ? `ends with the deactivation at version ${this.#currentVersionId}.` : `ends at version ${this.#currentVersionId}.`),
|
|
2878
|
+
import_common13.NOT_FOUND,
|
|
2879
|
+
{ versionId: this.#versionId, currentVersionId: this.#currentVersionId }
|
|
2880
|
+
);
|
|
2881
|
+
}
|
|
2882
|
+
this.#phase = "Complete" /* Complete */;
|
|
2883
|
+
continue;
|
|
2884
|
+
}
|
|
2885
|
+
this.#unsortedUpdates.sort(
|
|
2886
|
+
([upd0, blk0], [upd1, blk1]) => upd0.targetVersionId - upd1.targetVersionId || blk0.height - blk1.height
|
|
2887
|
+
);
|
|
2888
|
+
const [update, block] = this.#unsortedUpdates.shift();
|
|
2889
|
+
if (update.targetVersionId <= this.#currentVersionId) {
|
|
2890
|
+
_Resolver.confirmDuplicate(update, this.#updateHashHistory);
|
|
2891
|
+
continue;
|
|
2892
|
+
}
|
|
2893
|
+
if (this.#versionTime !== void 0 && block.mediantime * 1e3 > this.#versionTime) {
|
|
2894
|
+
this.#phase = "Complete" /* Complete */;
|
|
2895
|
+
continue;
|
|
2896
|
+
}
|
|
2897
|
+
if (update.targetVersionId !== this.#currentVersionId + 1) {
|
|
2898
|
+
throw new import_common13.ResolveError(
|
|
2899
|
+
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
2900
|
+
import_common13.LATE_PUBLISHING_ERROR,
|
|
2901
|
+
{
|
|
2902
|
+
targetVersionId: update.targetVersionId,
|
|
2903
|
+
currentVersionId: this.#currentVersionId + 1
|
|
2875
2904
|
}
|
|
2876
|
-
|
|
2877
|
-
|
|
2905
|
+
);
|
|
2906
|
+
}
|
|
2907
|
+
this.#currentDocument = _Resolver.applyUpdate(document, update);
|
|
2908
|
+
const unsignedUpdate = import_common13.JSONUtils.deleteKeys(update, ["proof"]);
|
|
2909
|
+
this.#updateHashHistory.push((0, import_common13.canonicalHashBytes)(unsignedUpdate));
|
|
2910
|
+
this.#currentVersionId++;
|
|
2911
|
+
this.#blockConfirmations = block.confirmations;
|
|
2912
|
+
this.#updated = import_common13.DateUtils.toISOStringNonFractional(import_common13.DateUtils.blocktimeToTimestamp(block.time));
|
|
2913
|
+
if (this.#hasUnscannedBeacons()) {
|
|
2914
|
+
if (++this.#discoveryRounds > this.#maxDiscoveryRounds) {
|
|
2915
|
+
throw new import_common13.ResolveError(
|
|
2916
|
+
`Exceeded the configured maximum of ${this.#maxDiscoveryRounds} beacon-discovery rounds. Raise or remove ResolutionOptions.maxDiscoveryRounds to resolve this DID.`,
|
|
2917
|
+
import_common13.INTERNAL_ERROR,
|
|
2918
|
+
{ maxDiscoveryRounds: this.#maxDiscoveryRounds, discoveryRounds: this.#discoveryRounds }
|
|
2919
|
+
);
|
|
2878
2920
|
}
|
|
2921
|
+
this.#phase = "BeaconDiscovery" /* BeaconDiscovery */;
|
|
2879
2922
|
}
|
|
2880
|
-
this.#phase = "Complete" /* Complete */;
|
|
2881
2923
|
continue;
|
|
2882
2924
|
}
|
|
2883
2925
|
// Phase: Complete
|
|
2926
|
+
// The document metadata of the specification: versionId is current_version_id,
|
|
2927
|
+
// confirmations is block_confirmations (0 when no update applied), deactivated
|
|
2928
|
+
// is the flag of the document. `updated` is present after the first apply.
|
|
2884
2929
|
case "Complete" /* Complete */: {
|
|
2885
|
-
|
|
2886
|
-
|
|
2887
|
-
|
|
2888
|
-
|
|
2889
|
-
|
|
2890
|
-
|
|
2891
|
-
|
|
2892
|
-
}
|
|
2930
|
+
this.#resolvedResponse ??= {
|
|
2931
|
+
didDocument: this.#currentDocument,
|
|
2932
|
+
metadata: {
|
|
2933
|
+
versionId: `${this.#currentVersionId}`,
|
|
2934
|
+
confirmations: this.#blockConfirmations,
|
|
2935
|
+
...this.#updated !== void 0 ? { updated: this.#updated } : {},
|
|
2936
|
+
deactivated: this.#currentDocument.deactivated || false
|
|
2893
2937
|
}
|
|
2894
2938
|
};
|
|
2939
|
+
return { status: "resolved", result: this.#resolvedResponse };
|
|
2895
2940
|
}
|
|
2896
2941
|
}
|
|
2897
2942
|
}
|
|
2898
2943
|
}
|
|
2944
|
+
/**
|
|
2945
|
+
* True if the current document carries a beacon service whose address the resolver
|
|
2946
|
+
* did not request signals for. "Find Beacon Signals" scans such an address on the
|
|
2947
|
+
* next pass.
|
|
2948
|
+
*/
|
|
2949
|
+
#hasUnscannedBeacons() {
|
|
2950
|
+
return BeaconUtils.getBeaconServices(this.#currentDocument).some(
|
|
2951
|
+
(service) => !this.#requestCache.has(BeaconUtils.parseBitcoinAddress(service.serviceEndpoint))
|
|
2952
|
+
);
|
|
2953
|
+
}
|
|
2899
2954
|
/**
|
|
2900
2955
|
* Return the signals of one beacon service that resolution may process: the
|
|
2901
2956
|
* signals with at least `#minConf` confirmations. The specification removes a
|
|
@@ -2904,10 +2959,11 @@ var Resolver = class _Resolver {
|
|
|
2904
2959
|
* integer confirmation count is excluded too: that is a mempool transaction
|
|
2905
2960
|
* from a driver that did not skip it.
|
|
2906
2961
|
*
|
|
2907
|
-
* An eligible signal must carry a finite block height
|
|
2908
|
-
* signal that passes the count but lacks them is
|
|
2909
|
-
* here with a typed error, in the style of the
|
|
2910
|
-
* not later with an invalid date
|
|
2962
|
+
* An eligible signal must carry a finite block height, block time, and block
|
|
2963
|
+
* median time past. A signal that passes the count but lacks them is
|
|
2964
|
+
* malformed. It fails fast here with a typed error, in the style of the
|
|
2965
|
+
* {@link provide} guards, and not later with an invalid date or a false
|
|
2966
|
+
* `versionTime` comparison in the ProcessUpdate phase.
|
|
2911
2967
|
* @param {Array<BeaconSignal>} signals The signals the caller provided for one service.
|
|
2912
2968
|
* @returns {Array<BeaconSignal>} The signals at or above the threshold, in the given order.
|
|
2913
2969
|
* @throws {ResolveError} `INVALID_DID_UPDATE` for an eligible signal with no valid block metadata.
|
|
@@ -2920,11 +2976,17 @@ var Resolver = class _Resolver {
|
|
|
2920
2976
|
if (!Number.isInteger(confirmations) || confirmations < this.#minConf) {
|
|
2921
2977
|
continue;
|
|
2922
2978
|
}
|
|
2923
|
-
if (!Number.isFinite(block?.height) || !Number.isFinite(block?.time)) {
|
|
2979
|
+
if (!Number.isFinite(block?.height) || !Number.isFinite(block?.time) || !Number.isFinite(block?.mediantime)) {
|
|
2924
2980
|
throw new import_common13.ResolveError(
|
|
2925
|
-
`Beacon signal ${signal.signalBytes} has ${confirmations} confirmations but no valid block height or block
|
|
2981
|
+
`Beacon signal ${signal.signalBytes} has ${confirmations} confirmations but no valid block height, block time, or block mediantime.`,
|
|
2926
2982
|
import_common13.INVALID_DID_UPDATE,
|
|
2927
|
-
{
|
|
2983
|
+
{
|
|
2984
|
+
signalBytes: signal.signalBytes,
|
|
2985
|
+
confirmations,
|
|
2986
|
+
height: block?.height,
|
|
2987
|
+
time: block?.time,
|
|
2988
|
+
mediantime: block?.mediantime
|
|
2989
|
+
}
|
|
2928
2990
|
);
|
|
2929
2991
|
}
|
|
2930
2992
|
eligible.push(signal);
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { GENESIS_TX_ID, TXIN_WITNESS_COINBASE } from '@did-btcr2/bitcoin';
|
|
2
|
-
import { ResolveError } from '@did-btcr2/common';
|
|
2
|
+
import { INTERNAL_ERROR, ResolveError } from '@did-btcr2/common';
|
|
3
3
|
import { BeaconUtils } from './utils.js';
|
|
4
4
|
/**
|
|
5
5
|
* The serialized form of a Beacon Signal output: `OP_RETURN` (`0x6a`), the 32-byte push
|
|
@@ -107,6 +107,9 @@ export class BeaconSignalDiscovery {
|
|
|
107
107
|
const beaconServiceSignals = new Map();
|
|
108
108
|
// Fetch the current block count once before the loop
|
|
109
109
|
const currentBlockCount = await bitcoin.rest.block.count();
|
|
110
|
+
// The median time past of each block that holds a signal, keyed by block hash.
|
|
111
|
+
// One fetch per distinct block for the whole run.
|
|
112
|
+
const mediantimes = new Map();
|
|
110
113
|
// Iterate over each beacon
|
|
111
114
|
for (const beaconService of beaconServices) {
|
|
112
115
|
beaconServiceSignals.set(beaconService, []);
|
|
@@ -156,6 +159,10 @@ export class BeaconSignalDiscovery {
|
|
|
156
159
|
}
|
|
157
160
|
// Use the pre-fetched block count instead of calling per-signal
|
|
158
161
|
const confirmations = currentBlockCount - status.block_height + 1;
|
|
162
|
+
// The address listing carries the header time of the block, not its median
|
|
163
|
+
// time past. The resolver compares `versionTime` with the median time past,
|
|
164
|
+
// so read it from the block record.
|
|
165
|
+
const mediantime = await BeaconSignalDiscovery.mediantime(status.block_hash, bitcoin, mediantimes);
|
|
159
166
|
// Push the beacon signal object to the signals array for the beacon service
|
|
160
167
|
beaconServiceSignals.get(beaconService)?.push({
|
|
161
168
|
tx: beaconSignal,
|
|
@@ -164,12 +171,37 @@ export class BeaconSignalDiscovery {
|
|
|
164
171
|
confirmations,
|
|
165
172
|
height: status.block_height,
|
|
166
173
|
time: status.block_time,
|
|
174
|
+
mediantime,
|
|
167
175
|
}
|
|
168
176
|
});
|
|
169
177
|
}
|
|
170
178
|
}
|
|
171
179
|
return beaconServiceSignals;
|
|
172
180
|
}
|
|
181
|
+
/**
|
|
182
|
+
* Return the median time past of a block, from the per-run cache or from the
|
|
183
|
+
* Esplora block record (`GET /block/:hash`). The specification compares
|
|
184
|
+
* `versionTime` with the block `mediantime`, and the address listing does not
|
|
185
|
+
* carry it. One block record serves every signal in that block.
|
|
186
|
+
* @param {string} blockhash The hash of the block that contains a signal.
|
|
187
|
+
* @param {BitcoinConnection} bitcoin Bitcoin network connection to use for REST calls.
|
|
188
|
+
* @param {Map<string, number>} cache The median time past values fetched so far, keyed by block hash.
|
|
189
|
+
* @returns {Promise<number>} The median time past of the block, in Unix seconds.
|
|
190
|
+
* @throws {ResolveError} `INTERNAL_ERROR` if the backend returns no block record or no `mediantime` for the hash.
|
|
191
|
+
*/
|
|
192
|
+
static async mediantime(blockhash, bitcoin, cache) {
|
|
193
|
+
const cached = cache.get(blockhash);
|
|
194
|
+
if (cached !== undefined) {
|
|
195
|
+
return cached;
|
|
196
|
+
}
|
|
197
|
+
const block = await bitcoin.rest.block.get({ blockhash });
|
|
198
|
+
const mediantime = block?.mediantime;
|
|
199
|
+
if (typeof mediantime !== 'number' || !Number.isFinite(mediantime)) {
|
|
200
|
+
throw new ResolveError(`Block ${blockhash} has no mediantime in the block record of the Bitcoin REST backend.`, INTERNAL_ERROR, { blockhash, block });
|
|
201
|
+
}
|
|
202
|
+
cache.set(blockhash, mediantime);
|
|
203
|
+
return mediantime;
|
|
204
|
+
}
|
|
173
205
|
/**
|
|
174
206
|
* Traverse the full blockchain from genesis to chain top looking for beacon signals.
|
|
175
207
|
* @param {Array<BeaconService>} beaconServices Array of BeaconService objects to search for signals.
|
|
@@ -290,6 +322,7 @@ export class BeaconSignalDiscovery {
|
|
|
290
322
|
blockMetadata: {
|
|
291
323
|
height: block.height,
|
|
292
324
|
time: block.time,
|
|
325
|
+
mediantime: block.mediantime,
|
|
293
326
|
confirmations: block.confirmations
|
|
294
327
|
}
|
|
295
328
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"signal-discovery.js","sourceRoot":"","sources":["../../../../src/core/beacon/signal-discovery.ts"],"names":[],"mappings":"AAKA,OAAO,EACL,aAAa,EACb,qBAAqB,EACtB,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;
|
|
1
|
+
{"version":3,"file":"signal-discovery.js","sourceRoot":"","sources":["../../../../src/core/beacon/signal-discovery.ts"],"names":[],"mappings":"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"}
|
|
@@ -111,8 +111,18 @@ export class Identifier {
|
|
|
111
111
|
if (encoded !== encoded.toLowerCase()) {
|
|
112
112
|
throw new IdentifierError(`Invalid method-specific id (must be lowercase): ${identifier}`, INVALID_DID, { identifier });
|
|
113
113
|
}
|
|
114
|
-
// 7. Bech32m-decode the id into its hrp and dataBytes.
|
|
115
|
-
|
|
114
|
+
// 7. Bech32m-decode the id into its hrp and dataBytes. The decoder throws its own Error
|
|
115
|
+
// for a bad character, a bad length, a bad checksum, or bad padding. The specification
|
|
116
|
+
// maps every decoding failure to INVALID_DID.
|
|
117
|
+
let hrp;
|
|
118
|
+
let dataBytes;
|
|
119
|
+
try {
|
|
120
|
+
({ prefix: hrp, bytes: dataBytes } = bech32m.decodeToBytes(encoded));
|
|
121
|
+
}
|
|
122
|
+
catch (error) {
|
|
123
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
124
|
+
throw new IdentifierError(`Invalid method-specific id (Bech32m decoding failed: ${reason}): ${identifier}`, INVALID_DID, { identifier, reason });
|
|
125
|
+
}
|
|
116
126
|
// 8. The hrp MUST be "k" (KEY) or "x" (EXTERNAL).
|
|
117
127
|
if (!['x', 'k'].includes(hrp)) {
|
|
118
128
|
throw new IdentifierError(`Invalid hrp: ${hrp}`, INVALID_DID, { identifier });
|