@did-btcr2/method 0.62.0 → 0.64.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 +14 -1
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +3 -3
- package/dist/browser.mjs +3 -3
- package/dist/cjs/index.js +1269 -986
- package/dist/esm/core/beacon/signal-discovery.js +34 -1
- package/dist/esm/core/beacon/signal-discovery.js.map +1 -1
- package/dist/esm/core/btcr2-update.js +11 -0
- package/dist/esm/core/btcr2-update.js.map +1 -1
- package/dist/esm/core/resolver.js +345 -252
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/esm/core/updater.js +33 -3
- package/dist/esm/core/updater.js.map +1 -1
- package/dist/esm/did-btcr2.js +53 -20
- package/dist/esm/did-btcr2.js.map +1 -1
- package/dist/esm/utils/appendix.js +39 -2
- package/dist/esm/utils/appendix.js.map +1 -1
- package/dist/esm/utils/error-cause.js +16 -0
- package/dist/esm/utils/error-cause.js.map +1 -0
- package/dist/types/core/beacon/interfaces.d.ts +9 -1
- package/dist/types/core/beacon/interfaces.d.ts.map +1 -1
- package/dist/types/core/beacon/signal-discovery.d.ts +12 -0
- package/dist/types/core/beacon/signal-discovery.d.ts.map +1 -1
- package/dist/types/core/btcr2-update.d.ts +15 -8
- package/dist/types/core/btcr2-update.d.ts.map +1 -1
- package/dist/types/core/interfaces.d.ts +16 -5
- package/dist/types/core/interfaces.d.ts.map +1 -1
- package/dist/types/core/resolver.d.ts +37 -23
- package/dist/types/core/resolver.d.ts.map +1 -1
- package/dist/types/core/updater.d.ts.map +1 -1
- package/dist/types/did-btcr2.d.ts +24 -3
- package/dist/types/did-btcr2.d.ts.map +1 -1
- package/dist/types/utils/appendix.d.ts +24 -0
- package/dist/types/utils/appendix.d.ts.map +1 -1
- package/dist/types/utils/error-cause.d.ts +16 -0
- package/dist/types/utils/error-cause.d.ts.map +1 -0
- package/package.json +3 -3
- package/src/core/beacon/interfaces.ts +10 -1
- package/src/core/beacon/signal-discovery.ts +44 -1
- package/src/core/btcr2-update.ts +20 -8
- package/src/core/interfaces.ts +16 -5
- package/src/core/resolver.ts +420 -315
- package/src/core/updater.ts +41 -3
- package/src/did-btcr2.ts +70 -25
- package/src/utils/appendix.ts +48 -2
- package/src/utils/error-cause.ts +23 -0
package/src/core/updater.ts
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import type { BitcoinConnection } from '@did-btcr2/bitcoin';
|
|
2
2
|
import type { PatchOperation } from '@did-btcr2/common';
|
|
3
|
-
import { canonicalHash, INVALID_DID_UPDATE, JSONPatch, UpdateError } from '@did-btcr2/common';
|
|
3
|
+
import { canonicalHash, canonicalize, INVALID_DID_UPDATE, JSONPatch, UpdateError } from '@did-btcr2/common';
|
|
4
4
|
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 { BTCR2_UPDATE_CONTEXT } from './btcr2-update.js';
|
|
8
8
|
import { DidDocument, type Btcr2DidDocument, type DidVerificationMethod } from '../utils/did-document.js';
|
|
9
|
+
import { errorCause } from '../utils/error-cause.js';
|
|
9
10
|
import type { BroadcastResult } from './beacon/beacon.js';
|
|
10
11
|
import type { CASBroadcastOptions } from './beacon/cas-beacon.js';
|
|
11
12
|
import { BeaconFactory } from './beacon/factory.js';
|
|
@@ -224,7 +225,19 @@ export class Updater {
|
|
|
224
225
|
sourceHash : canonicalHash(sourceDocument),
|
|
225
226
|
};
|
|
226
227
|
|
|
227
|
-
|
|
228
|
+
// Spec "Construct BTCR2 Unsigned Update": apply jsonPatch strictly. The first operation
|
|
229
|
+
// that fails, including a failed test, fails the whole patch. A patch that a lenient
|
|
230
|
+
// library applies with no effect is refused here, before it is signed and announced: a
|
|
231
|
+
// conformant resolver would reject the announced update.
|
|
232
|
+
let targetDocument: Record<string, any>;
|
|
233
|
+
try {
|
|
234
|
+
targetDocument = JSONPatch.apply(sourceDocument, patches, { strict: true });
|
|
235
|
+
} catch(error) {
|
|
236
|
+
throw new UpdateError(
|
|
237
|
+
`Invalid patch: ${errorCause(error).message}`,
|
|
238
|
+
INVALID_DID_UPDATE, { cause: errorCause(error) }
|
|
239
|
+
);
|
|
240
|
+
}
|
|
228
241
|
|
|
229
242
|
// Spec (operations/update.md): "An INVALID_DID_UPDATE error MUST be raised if
|
|
230
243
|
// didTargetDocument.id is not equal to didSourceDocument.id." `DidDocument.isValid`
|
|
@@ -327,7 +340,32 @@ export class Updater {
|
|
|
327
340
|
};
|
|
328
341
|
|
|
329
342
|
const diproof = multikey.toCryptosuite().toDataIntegrityProof();
|
|
330
|
-
|
|
343
|
+
const signedUpdate = diproof.addProof(unsignedUpdate, config) as SignedBTCR2Update;
|
|
344
|
+
|
|
345
|
+
// Spec "Construct BTCR2 Signed Update": verify update.proof before the announcement,
|
|
346
|
+
// with the public key that the verification method publishes, not the signer's key. A
|
|
347
|
+
// signer that returns a wrong signature for the right key passes the key comparison
|
|
348
|
+
// above. An announced update with an invalid proof permanently invalidates the DID, so
|
|
349
|
+
// the failure surfaces here, before the state machine asks for funding.
|
|
350
|
+
let verified: boolean;
|
|
351
|
+
try {
|
|
352
|
+
const verifier = SchnorrMultikey.fromVerificationMethod({ ...verificationMethod, id: absoluteMethodId });
|
|
353
|
+
verified = verifier.toCryptosuite().toDataIntegrityProof()
|
|
354
|
+
.verifyProof(canonicalize(signedUpdate), 'capabilityInvocation').verified;
|
|
355
|
+
} catch(error) {
|
|
356
|
+
throw new UpdateError(
|
|
357
|
+
`Invalid update: the proof does not verify with the public key of "${verificationMethod.id}": `
|
|
358
|
+
+ errorCause(error).message,
|
|
359
|
+
INVALID_DID_UPDATE, { verificationMethodId: verificationMethod.id, cause: errorCause(error) }
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
if(!verified) {
|
|
363
|
+
throw new UpdateError(
|
|
364
|
+
`Invalid update: the proof does not verify with the public key of "${verificationMethod.id}".`,
|
|
365
|
+
INVALID_DID_UPDATE, { verificationMethodId: verificationMethod.id }
|
|
366
|
+
);
|
|
367
|
+
}
|
|
368
|
+
return signedUpdate;
|
|
331
369
|
}
|
|
332
370
|
|
|
333
371
|
/**
|
package/src/did-btcr2.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
DidErrorCode
|
|
19
19
|
} from '@web5/dids';
|
|
20
20
|
import type { BeaconService } from './core/beacon/interfaces.js';
|
|
21
|
+
import { DEACTIVATION_PATCH } from './core/btcr2-update.js';
|
|
21
22
|
import { Identifier } from './core/identifier.js';
|
|
22
23
|
import type { ResolutionOptions } from './core/interfaces.js';
|
|
23
24
|
import { Resolver } from './core/resolver.js';
|
|
@@ -155,9 +156,11 @@ export class DidBtcr2 implements DidMethod {
|
|
|
155
156
|
* @param {string} params.verificationMethodId The verification method ID to sign with.
|
|
156
157
|
* @param {string} params.beaconId The beacon service ID to broadcast through.
|
|
157
158
|
* @returns {Updater} A sans-I/O state machine for driving the update.
|
|
158
|
-
* @throws {UpdateError}
|
|
159
|
-
*
|
|
160
|
-
*
|
|
159
|
+
* @throws {UpdateError} `INVALID_DID_UPDATE` if `sourceVersionId` is not an integer of at
|
|
160
|
+
* least 1, if no entry of `capabilityInvocation` identifies the verification method, if a
|
|
161
|
+
* reference entry names no member of `verificationMethod`, or if the beacon service is not
|
|
162
|
+
* found. `INVALID_DID_DOCUMENT` if the method is not of type `Multikey` or does not have a
|
|
163
|
+
* `zQ3s` publicKeyMultibase prefix.
|
|
161
164
|
*/
|
|
162
165
|
static update({
|
|
163
166
|
sourceDocument,
|
|
@@ -172,30 +175,37 @@ export class DidBtcr2 implements DidMethod {
|
|
|
172
175
|
verificationMethodId: string;
|
|
173
176
|
beaconId: string;
|
|
174
177
|
}): Updater {
|
|
175
|
-
//
|
|
176
|
-
//
|
|
177
|
-
//
|
|
178
|
-
|
|
179
|
-
// update this factory authorizes is one the resolver will also accept.
|
|
180
|
-
const authorizedMethodId = Appendix.relationshipMethodId(verificationMethodId, sourceDocument.id);
|
|
181
|
-
const authorized = authorizedMethodId !== undefined && sourceDocument.capabilityInvocation?.some(
|
|
182
|
-
entry => Appendix.relationshipMethodId(entry, sourceDocument.id) === authorizedMethodId
|
|
183
|
-
);
|
|
184
|
-
if(!authorized) {
|
|
178
|
+
// The version of the source document is a positive integer: versionId starts at 1, and
|
|
179
|
+
// targetVersionId is sourceVersionId + 1. Without the guard, Number(undefined) + 1 is
|
|
180
|
+
// NaN, and the update carries no usable version.
|
|
181
|
+
if(!Number.isInteger(sourceVersionId) || sourceVersionId < 1) {
|
|
185
182
|
throw new UpdateError(
|
|
186
|
-
|
|
187
|
-
|
|
183
|
+
`Invalid sourceVersionId: expected an integer of at least 1, got ${String(sourceVersionId)}.`,
|
|
184
|
+
INVALID_DID_UPDATE, { sourceVersionId }
|
|
188
185
|
);
|
|
189
186
|
}
|
|
190
187
|
|
|
191
|
-
//
|
|
192
|
-
|
|
188
|
+
// Spec "Construct BTCR2 Signed Update": an entry of capabilityInvocation must identify
|
|
189
|
+
// the verificationMethodId, in the reference form or the embedded form. Both sides are
|
|
190
|
+
// resolved to absolute DID URLs first, so the caller's spelling of the reference and the
|
|
191
|
+
// document's spelling of the entry may differ: either is legal per DID Core. This is the
|
|
192
|
+
// same rule the read path applies to an update's proof, so an update this factory
|
|
193
|
+
// authorizes is one the resolver will also accept.
|
|
194
|
+
const entry = Appendix.capabilityInvocationEntry(sourceDocument, verificationMethodId);
|
|
195
|
+
if(entry === undefined) {
|
|
196
|
+
throw new UpdateError(
|
|
197
|
+
'Invalid verificationMethodId: not authorized for capabilityInvocation',
|
|
198
|
+
INVALID_DID_UPDATE, { verificationMethodId, capabilityInvocation: sourceDocument.capabilityInvocation }
|
|
199
|
+
);
|
|
200
|
+
}
|
|
193
201
|
|
|
194
|
-
//
|
|
202
|
+
// The verification method is the embedded object of the entry, or the member of
|
|
203
|
+
// verificationMethod[] that a reference entry names.
|
|
204
|
+
const verificationMethod = Appendix.verificationMethodOfEntry(sourceDocument, entry);
|
|
195
205
|
if(!verificationMethod) {
|
|
196
206
|
throw new UpdateError(
|
|
197
|
-
'Invalid
|
|
198
|
-
|
|
207
|
+
'Invalid verificationMethodId: not found in source document',
|
|
208
|
+
INVALID_DID_UPDATE, { verificationMethodId }
|
|
199
209
|
);
|
|
200
210
|
}
|
|
201
211
|
|
|
@@ -233,16 +243,50 @@ export class DidBtcr2 implements DidMethod {
|
|
|
233
243
|
);
|
|
234
244
|
}
|
|
235
245
|
|
|
236
|
-
// Return a sans-I/O state machine the caller will drive
|
|
246
|
+
// Return a sans-I/O state machine the caller will drive. The prefix check above proves
|
|
247
|
+
// that the method carries a publicKeyMultibase, which the btcr2 method type requires.
|
|
237
248
|
return new Updater({
|
|
238
249
|
sourceDocument,
|
|
239
250
|
patches,
|
|
240
251
|
sourceVersionId,
|
|
241
|
-
verificationMethod,
|
|
252
|
+
verificationMethod : verificationMethod as DidVerificationMethod,
|
|
242
253
|
beaconService,
|
|
243
254
|
});
|
|
244
255
|
}
|
|
245
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Entry point for section {@link https://dcdpr.github.io/did-btcr2/operations/deactivate.html | 7.4 Deactivate}.
|
|
259
|
+
*
|
|
260
|
+
* Deactivate is the Update operation with the predetermined patch {@link DEACTIVATION_PATCH}:
|
|
261
|
+
* it adds the `deactivated` property with the value `true`. The factory returns the
|
|
262
|
+
* {@link Updater} that {@link DidBtcr2.update} returns for that patch, and the caller drives
|
|
263
|
+
* it in the same way. Resolution stops at the deactivation for good. The factory does not
|
|
264
|
+
* refuse a source document that is deactivated already; the api does (ADR 100).
|
|
265
|
+
*
|
|
266
|
+
* @param params Deactivation parameters: the parameters of {@link DidBtcr2.update} without `patches`.
|
|
267
|
+
* @returns {Updater} A sans-I/O state machine for driving the deactivation.
|
|
268
|
+
* @throws {UpdateError} As {@link DidBtcr2.update}.
|
|
269
|
+
*/
|
|
270
|
+
static deactivate({
|
|
271
|
+
sourceDocument,
|
|
272
|
+
sourceVersionId,
|
|
273
|
+
verificationMethodId,
|
|
274
|
+
beaconId,
|
|
275
|
+
}: {
|
|
276
|
+
sourceDocument: Btcr2DidDocument;
|
|
277
|
+
sourceVersionId: number;
|
|
278
|
+
verificationMethodId: string;
|
|
279
|
+
beaconId: string;
|
|
280
|
+
}): Updater {
|
|
281
|
+
return this.update({
|
|
282
|
+
sourceDocument,
|
|
283
|
+
patches : [{ ...DEACTIVATION_PATCH }],
|
|
284
|
+
sourceVersionId,
|
|
285
|
+
verificationMethodId,
|
|
286
|
+
beaconId,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
246
290
|
/**
|
|
247
291
|
* Given the W3C DID Document of a `did:btcr2` identifier, return the signing verification method that will be used
|
|
248
292
|
* for signing messages and credentials. If given, the `methodId` parameter is used to select the
|
|
@@ -272,9 +316,10 @@ export class DidBtcr2 implements DidMethod {
|
|
|
272
316
|
|
|
273
317
|
// An unusable target matches nothing: without this guard it compares equal to every
|
|
274
318
|
// method whose own id is unusable, and the document's first malformed method is
|
|
275
|
-
// returned as the signing method.
|
|
276
|
-
|
|
277
|
-
|
|
319
|
+
// returned as the signing method. The search covers verificationMethod[] first, then the
|
|
320
|
+
// methods that a verification relationship embeds.
|
|
321
|
+
const verificationMethod = targetId === undefined ? undefined : Appendix.getVerificationMethods(didDocument).find(
|
|
322
|
+
vm => Appendix.absoluteDidUrl(vm.id, didDocument.id) === targetId
|
|
278
323
|
);
|
|
279
324
|
|
|
280
325
|
// If no verification method is found, throw an error
|
package/src/utils/appendix.ts
CHANGED
|
@@ -60,6 +60,51 @@ export class Appendix {
|
|
|
60
60
|
return Appendix.absoluteDidUrl(id, did);
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
/**
|
|
64
|
+
* Finds the entry of `document.capabilityInvocation` that identifies `methodId`. A
|
|
65
|
+
* reference entry identifies it when the two DID URLs are equal. An embedded verification
|
|
66
|
+
* method object identifies it when its `id` is equal. Both spellings of a DID URL compare
|
|
67
|
+
* equal, as in {@link relationshipMethodId}. This is the lookup of the specification steps
|
|
68
|
+
* "Check `update.proof`" (the read path) and "Construct BTCR2 Signed Update" (the write
|
|
69
|
+
* path); the caller raises `INVALID_DID_UPDATE` when no entry identifies the method.
|
|
70
|
+
*
|
|
71
|
+
* @param {DidDocument} document The DID document.
|
|
72
|
+
* @param {unknown} methodId The verification method id, absolute or relative. A non-string yields `undefined`.
|
|
73
|
+
* @returns {string | DidVerificationMethod | undefined} The entry, or `undefined` if no entry identifies the id.
|
|
74
|
+
*/
|
|
75
|
+
public static capabilityInvocationEntry(
|
|
76
|
+
document: DidDocument,
|
|
77
|
+
methodId: unknown
|
|
78
|
+
): string | DidVerificationMethod | undefined {
|
|
79
|
+
const targetId = Appendix.relationshipMethodId(methodId, document.id);
|
|
80
|
+
if (targetId === undefined) return undefined;
|
|
81
|
+
return document.capabilityInvocation?.find(
|
|
82
|
+
entry => Appendix.relationshipMethodId(entry, document.id) === targetId
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Returns the verification method that a relationship entry denotes: the object itself
|
|
88
|
+
* when the entry embeds the method, else the member of `document.verificationMethod` whose
|
|
89
|
+
* `id` equals the reference. Both spellings of a DID URL compare equal. The caller raises
|
|
90
|
+
* `INVALID_DID_UPDATE` when a reference names no member.
|
|
91
|
+
*
|
|
92
|
+
* @param {DidDocument} document The DID document.
|
|
93
|
+
* @param {string | DidVerificationMethod} entry The relationship entry: a reference, or an embedded method.
|
|
94
|
+
* @returns {DidVerificationMethod | undefined} The method, or `undefined` if a reference names no member.
|
|
95
|
+
*/
|
|
96
|
+
public static verificationMethodOfEntry(
|
|
97
|
+
document: DidDocument,
|
|
98
|
+
entry: string | DidVerificationMethod
|
|
99
|
+
): DidVerificationMethod | undefined {
|
|
100
|
+
if (Appendix.isDidVerificationMethod(entry)) return entry;
|
|
101
|
+
const targetId = Appendix.absoluteDidUrl(entry, document.id);
|
|
102
|
+
if (targetId === undefined) return undefined;
|
|
103
|
+
return document.verificationMethod?.find(
|
|
104
|
+
method => Appendix.absoluteDidUrl(method?.id, document.id) === targetId
|
|
105
|
+
);
|
|
106
|
+
}
|
|
107
|
+
|
|
63
108
|
/**
|
|
64
109
|
* Validates that the given object is a DidVerificationMethod
|
|
65
110
|
* @param {unknown} obj The object to validate
|
|
@@ -194,13 +239,14 @@ export class Appendix {
|
|
|
194
239
|
const rootCapability = {} as RootCapability;
|
|
195
240
|
|
|
196
241
|
// 2. Set components to the result of capabilityId.split(":").
|
|
197
|
-
const
|
|
242
|
+
const components = capabilityId.split(':');
|
|
198
243
|
|
|
199
244
|
// 3. Validate components:
|
|
200
245
|
// 1. Assert length of components is 4.
|
|
201
|
-
if (
|
|
246
|
+
if (components.length !== 4) {
|
|
202
247
|
throw new DidError(DidErrorCode.InvalidDid, `Invalid capabilityId: ${capabilityId}`);
|
|
203
248
|
}
|
|
249
|
+
const [urn, zcap, root, did] = components;
|
|
204
250
|
|
|
205
251
|
// 2. components[0] == urn.
|
|
206
252
|
if (!urn || urn !== 'urn') {
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { DidMethodError } from '@did-btcr2/common';
|
|
2
|
+
|
|
3
|
+
/** The type and the message of an inner error, carried as `data.cause` by a wrapping error. */
|
|
4
|
+
export interface ErrorCause {
|
|
5
|
+
/** The `type` of a typed error, or the `name` of a plain `Error`. */
|
|
6
|
+
type: string;
|
|
7
|
+
|
|
8
|
+
/** The message of the inner error. */
|
|
9
|
+
message: string;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Describe an inner error for the `data.cause` of a wrapping typed error. The update paths
|
|
14
|
+
* raise `INVALID_DID_UPDATE` for every failure that the specification names; the inner error
|
|
15
|
+
* of the cryptosuite, the multikey, the hash decoder, or the common package rides along here.
|
|
16
|
+
* @param {unknown} error The inner error.
|
|
17
|
+
* @returns {ErrorCause} The type and the message of the inner error.
|
|
18
|
+
*/
|
|
19
|
+
export function errorCause(error: unknown): ErrorCause {
|
|
20
|
+
if(error instanceof DidMethodError) return { type: error.type, message: error.message };
|
|
21
|
+
if(error instanceof Error) return { type: error.name, message: error.message };
|
|
22
|
+
return { type: 'unknown', message: String(error) };
|
|
23
|
+
}
|