@interop/wallet-core 0.39.0 → 0.41.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 +5 -3
- package/dist/clients/listing.d.ts +4 -3
- package/dist/clients/listing.d.ts.map +1 -1
- package/dist/clients/listing.js.map +1 -1
- package/dist/clients/policy.d.ts +3 -2
- package/dist/clients/policy.d.ts.map +1 -1
- package/dist/clients/policy.js +3 -3
- package/dist/clients/policy.js.map +1 -1
- package/dist/clients/revocation.d.ts +14 -4
- package/dist/clients/revocation.d.ts.map +1 -1
- package/dist/clients/revocation.js +32 -22
- package/dist/clients/revocation.js.map +1 -1
- package/dist/enrollment/enrollment.d.ts +30 -2
- package/dist/enrollment/enrollment.d.ts.map +1 -1
- package/dist/enrollment/enrollment.js +44 -4
- package/dist/enrollment/enrollment.js.map +1 -1
- package/dist/enrollment/index.d.ts +4 -1
- package/dist/enrollment/index.d.ts.map +1 -1
- package/dist/enrollment/index.js +4 -1
- package/dist/enrollment/index.js.map +1 -1
- package/dist/keyring/fetch.d.ts +2 -1
- package/dist/keyring/fetch.d.ts.map +1 -1
- package/dist/keyring/fetch.js +7 -2
- package/dist/keyring/fetch.js.map +1 -1
- package/dist/keyring/index.d.ts +16 -9
- package/dist/keyring/index.d.ts.map +1 -1
- package/dist/keyring/index.js +15 -8
- package/dist/keyring/index.js.map +1 -1
- package/dist/keyring/kdf.d.ts +3 -1
- package/dist/keyring/kdf.d.ts.map +1 -1
- package/dist/keyring/kdf.js +16 -2
- package/dist/keyring/kdf.js.map +1 -1
- package/dist/keyring/record.d.ts +236 -50
- package/dist/keyring/record.d.ts.map +1 -1
- package/dist/keyring/record.js +348 -46
- package/dist/keyring/record.js.map +1 -1
- package/dist/keyring/unlockSpace.d.ts +25 -0
- package/dist/keyring/unlockSpace.d.ts.map +1 -1
- package/dist/keyring/unlockSpace.js +31 -2
- package/dist/keyring/unlockSpace.js.map +1 -1
- package/dist/keys/index.d.ts +4 -2
- package/dist/keys/index.d.ts.map +1 -1
- package/dist/keys/index.js +2 -1
- package/dist/keys/index.js.map +1 -1
- package/dist/keys/rosterLogStore.d.ts +18 -1
- package/dist/keys/rosterLogStore.d.ts.map +1 -1
- package/dist/keys/rosterLogStore.js +34 -6
- package/dist/keys/rosterLogStore.js.map +1 -1
- package/dist/keys/userKeyCascade.d.ts +10 -3
- package/dist/keys/userKeyCascade.d.ts.map +1 -1
- package/dist/keys/userKeyCascade.js +10 -4
- package/dist/keys/userKeyCascade.js.map +1 -1
- package/dist/keys/userKeyRoster.d.ts +39 -24
- package/dist/keys/userKeyRoster.d.ts.map +1 -1
- package/dist/keys/userKeyRoster.js +28 -77
- package/dist/keys/userKeyRoster.js.map +1 -1
- package/dist/recovery/index.d.ts +16 -3
- package/dist/recovery/index.d.ts.map +1 -1
- package/dist/recovery/index.js +14 -2
- package/dist/recovery/index.js.map +1 -1
- package/dist/recovery/recoveryCode.d.ts +7 -0
- package/dist/recovery/recoveryCode.d.ts.map +1 -1
- package/dist/recovery/recoveryCode.js +10 -0
- package/dist/recovery/recoveryCode.js.map +1 -1
- package/dist/recovery/recoveryDelegation.d.ts +120 -0
- package/dist/recovery/recoveryDelegation.d.ts.map +1 -0
- package/dist/recovery/recoveryDelegation.js +216 -0
- package/dist/recovery/recoveryDelegation.js.map +1 -0
- package/dist/recovery/recoveryRecord.d.ts +138 -46
- package/dist/recovery/recoveryRecord.d.ts.map +1 -1
- package/dist/recovery/recoveryRecord.js +231 -30
- package/dist/recovery/recoveryRecord.js.map +1 -1
- package/dist/recovery/recoveryWebvh.d.ts.map +1 -1
- package/dist/recovery/recoveryWebvh.js +20 -14
- package/dist/recovery/recoveryWebvh.js.map +1 -1
- package/dist/request/index.d.ts +1 -1
- package/dist/request/index.js +1 -1
- package/dist/request/matching.d.ts +24 -5
- package/dist/request/matching.d.ts.map +1 -1
- package/dist/request/matching.js +36 -39
- package/dist/request/matching.js.map +1 -1
- package/dist/request/walletInput.d.ts.map +1 -1
- package/dist/request/walletInput.js +3 -6
- package/dist/request/walletInput.js.map +1 -1
- package/dist/resourceLog/controller.d.ts +7 -4
- package/dist/resourceLog/controller.d.ts.map +1 -1
- package/dist/resourceLog/controller.js +17 -35
- package/dist/resourceLog/controller.js.map +1 -1
- package/dist/resourceLog/index.d.ts +1 -0
- package/dist/resourceLog/index.d.ts.map +1 -1
- package/dist/resourceLog/index.js +1 -0
- package/dist/resourceLog/index.js.map +1 -1
- package/dist/resourceLog/seal.d.ts +9 -1
- package/dist/resourceLog/seal.d.ts.map +1 -1
- package/dist/resourceLog/seal.js +21 -11
- package/dist/resourceLog/seal.js.map +1 -1
- package/dist/resourceLog/verify.d.ts.map +1 -1
- package/dist/resourceLog/verify.js +26 -9
- package/dist/resourceLog/verify.js.map +1 -1
- package/dist/resourceLog/vmFragment.d.ts +33 -0
- package/dist/resourceLog/vmFragment.d.ts.map +1 -0
- package/dist/resourceLog/vmFragment.js +39 -0
- package/dist/resourceLog/vmFragment.js.map +1 -0
- package/dist/space/publicLink.d.ts +2 -0
- package/dist/space/publicLink.d.ts.map +1 -1
- package/dist/space/publicLink.js +13 -1
- package/dist/space/publicLink.js.map +1 -1
- package/dist/space/wasLink.d.ts +5 -0
- package/dist/space/wasLink.d.ts.map +1 -1
- package/dist/space/wasLink.js +7 -2
- package/dist/space/wasLink.js.map +1 -1
- package/dist/webvh/didWeb.d.ts +3 -11
- package/dist/webvh/didWeb.d.ts.map +1 -1
- package/dist/webvh/didWeb.js +5 -1
- package/dist/webvh/didWeb.js.map +1 -1
- package/dist/webvh/didWebvh.d.ts +72 -1
- package/dist/webvh/didWebvh.d.ts.map +1 -1
- package/dist/webvh/didWebvh.js +111 -12
- package/dist/webvh/didWebvh.js.map +1 -1
- package/dist/webvh/index.d.ts +9 -2
- package/dist/webvh/index.d.ts.map +1 -1
- package/dist/webvh/index.js +8 -2
- package/dist/webvh/index.js.map +1 -1
- package/dist/webvh/keyAgreement.d.ts +56 -0
- package/dist/webvh/keyAgreement.d.ts.map +1 -0
- package/dist/webvh/keyAgreement.js +47 -0
- package/dist/webvh/keyAgreement.js.map +1 -0
- package/dist/webvh/listClients.d.ts +69 -18
- package/dist/webvh/listClients.d.ts.map +1 -1
- package/dist/webvh/listClients.js +141 -36
- package/dist/webvh/listClients.js.map +1 -1
- package/dist/webvh/revokeClient.d.ts +21 -11
- package/dist/webvh/revokeClient.d.ts.map +1 -1
- package/dist/webvh/revokeClient.js +31 -14
- package/dist/webvh/revokeClient.js.map +1 -1
- package/dist/webvh/verifyLog.d.ts.map +1 -1
- package/dist/webvh/verifyLog.js +5 -1
- package/dist/webvh/verifyLog.js.map +1 -1
- package/package.json +4 -4
|
@@ -1,80 +1,172 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
*/
|
|
1
|
+
import type { IKeyAgreementKey, IKeyResolver, IZcap } from '@interop/data-integrity-core';
|
|
2
|
+
import type { AccountPointer, RecordSigner, SignedRecord } from '../keyring/record.js';
|
|
4
3
|
/**
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* pointer
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
12
|
-
* a pure pointer.
|
|
13
|
-
*
|
|
14
|
-
* The wrap reuses the keyring record construction verbatim (cipher context and
|
|
15
|
-
* record-own epoch alike), so a recovery record IS a keyring record to every
|
|
16
|
-
* generic consumer (an ordinary `unwrapKeyringRecord` recovers its pointer and
|
|
17
|
-
* ignores the extra member) -- only the recovery flow demands the delegation.
|
|
4
|
+
* A recovery record's account binding is absent, malformed, or does not
|
|
5
|
+
* verify under the typed code's binding MAC key. Its own class, distinct from
|
|
6
|
+
* a proof or decrypt failure: this is the refusal that says the record's
|
|
7
|
+
* `{ controller, pointer }` core was not written by a holder of this code --
|
|
8
|
+
* a forged record redirecting recovery at another account, or a record from
|
|
9
|
+
* before the account moved hosts (either way the code cannot recover here
|
|
10
|
+
* and must be re-issued).
|
|
18
11
|
*/
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
12
|
+
export declare class RecoveryBindingError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
22
15
|
/**
|
|
23
|
-
* The unwrapped contents of a recovery keyring record: the
|
|
24
|
-
*
|
|
25
|
-
*
|
|
26
|
-
* without a Space).
|
|
16
|
+
* The unwrapped contents of a recovery keyring record: the code-authenticated
|
|
17
|
+
* account binding (controller + pointer) plus the delegation and bind
|
|
18
|
+
* timestamp. `pointer` is required -- a recovery record exists only on WAS
|
|
19
|
+
* deployments (there is nothing to recover toward without a Space).
|
|
27
20
|
*/
|
|
28
21
|
export interface RecoveryRecordContents {
|
|
29
22
|
controller: string;
|
|
30
|
-
email?: string;
|
|
31
23
|
pointer: AccountPointer;
|
|
32
24
|
delegation: IZcap;
|
|
25
|
+
createdAt: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A stored recovery record: the shared signed frame plus the account-binding
|
|
29
|
+
* tag the frame proof also covers.
|
|
30
|
+
*/
|
|
31
|
+
export interface SignedRecoveryRecord extends SignedRecord {
|
|
32
|
+
binding: string;
|
|
33
33
|
}
|
|
34
34
|
/**
|
|
35
|
-
*
|
|
35
|
+
* Where a recovery record's proof stands after the unwrap: `'verified'` when
|
|
36
|
+
* the code-derived unlock key signed it (checked before decryption), or a
|
|
37
|
+
* pending marker naming the signer the caller must still check against the
|
|
38
|
+
* account's verified did:webvh document -- the re-mint case, where an enrolled
|
|
39
|
+
* client signed on the code's behalf. The pending case is a value the caller
|
|
40
|
+
* cannot ignore by accident: the shell is not trustworthy until
|
|
41
|
+
* `verifyRecordProof` is run against the document-listed keys. The account
|
|
42
|
+
* the document belongs to is the one the code-authenticated pointer names --
|
|
43
|
+
* the binding is verified either way, so the pending state defers the shell's
|
|
44
|
+
* authenticity only, never the account identity.
|
|
45
|
+
*/
|
|
46
|
+
export type RecoveryRecordProofState = 'verified' | {
|
|
47
|
+
pending: {
|
|
48
|
+
verificationMethod: string;
|
|
49
|
+
keyMultibase: string;
|
|
50
|
+
};
|
|
51
|
+
};
|
|
52
|
+
/**
|
|
53
|
+
* Computes the account-binding tag: HMAC-SHA-256 over the binding values
|
|
54
|
+
* under the code-derived binding MAC key, base64url (no pad). Issuance calls
|
|
55
|
+
* it to stamp the record; recovery recomputes it to verify.
|
|
56
|
+
*
|
|
57
|
+
* @param options {object}
|
|
58
|
+
* @param options.bindingMacKey {Uint8Array} the code-derived MAC key
|
|
59
|
+
* @param options.controller {string} the account did:key
|
|
60
|
+
* @param options.pointer {AccountPointer} the account pointer
|
|
61
|
+
* @returns {string}
|
|
62
|
+
*/
|
|
63
|
+
export declare function computeRecoveryBinding({ bindingMacKey, controller, pointer }: {
|
|
64
|
+
bindingMacKey: Uint8Array;
|
|
65
|
+
controller: string;
|
|
66
|
+
pointer: AccountPointer;
|
|
67
|
+
}): string;
|
|
68
|
+
/**
|
|
69
|
+
* Reads the `binding` frame member off a stored recovery record without
|
|
70
|
+
* decrypting anything. The re-mint path uses it to carry the tag forward
|
|
71
|
+
* verbatim -- it cannot recompute the tag (no code bytes) and does not need
|
|
72
|
+
* to. Refuses a record with no binding: such a record predates the
|
|
73
|
+
* code-authenticated core and cannot be re-minted -- its code must be
|
|
74
|
+
* re-issued.
|
|
75
|
+
*
|
|
76
|
+
* @param options {object}
|
|
77
|
+
* @param options.record {unknown} the stored record envelope
|
|
78
|
+
* @returns {string}
|
|
79
|
+
*/
|
|
80
|
+
export declare function recoveryRecordBinding({ record }: {
|
|
81
|
+
record: unknown;
|
|
82
|
+
}): string;
|
|
83
|
+
/**
|
|
84
|
+
* Wraps the recovery record: controller, pointer, and the pre-minted
|
|
36
85
|
* `did.jsonl` PUT delegation, encrypted under the code's unlock KAK via the
|
|
37
|
-
* keyring EDV cipher context
|
|
86
|
+
* keyring EDV cipher context, then signed into the same frame the keyring
|
|
87
|
+
* record uses -- with the account-binding tag as one more frame member the
|
|
88
|
+
* proof covers. Issuance passes the code-derived `bindingMacKey` (the tag is
|
|
89
|
+
* computed here) and the code-derived unlock signer; the revocation cascade's
|
|
90
|
+
* re-mint path passes the standing record's `binding` verbatim and an
|
|
91
|
+
* enrolled client's account signer (see this module's header for the policy
|
|
92
|
+
* the reader applies to the two). Exactly one of the two binding inputs must
|
|
93
|
+
* be given.
|
|
38
94
|
*
|
|
39
95
|
* @param options {object}
|
|
40
96
|
* @param options.controller {string} the account did:key
|
|
41
|
-
* @param [options.email] {string} the account email, when known
|
|
42
97
|
* @param options.pointer {AccountPointer} the account pointer
|
|
43
98
|
* @param options.delegation {IZcap} the PUT-on-`did.jsonl` delegation to the
|
|
44
99
|
* code-derived signing DID
|
|
45
|
-
* @param options.keyAgreementKey {IKeyAgreementKey} the code's unlock KAK
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
100
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the code's unlock KAK --
|
|
101
|
+
* its public half is all the wrap uses (sealing needs no key-agreement
|
|
102
|
+
* secret), which is exactly what lets the re-mint path re-seal a record it
|
|
103
|
+
* can never open
|
|
104
|
+
* @param options.signer {RecordSigner} the signing key: the code's unlock
|
|
105
|
+
* key at issuance, an enrolled client's account key on a re-mint
|
|
106
|
+
* @param [options.bindingMacKey] {Uint8Array} the code-derived binding MAC
|
|
107
|
+
* key -- the issuance path, which computes the tag
|
|
108
|
+
* @param [options.binding] {string} the standing record's tag, carried
|
|
109
|
+
* forward verbatim -- the re-mint path, which cannot recompute it
|
|
110
|
+
* @param [options.createdAt] {string} the bind timestamp to stamp, as an ISO
|
|
111
|
+
* string; defaults to now. Supplied by a caller that pins record freshness.
|
|
112
|
+
* @returns {Promise<SignedRecoveryRecord>}
|
|
49
113
|
*/
|
|
50
|
-
export declare function wrapRecoveryRecord({ controller,
|
|
114
|
+
export declare function wrapRecoveryRecord({ controller, pointer, delegation, keyAgreementKey, signer, bindingMacKey, binding, createdAt }: {
|
|
51
115
|
controller: string;
|
|
52
|
-
email?: string;
|
|
53
116
|
pointer: AccountPointer;
|
|
54
117
|
delegation: IZcap;
|
|
55
118
|
keyAgreementKey: IKeyAgreementKey;
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
}>;
|
|
119
|
+
signer: RecordSigner;
|
|
120
|
+
bindingMacKey?: Uint8Array;
|
|
121
|
+
binding?: string;
|
|
122
|
+
createdAt?: string;
|
|
123
|
+
}): Promise<SignedRecoveryRecord>;
|
|
62
124
|
/**
|
|
63
125
|
* Unwraps and validates a recovery record: the ordinary keyring-record checks
|
|
64
|
-
* plus the required pointer and
|
|
65
|
-
* not a recovery record (an ordinary keyring record
|
|
66
|
-
* unlock Space would mean a corrupted issuance) and is
|
|
126
|
+
* plus the required pointer, delegation, and account binding. A record
|
|
127
|
+
* without a delegation is not a recovery record (an ordinary keyring record
|
|
128
|
+
* found under a code's unlock Space would mean a corrupted issuance) and is
|
|
129
|
+
* refused.
|
|
130
|
+
*
|
|
131
|
+
* The account binding is verified before the contents are returned: the
|
|
132
|
+
* decrypted `{ controller, pointer }` must carry the tag the code-derived
|
|
133
|
+
* MAC key computes over them, or the record is refused as forged
|
|
134
|
+
* ({@link RecoveryBindingError}) -- the check that closes the host-forgery
|
|
135
|
+
* redirect, since the host never holds the MAC key. Nothing downstream may
|
|
136
|
+
* trust the pointer before this returns.
|
|
137
|
+
*
|
|
138
|
+
* Proof verification is mixed-signer and ordered deliberately. A proof by the
|
|
139
|
+
* code's own unlock key is verified BEFORE decryption -- the strong path,
|
|
140
|
+
* where the typed code alone establishes what may have signed the record. A
|
|
141
|
+
* proof by any other key can only be checked after decryption, because the
|
|
142
|
+
* re-minting client is knowable only once the code-authenticated pointer says
|
|
143
|
+
* which account this is and that account's log has been verified; the
|
|
144
|
+
* contents come back with a pending proof state naming the signer, and the
|
|
145
|
+
* caller completes the check with {@link verifyRecordProof} against the
|
|
146
|
+
* document's keys. That second phase is what makes an unexpected signer
|
|
147
|
+
* refuse: a record whose proof belongs to neither class ends in a
|
|
148
|
+
* `RecordProofError` there.
|
|
67
149
|
*
|
|
68
150
|
* @param options {object}
|
|
69
151
|
* @param options.record {unknown} the stored `{ version, encryption,
|
|
70
|
-
* wrapped }` envelope
|
|
152
|
+
* wrapped, binding, proof }` envelope
|
|
71
153
|
* @param options.keyAgreementKey {IKeyAgreementKey} the code's unlock KAK
|
|
72
154
|
* @param options.keyResolver {IKeyResolver}
|
|
73
|
-
* @
|
|
155
|
+
* @param options.expectedKeyMultibase {string} the code-derived unlock
|
|
156
|
+
* signing key's multibase
|
|
157
|
+
* @param options.bindingMacKey {Uint8Array} the code-derived binding MAC
|
|
158
|
+
* key the record's account binding must verify under
|
|
159
|
+
* @returns {Promise<{ contents: RecoveryRecordContents,
|
|
160
|
+
* proofState: RecoveryRecordProofState }>}
|
|
74
161
|
*/
|
|
75
|
-
export declare function unwrapRecoveryRecord({ record, keyAgreementKey, keyResolver }: {
|
|
162
|
+
export declare function unwrapRecoveryRecord({ record, keyAgreementKey, keyResolver, expectedKeyMultibase, bindingMacKey }: {
|
|
76
163
|
record: unknown;
|
|
77
164
|
keyAgreementKey: IKeyAgreementKey;
|
|
78
165
|
keyResolver: IKeyResolver;
|
|
79
|
-
|
|
166
|
+
expectedKeyMultibase: string;
|
|
167
|
+
bindingMacKey: Uint8Array;
|
|
168
|
+
}): Promise<{
|
|
169
|
+
contents: RecoveryRecordContents;
|
|
170
|
+
proofState: RecoveryRecordProofState;
|
|
171
|
+
}>;
|
|
80
172
|
//# sourceMappingURL=recoveryRecord.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recoveryRecord.d.ts","sourceRoot":"","sources":["../../src/recovery/recoveryRecord.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recoveryRecord.d.ts","sourceRoot":"","sources":["../../src/recovery/recoveryRecord.ts"],"names":[],"mappings":"AA+CA,OAAO,KAAK,EACV,gBAAgB,EAChB,YAAY,EACZ,KAAK,EACN,MAAM,8BAA8B,CAAA;AAcrC,OAAO,KAAK,EACV,cAAc,EACd,YAAY,EACZ,YAAY,EACb,MAAM,sBAAsB,CAAA;AAQ7B;;;;;;;;GAQG;AACH,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM;CAI5B;AAED;;;;;GAKG;AACH,MAAM,WAAW,sBAAsB;IACrC,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB,UAAU,EAAE,KAAK,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;CAClB;AAED;;;GAGG;AACH,MAAM,WAAW,oBAAqB,SAAQ,YAAY;IACxD,OAAO,EAAE,MAAM,CAAA;CAChB;AAED;;;;;;;;;;;GAWG;AACH,MAAM,MAAM,wBAAwB,GAClC,UAAU,GAAG;IAAE,OAAO,EAAE;QAAE,kBAAkB,EAAE,MAAM,CAAC;QAAC,YAAY,EAAE,MAAM,CAAA;KAAE,CAAA;CAAE,CAAA;AAyBhF;;;;;;;;;;GAUG;AACH,wBAAgB,sBAAsB,CAAC,EACrC,aAAa,EACb,UAAU,EACV,OAAO,EACR,EAAE;IACD,aAAa,EAAE,UAAU,CAAA;IACzB,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;CACxB,GAAG,MAAM,CAIT;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,qBAAqB,CAAC,EAAE,MAAM,EAAE,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAS7E;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,wBAAsB,kBAAkB,CAAC,EACvC,UAAU,EACV,OAAO,EACP,UAAU,EACV,eAAe,EACf,MAAM,EACN,aAAa,EACb,OAAO,EACP,SAAS,EACV,EAAE;IACD,UAAU,EAAE,MAAM,CAAA;IAClB,OAAO,EAAE,cAAc,CAAA;IACvB,UAAU,EAAE,KAAK,CAAA;IACjB,eAAe,EAAE,gBAAgB,CAAA;IACjC,MAAM,EAAE,YAAY,CAAA;IACpB,aAAa,CAAC,EAAE,UAAU,CAAA;IAC1B,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,CAAC,EAAE,MAAM,CAAA;CACnB,GAAG,OAAO,CAAC,oBAAoB,CAAC,CAiChC;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAqCG;AACH,wBAAsB,oBAAoB,CAAC,EACzC,MAAM,EACN,eAAe,EACf,WAAW,EACX,oBAAoB,EACpB,aAAa,EACd,EAAE;IACD,MAAM,EAAE,OAAO,CAAA;IACf,eAAe,EAAE,gBAAgB,CAAA;IACjC,WAAW,EAAE,YAAY,CAAA;IACzB,oBAAoB,EAAE,MAAM,CAAA;IAC5B,aAAa,EAAE,UAAU,CAAA;CAC1B,GAAG,OAAO,CAAC;IACV,QAAQ,EAAE,sBAAsB,CAAA;IAChC,UAAU,EAAE,wBAAwB,CAAA;CACrC,CAAC,CAqFD"}
|
|
@@ -1,61 +1,243 @@
|
|
|
1
|
-
|
|
1
|
+
/*!
|
|
2
|
+
* Copyright (c) 2026 Interop Alliance. All rights reserved.
|
|
3
|
+
*/
|
|
2
4
|
/**
|
|
3
|
-
*
|
|
5
|
+
* The recovery keyring record codec: the `{ version, encryption, wrapped,
|
|
6
|
+
* binding, proof }` envelope stored as the one resource of a recovery code's
|
|
7
|
+
* unlock Space. Its plaintext is the ordinary keyring record's (controller,
|
|
8
|
+
* account pointer) PLUS the pre-minted PUT-on-`did.jsonl` delegation -- the
|
|
9
|
+
* narrow zcap bridge that lets the code-derived client write its
|
|
10
|
+
* self-enrolling log continuation. It carries **no key material of any
|
|
11
|
+
* kind**: never a seed, never a user key wrap (wraps live doc-and-roster
|
|
12
|
+
* only), so the record stays a pure pointer.
|
|
13
|
+
*
|
|
14
|
+
* The wrap reuses the keyring record construction (cipher context,
|
|
15
|
+
* record-own epoch, and signed frame alike), so a recovery record IS a
|
|
16
|
+
* keyring record to every generic consumer -- only the recovery flow demands
|
|
17
|
+
* the delegation and the binding.
|
|
18
|
+
*
|
|
19
|
+
* The record splits into a code-authenticated core and a re-mintable shell.
|
|
20
|
+
* The core is the account binding `{ controller, pointer }`, authenticated by
|
|
21
|
+
* the `binding` frame member: an HMAC tag under a key derived from the code
|
|
22
|
+
* bytes, computed at issuance and verified at recovery BEFORE the pointer is
|
|
23
|
+
* trusted. Only the issuer and the code holder ever hold that key -- the
|
|
24
|
+
* storage host never does -- so a host-forged record pointing recovery at
|
|
25
|
+
* another account fails the tag however it is encrypted or signed. The tag
|
|
26
|
+
* rides the frame in the clear (it reveals nothing) so the re-mint path,
|
|
27
|
+
* which cannot decrypt the record or recompute the tag, preserves it
|
|
28
|
+
* verbatim; the binding values themselves stay inside the plaintext. The
|
|
29
|
+
* consequence: a re-mint can never change the pointer, and an account that
|
|
30
|
+
* moves hosts must re-issue its codes.
|
|
31
|
+
*
|
|
32
|
+
* The shell is the delegation and the frame proof, and its signer is mixed.
|
|
33
|
+
* At issuance the record is signed by the code-derived unlock key, the one a
|
|
34
|
+
* typed code re-derives, so recovery verifies the proof before decrypting.
|
|
35
|
+
* The revocation cascade's re-mint path holds only the code's KAK public
|
|
36
|
+
* half plus an enrolled client's account key, so it re-PUTs the record
|
|
37
|
+
* signed by that client's account verification method instead. This codec is
|
|
38
|
+
* agnostic -- it signs with what it is given -- and the reader carries the
|
|
39
|
+
* policy: a proof by the expected unlock key verifies up front, and anything
|
|
40
|
+
* else comes back marked unverified, for the caller to check against the
|
|
41
|
+
* verified did:webvh document of the account the code-authenticated pointer
|
|
42
|
+
* names.
|
|
43
|
+
*/
|
|
44
|
+
import { base64urlnopad } from '@scure/base';
|
|
45
|
+
import { equalBytes } from '@noble/ciphers/utils.js';
|
|
46
|
+
import { hmac } from '@noble/hashes/hmac.js';
|
|
47
|
+
import { sha256 } from '@noble/hashes/sha2.js';
|
|
48
|
+
import { KEYRING_RECORD_VERSION, mintRecordEncryption, parseRecordCreatedAt, parseRecordFrame, parseRecordPointer, recordCipher, recordCreatedAtStamp, recordProofKeyMultibase, recordSealCipher, signRecordFrame, verifyRecordProof } from '../keyring/record.js';
|
|
49
|
+
/**
|
|
50
|
+
* The context label mixed into the binding MAC input, versioning the tag
|
|
51
|
+
* construction. Permanent -- changing it orphans every issued code.
|
|
52
|
+
*/
|
|
53
|
+
const RECOVERY_BINDING_CONTEXT = 'freewallet/recovery/binding/v1';
|
|
54
|
+
/**
|
|
55
|
+
* A recovery record's account binding is absent, malformed, or does not
|
|
56
|
+
* verify under the typed code's binding MAC key. Its own class, distinct from
|
|
57
|
+
* a proof or decrypt failure: this is the refusal that says the record's
|
|
58
|
+
* `{ controller, pointer }` core was not written by a holder of this code --
|
|
59
|
+
* a forged record redirecting recovery at another account, or a record from
|
|
60
|
+
* before the account moved hosts (either way the code cannot recover here
|
|
61
|
+
* and must be re-issued).
|
|
62
|
+
*/
|
|
63
|
+
export class RecoveryBindingError extends Error {
|
|
64
|
+
constructor(message) {
|
|
65
|
+
super(message);
|
|
66
|
+
this.name = 'RecoveryBindingError';
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* The deterministic MAC input over a record's account binding: a JSON array
|
|
71
|
+
* of the context label and the binding values, so no delimiter ambiguity can
|
|
72
|
+
* make two bindings collide.
|
|
73
|
+
*/
|
|
74
|
+
function bindingMacInput({ controller, pointer }) {
|
|
75
|
+
return new TextEncoder().encode(JSON.stringify([
|
|
76
|
+
RECOVERY_BINDING_CONTEXT,
|
|
77
|
+
controller,
|
|
78
|
+
pointer.did ?? '',
|
|
79
|
+
pointer.spaceId,
|
|
80
|
+
pointer.host
|
|
81
|
+
]));
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Computes the account-binding tag: HMAC-SHA-256 over the binding values
|
|
85
|
+
* under the code-derived binding MAC key, base64url (no pad). Issuance calls
|
|
86
|
+
* it to stamp the record; recovery recomputes it to verify.
|
|
87
|
+
*
|
|
88
|
+
* @param options {object}
|
|
89
|
+
* @param options.bindingMacKey {Uint8Array} the code-derived MAC key
|
|
90
|
+
* @param options.controller {string} the account did:key
|
|
91
|
+
* @param options.pointer {AccountPointer} the account pointer
|
|
92
|
+
* @returns {string}
|
|
93
|
+
*/
|
|
94
|
+
export function computeRecoveryBinding({ bindingMacKey, controller, pointer }) {
|
|
95
|
+
return base64urlnopad.encode(hmac(sha256, bindingMacKey, bindingMacInput({ controller, pointer })));
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Reads the `binding` frame member off a stored recovery record without
|
|
99
|
+
* decrypting anything. The re-mint path uses it to carry the tag forward
|
|
100
|
+
* verbatim -- it cannot recompute the tag (no code bytes) and does not need
|
|
101
|
+
* to. Refuses a record with no binding: such a record predates the
|
|
102
|
+
* code-authenticated core and cannot be re-minted -- its code must be
|
|
103
|
+
* re-issued.
|
|
104
|
+
*
|
|
105
|
+
* @param options {object}
|
|
106
|
+
* @param options.record {unknown} the stored record envelope
|
|
107
|
+
* @returns {string}
|
|
108
|
+
*/
|
|
109
|
+
export function recoveryRecordBinding({ record }) {
|
|
110
|
+
const { binding } = (record ?? {});
|
|
111
|
+
if (typeof binding !== 'string' || !binding) {
|
|
112
|
+
throw new RecoveryBindingError('The recovery record carries no code-authenticated account binding; ' +
|
|
113
|
+
'the recovery code must be re-issued.');
|
|
114
|
+
}
|
|
115
|
+
return binding;
|
|
116
|
+
}
|
|
117
|
+
/**
|
|
118
|
+
* Wraps the recovery record: controller, pointer, and the pre-minted
|
|
4
119
|
* `did.jsonl` PUT delegation, encrypted under the code's unlock KAK via the
|
|
5
|
-
* keyring EDV cipher context
|
|
120
|
+
* keyring EDV cipher context, then signed into the same frame the keyring
|
|
121
|
+
* record uses -- with the account-binding tag as one more frame member the
|
|
122
|
+
* proof covers. Issuance passes the code-derived `bindingMacKey` (the tag is
|
|
123
|
+
* computed here) and the code-derived unlock signer; the revocation cascade's
|
|
124
|
+
* re-mint path passes the standing record's `binding` verbatim and an
|
|
125
|
+
* enrolled client's account signer (see this module's header for the policy
|
|
126
|
+
* the reader applies to the two). Exactly one of the two binding inputs must
|
|
127
|
+
* be given.
|
|
6
128
|
*
|
|
7
129
|
* @param options {object}
|
|
8
130
|
* @param options.controller {string} the account did:key
|
|
9
|
-
* @param [options.email] {string} the account email, when known
|
|
10
131
|
* @param options.pointer {AccountPointer} the account pointer
|
|
11
132
|
* @param options.delegation {IZcap} the PUT-on-`did.jsonl` delegation to the
|
|
12
133
|
* code-derived signing DID
|
|
13
|
-
* @param options.keyAgreementKey {IKeyAgreementKey} the code's unlock KAK
|
|
14
|
-
*
|
|
15
|
-
*
|
|
16
|
-
*
|
|
134
|
+
* @param options.keyAgreementKey {IKeyAgreementKey} the code's unlock KAK --
|
|
135
|
+
* its public half is all the wrap uses (sealing needs no key-agreement
|
|
136
|
+
* secret), which is exactly what lets the re-mint path re-seal a record it
|
|
137
|
+
* can never open
|
|
138
|
+
* @param options.signer {RecordSigner} the signing key: the code's unlock
|
|
139
|
+
* key at issuance, an enrolled client's account key on a re-mint
|
|
140
|
+
* @param [options.bindingMacKey] {Uint8Array} the code-derived binding MAC
|
|
141
|
+
* key -- the issuance path, which computes the tag
|
|
142
|
+
* @param [options.binding] {string} the standing record's tag, carried
|
|
143
|
+
* forward verbatim -- the re-mint path, which cannot recompute it
|
|
144
|
+
* @param [options.createdAt] {string} the bind timestamp to stamp, as an ISO
|
|
145
|
+
* string; defaults to now. Supplied by a caller that pins record freshness.
|
|
146
|
+
* @returns {Promise<SignedRecoveryRecord>}
|
|
17
147
|
*/
|
|
18
|
-
export async function wrapRecoveryRecord({ controller,
|
|
148
|
+
export async function wrapRecoveryRecord({ controller, pointer, delegation, keyAgreementKey, signer, bindingMacKey, binding, createdAt }) {
|
|
149
|
+
if ((bindingMacKey === undefined) === (binding === undefined)) {
|
|
150
|
+
throw new Error('Exactly one of bindingMacKey (issuance) or binding (re-mint) is ' +
|
|
151
|
+
'required.');
|
|
152
|
+
}
|
|
153
|
+
const tag = bindingMacKey !== undefined
|
|
154
|
+
? computeRecoveryBinding({ bindingMacKey, controller, pointer })
|
|
155
|
+
: binding;
|
|
19
156
|
const encryption = await mintRecordEncryption({ keyAgreementKey });
|
|
20
|
-
const cipher = await
|
|
21
|
-
keyAgreementKey,
|
|
22
|
-
keyResolver,
|
|
23
|
-
encryption
|
|
24
|
-
});
|
|
157
|
+
const cipher = await recordSealCipher({ encryption });
|
|
25
158
|
const data = {
|
|
26
159
|
controller,
|
|
27
|
-
...(email ? { email } : {}),
|
|
28
160
|
pointer: {
|
|
29
161
|
...(pointer.did ? { did: pointer.did } : {}),
|
|
30
162
|
spaceId: pointer.spaceId,
|
|
31
163
|
host: pointer.host
|
|
32
164
|
},
|
|
33
165
|
delegation,
|
|
34
|
-
createdAt:
|
|
166
|
+
createdAt: recordCreatedAtStamp({ createdAt })
|
|
35
167
|
};
|
|
36
168
|
const { envelope } = await cipher.encrypt({
|
|
37
169
|
data: data
|
|
38
170
|
});
|
|
39
|
-
return {
|
|
171
|
+
return (await signRecordFrame({
|
|
172
|
+
version: KEYRING_RECORD_VERSION,
|
|
173
|
+
encryption,
|
|
174
|
+
wrapped: envelope,
|
|
175
|
+
signer,
|
|
176
|
+
members: { binding: tag }
|
|
177
|
+
}));
|
|
40
178
|
}
|
|
41
179
|
/**
|
|
42
180
|
* Unwraps and validates a recovery record: the ordinary keyring-record checks
|
|
43
|
-
* plus the required pointer and
|
|
44
|
-
* not a recovery record (an ordinary keyring record
|
|
45
|
-
* unlock Space would mean a corrupted issuance) and is
|
|
181
|
+
* plus the required pointer, delegation, and account binding. A record
|
|
182
|
+
* without a delegation is not a recovery record (an ordinary keyring record
|
|
183
|
+
* found under a code's unlock Space would mean a corrupted issuance) and is
|
|
184
|
+
* refused.
|
|
185
|
+
*
|
|
186
|
+
* The account binding is verified before the contents are returned: the
|
|
187
|
+
* decrypted `{ controller, pointer }` must carry the tag the code-derived
|
|
188
|
+
* MAC key computes over them, or the record is refused as forged
|
|
189
|
+
* ({@link RecoveryBindingError}) -- the check that closes the host-forgery
|
|
190
|
+
* redirect, since the host never holds the MAC key. Nothing downstream may
|
|
191
|
+
* trust the pointer before this returns.
|
|
192
|
+
*
|
|
193
|
+
* Proof verification is mixed-signer and ordered deliberately. A proof by the
|
|
194
|
+
* code's own unlock key is verified BEFORE decryption -- the strong path,
|
|
195
|
+
* where the typed code alone establishes what may have signed the record. A
|
|
196
|
+
* proof by any other key can only be checked after decryption, because the
|
|
197
|
+
* re-minting client is knowable only once the code-authenticated pointer says
|
|
198
|
+
* which account this is and that account's log has been verified; the
|
|
199
|
+
* contents come back with a pending proof state naming the signer, and the
|
|
200
|
+
* caller completes the check with {@link verifyRecordProof} against the
|
|
201
|
+
* document's keys. That second phase is what makes an unexpected signer
|
|
202
|
+
* refuse: a record whose proof belongs to neither class ends in a
|
|
203
|
+
* `RecordProofError` there.
|
|
46
204
|
*
|
|
47
205
|
* @param options {object}
|
|
48
206
|
* @param options.record {unknown} the stored `{ version, encryption,
|
|
49
|
-
* wrapped }` envelope
|
|
207
|
+
* wrapped, binding, proof }` envelope
|
|
50
208
|
* @param options.keyAgreementKey {IKeyAgreementKey} the code's unlock KAK
|
|
51
209
|
* @param options.keyResolver {IKeyResolver}
|
|
52
|
-
* @
|
|
210
|
+
* @param options.expectedKeyMultibase {string} the code-derived unlock
|
|
211
|
+
* signing key's multibase
|
|
212
|
+
* @param options.bindingMacKey {Uint8Array} the code-derived binding MAC
|
|
213
|
+
* key the record's account binding must verify under
|
|
214
|
+
* @returns {Promise<{ contents: RecoveryRecordContents,
|
|
215
|
+
* proofState: RecoveryRecordProofState }>}
|
|
53
216
|
*/
|
|
54
|
-
export async function unwrapRecoveryRecord({ record, keyAgreementKey, keyResolver }) {
|
|
55
|
-
const { encryption, wrapped } = parseRecordFrame({
|
|
217
|
+
export async function unwrapRecoveryRecord({ record, keyAgreementKey, keyResolver, expectedKeyMultibase, bindingMacKey }) {
|
|
218
|
+
const { encryption, wrapped, proof } = parseRecordFrame({
|
|
56
219
|
record,
|
|
57
220
|
label: 'recovery'
|
|
58
221
|
});
|
|
222
|
+
const binding = recoveryRecordBinding({ record });
|
|
223
|
+
// `parseRecordFrame` shape-checks the proof of a current-version frame, so
|
|
224
|
+
// it is present here.
|
|
225
|
+
const verificationMethod = proof.verificationMethod;
|
|
226
|
+
const keyMultibase = recordProofKeyMultibase({
|
|
227
|
+
verificationMethod,
|
|
228
|
+
label: 'recovery'
|
|
229
|
+
});
|
|
230
|
+
let proofState = {
|
|
231
|
+
pending: { verificationMethod, keyMultibase }
|
|
232
|
+
};
|
|
233
|
+
if (keyMultibase === expectedKeyMultibase) {
|
|
234
|
+
await verifyRecordProof({
|
|
235
|
+
record,
|
|
236
|
+
allowedKeyMultibases: expectedKeyMultibase,
|
|
237
|
+
label: 'recovery'
|
|
238
|
+
});
|
|
239
|
+
proofState = 'verified';
|
|
240
|
+
}
|
|
59
241
|
const cipher = await recordCipher({
|
|
60
242
|
keyAgreementKey,
|
|
61
243
|
keyResolver,
|
|
@@ -75,13 +257,32 @@ export async function unwrapRecoveryRecord({ record, keyAgreementKey, keyResolve
|
|
|
75
257
|
typeof plaintext.delegation !== 'object') {
|
|
76
258
|
throw new Error('Recovery record is missing its did.jsonl delegation.');
|
|
77
259
|
}
|
|
260
|
+
const createdAt = parseRecordCreatedAt({
|
|
261
|
+
value: plaintext.createdAt,
|
|
262
|
+
label: 'Recovery'
|
|
263
|
+
});
|
|
264
|
+
const expectedTag = hmac(sha256, bindingMacKey, bindingMacInput({ controller: plaintext.controller, pointer }));
|
|
265
|
+
let servedTag;
|
|
266
|
+
try {
|
|
267
|
+
servedTag = base64urlnopad.decode(binding);
|
|
268
|
+
}
|
|
269
|
+
catch {
|
|
270
|
+
servedTag = null;
|
|
271
|
+
}
|
|
272
|
+
// `equalBytes` is @noble/ciphers' authentication-tag comparison (no early
|
|
273
|
+
// exit), so the check leaks nothing through timing.
|
|
274
|
+
if (servedTag === null || !equalBytes(servedTag, expectedTag)) {
|
|
275
|
+
throw new RecoveryBindingError("The recovery record's account binding does not verify under this " +
|
|
276
|
+
'code; the record is refused as forged.');
|
|
277
|
+
}
|
|
78
278
|
return {
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
:
|
|
83
|
-
|
|
84
|
-
|
|
279
|
+
contents: {
|
|
280
|
+
controller: plaintext.controller,
|
|
281
|
+
pointer,
|
|
282
|
+
delegation: plaintext.delegation,
|
|
283
|
+
createdAt
|
|
284
|
+
},
|
|
285
|
+
proofState
|
|
85
286
|
};
|
|
86
287
|
}
|
|
87
288
|
//# sourceMappingURL=recoveryRecord.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recoveryRecord.js","sourceRoot":"","sources":["../../src/recovery/recoveryRecord.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recoveryRecord.js","sourceRoot":"","sources":["../../src/recovery/recoveryRecord.ts"],"names":[],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recoveryWebvh.d.ts","sourceRoot":"","sources":["../../src/recovery/recoveryWebvh.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"recoveryWebvh.d.ts","sourceRoot":"","sources":["../../src/recovery/recoveryWebvh.ts"],"names":[],"mappings":"AAiDA,OAAO,KAAK,EACV,qBAAqB,EAErB,mBAAmB,EACnB,YAAY,EACb,MAAM,sBAAsB,CAAA;AAE7B;;;;;;;;;;;GAWG;AACH,wBAAgB,YAAY,CAAC,EAC3B,GAAG,EACH,wBAAwB,EACzB,EAAE;IACD,GAAG,EAAE,MAAM,CAAA;IACX,wBAAwB,EAAE,MAAM,CAAA;CACjC,GAAG,MAAM,CAET;AAED;;;;GAIG;AACH,MAAM,WAAW,kBAAkB;IACjC,wBAAwB,EAAE,MAAM,CAAA;IAChC,kBAAkB,EAAE,MAAM,CAAA;CAC3B;AAED;;;;GAIG;AACH,qBAAa,4BAA6B,SAAQ,KAAK;gBAEnD,OAAO,SACuC;CAKjD;AAED;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,IAAI,CACjC,YAAY,EACZ,kBAAkB,GAAG,eAAe,CACrC,CAAA;AAsDD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,OAAO,EAAE,YAAY,CAAA;IACrB,UAAU,EAAE,qBAAqB,CAAA;IACjC,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAE3B;AA+ED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAsB,iBAAiB,CAAC,OAAO,EAAE;IAC/C,OAAO,EAAE,YAAY,CAAA;IACrB,UAAU,EAAE,qBAAqB,CAAA;IACjC,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAA;CAAE,CAAC,CAE3B;AAqED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAiCG;AACH,wBAAsB,kBAAkB,CAAC,OAAO,EAAE;IAChD,KAAK,EAAE,gBAAgB,CAAA;IACvB,QAAQ,EAAE,kBAAkB,GAAG;QAAE,UAAU,EAAE,UAAU,CAAA;KAAE,CAAA;IACzD,aAAa,EAAE,mBAAmB,CAAA;IAClC,oBAAoB,EAAE,qBAAqB,CAAA;IAC3C,WAAW,EAAE,kBAAkB,CAAA;IAC/B,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB,GAAG,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CAE5C"}
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
* with the same key material converges without forking the log.
|
|
35
35
|
*/
|
|
36
36
|
import { deriveNextKeyHash, updateDID } from '@interop/did-method-webvh';
|
|
37
|
-
import { assertCarryOverCommitments, MULTIKEY_VM_TYPE, publishUpdatedLog, putLogResource, readPublishedLog, relationIds, updateKeyMultibase, updateKeySigner, withLogConflictRetry } from '../webvh/didWebvh.js';
|
|
37
|
+
import { assertCarryOverCommitments, markedVerificationMethodPair, MULTIKEY_VM_TYPE, publishUpdatedLog, putLogResource, readPublishedLog, relationIds, updateKeyMultibase, updateKeySigner, withLogConflictRetry } from '../webvh/didWebvh.js';
|
|
38
38
|
/**
|
|
39
39
|
* The verification-method id a code's key-agreement key publishes under --
|
|
40
40
|
* the ordinary `<did>#<multibase>` form, indistinguishable by id from any
|
|
@@ -150,6 +150,9 @@ async function publishRecoveryKeyOnce({ idStore, updateKeys, recovery, expectedD
|
|
|
150
150
|
}
|
|
151
151
|
await assertCarryOverCommitments({ published });
|
|
152
152
|
const existingMethods = (doc.verificationMethod ?? []);
|
|
153
|
+
// Controlled by the account, deliberately unmarked: a recovery code is not
|
|
154
|
+
// a listed client, so its key-agreement method must never carry the
|
|
155
|
+
// controller marker a client listing and a revocation removal match on.
|
|
153
156
|
const recoveryMethod = {
|
|
154
157
|
id: vmId,
|
|
155
158
|
type: MULTIKEY_VM_TYPE,
|
|
@@ -355,24 +358,27 @@ async function recoverWebvhClientOnce({ store, recovery, newClientKeys, newClien
|
|
|
355
358
|
did,
|
|
356
359
|
keyAgreementKeyMultibase: replacement.keyAgreementKeyMultibase
|
|
357
360
|
});
|
|
361
|
+
// A three-way controller split. The new client's signing method and the
|
|
362
|
+
// replacement code's key-agreement method are controlled by the account;
|
|
363
|
+
// the new client's key-agreement method alone carries the controller marker
|
|
364
|
+
// (see clientKeyAgreementController) -- which is exactly what tells the two
|
|
365
|
+
// simultaneously published keyAgreement methods apart. The marked pair goes
|
|
366
|
+
// through the shared builder, which refuses a new client whose key-agreement
|
|
367
|
+
// key is not its signing key's canonical twin; the replacement code's
|
|
368
|
+
// unmarked method is appended after it.
|
|
358
369
|
const addedMethods = [
|
|
359
|
-
{
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
id: vmId(newClientKeys.keyAgreementKeyMultibase),
|
|
365
|
-
publicKeyMultibase: newClientKeys.keyAgreementKeyMultibase
|
|
366
|
-
},
|
|
370
|
+
...markedVerificationMethodPair({
|
|
371
|
+
controller: did,
|
|
372
|
+
signingKeyMultibase: newClientKeys.signingKeyMultibase,
|
|
373
|
+
keyAgreementKeyMultibase: newClientKeys.keyAgreementKeyMultibase
|
|
374
|
+
}),
|
|
367
375
|
{
|
|
368
376
|
id: replacementVmId,
|
|
377
|
+
type: MULTIKEY_VM_TYPE,
|
|
378
|
+
controller: did,
|
|
369
379
|
publicKeyMultibase: replacement.keyAgreementKeyMultibase
|
|
370
380
|
}
|
|
371
|
-
]
|
|
372
|
-
...method,
|
|
373
|
-
type: MULTIKEY_VM_TYPE,
|
|
374
|
-
controller: did
|
|
375
|
-
}));
|
|
381
|
+
];
|
|
376
382
|
const existingMethods = (doc.verificationMethod ?? []);
|
|
377
383
|
const verificationMethods = [
|
|
378
384
|
...existingMethods.filter(method => method.id !== spentVmId &&
|