@did-btcr2/method 0.65.1 → 0.66.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/dist/.tsbuildinfo +1 -1
- package/dist/browser.js +3 -3
- package/dist/browser.mjs +3 -3
- package/dist/cjs/index.js +58 -35
- package/dist/esm/core/beacon/smt-beacon.js +37 -31
- package/dist/esm/core/beacon/smt-beacon.js.map +1 -1
- package/dist/esm/core/resolver.js +56 -21
- package/dist/esm/core/resolver.js.map +1 -1
- package/dist/types/core/beacon/smt-beacon.d.ts +8 -6
- package/dist/types/core/beacon/smt-beacon.d.ts.map +1 -1
- package/dist/types/core/interfaces.d.ts +27 -14
- package/dist/types/core/interfaces.d.ts.map +1 -1
- package/dist/types/core/resolver.d.ts.map +1 -1
- package/dist/types/core/types.d.ts +4 -2
- package/dist/types/core/types.d.ts.map +1 -1
- package/package.json +4 -4
- package/src/core/beacon/smt-beacon.ts +40 -34
- package/src/core/interfaces.ts +27 -14
- package/src/core/resolver.ts +57 -21
- package/src/core/types.ts +4 -2
package/src/core/resolver.ts
CHANGED
|
@@ -10,9 +10,11 @@ import {
|
|
|
10
10
|
INVALID_DID,
|
|
11
11
|
INVALID_DID_UPDATE,
|
|
12
12
|
INVALID_OPTIONS,
|
|
13
|
+
INVALID_SIGNAL_DATA,
|
|
13
14
|
JSONPatch,
|
|
14
15
|
JSONUtils,
|
|
15
16
|
LATE_PUBLISHING_ERROR,
|
|
17
|
+
MISSING_UPDATE_DATA,
|
|
16
18
|
NOT_FOUND,
|
|
17
19
|
ResolveError
|
|
18
20
|
} from '@did-btcr2/common';
|
|
@@ -173,12 +175,15 @@ function isSignedBTCR2Update(value: unknown): value is SignedBTCR2Update {
|
|
|
173
175
|
&& isRecord(value.proof);
|
|
174
176
|
}
|
|
175
177
|
|
|
176
|
-
/** True if `value` has the shape of an SMT
|
|
178
|
+
/** True if `value` has the shape of an SMT proof: string fields, `hashes` an array of strings. */
|
|
177
179
|
function isSMTProof(value: unknown): value is SMTProof {
|
|
178
180
|
if(!isRecord(value)) return false;
|
|
179
181
|
return typeof value.id === 'string'
|
|
180
182
|
&& typeof value.collapsed === 'string'
|
|
181
|
-
&& Array.isArray(value.hashes)
|
|
183
|
+
&& Array.isArray(value.hashes)
|
|
184
|
+
&& value.hashes.every(h => typeof h === 'string')
|
|
185
|
+
&& (value.nonce === undefined || typeof value.nonce === 'string')
|
|
186
|
+
&& (value.updateId === undefined || typeof value.updateId === 'string');
|
|
182
187
|
}
|
|
183
188
|
|
|
184
189
|
/**
|
|
@@ -322,16 +327,25 @@ export class Resolver {
|
|
|
322
327
|
* The state of the specification loop, carried across every pass: the version counter
|
|
323
328
|
* (`current_version_id`), the update-hash history that backs duplicate confirmation
|
|
324
329
|
* (`update_hash_history`), the confirmations of the block that contains the most
|
|
325
|
-
* recently applied unique update (`block_confirmations`),
|
|
326
|
-
*
|
|
327
|
-
*
|
|
328
|
-
*
|
|
330
|
+
* recently applied unique update (`block_confirmations`), the height of that block
|
|
331
|
+
* (`current_block_height`), and the header time of that block as `updated`. A pass
|
|
332
|
+
* that finds a new beacon address returns to discovery, so the state must not
|
|
333
|
+
* restart: a restart would reject a linear history whose later updates are
|
|
334
|
+
* announced on beacons that earlier updates added.
|
|
329
335
|
*/
|
|
330
336
|
#currentVersionId = 1;
|
|
331
337
|
#updateHashHistory: HashBytes[] = [];
|
|
332
338
|
#blockConfirmations = 0;
|
|
333
339
|
#updated?: string;
|
|
334
340
|
|
|
341
|
+
/**
|
|
342
|
+
* The height of the block that contains the most recently applied update
|
|
343
|
+
* (`current_block_height`). "Find Beacon Signals" keeps only the signals at or above
|
|
344
|
+
* it: a beacon address that an update added has no signals for this DID before the
|
|
345
|
+
* block of that update.
|
|
346
|
+
*/
|
|
347
|
+
#currentBlockHeight = 0;
|
|
348
|
+
|
|
335
349
|
/**
|
|
336
350
|
* Opt-in upper bound on multi-round beacon-discovery passes. `Infinity` (the
|
|
337
351
|
* default) leaves discovery unbounded; termination is already guaranteed by
|
|
@@ -958,7 +972,7 @@ export class Resolver {
|
|
|
958
972
|
);
|
|
959
973
|
if(removed) continue;
|
|
960
974
|
|
|
961
|
-
// Step
|
|
975
|
+
// Step 6, "Check targetVersionId", first arm: targetVersionId <= currentVersionId
|
|
962
976
|
// re-announces an applied version. Confirm that it is a true duplicate, then
|
|
963
977
|
// skip it. A duplicate does not advance the version counter, does not append
|
|
964
978
|
// to the history (the slot already holds the applied update, ADR 067), and
|
|
@@ -980,7 +994,7 @@ export class Resolver {
|
|
|
980
994
|
continue;
|
|
981
995
|
}
|
|
982
996
|
|
|
983
|
-
// Step
|
|
997
|
+
// Step 6, third arm: a version was skipped, so raise LATE_PUBLISHING.
|
|
984
998
|
if(update.targetVersionId !== this.#currentVersionId + 1) {
|
|
985
999
|
throw new ResolveError(
|
|
986
1000
|
`Version Id Mismatch: targetVersionId cannot be > currentVersionId + 1`,
|
|
@@ -991,16 +1005,18 @@ export class Resolver {
|
|
|
991
1005
|
);
|
|
992
1006
|
}
|
|
993
1007
|
|
|
994
|
-
// Step
|
|
1008
|
+
// Step 6, second arm: targetVersionId == currentVersionId + 1. Apply the update,
|
|
995
1009
|
// append the unsigned update hash to the history, increment the version.
|
|
996
1010
|
this.#currentDocument = Resolver.applyUpdate(document, update, block);
|
|
997
1011
|
const unsignedUpdate = JSONUtils.deleteKeys(update, ['proof']) as UnsignedBTCR2Update;
|
|
998
1012
|
this.#updateHashHistory.push(canonicalHashBytes(unsignedUpdate));
|
|
999
1013
|
this.#currentVersionId++;
|
|
1000
1014
|
|
|
1001
|
-
//
|
|
1002
|
-
// path only: the stop above and the duplicate branch
|
|
1015
|
+
// "Apply Update": block_confirmations, current_block_height, and the header time
|
|
1016
|
+
// as `updated`. On the apply path only: the stop above and the duplicate branch
|
|
1017
|
+
// stamp nothing.
|
|
1003
1018
|
this.#blockConfirmations = block.confirmations;
|
|
1019
|
+
this.#currentBlockHeight = block.height;
|
|
1004
1020
|
this.#updated = DateUtils.toISOStringNonFractional(DateUtils.blocktimeToTimestamp(block.time));
|
|
1005
1021
|
|
|
1006
1022
|
// The applied update can add a beacon service. "Find Beacon Signals" runs at
|
|
@@ -1068,8 +1084,12 @@ export class Resolver {
|
|
|
1068
1084
|
* malformed. It fails fast here with a typed error, in the style of the
|
|
1069
1085
|
* {@link provide} guards, and not later with an invalid date or a false
|
|
1070
1086
|
* `versionTime` comparison in the ProcessUpdate phase.
|
|
1087
|
+
*
|
|
1088
|
+
* "Find Beacon Signals" finds only the transactions at or above
|
|
1089
|
+
* `current_block_height`, the height of the block of the most recently applied
|
|
1090
|
+
* update. A signal below it is excluded: it emits no data need and applies no update.
|
|
1071
1091
|
* @param {Array<BeaconSignal>} signals The signals the caller provided for one service.
|
|
1072
|
-
* @returns {Array<BeaconSignal>} The signals at or above the threshold, in the given order.
|
|
1092
|
+
* @returns {Array<BeaconSignal>} The signals at or above the threshold and the height, in the given order.
|
|
1073
1093
|
* @throws {ResolveError} `INVALID_DID_UPDATE` for an eligible signal with no valid block metadata.
|
|
1074
1094
|
*/
|
|
1075
1095
|
#eligibleSignals(signals: Array<BeaconSignal>): Array<BeaconSignal> {
|
|
@@ -1094,6 +1114,10 @@ export class Resolver {
|
|
|
1094
1114
|
}
|
|
1095
1115
|
);
|
|
1096
1116
|
}
|
|
1117
|
+
// "Find Beacon Signals" finds only the transactions at or above current_block_height.
|
|
1118
|
+
// A signal before the block of the update that added the address is not a signal
|
|
1119
|
+
// of this DID.
|
|
1120
|
+
if((block!.height as number) < this.#currentBlockHeight) continue;
|
|
1097
1121
|
eligible.push(signal);
|
|
1098
1122
|
}
|
|
1099
1123
|
return eligible;
|
|
@@ -1147,12 +1171,14 @@ export class Resolver {
|
|
|
1147
1171
|
);
|
|
1148
1172
|
}
|
|
1149
1173
|
// Fail fast if the provided announcement is not the one the on-chain
|
|
1150
|
-
// signal requested: its canonical hash must equal the need's hash.
|
|
1174
|
+
// signal requested: its canonical hash must equal the need's hash. The
|
|
1175
|
+
// specification ("Process CAS Beacon") treats an announcement whose hash is
|
|
1176
|
+
// not map_update_hash as not available from CAS: MISSING_UPDATE_DATA.
|
|
1151
1177
|
const announcementHash = canonicalHash(data, { encoding: 'hex' });
|
|
1152
1178
|
if(announcementHash !== need.announcementHash) {
|
|
1153
1179
|
throw new ResolveError(
|
|
1154
1180
|
`CAS announcement hash mismatch: expected ${need.announcementHash}, got ${announcementHash}.`,
|
|
1155
|
-
|
|
1181
|
+
MISSING_UPDATE_DATA, { expected: need.announcementHash, actual: announcementHash }
|
|
1156
1182
|
);
|
|
1157
1183
|
}
|
|
1158
1184
|
this.#sidecarData.casMap.set(announcementHash, data);
|
|
@@ -1167,12 +1193,13 @@ export class Resolver {
|
|
|
1167
1193
|
);
|
|
1168
1194
|
}
|
|
1169
1195
|
// Fail fast if the provided update is not the one the on-chain signal
|
|
1170
|
-
// requested:
|
|
1196
|
+
// requested: the specification compares the JSON Document Hash of a
|
|
1197
|
+
// retrieved update to update_hash, and a mismatch is INVALID_SIGNAL_DATA.
|
|
1171
1198
|
const updateHash = canonicalHash(data, { encoding: 'hex' });
|
|
1172
1199
|
if(updateHash !== need.updateHash) {
|
|
1173
1200
|
throw new ResolveError(
|
|
1174
1201
|
`Signed update hash mismatch: expected ${need.updateHash}, got ${updateHash}.`,
|
|
1175
|
-
|
|
1202
|
+
INVALID_SIGNAL_DATA, { expected: need.updateHash, actual: updateHash }
|
|
1176
1203
|
);
|
|
1177
1204
|
}
|
|
1178
1205
|
this.#sidecarData.updateMap.set(updateHash, data);
|
|
@@ -1180,18 +1207,27 @@ export class Resolver {
|
|
|
1180
1207
|
}
|
|
1181
1208
|
|
|
1182
1209
|
case 'NeedSMTProof': {
|
|
1210
|
+
// A proof of another shape is data for the signal that does not agree with
|
|
1211
|
+
// its Signal Bytes: INVALID_SIGNAL_DATA, as for the id and the walk below.
|
|
1183
1212
|
if(!isSMTProof(data)) {
|
|
1184
1213
|
throw new ResolveError(
|
|
1185
1214
|
'Provided data for NeedSMTProof is not an SMT proof.',
|
|
1186
|
-
|
|
1215
|
+
INVALID_SIGNAL_DATA, { kind: need.kind }
|
|
1187
1216
|
);
|
|
1188
1217
|
}
|
|
1189
|
-
// proof.id is base64url per spec; smtRootHash is the hex on-chain signal.
|
|
1190
|
-
|
|
1218
|
+
// proof.id is base64url per spec; smtRootHash is the hex on-chain signal. The
|
|
1219
|
+
// specification ("Process SMT Beacon") compares the id of smt_proof to
|
|
1220
|
+
// smt_root: a mismatch, or an id that does not decode, is INVALID_SIGNAL_DATA.
|
|
1221
|
+
let proofIdHex: string | undefined;
|
|
1222
|
+
try {
|
|
1223
|
+
proofIdHex = encodeHash(decodeHash(data.id, 'base64urlnopad'), 'hex');
|
|
1224
|
+
} catch {
|
|
1225
|
+
proofIdHex = undefined;
|
|
1226
|
+
}
|
|
1191
1227
|
if(proofIdHex !== need.smtRootHash) {
|
|
1192
1228
|
throw new ResolveError(
|
|
1193
|
-
`SMT proof root hash mismatch: expected ${need.smtRootHash}, got ${proofIdHex}
|
|
1194
|
-
|
|
1229
|
+
`SMT proof root hash mismatch: expected ${need.smtRootHash}, got ${proofIdHex ?? 'an id that does not decode'}.`,
|
|
1230
|
+
INVALID_SIGNAL_DATA, { expected: need.smtRootHash, actual: proofIdHex }
|
|
1195
1231
|
);
|
|
1196
1232
|
}
|
|
1197
1233
|
this.#sidecarData.smtMap.set(need.smtRootHash, data);
|
package/src/core/types.ts
CHANGED
|
@@ -49,8 +49,10 @@ export type Sidecar = {
|
|
|
49
49
|
casUpdates?: Array<CASAnnouncement>;
|
|
50
50
|
|
|
51
51
|
/**
|
|
52
|
-
* Optional array of SMT Proofs
|
|
53
|
-
*
|
|
52
|
+
* Optional array of SMT Proofs: one proof for each SMT beacon signal that the
|
|
53
|
+
* resolver finds for the DID, with an update announced or not. The DID
|
|
54
|
+
* controller keeps every proof for the life of the DID. Sidecar is the only
|
|
55
|
+
* channel for a proof.
|
|
54
56
|
*/
|
|
55
57
|
smtProofs?: Array<SMTProof>;
|
|
56
58
|
};
|