@did-btcr2/method 0.50.0 → 0.52.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 +13 -13
- package/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +3 -41554
- package/dist/browser.mjs +3 -41548
- package/dist/cjs/index.js +51 -21
- package/dist/esm/core/did-sender-resolver.js +74 -9
- package/dist/esm/core/did-sender-resolver.js.map +1 -1
- package/dist/esm/core/resolver.js +17 -8
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/types/core/did-sender-resolver.d.ts +49 -9
- package/dist/types/core/did-sender-resolver.d.ts.map +1 -1
- package/dist/types/core/resolver.d.ts.map +1 -1
- package/package.json +5 -19
- package/src/core/did-sender-resolver.ts +90 -9
- package/src/core/resolver.ts +17 -8
|
@@ -1,25 +1,106 @@
|
|
|
1
|
+
import { DidDocumentError, INVALID_DID_DOCUMENT } from '@did-btcr2/common';
|
|
2
|
+
import { SchnorrMultikey } from '@did-btcr2/cryptosuite';
|
|
1
3
|
import { CompressedSecp256k1PublicKey } from '@did-btcr2/keypair';
|
|
4
|
+
import type { DidDocument, DidVerificationMethod } from '../utils/did-document.js';
|
|
2
5
|
import { Identifier } from './identifier.js';
|
|
6
|
+
import { Resolver } from './resolver.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Derive the aggregation communication public key from a resolved did:btcr2 DID Document.
|
|
10
|
+
*
|
|
11
|
+
* The communication key is the verification method referenced by
|
|
12
|
+
* `capabilityInvocation[0]`, resolved to its public key. This is the exact relationship
|
|
13
|
+
* the method already enforces for DID updates (construct and sign require the signing
|
|
14
|
+
* method to be in `capabilityInvocation`, the update proof is built and verified with
|
|
15
|
+
* `proofPurpose: 'capabilityInvocation'`), so binding the transport communication key to
|
|
16
|
+
* it yields the invariant "transport-authenticated as D implies authorized to update D."
|
|
17
|
+
* For a KEY (`k1`) document the single deterministic key is already the sole
|
|
18
|
+
* `capabilityInvocation` entry, so this is a no-op for KEY DIDs.
|
|
19
|
+
*
|
|
20
|
+
* There is deliberately no `verificationMethod[0]` fallback: a document without
|
|
21
|
+
* `capabilityInvocation` cannot be updated at all, so it is useless for aggregation and is
|
|
22
|
+
* rejected here rather than bound to an unrelated key.
|
|
23
|
+
*
|
|
24
|
+
* @param {DidDocument} document The resolved DID Document (placeholder id already replaced).
|
|
25
|
+
* @returns {CompressedSecp256k1PublicKey} The compressed public key of the communication method.
|
|
26
|
+
* @throws {DidDocumentError} If `capabilityInvocation` is absent or its first entry does not
|
|
27
|
+
* resolve to a verification method in the document.
|
|
28
|
+
*/
|
|
29
|
+
export function getAggregationCommunicationKey(
|
|
30
|
+
document: DidDocument,
|
|
31
|
+
): CompressedSecp256k1PublicKey {
|
|
32
|
+
const invocation = document.capabilityInvocation?.[0];
|
|
33
|
+
if(invocation === undefined) {
|
|
34
|
+
throw new DidDocumentError(
|
|
35
|
+
'Cannot derive aggregation communication key: capabilityInvocation is absent',
|
|
36
|
+
INVALID_DID_DOCUMENT, { id: document.id }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Resolve capabilityInvocation[0] to a verification method: a string reference is
|
|
41
|
+
// dereferenced by id against verificationMethod; an embedded method is used directly.
|
|
42
|
+
// A local id lookup is used rather than getSigningMethod, which defaults to #initialKey
|
|
43
|
+
// and does not resolve embedded methods.
|
|
44
|
+
const vm: DidVerificationMethod | undefined = typeof invocation === 'string'
|
|
45
|
+
? document.verificationMethod?.find(method => method.id === invocation)
|
|
46
|
+
: invocation;
|
|
47
|
+
|
|
48
|
+
if(!vm) {
|
|
49
|
+
throw new DidDocumentError(
|
|
50
|
+
`Cannot derive aggregation communication key: capabilityInvocation[0] "${invocation}" `
|
|
51
|
+
+ 'does not resolve to a verification method',
|
|
52
|
+
INVALID_DID_DOCUMENT, { id: document.id, invocation }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
return SchnorrMultikey.fromVerificationMethod(vm).publicKey;
|
|
57
|
+
}
|
|
3
58
|
|
|
4
59
|
/**
|
|
5
60
|
* Resolve a did:btcr2 sender's communication public key from its DID, for the
|
|
6
|
-
* aggregation HTTP transport's `resolveSenderPk` option
|
|
7
|
-
* its genesis public key. The aggregation transport is DID-method-agnostic and does
|
|
8
|
-
* not name `Identifier`; method supplies this resolver when it wires the transport so
|
|
9
|
-
* a sender that is not a pre-registered peer can still be authenticated from its DID.
|
|
61
|
+
* aggregation HTTP transport's `resolveSenderPk` option.
|
|
10
62
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
63
|
+
* A KEY (`k1`) identifier decodes directly to its genesis public key: the DID string is
|
|
64
|
+
* the key. An EXTERNAL (`x1`) identifier is a commitment to the hash of a genesis
|
|
65
|
+
* document, so there is no key in the DID string. When the genesis document is supplied
|
|
66
|
+
* in-band (via `opts.genesisDocument`), it is self-verifying against the DID: `Resolver`'s
|
|
67
|
+
* external path recomputes its canonical hash, compares it to the identifier's genesis
|
|
68
|
+
* bytes (throwing on mismatch), and resolves it, after which the communication key is
|
|
69
|
+
* derived from `capabilityInvocation[0]` ({@link getAggregationCommunicationKey}). Without
|
|
70
|
+
* a genesis document, an `x1` DID still resolves to `undefined`, so callers that pass no
|
|
71
|
+
* second argument behave exactly as before.
|
|
72
|
+
*
|
|
73
|
+
* The aggregation transport is DID-method-agnostic and does not name `Identifier`; method
|
|
74
|
+
* supplies this resolver when it wires the transport so a sender that is not a
|
|
75
|
+
* pre-registered peer can still be authenticated from its DID.
|
|
76
|
+
*
|
|
77
|
+
* @param {string} did The sender's DID.
|
|
78
|
+
* @param {object} [opts] Optional resolution inputs.
|
|
79
|
+
* @param {object} [opts.genesisDocument] The `x1` sender's genesis document, carried in-band
|
|
80
|
+
* on the bootstrap opt-in. Ignored for KEY identifiers.
|
|
81
|
+
* @returns {CompressedSecp256k1PublicKey | undefined} The sender's compressed public key, or
|
|
82
|
+
* `undefined` when the DID is not a decodable did:btcr2 identifier, is an `x1` identifier
|
|
83
|
+
* with no (or a non-matching) genesis document, or has no usable communication key.
|
|
14
84
|
*/
|
|
15
|
-
export function resolveBtcr2SenderPk(
|
|
85
|
+
export function resolveBtcr2SenderPk(
|
|
86
|
+
did: string,
|
|
87
|
+
opts?: { genesisDocument?: object },
|
|
88
|
+
): CompressedSecp256k1PublicKey | undefined {
|
|
16
89
|
try {
|
|
17
90
|
const components = Identifier.decode(did);
|
|
18
91
|
if(components.idType === 'KEY') {
|
|
19
92
|
return new CompressedSecp256k1PublicKey(components.genesisBytes);
|
|
20
93
|
}
|
|
94
|
+
// EXTERNAL (x1): the DID commits to the hash of a genesis document. With the genesis
|
|
95
|
+
// supplied in-band, verify it hashes to the DID and derive the communication key from
|
|
96
|
+
// it; without it, there is no key to return.
|
|
97
|
+
if(opts?.genesisDocument) {
|
|
98
|
+
const document = Resolver.external(components, opts.genesisDocument);
|
|
99
|
+
return getAggregationCommunicationKey(document);
|
|
100
|
+
}
|
|
21
101
|
} catch {
|
|
22
|
-
// Not a decodable did:btcr2
|
|
102
|
+
// Not a decodable did:btcr2 identifier, the genesis does not hash to the DID, or the
|
|
103
|
+
// document has no usable capabilityInvocation communication key.
|
|
23
104
|
}
|
|
24
105
|
return undefined;
|
|
25
106
|
}
|
package/src/core/resolver.ts
CHANGED
|
@@ -419,15 +419,23 @@ export class Resolver {
|
|
|
419
419
|
}
|
|
420
420
|
}
|
|
421
421
|
|
|
422
|
-
// Check update.targetVersionId against currentVersionId
|
|
423
|
-
// If update.targetVersionId <= currentVersionId,
|
|
422
|
+
// Check update.targetVersionId against currentVersionId.
|
|
423
|
+
// If update.targetVersionId <= currentVersionId, this update re-announces a version
|
|
424
|
+
// that has already been applied. Confirm it is a true duplicate, then skip it: a
|
|
425
|
+
// duplicate does not advance the version counter (the increment and the
|
|
426
|
+
// metadata.versionId it sets run only on the apply path below), and confirmation
|
|
427
|
+
// compares against the update-hash history without appending to it, because the
|
|
428
|
+
// history already holds the applied update at updateHashHistory[targetVersionId - 2].
|
|
429
|
+
// Holding the increment off the duplicate path is the deliberate did:btcr2 deviation
|
|
430
|
+
// recorded in ADR 067: the read algorithm's "Increment current_version_id" belongs
|
|
431
|
+
// to the apply branch, not to every tuple.
|
|
424
432
|
if(update.targetVersionId <= currentVersionId) {
|
|
425
|
-
updateHashHistory.push(currentDocumentHash);
|
|
426
433
|
this.confirmDuplicate(update, updateHashHistory);
|
|
434
|
+
continue;
|
|
427
435
|
}
|
|
428
436
|
|
|
429
437
|
// If update.targetVersionId == currentVersionId + 1, apply the update
|
|
430
|
-
|
|
438
|
+
if (update.targetVersionId === currentVersionId + 1) {
|
|
431
439
|
// Check if update.sourceHash !== currentDocumentHash (byte comparison)
|
|
432
440
|
const sourceHashBytes = decodeHash(update.sourceHash, 'base64urlnopad');
|
|
433
441
|
if (!equalBytes(sourceHashBytes, currentDocumentHash)) {
|
|
@@ -448,11 +456,12 @@ export class Resolver {
|
|
|
448
456
|
updateHashHistory.push(canonicalHashBytes(unsignedUpdate));
|
|
449
457
|
}
|
|
450
458
|
|
|
451
|
-
//
|
|
452
|
-
|
|
459
|
+
// Otherwise update.targetVersionId > currentVersionId + 1: a version was skipped,
|
|
460
|
+
// so throw LATE_PUBLISHING error. The duplicate case already continued above.
|
|
461
|
+
else {
|
|
453
462
|
throw new ResolveError(
|
|
454
463
|
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
455
|
-
|
|
464
|
+
LATE_PUBLISHING_ERROR, {
|
|
456
465
|
targetVersionId : update.targetVersionId,
|
|
457
466
|
currentVersionId : currentVersionId + 1
|
|
458
467
|
}
|
|
@@ -462,7 +471,7 @@ export class Resolver {
|
|
|
462
471
|
// Increment currentVersionId
|
|
463
472
|
currentVersionId++;
|
|
464
473
|
|
|
465
|
-
// Set response.versionId to be the new
|
|
474
|
+
// Set response.versionId to be the new currentVersionId
|
|
466
475
|
response.metadata.versionId = `${currentVersionId}`;
|
|
467
476
|
|
|
468
477
|
// If resolutionOptions.versionId is defined and <= currentVersionId, return currentDocument
|