@did-btcr2/method 0.23.0 → 0.25.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 +96 -50
- package/dist/browser.js +36332 -37280
- package/dist/browser.mjs +36331 -37279
- package/dist/cjs/core/beacon/aggregation/communication/adapter/did-comm.js +1 -1
- package/dist/cjs/core/beacon/aggregation/communication/adapter/did-comm.js.map +1 -1
- package/dist/cjs/core/beacon/aggregation/communication/adapter/nostr.js +1 -1
- package/dist/cjs/core/beacon/aggregation/communication/adapter/nostr.js.map +1 -1
- package/dist/cjs/core/beacon/aggregation/coordinator.js +40 -44
- package/dist/cjs/core/beacon/aggregation/coordinator.js.map +1 -1
- package/dist/cjs/core/beacon/aggregation/participant.js +35 -38
- package/dist/cjs/core/beacon/aggregation/participant.js.map +1 -1
- package/dist/cjs/core/beacon/aggregation/session/index.js +3 -4
- package/dist/cjs/core/beacon/aggregation/session/index.js.map +1 -1
- package/dist/cjs/core/beacon/beacon.js.map +1 -1
- package/dist/cjs/core/beacon/cas-beacon.js +119 -7
- package/dist/cjs/core/beacon/cas-beacon.js.map +1 -1
- package/dist/cjs/core/beacon/factory.js +1 -1
- package/dist/cjs/core/beacon/factory.js.map +1 -1
- package/dist/cjs/core/beacon/{singleton.js → singleton-beacon.js} +19 -27
- package/dist/cjs/core/beacon/singleton-beacon.js.map +1 -0
- package/dist/cjs/core/beacon/smt-beacon.js +1 -1
- package/dist/cjs/core/beacon/smt-beacon.js.map +1 -1
- package/dist/cjs/core/identifier.js +1 -1
- package/dist/cjs/core/identifier.js.map +1 -1
- package/dist/{esm/core/resolve.js → cjs/core/resolver.js} +244 -92
- package/dist/cjs/core/resolver.js.map +1 -0
- package/dist/cjs/core/update.js +7 -7
- package/dist/cjs/core/update.js.map +1 -1
- package/dist/cjs/did-btcr2.js +34 -94
- package/dist/cjs/did-btcr2.js.map +1 -1
- package/dist/cjs/index.js +2 -3
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/utils/did-document.js +9 -19
- package/dist/cjs/utils/did-document.js.map +1 -1
- package/dist/esm/core/beacon/aggregation/communication/adapter/did-comm.js +1 -1
- package/dist/esm/core/beacon/aggregation/communication/adapter/did-comm.js.map +1 -1
- package/dist/esm/core/beacon/aggregation/communication/adapter/nostr.js +1 -1
- package/dist/esm/core/beacon/aggregation/communication/adapter/nostr.js.map +1 -1
- package/dist/esm/core/beacon/aggregation/coordinator.js +40 -44
- package/dist/esm/core/beacon/aggregation/coordinator.js.map +1 -1
- package/dist/esm/core/beacon/aggregation/participant.js +35 -38
- package/dist/esm/core/beacon/aggregation/participant.js.map +1 -1
- package/dist/esm/core/beacon/aggregation/session/index.js +3 -4
- package/dist/esm/core/beacon/aggregation/session/index.js.map +1 -1
- package/dist/esm/core/beacon/beacon.js.map +1 -1
- package/dist/esm/core/beacon/cas-beacon.js +119 -7
- package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
- package/dist/esm/core/beacon/factory.js +1 -1
- package/dist/esm/core/beacon/factory.js.map +1 -1
- package/dist/esm/core/beacon/{singleton.js → singleton-beacon.js} +19 -27
- package/dist/esm/core/beacon/singleton-beacon.js.map +1 -0
- package/dist/esm/core/beacon/smt-beacon.js +1 -1
- package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
- package/dist/esm/core/identifier.js +1 -1
- package/dist/esm/core/identifier.js.map +1 -1
- package/dist/{cjs/core/resolve.js → esm/core/resolver.js} +244 -92
- package/dist/esm/core/resolver.js.map +1 -0
- package/dist/esm/core/update.js +7 -7
- package/dist/esm/core/update.js.map +1 -1
- package/dist/esm/did-btcr2.js +34 -94
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/index.js +2 -3
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/did-document.js +9 -19
- package/dist/esm/utils/did-document.js.map +1 -1
- package/dist/types/core/beacon/aggregation/cohort/index.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/base.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/constants.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/index.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-advert.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.d.ts +2 -2
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.d.ts.map +1 -1
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in-accept.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/opt-in.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/keygen/subscribe.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/aggregated-nonce.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/authorization-request.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/nonce-contribution.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/request-signature.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/messages/sign/signature-authorization.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/cohort/status.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/communication/adapter/did-comm.d.ts +4 -3
- package/dist/types/core/beacon/aggregation/communication/adapter/did-comm.d.ts.map +1 -1
- package/dist/types/core/beacon/aggregation/communication/adapter/nostr.d.ts +5 -3
- package/dist/types/core/beacon/aggregation/communication/adapter/nostr.d.ts.map +1 -1
- package/dist/types/core/beacon/aggregation/communication/error.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/communication/factory.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/communication/service.d.ts +3 -2
- package/dist/types/core/beacon/aggregation/communication/service.d.ts.map +1 -1
- package/dist/types/core/beacon/aggregation/coordinator.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/coordinator.d.ts.map +1 -1
- package/dist/types/core/beacon/aggregation/participant.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/participant.d.ts.map +1 -1
- package/dist/types/core/beacon/aggregation/session/index.d.ts +1 -0
- package/dist/types/core/beacon/aggregation/session/index.d.ts.map +1 -1
- package/dist/types/core/beacon/aggregation/session/status.d.ts +1 -0
- package/dist/types/core/beacon/beacon.d.ts +10 -4
- package/dist/types/core/beacon/beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/cas-beacon.d.ts +27 -7
- package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/error.d.ts +1 -0
- package/dist/types/core/beacon/factory.d.ts +1 -0
- package/dist/types/core/beacon/interfaces.d.ts +1 -0
- package/dist/types/core/beacon/signal-discovery.d.ts +1 -0
- package/dist/types/core/beacon/{singleton.d.ts → singleton-beacon.d.ts} +7 -5
- package/dist/types/core/beacon/singleton-beacon.d.ts.map +1 -0
- 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/beacon/utils.d.ts +1 -0
- package/dist/types/core/identifier.d.ts +1 -0
- package/dist/types/core/interfaces.d.ts +6 -15
- package/dist/types/core/interfaces.d.ts.map +1 -1
- package/dist/types/core/resolver.d.ts +167 -0
- package/dist/types/core/resolver.d.ts.map +1 -0
- package/dist/types/core/types.d.ts +1 -0
- package/dist/types/core/update.d.ts +4 -3
- package/dist/types/core/update.d.ts.map +1 -1
- package/dist/types/did-btcr2.d.ts +17 -16
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -3
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/utils/appendix.d.ts +1 -0
- package/dist/types/utils/did-document-builder.d.ts +1 -0
- package/dist/types/utils/did-document.d.ts +2 -6
- package/dist/types/utils/did-document.d.ts.map +1 -1
- package/package.json +5 -5
- package/src/core/beacon/aggregation/cohort/messages/keygen/cohort-ready.ts +1 -1
- package/src/core/beacon/aggregation/communication/adapter/did-comm.ts +4 -3
- package/src/core/beacon/aggregation/communication/adapter/nostr.ts +4 -3
- package/src/core/beacon/aggregation/communication/service.ts +2 -2
- package/src/core/beacon/aggregation/coordinator.ts +40 -44
- package/src/core/beacon/aggregation/participant.ts +38 -40
- package/src/core/beacon/aggregation/session/index.ts +3 -4
- package/src/core/beacon/beacon.ts +9 -5
- package/src/core/beacon/cas-beacon.ts +156 -10
- package/src/core/beacon/factory.ts +1 -1
- package/src/core/beacon/{singleton.ts → singleton-beacon.ts} +20 -36
- package/src/core/beacon/smt-beacon.ts +4 -3
- package/src/core/identifier.ts +1 -1
- package/src/core/interfaces.ts +5 -16
- package/src/core/resolver.ts +706 -0
- package/src/core/update.ts +9 -9
- package/src/did-btcr2.ts +37 -130
- package/src/index.ts +2 -3
- package/src/utils/did-document.ts +10 -18
- package/dist/cjs/core/beacon/singleton.js.map +0 -1
- package/dist/cjs/core/resolve.js.map +0 -1
- package/dist/cjs/utils/general.js +0 -195
- package/dist/cjs/utils/general.js.map +0 -1
- package/dist/esm/core/beacon/singleton.js.map +0 -1
- package/dist/esm/core/resolve.js.map +0 -1
- package/dist/esm/utils/general.js +0 -195
- package/dist/esm/utils/general.js.map +0 -1
- package/dist/types/core/beacon/singleton.d.ts.map +0 -1
- package/dist/types/core/resolve.d.ts +0 -92
- package/dist/types/core/resolve.d.ts.map +0 -1
- package/dist/types/utils/general.d.ts +0 -85
- package/dist/types/utils/general.d.ts.map +0 -1
- package/src/core/resolve.ts +0 -474
- package/src/utils/general.ts +0 -204
|
@@ -0,0 +1,706 @@
|
|
|
1
|
+
import { getNetwork } from '@did-btcr2/bitcoin';
|
|
2
|
+
import {
|
|
3
|
+
canonicalHash,
|
|
4
|
+
canonicalize,
|
|
5
|
+
DateUtils,
|
|
6
|
+
encode as encodeHash,
|
|
7
|
+
decode as decodeHash,
|
|
8
|
+
INVALID_DID_DOCUMENT,
|
|
9
|
+
INVALID_DID_UPDATE,
|
|
10
|
+
JSONPatch,
|
|
11
|
+
JSONUtils,
|
|
12
|
+
LATE_PUBLISHING_ERROR,
|
|
13
|
+
ResolveError
|
|
14
|
+
} from '@did-btcr2/common';
|
|
15
|
+
import {
|
|
16
|
+
BIP340Cryptosuite,
|
|
17
|
+
BIP340DataIntegrityProof,
|
|
18
|
+
SchnorrMultikey,
|
|
19
|
+
SignedBTCR2Update,
|
|
20
|
+
UnsignedBTCR2Update
|
|
21
|
+
} from '@did-btcr2/cryptosuite';
|
|
22
|
+
import { CompressedSecp256k1PublicKey } from '@did-btcr2/keypair';
|
|
23
|
+
import { DidBtcr2 } from '../did-btcr2.js';
|
|
24
|
+
import { Appendix } from '../utils/appendix.js';
|
|
25
|
+
import { DidDocument, ID_PLACEHOLDER_VALUE } from '../utils/did-document.js';
|
|
26
|
+
import { BeaconFactory } from './beacon/factory.js';
|
|
27
|
+
import { BeaconService, BeaconSignal, BlockMetadata } from './beacon/interfaces.js';
|
|
28
|
+
import { BeaconUtils } from './beacon/utils.js';
|
|
29
|
+
import { DidComponents, Identifier } from './identifier.js';
|
|
30
|
+
import { SMTProof } from './interfaces.js';
|
|
31
|
+
import { CASAnnouncement, Sidecar, SidecarData } from './types.js';
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* The response object for DID Resolution.
|
|
35
|
+
*/
|
|
36
|
+
export interface DidResolutionResponse {
|
|
37
|
+
didDocument: DidDocument;
|
|
38
|
+
metadata: {
|
|
39
|
+
confirmations?: number;
|
|
40
|
+
versionId: string;
|
|
41
|
+
updated?: string;
|
|
42
|
+
deactivated?: boolean;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** The resolver needs a genesis document whose hash matches genesisHash. */
|
|
47
|
+
export interface NeedGenesisDocument {
|
|
48
|
+
readonly kind: 'NeedGenesisDocument';
|
|
49
|
+
/** Base64url-encoded SHA-256 hash from the DID identifier's genesisBytes. */
|
|
50
|
+
readonly genesisHash: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** The resolver needs beacon signals for these beacon service addresses. */
|
|
54
|
+
export interface NeedBeaconSignals {
|
|
55
|
+
readonly kind: 'NeedBeaconSignals';
|
|
56
|
+
/** The beacon services that need signal data. Pass directly to BeaconSignalDiscovery. */
|
|
57
|
+
readonly beaconServices: ReadonlyArray<BeaconService>;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** The resolver needs a CAS Announcement whose canonical hash matches announcementHash. */
|
|
61
|
+
export interface NeedCASAnnouncement {
|
|
62
|
+
readonly kind: 'NeedCASAnnouncement';
|
|
63
|
+
/** Base64url-encoded canonical hash of the CAS Announcement. */
|
|
64
|
+
readonly announcementHash: string;
|
|
65
|
+
/** The beacon service that produced this signal. */
|
|
66
|
+
readonly beaconServiceId: string;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/** The resolver needs a SignedBTCR2Update whose canonical hash matches updateHash. */
|
|
70
|
+
export interface NeedSignedUpdate {
|
|
71
|
+
readonly kind: 'NeedSignedUpdate';
|
|
72
|
+
/** Base64url-encoded canonical hash of the signed update. */
|
|
73
|
+
readonly updateHash: string;
|
|
74
|
+
/** The beacon service that produced this signal. */
|
|
75
|
+
readonly beaconServiceId: string;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** Discriminated union of all data the resolver may request from the caller. */
|
|
79
|
+
export type DataNeed = NeedGenesisDocument | NeedBeaconSignals | NeedCASAnnouncement | NeedSignedUpdate;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Output of {@link Resolver.resolve}. Analogous to Rust's `ResolverState` enum.
|
|
83
|
+
* Either the resolver needs data from the caller, or resolution is complete.
|
|
84
|
+
*/
|
|
85
|
+
export type ResolverState =
|
|
86
|
+
| { status: 'action-required'; needs: ReadonlyArray<DataNeed> }
|
|
87
|
+
| { status: 'resolved'; result: DidResolutionResponse };
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Return type from {@link Beacon.processSignals}.
|
|
91
|
+
* Contains successfully resolved updates and any data needs that must be
|
|
92
|
+
* satisfied before the remaining signals can be processed.
|
|
93
|
+
*/
|
|
94
|
+
export interface BeaconProcessResult {
|
|
95
|
+
updates: Array<[SignedBTCR2Update, BlockMetadata]>;
|
|
96
|
+
needs: Array<DataNeed>;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Different possible Resolver states representing phases in the resolution process.
|
|
101
|
+
*/
|
|
102
|
+
enum ResolverPhase {
|
|
103
|
+
GenesisDocument = 'GenesisDocument',
|
|
104
|
+
BeaconDiscovery = 'BeaconDiscovery',
|
|
105
|
+
BeaconProcess = 'BeaconProcess',
|
|
106
|
+
ApplyUpdates = 'ApplyUpdates',
|
|
107
|
+
Complete = 'Complete',
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Sans-I/O state machine for did:btcr2 resolution.
|
|
112
|
+
*
|
|
113
|
+
* Created by {@link DidBtcr2.resolve} (the factory). The caller drives resolution
|
|
114
|
+
* by repeatedly calling {@link resolve} and {@link provide}:
|
|
115
|
+
*
|
|
116
|
+
* ```typescript
|
|
117
|
+
* const resolver = DidBtcr2.resolve(did, { sidecar });
|
|
118
|
+
* let state = resolver.resolve();
|
|
119
|
+
*
|
|
120
|
+
* while (state.status === 'action-required') {
|
|
121
|
+
* for (const need of state.needs) { ... fetch & provide ... }
|
|
122
|
+
* state = resolver.resolve();
|
|
123
|
+
* }
|
|
124
|
+
* const { didDocument, metadata } = state.result;
|
|
125
|
+
* ```
|
|
126
|
+
*
|
|
127
|
+
* The Resolver performs **zero I/O**. All external data (Bitcoin signals, CAS
|
|
128
|
+
* data, genesis documents) flows through the advance/provide protocol.
|
|
129
|
+
*
|
|
130
|
+
* @class Resolver
|
|
131
|
+
*/
|
|
132
|
+
export class Resolver {
|
|
133
|
+
// --- Immutable inputs ---
|
|
134
|
+
readonly #didComponents: DidComponents;
|
|
135
|
+
readonly #versionId?: string;
|
|
136
|
+
readonly #versionTime?: string;
|
|
137
|
+
|
|
138
|
+
/**
|
|
139
|
+
* The specific phase the Resolver is current in.
|
|
140
|
+
*/
|
|
141
|
+
#phase: ResolverPhase;
|
|
142
|
+
#sidecarData: SidecarData;
|
|
143
|
+
#currentDocument: DidDocument | null;
|
|
144
|
+
#providedGenesisDocument: object | null = null;
|
|
145
|
+
#beaconServicesSignals: Map<BeaconService, Array<BeaconSignal>> = new Map();
|
|
146
|
+
#processedServices: Set<string> = new Set();
|
|
147
|
+
#requestCache: Set<string> = new Set();
|
|
148
|
+
#unsortedUpdates: Array<[SignedBTCR2Update, BlockMetadata]> = [];
|
|
149
|
+
#resolvedResponse: DidResolutionResponse | null = null;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @internal — Use {@link DidBtcr2.resolve} to create instances.
|
|
153
|
+
*/
|
|
154
|
+
constructor(
|
|
155
|
+
didComponents: DidComponents,
|
|
156
|
+
sidecarData: SidecarData,
|
|
157
|
+
currentDocument: DidDocument | null,
|
|
158
|
+
options?: { versionId?: string; versionTime?: string; genesisDocument?: object }
|
|
159
|
+
) {
|
|
160
|
+
this.#didComponents = didComponents;
|
|
161
|
+
this.#sidecarData = sidecarData;
|
|
162
|
+
this.#currentDocument = currentDocument;
|
|
163
|
+
this.#versionId = options?.versionId;
|
|
164
|
+
this.#versionTime = options?.versionTime;
|
|
165
|
+
|
|
166
|
+
// If a genesis document was provided (from sidecar), pre-seed it for validation
|
|
167
|
+
if(options?.genesisDocument) {
|
|
168
|
+
this.#providedGenesisDocument = options.genesisDocument;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// If current document was established by the factory, skip GenesisDocument phase
|
|
172
|
+
this.#phase = currentDocument
|
|
173
|
+
? ResolverPhase.BeaconDiscovery
|
|
174
|
+
: ResolverPhase.GenesisDocument;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* 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}.
|
|
179
|
+
* @param {DidComponents} didComponents The decoded components of the did.
|
|
180
|
+
* @returns {DidDocument} The resolved DID Document object.
|
|
181
|
+
*/
|
|
182
|
+
static deterministic(didComponents: DidComponents): DidDocument {
|
|
183
|
+
// Deconstruct the bytes from the given components
|
|
184
|
+
const genesisBytes = didComponents.genesisBytes;
|
|
185
|
+
|
|
186
|
+
// Encode the did from the didComponents
|
|
187
|
+
const did = Identifier.encode(genesisBytes, didComponents);
|
|
188
|
+
|
|
189
|
+
// Construct a new CompressedSecp256k1PublicKey and deconstruct the publicKey and publicKeyMultibase
|
|
190
|
+
const { multibase } = new CompressedSecp256k1PublicKey(genesisBytes);
|
|
191
|
+
|
|
192
|
+
// Generate the service field for the DID Document
|
|
193
|
+
const service = BeaconUtils.generateBeaconServices({
|
|
194
|
+
id : did,
|
|
195
|
+
publicKey : genesisBytes,
|
|
196
|
+
network : getNetwork(didComponents.network),
|
|
197
|
+
beaconType : 'SingletonBeacon'
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
return new DidDocument({
|
|
201
|
+
id : did,
|
|
202
|
+
verificationMethod : [{
|
|
203
|
+
id : `${did}#initialKey`,
|
|
204
|
+
type : 'Multikey',
|
|
205
|
+
controller : did,
|
|
206
|
+
publicKeyMultibase : multibase.encoded
|
|
207
|
+
}],
|
|
208
|
+
service
|
|
209
|
+
});
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#if-genesis_bytes-is-a-sha-256-hash | 7.2.d.2 if genesis_bytes is a SHA-256 Hash}.
|
|
214
|
+
* @param {DidComponents} didComponents BTCR2 DID components used to resolve the DID Document
|
|
215
|
+
* @param {object} genesisDocument The genesis document for resolving the DID Document.
|
|
216
|
+
* @returns {DidDocument} The resolved DID Document object
|
|
217
|
+
* @throws {ResolveError} InvalidDidDocument if not conformant to DID Core v1.1
|
|
218
|
+
*/
|
|
219
|
+
static external(
|
|
220
|
+
didComponents: DidComponents,
|
|
221
|
+
genesisDocument: object,
|
|
222
|
+
): DidDocument {
|
|
223
|
+
// Encode the genesis bytes from the did components
|
|
224
|
+
const genesisBytes = encodeHash(didComponents.genesisBytes);
|
|
225
|
+
|
|
226
|
+
// Canonicalize and sha256 hash the currentDocument
|
|
227
|
+
const genesisDocumentBytes = canonicalHash(genesisDocument);
|
|
228
|
+
|
|
229
|
+
// If the genesisBytes do not match the hashBytes, throw an error
|
|
230
|
+
if (genesisBytes !== genesisDocumentBytes) {
|
|
231
|
+
throw new ResolveError(
|
|
232
|
+
`Initial document mismatch: genesisBytes !== genesisDocumentBytes`,
|
|
233
|
+
INVALID_DID_DOCUMENT, { genesisBytes, genesisDocumentBytes }
|
|
234
|
+
);
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Encode the did from the didComponents
|
|
238
|
+
const did = Identifier.encode(decodeHash(genesisBytes), didComponents);
|
|
239
|
+
|
|
240
|
+
// Replace the placeholder did with the did throughout the currentDocument.
|
|
241
|
+
const currentDocument = JSON.parse(
|
|
242
|
+
JSON.stringify(genesisDocument).replaceAll(ID_PLACEHOLDER_VALUE, did)
|
|
243
|
+
);
|
|
244
|
+
|
|
245
|
+
// Return a W3C conformant DID Document
|
|
246
|
+
return new DidDocument(currentDocument);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-sidecar-data | Process Sidecar Data}
|
|
251
|
+
* @param {Sidecar} sidecar The sidecar data to process.
|
|
252
|
+
* @returns {SidecarData} The processed sidecar data containing maps of updates, CAS announcements, and SMT proofs.
|
|
253
|
+
*/
|
|
254
|
+
static sidecarData(sidecar: Sidecar = {} as Sidecar): SidecarData {
|
|
255
|
+
// BTCR2 Signed Updates map
|
|
256
|
+
const updateMap = new Map<string, SignedBTCR2Update>();
|
|
257
|
+
if(sidecar.updates?.length)
|
|
258
|
+
for(const update of sidecar.updates) {
|
|
259
|
+
updateMap.set(canonicalHash(update), update);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
// CAS Announcements map
|
|
263
|
+
const casMap = new Map<string, CASAnnouncement>();
|
|
264
|
+
if(sidecar.casUpdates?.length)
|
|
265
|
+
for(const update of sidecar.casUpdates) {
|
|
266
|
+
casMap.set(canonicalHash(update), update);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
// SMT Proofs map
|
|
270
|
+
const smtMap = new Map<string, SMTProof>();
|
|
271
|
+
if(sidecar.smtProofs?.length)
|
|
272
|
+
for(const proof of sidecar.smtProofs) {
|
|
273
|
+
smtMap.set(proof.id, proof);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return { updateMap, casMap, smtMap };
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
/**
|
|
280
|
+
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-updates | 7.2.f Process updates Array}.
|
|
281
|
+
* @param {DidDocument} currentDocument The current DID Document to apply the updates to.
|
|
282
|
+
* @param {Array<[SignedBTCR2Update, BlockMetadata]>} unsortedUpdates The unsorted array of BTCR2 Signed Updates and their associated Block Metadata.
|
|
283
|
+
* @param {string} [versionTime] The optional version time to limit updates to.
|
|
284
|
+
* @param {string} [versionId] The optional version id to limit updates to.
|
|
285
|
+
* @returns {DidResolutionResponse} The updated DID Document, number of confirmations, and version id.
|
|
286
|
+
*/
|
|
287
|
+
static updates(
|
|
288
|
+
currentDocument: DidDocument,
|
|
289
|
+
unsortedUpdates: Array<[SignedBTCR2Update, BlockMetadata]>,
|
|
290
|
+
versionTime?: string,
|
|
291
|
+
versionId?: string
|
|
292
|
+
): DidResolutionResponse {
|
|
293
|
+
// Start the version number being processed at 1
|
|
294
|
+
let currentVersionId = 1;
|
|
295
|
+
|
|
296
|
+
// Initialize an empty array to hold the update hashes
|
|
297
|
+
const updateHashHistory: string[] = [];
|
|
298
|
+
|
|
299
|
+
// 1. Sort updates by targetVersionId (ascending), using blockheight as tie-breaker
|
|
300
|
+
const updates = unsortedUpdates.sort(([upd0, blk0], [upd1, blk1]) =>
|
|
301
|
+
upd0.targetVersionId - upd1.targetVersionId || blk0.height - blk1.height
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
// Create a default response object
|
|
305
|
+
const response: DidResolutionResponse = {
|
|
306
|
+
didDocument : currentDocument,
|
|
307
|
+
metadata : {
|
|
308
|
+
versionId : `${currentVersionId}`,
|
|
309
|
+
confirmations : 0,
|
|
310
|
+
updated : '',
|
|
311
|
+
deactivated : currentDocument.deactivated || false
|
|
312
|
+
}
|
|
313
|
+
};
|
|
314
|
+
|
|
315
|
+
// Iterate over each (update block) pair
|
|
316
|
+
for(const [update, block] of updates) {
|
|
317
|
+
// Get the hash of the current document
|
|
318
|
+
const currentDocumentHash = canonicalHash(response.didDocument);
|
|
319
|
+
|
|
320
|
+
// Safely convert block.time to timestamp
|
|
321
|
+
const blocktime = DateUtils.blocktimeToTimestamp(block.time);
|
|
322
|
+
|
|
323
|
+
// TODO: How to detect if block is unconfirmed and exit gracefully or return without it
|
|
324
|
+
|
|
325
|
+
// Set the updated field to the blocktime of the current update
|
|
326
|
+
response.metadata.updated = DateUtils.toISOStringNonFractional(blocktime);
|
|
327
|
+
|
|
328
|
+
// Set confirmations to the block confirmations
|
|
329
|
+
response.metadata.confirmations = block.confirmations;
|
|
330
|
+
|
|
331
|
+
// if resolutionOptions.versionTime is defined and the blocktime is more recent, return currentDocument
|
|
332
|
+
if(versionTime) {
|
|
333
|
+
// Safely convert versionTime to timestamp
|
|
334
|
+
if(blocktime > DateUtils.dateStringToTimestamp(versionTime)) {
|
|
335
|
+
return response;
|
|
336
|
+
}
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
// Check update.targetVersionId against currentVersionId
|
|
340
|
+
// If update.targetVersionId <= currentVersionId, confirm duplicate update
|
|
341
|
+
if(update.targetVersionId <= currentVersionId) {
|
|
342
|
+
updateHashHistory.push(currentDocumentHash);
|
|
343
|
+
this.confirmDuplicate(update, updateHashHistory);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
// If update.targetVersionId == currentVersionId + 1, apply the update
|
|
347
|
+
else if (update.targetVersionId === currentVersionId + 1) {
|
|
348
|
+
// Check if update.sourceHash !== currentDocumentHash
|
|
349
|
+
if (update.sourceHash !== currentDocumentHash) {
|
|
350
|
+
// Raise an INVALID_DID_UPDATE error if they do not match
|
|
351
|
+
throw new ResolveError(
|
|
352
|
+
`Hash mismatch: update.sourceHash !== currentDocumentHash`,
|
|
353
|
+
INVALID_DID_UPDATE, { sourceHash: update.sourceHash, currentDocumentHash }
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
// Apply the update to the currentDocument and set it in the response
|
|
357
|
+
response.didDocument = this.applyUpdate(response.didDocument, update);
|
|
358
|
+
|
|
359
|
+
// Create unsigned_update by removing the proof property from update.
|
|
360
|
+
const unsignedUpdate = JSONUtils.deleteKeys(update, ['proof']) as UnsignedBTCR2Update;
|
|
361
|
+
// Push the canonicalized unsigned update hash to the updateHashHistory
|
|
362
|
+
updateHashHistory.push(canonicalHash(unsignedUpdate));
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// If update.targetVersionId > currentVersionId + 1, throw LATE_PUBLISHING error
|
|
366
|
+
else if(update.targetVersionId > currentVersionId + 1) {
|
|
367
|
+
throw new ResolveError(
|
|
368
|
+
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
369
|
+
'LATE_PUBLISHING_ERROR', {
|
|
370
|
+
targetVersionId : update.targetVersionId,
|
|
371
|
+
currentVersionId : currentVersionId + 1
|
|
372
|
+
}
|
|
373
|
+
);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
// Increment currentVersionId
|
|
377
|
+
currentVersionId++;
|
|
378
|
+
|
|
379
|
+
// Set response.versionId to be the new currentVersionId
|
|
380
|
+
response.metadata.versionId = `${currentVersionId}`;
|
|
381
|
+
|
|
382
|
+
// If resolutionOptions.versionId is defined and <= currentVersionId, return currentDocument
|
|
383
|
+
const versionIdNumber = Number(versionId);
|
|
384
|
+
if(!isNaN(versionIdNumber) && versionIdNumber <= currentVersionId) {
|
|
385
|
+
return response;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
// Check if the current document is deactivated before further processing
|
|
389
|
+
if(response.didDocument.deactivated) {
|
|
390
|
+
// Set the response deactivated flag to true
|
|
391
|
+
response.metadata.deactivated = response.didDocument.deactivated;
|
|
392
|
+
// If deactivated, stop processing further updates and return the response
|
|
393
|
+
return response;
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
// Return response data
|
|
398
|
+
return response;
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
// ─── Private static: update internals ──────────────────────────────
|
|
402
|
+
|
|
403
|
+
/**
|
|
404
|
+
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/#confirm-duplicate-update | 7.2.f.1 Confirm Duplicate Update}.
|
|
405
|
+
* This step confirms that an update with a lower-than-expected targetVersionId is a true duplicate.
|
|
406
|
+
* @param {SignedBTCR2Update} update The BTCR2 Signed Update to confirm as a duplicate.
|
|
407
|
+
* @param {string[]} updateHashHistory The accumulated hash history for comparison.
|
|
408
|
+
* @returns {void} Does not return a value, but throws an error if the update is not a valid duplicate.
|
|
409
|
+
*/
|
|
410
|
+
private static confirmDuplicate(update: SignedBTCR2Update, updateHashHistory: string[]): void {
|
|
411
|
+
// Create unsigned_update by removing the proof property from update.
|
|
412
|
+
const { proof: _, ...unsignedUpdate } = update;
|
|
413
|
+
|
|
414
|
+
// Hash unsignedUpdate with JSON Document Hashing algorithm
|
|
415
|
+
const unsignedUpdateHash = canonicalHash(unsignedUpdate);
|
|
416
|
+
|
|
417
|
+
// Let historicalUpdateHash equal updateHashHistory[updateHashIndex].
|
|
418
|
+
const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
|
|
419
|
+
|
|
420
|
+
// Check if the updateHash matches the historical hash
|
|
421
|
+
if (updateHashHistory[update.targetVersionId - 2] !== unsignedUpdateHash) {
|
|
422
|
+
throw new ResolveError(
|
|
423
|
+
`Invalid duplicate: ${unsignedUpdateHash} does not match ${historicalUpdateHash}`,
|
|
424
|
+
LATE_PUBLISHING_ERROR, { unsignedUpdateHash, updateHashHistory }
|
|
425
|
+
);
|
|
426
|
+
}
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
/**
|
|
430
|
+
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#apply-update | 7.2.f.3 Apply Update}.
|
|
431
|
+
* @param {DidDocument} currentDocument The current DID Document to apply the update to.
|
|
432
|
+
* @param {SignedBTCR2Update} update The BTCR2 Signed Update to apply.
|
|
433
|
+
* @returns {DidDocument} The updated DID Document after applying the update.
|
|
434
|
+
* @throws {ResolveError} If the update is invalid or cannot be applied.
|
|
435
|
+
*/
|
|
436
|
+
private static applyUpdate(
|
|
437
|
+
currentDocument: DidDocument,
|
|
438
|
+
update: SignedBTCR2Update
|
|
439
|
+
): DidDocument {
|
|
440
|
+
// Get the capability id from the to update proof.
|
|
441
|
+
const capabilityId = update.proof?.capability;
|
|
442
|
+
// Since this field is optional, check that it exists
|
|
443
|
+
if (!capabilityId) {
|
|
444
|
+
// If it does not exist, throw INVALID_DID_UPDATE error
|
|
445
|
+
throw new ResolveError('No root capability found in update', INVALID_DID_UPDATE, update);
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
// Get the root capability object by dereferencing the capabilityId
|
|
449
|
+
const rootCapability = Appendix.dereferenceZcapId(capabilityId);
|
|
450
|
+
|
|
451
|
+
// Deconstruct the invocationTarget and controller from the root capability
|
|
452
|
+
const { invocationTarget, controller: rootController } = rootCapability;
|
|
453
|
+
// Check that both invocationTarget and rootController equal currentDocument.id
|
|
454
|
+
if (![invocationTarget, rootController].every((id) => id === currentDocument.id)) {
|
|
455
|
+
// If they do not all match, throw INVALID_DID_UPDATE error
|
|
456
|
+
throw new ResolveError(
|
|
457
|
+
'Invalid root capability',
|
|
458
|
+
INVALID_DID_UPDATE, { rootCapability, currentDocument }
|
|
459
|
+
);
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// Get the verificationMethod field from the update proof as verificationMethodId.
|
|
463
|
+
const verificationMethodId = update.proof?.verificationMethod;
|
|
464
|
+
// Since this field is optional, check that it exists
|
|
465
|
+
if(!verificationMethodId) {
|
|
466
|
+
// If it does not exist, throw INVALID_DID_UPDATE error
|
|
467
|
+
throw new ResolveError('No verificationMethod found in update', INVALID_DID_UPDATE, update);
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Get the verificationMethod from the DID Document using the verificationMethodId.
|
|
471
|
+
const vm = DidBtcr2.getSigningMethod(currentDocument, verificationMethodId);
|
|
472
|
+
|
|
473
|
+
// Construct a new SchnorrMultikey.
|
|
474
|
+
const multikey = SchnorrMultikey.fromVerificationMethod(vm);
|
|
475
|
+
|
|
476
|
+
// Construct a new BIP340Cryptosuite with the SchnorrMultikey.
|
|
477
|
+
const cryptosuite = new BIP340Cryptosuite(multikey);
|
|
478
|
+
|
|
479
|
+
// Canonicalize the update
|
|
480
|
+
const canonicalUpdate = canonicalize(update);
|
|
481
|
+
|
|
482
|
+
// Construct a DataIntegrityProof with the cryptosuite
|
|
483
|
+
const diProof = new BIP340DataIntegrityProof(cryptosuite);
|
|
484
|
+
|
|
485
|
+
// Call the verifyProof method
|
|
486
|
+
const verificationResult = diProof.verifyProof(canonicalUpdate, 'capabilityInvocation');
|
|
487
|
+
|
|
488
|
+
// If the result is not verified, throw INVALID_DID_UPDATE error
|
|
489
|
+
if (!verificationResult.verified) {
|
|
490
|
+
throw new ResolveError(
|
|
491
|
+
'Invalid update: proof not verified',
|
|
492
|
+
INVALID_DID_UPDATE, verificationResult
|
|
493
|
+
);
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Apply the update.patch to the currentDocument to get the updatedDocument.
|
|
497
|
+
const updatedDocument = JSONPatch.apply(currentDocument, update.patch) as DidDocument;
|
|
498
|
+
|
|
499
|
+
// Verify that updatedDocument is conformant to DID Core v1.1.
|
|
500
|
+
DidDocument.validate(updatedDocument);
|
|
501
|
+
|
|
502
|
+
// Canonicalize and hash the updatedDocument to get the currentDocumentHash.
|
|
503
|
+
const currentDocumentHash = canonicalHash(updatedDocument);
|
|
504
|
+
|
|
505
|
+
// Prepare the update targetHash for comparison with currentDocumentHash.
|
|
506
|
+
const updateTargetHash = update.targetHash;
|
|
507
|
+
|
|
508
|
+
// Make sure the update.targetHash equals currentDocumentHash.
|
|
509
|
+
if (update.targetHash !== currentDocumentHash) {
|
|
510
|
+
// If they do not match, throw INVALID_DID_UPDATE error.
|
|
511
|
+
throw new ResolveError(
|
|
512
|
+
`Invalid update: update.targetHash !== currentDocumentHash`,
|
|
513
|
+
INVALID_DID_UPDATE, { updateTargetHash, currentDocumentHash }
|
|
514
|
+
);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
// Return final updatedDocument.
|
|
518
|
+
return updatedDocument;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// ─── Instance: state machine ───────────────────────────────────────
|
|
522
|
+
|
|
523
|
+
/**
|
|
524
|
+
* Advance the state machine. Returns either:
|
|
525
|
+
* - `{ status: 'action-required', needs }` — caller must provide data via {@link provide}
|
|
526
|
+
* - `{ status: 'resolved', result }` — resolution complete
|
|
527
|
+
*
|
|
528
|
+
* Analogous to Rust's `Resolver::resolve()`.
|
|
529
|
+
*/
|
|
530
|
+
resolve(): ResolverState {
|
|
531
|
+
// Internal loop — keeps advancing through phases until data is needed or done
|
|
532
|
+
while(true) {
|
|
533
|
+
switch(this.#phase) {
|
|
534
|
+
|
|
535
|
+
// Phase: GenesisDocument
|
|
536
|
+
// Only entered for EXTERNAL (x HRP) identifiers when genesis doc was not in sidecar.
|
|
537
|
+
case ResolverPhase.GenesisDocument: {
|
|
538
|
+
if(this.#providedGenesisDocument) {
|
|
539
|
+
// Genesis doc was provided — establish the current document
|
|
540
|
+
this.#currentDocument = Resolver.external(
|
|
541
|
+
this.#didComponents, this.#providedGenesisDocument
|
|
542
|
+
);
|
|
543
|
+
this.#providedGenesisDocument = null;
|
|
544
|
+
this.#phase = ResolverPhase.BeaconDiscovery;
|
|
545
|
+
continue;
|
|
546
|
+
}
|
|
547
|
+
|
|
548
|
+
// Need genesis document from caller
|
|
549
|
+
const genesisHash = encodeHash(this.#didComponents.genesisBytes);
|
|
550
|
+
return {
|
|
551
|
+
status : 'action-required',
|
|
552
|
+
needs : [{ kind: 'NeedGenesisDocument', genesisHash }]
|
|
553
|
+
};
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
// Phase: BeaconDiscovery
|
|
557
|
+
// Extract beacon services, emit NeedBeaconSignals for addresses not yet queried.
|
|
558
|
+
case ResolverPhase.BeaconDiscovery: {
|
|
559
|
+
const beaconServices = BeaconUtils.getBeaconServices(this.#currentDocument!);
|
|
560
|
+
|
|
561
|
+
// Filter to services whose addresses haven't been requested yet
|
|
562
|
+
const newServices = beaconServices.filter(service => {
|
|
563
|
+
const address = BeaconUtils.parseBitcoinAddress(service.serviceEndpoint as string);
|
|
564
|
+
return !this.#requestCache.has(address);
|
|
565
|
+
});
|
|
566
|
+
|
|
567
|
+
if(newServices.length > 0) {
|
|
568
|
+
// Mark addresses as requested so we don't re-request on subsequent rounds
|
|
569
|
+
for(const service of newServices) {
|
|
570
|
+
const address = BeaconUtils.parseBitcoinAddress(service.serviceEndpoint as string);
|
|
571
|
+
this.#requestCache.add(address);
|
|
572
|
+
}
|
|
573
|
+
|
|
574
|
+
return {
|
|
575
|
+
status : 'action-required',
|
|
576
|
+
needs : [{ kind: 'NeedBeaconSignals', beaconServices: newServices }]
|
|
577
|
+
};
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
// No new beacon services to query — move to processing
|
|
581
|
+
this.#phase = ResolverPhase.BeaconProcess;
|
|
582
|
+
continue;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
// Phase: BeaconProcess
|
|
586
|
+
// Process each beacon's signals. Collect updates and data needs.
|
|
587
|
+
case ResolverPhase.BeaconProcess: {
|
|
588
|
+
const allNeeds: Array<DataNeed> = [];
|
|
589
|
+
|
|
590
|
+
for(const [service, signals] of this.#beaconServicesSignals) {
|
|
591
|
+
// Skip already-processed services and services with no signals
|
|
592
|
+
if(this.#processedServices.has(service.id) || !signals.length) continue;
|
|
593
|
+
|
|
594
|
+
// Establish a typed beacon and process its signals
|
|
595
|
+
const beacon = BeaconFactory.establish(service);
|
|
596
|
+
const result = beacon.processSignals(signals, this.#sidecarData);
|
|
597
|
+
|
|
598
|
+
if(result.needs.length > 0) {
|
|
599
|
+
// This service has unmet data needs — collect them
|
|
600
|
+
allNeeds.push(...result.needs);
|
|
601
|
+
} else {
|
|
602
|
+
// All signals for this service resolved — collect updates, mark processed
|
|
603
|
+
this.#unsortedUpdates.push(...result.updates);
|
|
604
|
+
this.#processedServices.add(service.id);
|
|
605
|
+
}
|
|
606
|
+
}
|
|
607
|
+
|
|
608
|
+
if(allNeeds.length > 0) {
|
|
609
|
+
return { status: 'action-required', needs: allNeeds };
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
this.#phase = ResolverPhase.ApplyUpdates;
|
|
613
|
+
continue;
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
// Phase: ApplyUpdates
|
|
617
|
+
// Apply collected updates, then check for new beacon services (multi-round).
|
|
618
|
+
case ResolverPhase.ApplyUpdates: {
|
|
619
|
+
if(this.#unsortedUpdates.length > 0) {
|
|
620
|
+
// Apply all collected updates to the current document
|
|
621
|
+
this.#resolvedResponse = Resolver.updates(
|
|
622
|
+
this.#currentDocument!,
|
|
623
|
+
this.#unsortedUpdates,
|
|
624
|
+
this.#versionTime,
|
|
625
|
+
this.#versionId
|
|
626
|
+
);
|
|
627
|
+
this.#currentDocument = this.#resolvedResponse.didDocument;
|
|
628
|
+
this.#unsortedUpdates = [];
|
|
629
|
+
|
|
630
|
+
// Check for new beacon services added by updates (multi-round discovery)
|
|
631
|
+
const beaconServices = BeaconUtils.getBeaconServices(this.#currentDocument);
|
|
632
|
+
const hasNewServices = beaconServices.some(service => {
|
|
633
|
+
const address = BeaconUtils.parseBitcoinAddress(service.serviceEndpoint as string);
|
|
634
|
+
return !this.#requestCache.has(address);
|
|
635
|
+
});
|
|
636
|
+
|
|
637
|
+
if(hasNewServices) {
|
|
638
|
+
// Loop back to discover signals for new beacon services
|
|
639
|
+
this.#phase = ResolverPhase.BeaconDiscovery;
|
|
640
|
+
continue;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
|
|
644
|
+
this.#phase = ResolverPhase.Complete;
|
|
645
|
+
continue;
|
|
646
|
+
}
|
|
647
|
+
|
|
648
|
+
// Phase: Complete
|
|
649
|
+
case ResolverPhase.Complete: {
|
|
650
|
+
return {
|
|
651
|
+
status : 'resolved',
|
|
652
|
+
result : this.#resolvedResponse ?? {
|
|
653
|
+
didDocument : this.#currentDocument!,
|
|
654
|
+
metadata : {
|
|
655
|
+
versionId : this.#versionId ?? '1',
|
|
656
|
+
deactivated : this.#currentDocument!.deactivated || false
|
|
657
|
+
}
|
|
658
|
+
}
|
|
659
|
+
};
|
|
660
|
+
}
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
/**
|
|
666
|
+
* Provide data the resolver requested in a previous {@link resolve} call.
|
|
667
|
+
* Call once per need, then call {@link resolve} again to continue.
|
|
668
|
+
*
|
|
669
|
+
* Analogous to Rust's `Resolver::process_responses()`.
|
|
670
|
+
*
|
|
671
|
+
* @param need The DataNeed being fulfilled (from the `needs` array).
|
|
672
|
+
* @param data The data payload corresponding to the need kind.
|
|
673
|
+
*/
|
|
674
|
+
provide(need: NeedGenesisDocument, data: object): void;
|
|
675
|
+
provide(need: NeedBeaconSignals, data: Map<BeaconService, Array<BeaconSignal>>): void;
|
|
676
|
+
provide(need: NeedCASAnnouncement, data: CASAnnouncement): void;
|
|
677
|
+
provide(need: NeedSignedUpdate, data: SignedBTCR2Update): void;
|
|
678
|
+
provide(need: DataNeed, data: object | Map<BeaconService, Array<BeaconSignal>> | CASAnnouncement | SignedBTCR2Update): void {
|
|
679
|
+
switch(need.kind) {
|
|
680
|
+
case 'NeedGenesisDocument': {
|
|
681
|
+
this.#providedGenesisDocument = data;
|
|
682
|
+
break;
|
|
683
|
+
}
|
|
684
|
+
|
|
685
|
+
case 'NeedBeaconSignals': {
|
|
686
|
+
const signals = data as Map<BeaconService, Array<BeaconSignal>>;
|
|
687
|
+
for(const [service, serviceSignals] of signals) {
|
|
688
|
+
this.#beaconServicesSignals.set(service, serviceSignals);
|
|
689
|
+
}
|
|
690
|
+
break;
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
case 'NeedCASAnnouncement': {
|
|
694
|
+
const announcement = data as CASAnnouncement;
|
|
695
|
+
this.#sidecarData.casMap.set(canonicalHash(announcement), announcement);
|
|
696
|
+
break;
|
|
697
|
+
}
|
|
698
|
+
|
|
699
|
+
case 'NeedSignedUpdate': {
|
|
700
|
+
const update = data as SignedBTCR2Update;
|
|
701
|
+
this.#sidecarData.updateMap.set(canonicalHash(update), update);
|
|
702
|
+
break;
|
|
703
|
+
}
|
|
704
|
+
}
|
|
705
|
+
}
|
|
706
|
+
}
|