@did-btcr2/method 0.19.0 → 0.22.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 +223 -1
- package/dist/browser.js +3496 -4202
- package/dist/browser.mjs +3496 -4202
- package/dist/cjs/core/beacon/beacon.js +25 -0
- package/dist/cjs/core/beacon/beacon.js.map +1 -0
- package/dist/cjs/core/beacon/cas-beacon.js +20 -36
- package/dist/cjs/core/beacon/cas-beacon.js.map +1 -1
- package/dist/cjs/core/beacon/error.js +4 -4
- package/dist/cjs/core/beacon/error.js.map +1 -1
- package/dist/cjs/core/beacon/factory.js +5 -7
- package/dist/cjs/core/beacon/factory.js.map +1 -1
- package/dist/cjs/core/beacon/interfaces.js +1 -31
- package/dist/cjs/core/beacon/interfaces.js.map +1 -1
- package/dist/cjs/core/beacon/signal-discovery.js +183 -0
- package/dist/cjs/core/beacon/signal-discovery.js.map +1 -0
- package/dist/cjs/core/beacon/singleton.js +56 -86
- package/dist/cjs/core/beacon/singleton.js.map +1 -1
- package/dist/cjs/core/beacon/smt-beacon.js +22 -39
- package/dist/cjs/core/beacon/smt-beacon.js.map +1 -1
- package/dist/cjs/core/beacon/utils.js +4 -9
- package/dist/cjs/core/beacon/utils.js.map +1 -1
- package/dist/cjs/core/resolve.js +121 -307
- package/dist/cjs/core/resolve.js.map +1 -1
- package/dist/cjs/core/update.js +62 -154
- package/dist/cjs/core/update.js.map +1 -1
- package/dist/cjs/did-btcr2.js +100 -91
- package/dist/cjs/did-btcr2.js.map +1 -1
- package/dist/cjs/index.js +3 -1
- package/dist/cjs/index.js.map +1 -1
- package/dist/cjs/utils/appendix.js +6 -15
- package/dist/cjs/utils/appendix.js.map +1 -1
- package/dist/cjs/utils/did-document-builder.js +5 -6
- package/dist/cjs/utils/did-document-builder.js.map +1 -1
- package/dist/cjs/utils/did-document.js +42 -38
- package/dist/cjs/utils/did-document.js.map +1 -1
- package/dist/esm/core/beacon/beacon.js +25 -0
- package/dist/esm/core/beacon/beacon.js.map +1 -0
- package/dist/esm/core/beacon/cas-beacon.js +20 -36
- package/dist/esm/core/beacon/cas-beacon.js.map +1 -1
- package/dist/esm/core/beacon/error.js +4 -4
- package/dist/esm/core/beacon/error.js.map +1 -1
- package/dist/esm/core/beacon/factory.js +5 -7
- package/dist/esm/core/beacon/factory.js.map +1 -1
- package/dist/esm/core/beacon/interfaces.js +1 -31
- package/dist/esm/core/beacon/interfaces.js.map +1 -1
- package/dist/esm/core/beacon/signal-discovery.js +183 -0
- package/dist/esm/core/beacon/signal-discovery.js.map +1 -0
- package/dist/esm/core/beacon/singleton.js +56 -86
- package/dist/esm/core/beacon/singleton.js.map +1 -1
- package/dist/esm/core/beacon/smt-beacon.js +22 -39
- package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
- package/dist/esm/core/beacon/utils.js +4 -9
- package/dist/esm/core/beacon/utils.js.map +1 -1
- package/dist/esm/core/resolve.js +121 -307
- package/dist/esm/core/resolve.js.map +1 -1
- package/dist/esm/core/update.js +62 -154
- package/dist/esm/core/update.js.map +1 -1
- package/dist/esm/did-btcr2.js +100 -91
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/index.js +3 -1
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/utils/appendix.js +6 -15
- package/dist/esm/utils/appendix.js.map +1 -1
- package/dist/esm/utils/did-document-builder.js +5 -6
- package/dist/esm/utils/did-document-builder.js.map +1 -1
- package/dist/esm/utils/did-document.js +42 -38
- package/dist/esm/utils/did-document.js.map +1 -1
- package/dist/types/core/beacon/beacon.d.ts +44 -0
- package/dist/types/core/beacon/beacon.d.ts.map +1 -0
- package/dist/types/core/beacon/cas-beacon.d.ts +19 -30
- package/dist/types/core/beacon/cas-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/error.d.ts +2 -2
- package/dist/types/core/beacon/error.d.ts.map +1 -1
- package/dist/types/core/beacon/factory.d.ts +4 -6
- package/dist/types/core/beacon/factory.d.ts.map +1 -1
- package/dist/types/core/beacon/interfaces.d.ts +7 -46
- package/dist/types/core/beacon/interfaces.d.ts.map +1 -1
- package/dist/types/core/beacon/signal-discovery.d.ts +25 -0
- package/dist/types/core/beacon/signal-discovery.d.ts.map +1 -0
- package/dist/types/core/beacon/singleton.d.ts +17 -30
- package/dist/types/core/beacon/singleton.d.ts.map +1 -1
- package/dist/types/core/beacon/smt-beacon.d.ts +21 -33
- package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
- package/dist/types/core/beacon/utils.d.ts.map +1 -1
- package/dist/types/core/interfaces.d.ts +1 -8
- package/dist/types/core/interfaces.d.ts.map +1 -1
- package/dist/types/core/resolve.d.ts +34 -47
- package/dist/types/core/resolve.d.ts.map +1 -1
- package/dist/types/core/types.d.ts +21 -8
- package/dist/types/core/types.d.ts.map +1 -1
- package/dist/types/core/update.d.ts +30 -73
- package/dist/types/core/update.d.ts.map +1 -1
- package/dist/types/did-btcr2.d.ts +44 -47
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/index.d.ts +3 -1
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/utils/appendix.d.ts.map +1 -1
- package/dist/types/utils/did-document-builder.d.ts +5 -1
- package/dist/types/utils/did-document-builder.d.ts.map +1 -1
- package/dist/types/utils/did-document.d.ts +26 -21
- package/dist/types/utils/did-document.d.ts.map +1 -1
- package/package.json +8 -7
- package/src/core/beacon/beacon.ts +58 -0
- package/src/core/beacon/cas-beacon.ts +30 -44
- package/src/core/beacon/error.ts +5 -6
- package/src/core/beacon/factory.ts +7 -9
- package/src/core/beacon/interfaces.ts +7 -64
- package/src/core/beacon/signal-discovery.ts +237 -0
- package/src/core/beacon/singleton.ts +78 -100
- package/src/core/beacon/smt-beacon.ts +32 -49
- package/src/core/beacon/utils.ts +16 -13
- package/src/core/interfaces.ts +1 -9
- package/src/core/resolve.ts +163 -395
- package/src/core/types.ts +25 -8
- package/src/core/update.ts +91 -236
- package/src/did-btcr2.ts +154 -116
- package/src/index.ts +8 -1
- package/src/utils/appendix.ts +8 -22
- package/src/utils/did-document-builder.ts +5 -7
- package/src/utils/did-document.ts +80 -73
package/src/core/resolve.ts
CHANGED
|
@@ -1,12 +1,9 @@
|
|
|
1
1
|
import {
|
|
2
2
|
BitcoinNetworkConnection,
|
|
3
|
-
|
|
4
|
-
GENESIS_TX_ID,
|
|
5
|
-
getNetwork,
|
|
6
|
-
RawTransactionV2,
|
|
7
|
-
TXIN_WITNESS_COINBASE
|
|
3
|
+
getNetwork
|
|
8
4
|
} from '@did-btcr2/bitcoin';
|
|
9
5
|
import {
|
|
6
|
+
Canonicalization,
|
|
10
7
|
DateUtils,
|
|
11
8
|
IdentifierHrp,
|
|
12
9
|
INVALID_DID,
|
|
@@ -15,24 +12,24 @@ import {
|
|
|
15
12
|
JSONPatch,
|
|
16
13
|
JSONUtils,
|
|
17
14
|
LATE_PUBLISHING_ERROR,
|
|
18
|
-
MethodError,
|
|
19
15
|
MISSING_UPDATE_DATA,
|
|
20
16
|
ResolveError
|
|
21
17
|
} from '@did-btcr2/common';
|
|
22
18
|
import {
|
|
23
19
|
BIP340Cryptosuite,
|
|
24
20
|
BIP340DataIntegrityProof,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
21
|
+
SchnorrMultikey,
|
|
22
|
+
SignedBTCR2Update,
|
|
23
|
+
UnsignedBTCR2Update
|
|
28
24
|
} from '@did-btcr2/cryptosuite';
|
|
29
25
|
import { CompressedSecp256k1PublicKey } from '@did-btcr2/keypair';
|
|
30
26
|
import { bytesToHex } from '@noble/hashes/utils';
|
|
31
|
-
import {
|
|
27
|
+
import { DidBtcr2 } from '../did-btcr2.js';
|
|
32
28
|
import { Appendix } from '../utils/appendix.js';
|
|
33
29
|
import { DidDocument, ID_PLACEHOLDER_VALUE } from '../utils/did-document.js';
|
|
34
30
|
import { BeaconFactory } from './beacon/factory.js';
|
|
35
|
-
import { BeaconService,
|
|
31
|
+
import { BeaconService, BlockMetadata } from './beacon/interfaces.js';
|
|
32
|
+
import { BeaconSignalDiscovery } from './beacon/signal-discovery.js';
|
|
36
33
|
import { BeaconUtils } from './beacon/utils.js';
|
|
37
34
|
import { DidComponents, Identifier } from './identifier.js';
|
|
38
35
|
import { SMTProof } from './interfaces.js';
|
|
@@ -42,10 +39,13 @@ import { CASAnnouncement, Sidecar, SidecarData } from './types.js';
|
|
|
42
39
|
* The response object for DID Resolution.
|
|
43
40
|
*/
|
|
44
41
|
export interface DidResolutionResponse {
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
42
|
+
didDocument: DidDocument;
|
|
43
|
+
metadata: {
|
|
44
|
+
confirmations?: number;
|
|
45
|
+
versionId: string;
|
|
46
|
+
updated?: string;
|
|
47
|
+
deactivated?: boolean;
|
|
48
|
+
}
|
|
49
49
|
}
|
|
50
50
|
|
|
51
51
|
/**
|
|
@@ -58,79 +58,6 @@ export interface DidResolutionResponse {
|
|
|
58
58
|
* @type {Resolve}
|
|
59
59
|
*/
|
|
60
60
|
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
61
|
/**
|
|
135
62
|
* 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
63
|
* @param {DidComponents} didComponents The decoded components of the did.
|
|
@@ -156,7 +83,6 @@ export class Resolve {
|
|
|
156
83
|
|
|
157
84
|
return new DidDocument({
|
|
158
85
|
id : did,
|
|
159
|
-
controller : [did],
|
|
160
86
|
verificationMethod : [{
|
|
161
87
|
id : `${did}#initialKey`,
|
|
162
88
|
type : 'Multikey',
|
|
@@ -172,21 +98,21 @@ export class Resolve {
|
|
|
172
98
|
* @param {DidComponents} didComponents BTCR2 DID components used to resolve the DID Document
|
|
173
99
|
* @param {GenesisDocument} genesisDocument The genesis document for resolving the DID Document.
|
|
174
100
|
* @returns {Promise<DidDocument>} The resolved DID Document object
|
|
175
|
-
* @throws {
|
|
101
|
+
* @throws {ResolveError} InvalidDidDocument if not conformant to DID Core v1.1
|
|
176
102
|
*/
|
|
177
103
|
static async external(
|
|
178
104
|
didComponents: DidComponents,
|
|
179
105
|
genesisDocument: object,
|
|
180
106
|
): Promise<DidDocument> {
|
|
181
107
|
// Canonicalize and sha256 hash the currentDocument
|
|
182
|
-
const hashBytes =
|
|
108
|
+
const hashBytes = Canonicalization.process(genesisDocument, { encoding: 'hex' });
|
|
183
109
|
|
|
184
110
|
// Compare the genesisBytes to the hashBytes
|
|
185
111
|
const genesisBytes = bytesToHex(didComponents.genesisBytes);
|
|
186
112
|
|
|
187
113
|
// If the genesisBytes do not match the hashBytes, throw an error
|
|
188
114
|
if (genesisBytes !== hashBytes) {
|
|
189
|
-
throw new
|
|
115
|
+
throw new ResolveError(
|
|
190
116
|
`Initial document mismatch: genesisBytes ${genesisBytes} !== hashBytes ${hashBytes}`,
|
|
191
117
|
INVALID_DID_DOCUMENT, { genesisBytes, hashBytes }
|
|
192
118
|
);
|
|
@@ -204,54 +130,124 @@ export class Resolve {
|
|
|
204
130
|
return new DidDocument(currentDocument);
|
|
205
131
|
}
|
|
206
132
|
|
|
133
|
+
/**
|
|
134
|
+
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-sidecar-data | Process Sidecar Data}
|
|
135
|
+
* @param {Sidecar} sidecar The sidecar data to process.
|
|
136
|
+
* @returns {ProcessedSidecar} The processed sidecar data containing maps of updates, CAS announcements, and SMT proofs.
|
|
137
|
+
*/
|
|
138
|
+
static sidecarData(sidecar: Sidecar = {} as Sidecar): SidecarData {
|
|
139
|
+
// BTCR2 Signed Updates map
|
|
140
|
+
const updateMap = new Map<string, SignedBTCR2Update>();
|
|
141
|
+
if(sidecar.updates?.length)
|
|
142
|
+
for(const update of sidecar.updates) {
|
|
143
|
+
updateMap.set(Canonicalization.process(update, { encoding: 'hex' }), update);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
// CAS Announcements map
|
|
147
|
+
const casMap = new Map<string, CASAnnouncement>();
|
|
148
|
+
if(sidecar.casUpdates?.length)
|
|
149
|
+
for(const update of sidecar.casUpdates) {
|
|
150
|
+
casMap.set(Canonicalization.process(update, { encoding: 'hex' }), update);
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// SMT Proofs map
|
|
154
|
+
const smtMap = new Map<string, SMTProof>();
|
|
155
|
+
if(sidecar.smtProofs?.length)
|
|
156
|
+
for(const proof of sidecar.smtProofs) {
|
|
157
|
+
smtMap.set(proof.id, proof);
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
return { updateMap, casMap, smtMap };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#establish-current-document | 7.2.d Establish current_document}.
|
|
165
|
+
* Resolution begins by creating an Initial Did Document called current_document (Current DID Document).
|
|
166
|
+
* The current_document is iteratively patched with BTCR2 Signed Updates announced by Authorized Beacon Signals.
|
|
167
|
+
* @param {DidComponents} didComponents The decoded components of the did.
|
|
168
|
+
* @param {GenesisDocument} genesisDocument The genesis document for resolving the DID Document.
|
|
169
|
+
* @returns {Promise<DidDocument>} The resolved DID Document object.
|
|
170
|
+
* @throws {ResolveError} if the DID hrp is invalid, no sidecarData passed and hrp = "x".
|
|
171
|
+
*/
|
|
172
|
+
|
|
173
|
+
static async currentDocument(
|
|
174
|
+
didComponents: DidComponents,
|
|
175
|
+
genesisDocument?: object,
|
|
176
|
+
): Promise<DidDocument> {
|
|
177
|
+
// Deconstruct the hrp from the components
|
|
178
|
+
const { hrp, genesisBytes } = didComponents;
|
|
179
|
+
|
|
180
|
+
// If hrp `x`, perform external resolution
|
|
181
|
+
if (hrp === IdentifierHrp.x) {
|
|
182
|
+
if(!genesisDocument)
|
|
183
|
+
throw new ResolveError(
|
|
184
|
+
'External resolution requires genesisDocument',
|
|
185
|
+
MISSING_UPDATE_DATA, { didComponents }
|
|
186
|
+
);
|
|
187
|
+
return await this.external(didComponents, genesisDocument);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Check for hrp `k`
|
|
191
|
+
if(hrp === IdentifierHrp.k){
|
|
192
|
+
// Validate genesis bytes as a compressed secp256k1 public key
|
|
193
|
+
if(!CompressedSecp256k1PublicKey.isValid(genesisBytes)) {
|
|
194
|
+
throw new ResolveError(
|
|
195
|
+
'Deterministic resolution requires valid secp256k1 public key',
|
|
196
|
+
INVALID_DID, { genesisBytes }
|
|
197
|
+
);
|
|
198
|
+
}
|
|
199
|
+
// Perform deterministic resolution
|
|
200
|
+
return this.deterministic(didComponents);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Else, throw an error for unsupported hrp
|
|
204
|
+
throw new ResolveError(`Unsupported DID hrp ${hrp}`, INVALID_DID, { hrp });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
207
|
/**
|
|
208
208
|
* Finds uses the beacon services in the currentDocument to scan for onchain Beacon Signals (transactions) containing
|
|
209
209
|
* Signal Bytes (last output in OP_RETURN transaction).
|
|
210
210
|
* @param {Array<BeaconService>} beaconServices The array of BeaconService objects to search for signals.
|
|
211
211
|
* @param {SidecarData} sidecarData The sidecar data containing maps of updates, CAS announcements, and SMT proofs.
|
|
212
212
|
* @param {BitcoinNetworkConnection} bitcoin The bitcoin network connection used to fetch beacon signals
|
|
213
|
-
* @
|
|
214
|
-
* @returns {Promise<Array<[BTCR2SignedUpdate, BlockMetadata]>>} The array of BTCR2 Signed Updates announced by the Beacon Signals.
|
|
213
|
+
* @returns {Promise<Array<[SignedBTCR2Update, BlockMetadata]>>} The array of BTCR2 Signed Updates announced by the Beacon Signals.
|
|
215
214
|
*/
|
|
216
|
-
static async
|
|
215
|
+
static async beaconSignals(
|
|
217
216
|
beaconServices: Array<BeaconService>,
|
|
218
217
|
sidecarData: SidecarData,
|
|
219
|
-
bitcoin: BitcoinNetworkConnection
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
// Append the processed updates to the updates array
|
|
238
|
-
unsortedUpdates.push(...processed);
|
|
239
|
-
}
|
|
218
|
+
bitcoin: BitcoinNetworkConnection
|
|
219
|
+
): Promise<Array<[SignedBTCR2Update, BlockMetadata]>> {
|
|
220
|
+
// Discover Beacon Signals via indexer server (esplora/electrs) or full node
|
|
221
|
+
const beaconServicesSignals = bitcoin.network.rest
|
|
222
|
+
? await BeaconSignalDiscovery.indexer(beaconServices, bitcoin)
|
|
223
|
+
: await BeaconSignalDiscovery.fullnode(beaconServices, bitcoin);
|
|
224
|
+
|
|
225
|
+
// Process each beacon's signals in parallel
|
|
226
|
+
const promises = await Promise.all(
|
|
227
|
+
Array.from(beaconServicesSignals.entries()).map(
|
|
228
|
+
async ([service, signals]) => {
|
|
229
|
+
// Skip beacons with no signals
|
|
230
|
+
if (!signals.length) return [];
|
|
231
|
+
// Establish a typed beacon and process its signals
|
|
232
|
+
return BeaconFactory.establish(service).processSignals(signals, sidecarData);
|
|
233
|
+
}
|
|
234
|
+
)
|
|
235
|
+
);
|
|
240
236
|
|
|
241
|
-
return
|
|
237
|
+
return promises.flat();
|
|
242
238
|
}
|
|
243
239
|
|
|
244
240
|
/**
|
|
245
241
|
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#process-updates | 7.2.f Process updates Array}.
|
|
246
242
|
* @param {DidDocument} currentDocument The current DID Document to apply the updates to.
|
|
247
|
-
* @param {Array<[
|
|
243
|
+
* @param {Array<[SignedBTCR2Update, BlockMetadata]>} unsortedUpdates The unsorted array of BTCR2 Signed Updates and their associated Block Metadata.
|
|
248
244
|
* @param {string} [versionTime] The optional version time to limit updates to.
|
|
249
245
|
* @param {string} [versionId] The optional version id to limit updates to.
|
|
250
246
|
* @returns {Promise<DidResolutionResponse>} The updated DID Document, number of confirmations, and version id.
|
|
251
247
|
*/
|
|
252
|
-
static async
|
|
248
|
+
static async updates(
|
|
253
249
|
currentDocument: DidDocument,
|
|
254
|
-
unsortedUpdates: Array<[
|
|
250
|
+
unsortedUpdates: Array<[SignedBTCR2Update, BlockMetadata]>,
|
|
255
251
|
versionTime?: string,
|
|
256
252
|
versionId?: string
|
|
257
253
|
): Promise<DidResolutionResponse> {
|
|
@@ -267,59 +263,62 @@ export class Resolve {
|
|
|
267
263
|
);
|
|
268
264
|
|
|
269
265
|
// Create a default response object
|
|
270
|
-
const response = {
|
|
271
|
-
currentDocument,
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
266
|
+
const response: DidResolutionResponse = {
|
|
267
|
+
didDocument : currentDocument,
|
|
268
|
+
metadata : {
|
|
269
|
+
versionId : `${currentVersionId}`,
|
|
270
|
+
confirmations : 0,
|
|
271
|
+
updated : '',
|
|
272
|
+
deactivated : currentDocument.deactivated || false
|
|
273
|
+
}
|
|
275
274
|
};
|
|
276
275
|
|
|
277
276
|
// Iterate over each (update block) pair
|
|
278
277
|
for(const [update, block] of updates) {
|
|
279
278
|
// Get the hash of the current document
|
|
280
|
-
const currentDocumentHash =
|
|
279
|
+
const currentDocumentHash = Canonicalization.process(response.didDocument, { encoding: 'base58' });
|
|
281
280
|
|
|
282
|
-
//
|
|
283
|
-
|
|
281
|
+
// Safely convert block.time to timestamp
|
|
282
|
+
const blocktime = DateUtils.blocktimeToTimestamp(block.time);
|
|
284
283
|
|
|
285
|
-
// Safely convert versionTime to timestamp
|
|
286
|
-
const vTime = DateUtils.dateStringToTimestamp(versionTime || '');
|
|
287
|
-
// Safely convert block.blocktime to timestamp
|
|
288
|
-
const bTime = DateUtils.blocktimeToTimestamp(block.time);
|
|
289
284
|
// Set the updated field to the blocktime of the current update
|
|
290
|
-
response.updated = DateUtils.toISOStringNonFractional(
|
|
285
|
+
response.metadata.updated = DateUtils.toISOStringNonFractional(blocktime);
|
|
286
|
+
|
|
287
|
+
// Set confirmations to the block confirmations
|
|
288
|
+
response.metadata.confirmations = block.confirmations;
|
|
291
289
|
|
|
292
290
|
// if resolutionOptions.versionTime is defined and the blocktime is more recent, return currentDocument
|
|
293
|
-
if(
|
|
294
|
-
|
|
291
|
+
if(versionTime) {
|
|
292
|
+
// Safely convert versionTime to timestamp
|
|
293
|
+
if(blocktime > DateUtils.dateStringToTimestamp(versionTime)) {
|
|
294
|
+
return response;
|
|
295
|
+
}
|
|
295
296
|
}
|
|
296
297
|
|
|
297
298
|
// Check update.targetVersionId against currentVersionId
|
|
298
299
|
// If update.targetVersionId <= currentVersionId, confirm duplicate update
|
|
299
300
|
if(update.targetVersionId <= currentVersionId) {
|
|
300
301
|
updateHashHistory.push(currentDocumentHash);
|
|
301
|
-
this.
|
|
302
|
+
this.confirmDuplicate(update, updateHashHistory);
|
|
302
303
|
}
|
|
303
304
|
|
|
304
305
|
// If update.targetVersionId == currentVersionId + 1, apply the update
|
|
305
306
|
else if (update.targetVersionId === currentVersionId + 1) {
|
|
306
|
-
// Prepend `z` to the sourceHash if it does not start with it
|
|
307
|
-
const sourceHash = update.sourceHash.startsWith('z') ? update.sourceHash : `z${update.sourceHash}`;
|
|
308
|
-
|
|
309
307
|
// Check if update.sourceHash !== currentDocumentHash
|
|
310
|
-
if (sourceHash !== currentDocumentHash) {
|
|
308
|
+
if (update.sourceHash !== currentDocumentHash) {
|
|
311
309
|
// Raise an INVALID_DID_UPDATE error if they do not match
|
|
312
310
|
throw new ResolveError(
|
|
313
311
|
`Hash mismatch: update.sourceHash !== currentDocumentHash`,
|
|
314
|
-
INVALID_DID_UPDATE, { sourceHash, currentDocumentHash }
|
|
312
|
+
INVALID_DID_UPDATE, { sourceHash: update.sourceHash, currentDocumentHash }
|
|
315
313
|
);
|
|
316
314
|
}
|
|
317
315
|
// Apply the update to the currentDocument and set it in the response
|
|
318
|
-
response.
|
|
316
|
+
response.didDocument = await this.applyUpdate(response.didDocument, update);
|
|
317
|
+
|
|
319
318
|
// Create unsigned_update by removing the proof property from update.
|
|
320
|
-
const unsignedUpdate = JSONUtils.deleteKeys(update, ['proof']) as
|
|
319
|
+
const unsignedUpdate = JSONUtils.deleteKeys(update, ['proof']) as UnsignedBTCR2Update;
|
|
321
320
|
// Push the canonicalized unsigned update hash to the updateHashHistory
|
|
322
|
-
updateHashHistory.push(
|
|
321
|
+
updateHashHistory.push(Canonicalization.process(unsignedUpdate, { encoding: 'base58' }));
|
|
323
322
|
}
|
|
324
323
|
|
|
325
324
|
// If update.targetVersionId > currentVersionId + 1, throw LATE_PUBLISHING error
|
|
@@ -328,15 +327,16 @@ export class Resolve {
|
|
|
328
327
|
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
329
328
|
'LATE_PUBLISHING_ERROR', {
|
|
330
329
|
targetVersionId : update.targetVersionId,
|
|
331
|
-
currentVersionId :
|
|
330
|
+
currentVersionId : currentVersionId + 1
|
|
332
331
|
}
|
|
333
332
|
);
|
|
334
333
|
}
|
|
335
334
|
|
|
336
335
|
// Increment currentVersionId
|
|
337
336
|
currentVersionId++;
|
|
338
|
-
|
|
339
|
-
response.versionId
|
|
337
|
+
|
|
338
|
+
// Set response.versionId to be the new currentVersionId
|
|
339
|
+
response.metadata.versionId = `${currentVersionId}`;
|
|
340
340
|
|
|
341
341
|
// If resolutionOptions.versionId is defined and <= currentVersionId, return currentDocument
|
|
342
342
|
if(currentVersionId >= Number(versionId)) {
|
|
@@ -345,6 +345,9 @@ export class Resolve {
|
|
|
345
345
|
|
|
346
346
|
// Check if the current document is deactivated before further processing
|
|
347
347
|
if(currentDocument.deactivated) {
|
|
348
|
+
// Set the response deactivated flag to true
|
|
349
|
+
response.metadata.deactivated = currentDocument.deactivated;
|
|
350
|
+
// If deactivated, stop processing further updates and return the response
|
|
348
351
|
return response;
|
|
349
352
|
}
|
|
350
353
|
}
|
|
@@ -353,250 +356,20 @@ export class Resolve {
|
|
|
353
356
|
return response;
|
|
354
357
|
}
|
|
355
358
|
|
|
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
359
|
/**
|
|
587
360
|
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/#confirm-duplicate-update | 7.2.f.1 Confirm Duplicate Update}.
|
|
588
361
|
* This step confirms that an update with a lower-than-expected targetVersionId is a true duplicate.
|
|
589
|
-
* @param {
|
|
362
|
+
* @param {SignedBTCR2Update} update The BTCR2 Signed Update to confirm as a duplicate.
|
|
590
363
|
* @returns {void} Does not return a value, but throws an error if the update is not a valid duplicate.
|
|
591
364
|
*/
|
|
592
|
-
static
|
|
365
|
+
static confirmDuplicate(update: SignedBTCR2Update, updateHashHistory: string[]): void {
|
|
593
366
|
// Create unsigned_update by removing the proof property from update.
|
|
594
|
-
const
|
|
367
|
+
const { proof: _, ...unsignedUpdate } = update;
|
|
595
368
|
|
|
596
369
|
// Hash unsignedUpdate with JSON Document Hashing algorithm
|
|
597
|
-
const unsignedUpdateHash =
|
|
370
|
+
const unsignedUpdateHash = Canonicalization.process(unsignedUpdate);
|
|
598
371
|
|
|
599
|
-
//
|
|
372
|
+
// Let historicalUpdateHash equal updateHashHistory[updateHashIndex].
|
|
600
373
|
const historicalUpdateHash = updateHashHistory[update.targetVersionId - 2];
|
|
601
374
|
|
|
602
375
|
// Check if the updateHash matches the historical hash
|
|
@@ -611,13 +384,13 @@ export class Resolve {
|
|
|
611
384
|
/**
|
|
612
385
|
* Implements subsection {@link https://dcdpr.github.io/did-btcr2/operations/resolve.html#apply-update | 7.2.f.3 Apply Update}.
|
|
613
386
|
* @param {DidDocument} currentDocument The current DID Document to apply the update to.
|
|
614
|
-
* @param {
|
|
387
|
+
* @param {SignedBTCR2Update} update The BTCR2 Signed Update to apply.
|
|
615
388
|
* @returns {Promise<DidDocument>} The updated DID Document after applying the update.
|
|
616
389
|
* @throws {ResolveError} If the update is invalid or cannot be applied.
|
|
617
390
|
*/
|
|
618
|
-
static async
|
|
391
|
+
static async applyUpdate(
|
|
619
392
|
currentDocument: DidDocument,
|
|
620
|
-
update:
|
|
393
|
+
update: SignedBTCR2Update
|
|
621
394
|
): Promise<DidDocument> {
|
|
622
395
|
// Get the capability id from the to update proof.
|
|
623
396
|
const capabilityId = update.proof?.capability;
|
|
@@ -652,19 +425,14 @@ export class Resolve {
|
|
|
652
425
|
// Get the verificationMethod from the DID Document using the verificationMethodId.
|
|
653
426
|
const vm = DidBtcr2.getSigningMethod(currentDocument, verificationMethodId);
|
|
654
427
|
|
|
655
|
-
// Split the vmId by the `#` to get the id and controller.
|
|
656
|
-
const [vmController, vmId] = vm.id.split('#');
|
|
657
|
-
|
|
658
428
|
// Construct a new SchnorrMultikey.
|
|
659
|
-
const multikey = SchnorrMultikey.
|
|
660
|
-
`#${vmId}`, vmController, vm.publicKeyMultibase
|
|
661
|
-
);
|
|
429
|
+
const multikey = SchnorrMultikey.fromVerificationMethod(vm);
|
|
662
430
|
|
|
663
431
|
// Construct a new BIP340Cryptosuite with the SchnorrMultikey.
|
|
664
432
|
const cryptosuite = new BIP340Cryptosuite(multikey);
|
|
665
433
|
|
|
666
434
|
// Canonicalize the update
|
|
667
|
-
const canonicalUpdate =
|
|
435
|
+
const canonicalUpdate = Canonicalization.canonicalize(update);
|
|
668
436
|
|
|
669
437
|
// Construct a DataIntegrityProof with the cryptosuite
|
|
670
438
|
const diProof = new BIP340DataIntegrityProof(cryptosuite);
|
|
@@ -674,7 +442,7 @@ export class Resolve {
|
|
|
674
442
|
|
|
675
443
|
// If the result is not verified, throw INVALID_DID_UPDATE error
|
|
676
444
|
if (!verificationResult.verified) {
|
|
677
|
-
throw new
|
|
445
|
+
throw new ResolveError(
|
|
678
446
|
'Invalid update: proof not verified',
|
|
679
447
|
INVALID_DID_UPDATE, verificationResult
|
|
680
448
|
);
|
|
@@ -687,7 +455,7 @@ export class Resolve {
|
|
|
687
455
|
DidDocument.validate(updatedDocument);
|
|
688
456
|
|
|
689
457
|
// Canonicalize and hash the updatedDocument to get the currentDocumentHash.
|
|
690
|
-
const currentDocumentHash =
|
|
458
|
+
const currentDocumentHash = Canonicalization.process(updatedDocument, { encoding: 'base58' });
|
|
691
459
|
|
|
692
460
|
// Prepare the update targetHash for comparison with currentDocumentHash.
|
|
693
461
|
const updateTargetHash = update.targetHash.startsWith('z') ? update.targetHash : `z${update.targetHash}`;
|
|
@@ -695,7 +463,7 @@ export class Resolve {
|
|
|
695
463
|
// Make sure the update.targetHash equals currentDocumentHash.
|
|
696
464
|
if (updateTargetHash !== currentDocumentHash) {
|
|
697
465
|
// If they do not match, throw INVALID_DID_UPDATE error.
|
|
698
|
-
throw new
|
|
466
|
+
throw new ResolveError(
|
|
699
467
|
`Invalid update: updateTargetHash !== currentDocumentHash`,
|
|
700
468
|
INVALID_DID_UPDATE, { updateTargetHash, currentDocumentHash }
|
|
701
469
|
);
|