@did-btcr2/method 0.52.0 → 0.54.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 -2
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +3 -3
- package/dist/browser.mjs +3 -3
- package/dist/cjs/index.js +50 -20
- package/dist/esm/core/beacon/beacon.js.map +1 -1
- package/dist/esm/core/beacon/cas-beacon.js +15 -8
- package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
- package/dist/esm/core/beacon/singleton-beacon.js +3 -3
- package/dist/esm/core/beacon/singleton-beacon.js.map +1 -1
- package/dist/esm/core/beacon/smt-beacon.js +9 -3
- package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
- package/dist/esm/core/resolver.js +40 -10
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/esm/core/updater.js +13 -5
- package/dist/esm/core/updater.js.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +31 -3
- package/dist/types/core/beacon/beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/cas-beacon.d.ts +15 -8
- package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/interfaces.d.ts +5 -2
- package/dist/types/core/beacon/interfaces.d.ts.map +1 -1
- package/dist/types/core/beacon/singleton-beacon.d.ts +3 -3
- package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/smt-beacon.d.ts +5 -3
- package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
- package/dist/types/core/resolver.d.ts.map +1 -1
- package/dist/types/core/updater.d.ts +20 -5
- package/dist/types/core/updater.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/core/beacon/beacon.ts +32 -3
- package/src/core/beacon/cas-beacon.ts +21 -13
- package/src/core/beacon/interfaces.ts +5 -2
- package/src/core/beacon/singleton-beacon.ts +5 -5
- package/src/core/beacon/smt-beacon.ts +12 -5
- package/src/core/resolver.ts +49 -11
- package/src/core/updater.ts +23 -7
package/src/core/resolver.ts
CHANGED
|
@@ -126,13 +126,18 @@ function isCASAnnouncement(value: unknown): value is CASAnnouncement {
|
|
|
126
126
|
return isRecord(value) && Object.values(value).every(v => typeof v === 'string');
|
|
127
127
|
}
|
|
128
128
|
|
|
129
|
-
/**
|
|
129
|
+
/**
|
|
130
|
+
* True if `value` has the shape of a signed BTCR2 update. targetVersionId must be an
|
|
131
|
+
* integer of at least 2: an update targets the version after the one it patches, and
|
|
132
|
+
* genesis is version 1, so no conformant update can target a lower version (ADR 068).
|
|
133
|
+
*/
|
|
130
134
|
function isSignedBTCR2Update(value: unknown): value is SignedBTCR2Update {
|
|
131
135
|
if(!isRecord(value)) return false;
|
|
132
136
|
return Array.isArray(value.patch)
|
|
133
137
|
&& typeof value.sourceHash === 'string'
|
|
134
138
|
&& typeof value.targetHash === 'string'
|
|
135
|
-
&&
|
|
139
|
+
&& Number.isInteger(value.targetVersionId)
|
|
140
|
+
&& (value.targetVersionId as number) >= 2
|
|
136
141
|
&& isRecord(value.proof);
|
|
137
142
|
}
|
|
138
143
|
|
|
@@ -411,14 +416,6 @@ export class Resolver {
|
|
|
411
416
|
// Set confirmations to the block confirmations
|
|
412
417
|
response.metadata.confirmations = block.confirmations;
|
|
413
418
|
|
|
414
|
-
// if resolutionOptions.versionTime is defined and the blocktime is more recent, return currentDocument
|
|
415
|
-
if(versionTime) {
|
|
416
|
-
// Safely convert versionTime to timestamp
|
|
417
|
-
if(blocktime > DateUtils.dateStringToTimestamp(versionTime)) {
|
|
418
|
-
return response;
|
|
419
|
-
}
|
|
420
|
-
}
|
|
421
|
-
|
|
422
419
|
// Check update.targetVersionId against currentVersionId.
|
|
423
420
|
// If update.targetVersionId <= currentVersionId, this update re-announces a version
|
|
424
421
|
// that has already been applied. Confirm it is a true duplicate, then skip it: a
|
|
@@ -428,12 +425,28 @@ export class Resolver {
|
|
|
428
425
|
// history already holds the applied update at updateHashHistory[targetVersionId - 2].
|
|
429
426
|
// Holding the increment off the duplicate path is the deliberate did:btcr2 deviation
|
|
430
427
|
// recorded in ADR 067: the read algorithm's "Increment current_version_id" belongs
|
|
431
|
-
// to the apply branch, not to every tuple.
|
|
428
|
+
// to the apply branch, not to every tuple. Duplicates are confirmed whatever their
|
|
429
|
+
// blocktime, before the versionTime check below, so a re-announcement mined after
|
|
430
|
+
// versionTime can neither truncate the in-window history nor dodge late-publishing
|
|
431
|
+
// detection (ADR 068).
|
|
432
432
|
if(update.targetVersionId <= currentVersionId) {
|
|
433
433
|
this.confirmDuplicate(update, updateHashHistory);
|
|
434
434
|
continue;
|
|
435
435
|
}
|
|
436
436
|
|
|
437
|
+
// if resolutionOptions.versionTime is defined and the blocktime is more recent, return
|
|
438
|
+
// currentDocument. Evaluated only for tuples that would change state (apply or late
|
|
439
|
+
// publishing). The spec places this check before the duplicate branch, where the sort
|
|
440
|
+
// by targetVersionId lets a duplicate of an early version mined after versionTime end
|
|
441
|
+
// resolution before genuine in-window updates are processed; checking it here is the
|
|
442
|
+
// deliberate deviation recorded in ADR 068.
|
|
443
|
+
if(versionTime) {
|
|
444
|
+
// Safely convert versionTime to timestamp
|
|
445
|
+
if(blocktime > DateUtils.dateStringToTimestamp(versionTime)) {
|
|
446
|
+
return response;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
437
450
|
// If update.targetVersionId == currentVersionId + 1, apply the update
|
|
438
451
|
if (update.targetVersionId === currentVersionId + 1) {
|
|
439
452
|
// Check if update.sourceHash !== currentDocumentHash (byte comparison)
|
|
@@ -501,6 +514,17 @@ export class Resolver {
|
|
|
501
514
|
* @returns {void} Does not return a value, but throws an error if the update is not a valid duplicate.
|
|
502
515
|
*/
|
|
503
516
|
private static confirmDuplicate(update: SignedBTCR2Update, updateHashHistory: HashBytes[]): void {
|
|
517
|
+
// A conformant update targets the version after the one it patches, so targetVersionId
|
|
518
|
+
// is an integer of at least 2 (genesis is version 1). Anything else cannot name an
|
|
519
|
+
// applied update: it is a malformed update, not a duplicate, and without this guard it
|
|
520
|
+
// would read a nonexistent history slot below and crash on the byte comparison (ADR 068).
|
|
521
|
+
if (!Number.isInteger(update.targetVersionId) || update.targetVersionId < 2) {
|
|
522
|
+
throw new ResolveError(
|
|
523
|
+
`Invalid duplicate: targetVersionId must be an integer >= 2`,
|
|
524
|
+
INVALID_DID_UPDATE, { targetVersionId: update.targetVersionId }
|
|
525
|
+
);
|
|
526
|
+
}
|
|
527
|
+
|
|
504
528
|
// Create unsigned_update by removing the proof property from update.
|
|
505
529
|
const { proof: _, ...unsignedUpdate } = update;
|
|
506
530
|
|
|
@@ -510,6 +534,20 @@ export class Resolver {
|
|
|
510
534
|
// Let historicalUpdateHash equal updateHashHistory[updateHashIndex].
|
|
511
535
|
const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
|
|
512
536
|
|
|
537
|
+
// The resolver's own loop records one history entry per applied version, so this slot
|
|
538
|
+
// always exists on that path; a standalone caller, however, can pass a resolutionState
|
|
539
|
+
// whose version counter outruns its history. A duplicate that cannot be checked against
|
|
540
|
+
// an applied update is unconfirmable, which is late-publishing evidence, not a pass.
|
|
541
|
+
if (historicalUpdateHash === undefined) {
|
|
542
|
+
throw new ResolveError(
|
|
543
|
+
`Invalid duplicate: no applied update in history for targetVersionId`,
|
|
544
|
+
LATE_PUBLISHING_ERROR, {
|
|
545
|
+
targetVersionId : update.targetVersionId,
|
|
546
|
+
historyLength : updateHashHistory.length
|
|
547
|
+
}
|
|
548
|
+
);
|
|
549
|
+
}
|
|
550
|
+
|
|
513
551
|
// Check if the updateHash matches the historical hash (byte comparison)
|
|
514
552
|
if (!equalBytes(historicalUpdateHash, unsignedUpdateHash)) {
|
|
515
553
|
throw new ResolveError(
|
package/src/core/updater.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { SchnorrMultikey } from '@did-btcr2/cryptosuite';
|
|
|
5
5
|
import type { Signer } from '@did-btcr2/keypair';
|
|
6
6
|
import type { Btcr2DataIntegrityConfig, SignedBTCR2Update, UnsignedBTCR2Update } from './btcr2-update.js';
|
|
7
7
|
import { DidDocument, type Btcr2DidDocument, type DidVerificationMethod } from '../utils/did-document.js';
|
|
8
|
+
import type { BroadcastResult } from './beacon/beacon.js';
|
|
9
|
+
import type { CASBroadcastOptions } from './beacon/cas-beacon.js';
|
|
8
10
|
import { BeaconFactory } from './beacon/factory.js';
|
|
9
11
|
import type { BeaconService } from './beacon/interfaces.js';
|
|
10
12
|
|
|
@@ -56,10 +58,15 @@ export interface FundingProof {
|
|
|
56
58
|
* The updater needs the caller to broadcast the signed update via the beacon.
|
|
57
59
|
*
|
|
58
60
|
* The caller decides how: for single-party beacons, call
|
|
59
|
-
* `Updater.announce(beaconService, signedUpdate,
|
|
61
|
+
* `Updater.announce(beaconService, signedUpdate, signer, bitcoin, options?)` or
|
|
60
62
|
* `BeaconFactory.establish(beaconService).broadcastSignal(...)`. For multi-party
|
|
61
63
|
* aggregate beacons, hand off to the aggregation protocol.
|
|
62
64
|
*
|
|
65
|
+
* Both single-party paths return a `BroadcastResult`; capture it. Beyond the txid,
|
|
66
|
+
* it carries the per-beacon-type sidecar artifacts: the CAS Announcement (CAS
|
|
67
|
+
* beacons) and the SMT inclusion proof (SMT beacons), whose embedded nonce exists
|
|
68
|
+
* nowhere else, so a discarded proof makes the signal permanently unresolvable.
|
|
69
|
+
*
|
|
63
70
|
* After the broadcast succeeds, the caller calls `updater.provide(need)` (with no
|
|
64
71
|
* data) to transition the updater to Complete.
|
|
65
72
|
*/
|
|
@@ -137,10 +144,14 @@ export interface UpdaterParams {
|
|
|
137
144
|
* // Check UTXOs at need.beaconAddress, fund if needed
|
|
138
145
|
* updater.provide(need);
|
|
139
146
|
* break;
|
|
140
|
-
* case 'NeedBroadcast':
|
|
141
|
-
*
|
|
147
|
+
* case 'NeedBroadcast': {
|
|
148
|
+
* // Capture the BroadcastResult: broadcast.txid, plus broadcast.announcement
|
|
149
|
+
* // (CAS beacons) and broadcast.proof (SMT beacons; must be retained, the
|
|
150
|
+
* // proof's nonce exists nowhere else).
|
|
151
|
+
* const broadcast = await Updater.announce(need.beaconService, need.signedUpdate, signer, bitcoin);
|
|
142
152
|
* updater.provide(need);
|
|
143
153
|
* break;
|
|
154
|
+
* }
|
|
144
155
|
* }
|
|
145
156
|
* }
|
|
146
157
|
* state = updater.advance();
|
|
@@ -315,16 +326,21 @@ export class Updater {
|
|
|
315
326
|
* @param {SignedBTCR2Update} update The signed update to announce.
|
|
316
327
|
* @param {Signer} signer Signer that produces the ECDSA signature for the Bitcoin transaction.
|
|
317
328
|
* @param {BitcoinConnection} bitcoin The Bitcoin network connection.
|
|
318
|
-
* @
|
|
329
|
+
* @param {CASBroadcastOptions} [options] Optional broadcast configuration (fee estimator,
|
|
330
|
+
* change address, and, for CAS beacons, a `casPublish` callback invoked before the
|
|
331
|
+
* transaction broadcast; other beacon types ignore `casPublish`).
|
|
332
|
+
* @returns {Promise<BroadcastResult>} The broadcast artifacts: the signed update, the signal
|
|
333
|
+
* txid, and any per-beacon-type sidecar data (CAS announcement / SMT proof).
|
|
319
334
|
*/
|
|
320
335
|
static async announce(
|
|
321
336
|
beaconService: BeaconService,
|
|
322
337
|
update: SignedBTCR2Update,
|
|
323
338
|
signer: Signer,
|
|
324
|
-
bitcoin: BitcoinConnection
|
|
325
|
-
|
|
339
|
+
bitcoin: BitcoinConnection,
|
|
340
|
+
options?: CASBroadcastOptions
|
|
341
|
+
): Promise<BroadcastResult> {
|
|
326
342
|
const beacon = BeaconFactory.establish(beaconService);
|
|
327
|
-
return beacon.broadcastSignal(update, signer, bitcoin);
|
|
343
|
+
return beacon.broadcastSignal(update, signer, bitcoin, options);
|
|
328
344
|
}
|
|
329
345
|
|
|
330
346
|
// Private instance wrappers
|