@peerbit/trusted-network 6.0.113 → 6.0.114

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.
@@ -1,45 +1,36 @@
1
1
  import { deserialize, serialize } from "@dao-xyz/borsh";
2
- import {
3
- calculateRawCid,
4
- cidifyString,
5
- codecMap,
6
- defaultHasher,
7
- stringifyCid,
8
- } from "@peerbit/blocks-interface";
9
2
  import { DecryptedThing, verify } from "@peerbit/crypto";
10
- import { Entry, EntryType, EntryV0, NO_ENCODING } from "@peerbit/log";
3
+ import { Entry, EntryV0, NO_ENCODING } from "@peerbit/log";
11
4
  import { equals } from "uint8arrays";
12
5
  import {
13
6
  NetworkDescriptorV2,
14
7
  assertNetworkDescriptorV2,
15
- copyUint8ArrayV2,
16
8
  copyUint8ArrayWithLengthV2,
17
9
  exactUint8ArrayByteLengthV2,
18
10
  } from "./v2.js";
19
11
 
20
- /** Internal limits selected by a concrete TrustedNetwork v2 record profile. */
21
- export type AuthorityEntryV0LimitsV2 = {
22
- maximumEntryBytes: number;
23
- maximumDirectParents: number;
24
- exactPayloadBytes?: number;
25
- };
26
-
27
12
  export type AuthenticatedAuthorityEntryV0V2 = {
28
13
  descriptor: NetworkDescriptorV2;
29
14
  entryBytes: Uint8Array;
30
- entryCid: string;
31
- entryDigest: Uint8Array;
32
- gid: string;
33
- metaData?: Uint8Array;
34
- directParents: Array<{ cid: string; digest: Uint8Array }>;
15
+ entry: EntryV0<Uint8Array>;
16
+ metaBytes: Uint8Array;
35
17
  payloadBytes: Uint8Array;
18
+ reservedBytes: Uint8Array;
19
+ hasHash: boolean;
20
+ directParentCount: number;
36
21
  };
37
22
 
38
- type ScannedAuthorityEntryV0 = {
23
+ export type AuthorityEntryV0ProfileV2 = (
24
+ entry: AuthenticatedAuthorityEntryV0V2,
25
+ ) => void;
26
+
27
+ type ScannedEntryV0StructureV2 = {
39
28
  metaBytes: Uint8Array;
40
29
  payloadBytes: Uint8Array;
41
- signatureBytes: Uint8Array;
42
30
  signableBytes: Uint8Array;
31
+ reservedBytes: Uint8Array;
32
+ hasHash: boolean;
33
+ directParentCount: number;
43
34
  };
44
35
 
45
36
  class BoundsReaderV2 {
@@ -54,6 +45,10 @@ class BoundsReaderV2 {
54
45
  return this.offset;
55
46
  }
56
47
 
48
+ get remaining(): number {
49
+ return this.bytes.byteLength - this.offset;
50
+ }
51
+
57
52
  readU8(label: string): number {
58
53
  return this.readExact(1, label)[0]!;
59
54
  }
@@ -103,40 +98,36 @@ const readPublicWrapperV2 = (
103
98
  reader: BoundsReaderV2,
104
99
  label: string,
105
100
  ): Uint8Array => {
106
- reader.expectU8(0, `${label} MaybeEncrypted variant`);
107
- reader.expectU8(0, `${label} DecryptedThing variant`);
101
+ if (
102
+ reader.readU8(`${label} MaybeEncrypted variant`) !== 0 ||
103
+ reader.readU8(`${label} DecryptedThing variant`) !== 0
104
+ ) {
105
+ throw new Error(`Authority EntryV0 ${label} must be public`);
106
+ }
108
107
  return reader.readBytes(label);
109
108
  };
110
109
 
111
- const scanMetaV2 = (
112
- metaBytes: Uint8Array,
113
- authorityBytes: Uint8Array,
114
- maximumDirectParents: number,
115
- ): void => {
110
+ const scanMetaStructureV2 = (metaBytes: Uint8Array): number => {
116
111
  const reader = new BoundsReaderV2(metaBytes);
117
112
  reader.expectU8(0, "metadata variant");
118
113
  reader.expectU8(0, "clock variant");
119
- if (!equals(reader.readBytes("clock id"), authorityBytes)) {
120
- throw new Error("Authority EntryV0 clock id is not the policy authority");
121
- }
114
+ reader.readBytes("clock id");
122
115
  reader.expectU8(0, "timestamp variant");
123
116
  reader.readExact(8, "timestamp wall time");
124
117
  reader.readExact(4, "timestamp logical time");
125
118
  reader.readBytes("gid");
126
119
 
127
120
  const directParentCount = reader.readU32("direct-parent count");
128
- if (directParentCount > maximumDirectParents) {
129
- throw new Error(
130
- `Authority EntryV0 may contain at most ${maximumDirectParents} direct parents`,
131
- );
121
+ // Every Borsh string starts with a four-byte length. Reject an impossible
122
+ // input-backed count before iterating or invoking the generic decoder.
123
+ if (directParentCount > Math.floor(reader.remaining / 4)) {
124
+ throw new Error("Authority EntryV0 has impossible direct-parent count");
132
125
  }
133
126
  for (let i = 0; i < directParentCount; i++) {
134
127
  reader.readBytes("direct parent");
135
128
  }
136
129
 
137
- if (reader.readU8("entry type") !== EntryType.APPEND) {
138
- throw new Error("Authority EntryV0 must be an APPEND entry");
139
- }
130
+ reader.readU8("entry type");
140
131
  const metaDataOption = reader.readU8("metadata data option");
141
132
  if (metaDataOption === 1) {
142
133
  reader.readBytes("metadata data");
@@ -144,206 +135,111 @@ const scanMetaV2 = (
144
135
  throw new Error("Authority EntryV0 has invalid metadata data option");
145
136
  }
146
137
  reader.expectDone("metadata");
138
+ return directParentCount;
147
139
  };
148
140
 
149
- const scanPayloadV2 = (
150
- payloadBytes: Uint8Array,
151
- exactPayloadBytes?: number,
152
- ): void => {
141
+ const scanPayloadStructureV2 = (payloadBytes: Uint8Array): Uint8Array => {
153
142
  const reader = new BoundsReaderV2(payloadBytes);
154
143
  reader.expectU8(0, "payload variant");
155
144
  const data = reader.readBytes("payload data");
156
- if (
157
- exactPayloadBytes !== undefined &&
158
- data.byteLength !== exactPayloadBytes
159
- ) {
160
- throw new Error(
161
- `Authority EntryV0 payload must contain exactly ${exactPayloadBytes} bytes`,
162
- );
163
- }
164
145
  reader.expectDone("payload");
146
+ return data;
165
147
  };
166
148
 
167
- const SECP256K1_SIGNATURE_TEXT_BYTES = 132;
168
- const SECP256K1_LOW_S_MAX_HEX =
169
- "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0";
170
-
171
- const isLowerHexByteV2 = (value: number): boolean =>
172
- (value >= 0x30 && value <= 0x39) || (value >= 0x61 && value <= 0x66);
173
-
174
- const assertCanonicalSecp256k1SignatureV2 = (signature: Uint8Array): void => {
175
- if (
176
- signature.byteLength !== SECP256K1_SIGNATURE_TEXT_BYTES ||
177
- signature[0] !== 0x30 ||
178
- signature[1] !== 0x78
179
- ) {
180
- throw new Error("Authority EntryV0 secp256k1 signature is not canonical");
181
- }
182
- for (let i = 2; i < signature.byteLength; i++) {
183
- if (!isLowerHexByteV2(signature[i]!)) {
184
- throw new Error("Authority EntryV0 secp256k1 signature is not canonical");
185
- }
186
- }
187
- if (
188
- signature[130] !== 0x31 ||
189
- (signature[131] !== 0x62 && signature[131] !== 0x63)
190
- ) {
191
- throw new Error("Authority EntryV0 secp256k1 signature is not canonical");
192
- }
193
- for (let i = 0; i < SECP256K1_LOW_S_MAX_HEX.length; i++) {
194
- const actual = signature[66 + i]!;
195
- const maximum = SECP256K1_LOW_S_MAX_HEX.charCodeAt(i);
196
- if (actual < maximum) break;
197
- if (actual > maximum) {
198
- throw new Error("Authority EntryV0 secp256k1 signature is not canonical");
199
- }
200
- }
201
- };
202
-
203
- const scanSignatureV2 = (
204
- signatureBytes: Uint8Array,
205
- authorityBytes: Uint8Array,
206
- ): void => {
149
+ const scanSignatureStructureV2 = (signatureBytes: Uint8Array): void => {
207
150
  const reader = new BoundsReaderV2(signatureBytes);
208
151
  reader.expectU8(0, "signature variant");
209
- const signature = reader.readBytes("signature data");
152
+ reader.readBytes("signature data");
210
153
  const publicKeyVariant = reader.readU8("signature public-key variant");
211
154
  const publicKeyLength =
212
155
  publicKeyVariant === 0 ? 32 : publicKeyVariant === 1 ? 33 : undefined;
213
156
  if (publicKeyLength === undefined) {
214
157
  throw new Error("Authority EntryV0 uses an unsupported signing key");
215
158
  }
216
- const publicKey = reader.readExact(publicKeyLength, "signature public key");
217
- const serializedPublicKey = new Uint8Array(publicKeyLength + 1);
218
- serializedPublicKey[0] = publicKeyVariant;
219
- serializedPublicKey.set(publicKey, 1);
220
- if (!equals(serializedPublicKey, authorityBytes)) {
221
- throw new Error("Authority EntryV0 signer is not the policy authority");
222
- }
223
- if (publicKeyVariant === 1) {
224
- assertCanonicalSecp256k1SignatureV2(signature);
225
- }
159
+ reader.readExact(publicKeyLength, "signature public key");
226
160
  reader.readU8("signature prehash");
227
161
  reader.expectDone("signature");
228
162
  };
229
163
 
230
- const scanAuthorityEntryV0 = (
164
+ const scanEntryV0StructureV2 = (
231
165
  entryBytes: Uint8Array,
232
- authorityBytes: Uint8Array,
233
- limits: AuthorityEntryV0LimitsV2,
234
- ): ScannedAuthorityEntryV0 => {
166
+ ): ScannedEntryV0StructureV2 => {
235
167
  const reader = new BoundsReaderV2(entryBytes);
236
168
  reader.expectU8(0, "entry variant");
237
169
  const metaBytes = readPublicWrapperV2(reader, "metadata");
238
- const payloadBytes = readPublicWrapperV2(reader, "payload");
239
- const reserved = reader.readExact(4, "reserved bytes");
240
- if (
241
- reserved[0] !== 0 ||
242
- reserved[1] !== 0 ||
243
- reserved[2] !== 0 ||
244
- reserved[3] !== 0
245
- ) {
246
- throw new Error("Authority EntryV0 reserved bytes must be zero");
247
- }
170
+ const payloadContainerBytes = readPublicWrapperV2(reader, "payload");
171
+ const reservedBytes = reader.readExact(4, "reserved bytes");
248
172
  const signablePrefixLength = reader.position;
249
- reader.expectU8(1, "signatures option");
173
+ if (reader.readU8("signatures option") !== 1) {
174
+ throw new Error("Authority EntryV0 must contain exactly one signature");
175
+ }
250
176
  reader.expectU8(0, "signatures variant");
251
177
  if (reader.readU32("signature count") !== 1) {
252
178
  throw new Error("Authority EntryV0 must contain exactly one signature");
253
179
  }
254
180
  const signatureBytes = readPublicWrapperV2(reader, "signature");
255
- reader.expectU8(0, "hash option");
181
+ const hashOption = reader.readU8("hash option");
182
+ if (hashOption === 1) {
183
+ reader.readBytes("hash");
184
+ } else if (hashOption !== 0) {
185
+ throw new Error("Authority EntryV0 has invalid hash option");
186
+ }
256
187
  reader.expectDone("storage");
257
188
 
258
- scanMetaV2(metaBytes, authorityBytes, limits.maximumDirectParents);
259
- scanPayloadV2(payloadBytes, limits.exactPayloadBytes);
260
- scanSignatureV2(signatureBytes, authorityBytes);
189
+ const directParentCount = scanMetaStructureV2(metaBytes);
190
+ const payloadBytes = scanPayloadStructureV2(payloadContainerBytes);
191
+ scanSignatureStructureV2(signatureBytes);
261
192
  const signableBytes = new Uint8Array(signablePrefixLength + 2);
262
193
  signableBytes.set(entryBytes.subarray(0, signablePrefixLength));
263
- return { metaBytes, payloadBytes, signatureBytes, signableBytes };
194
+ return {
195
+ metaBytes,
196
+ payloadBytes,
197
+ signableBytes,
198
+ reservedBytes,
199
+ hasHash: hashOption === 1,
200
+ directParentCount,
201
+ };
264
202
  };
265
203
 
266
- const validateLimitsV2 = (limits: AuthorityEntryV0LimitsV2): void => {
267
- if (
268
- !Number.isSafeInteger(limits.maximumEntryBytes) ||
269
- limits.maximumEntryBytes < 1 ||
270
- !Number.isSafeInteger(limits.maximumDirectParents) ||
271
- limits.maximumDirectParents < 0 ||
272
- (limits.exactPayloadBytes !== undefined &&
273
- (!Number.isSafeInteger(limits.exactPayloadBytes) ||
274
- limits.exactPayloadBytes < 0))
275
- ) {
276
- throw new Error("Invalid internal authority EntryV0 limits");
204
+ const validateMaximumEntryBytesV2 = (maximumEntryBytes: number): void => {
205
+ if (!Number.isSafeInteger(maximumEntryBytes) || maximumEntryBytes < 1) {
206
+ throw new Error("Invalid internal authority EntryV0 byte limit");
277
207
  }
278
208
  };
279
209
 
280
- const canonicalDirectParentsV2 = (
281
- parents: string[],
282
- ): Array<{ cid: string; digest: Uint8Array }> => {
283
- const seen = new Set<string>();
284
- return parents.map((cid) => {
285
- let parsed: ReturnType<typeof cidifyString>;
286
- try {
287
- parsed = cidifyString(cid);
288
- } catch {
289
- throw new Error(
290
- "Authority EntryV0 direct parents must use canonical CIDv1/raw/sha2-256",
291
- );
292
- }
293
- if (
294
- !cid ||
295
- parsed.version !== 1 ||
296
- parsed.code !== codecMap.raw.code ||
297
- parsed.multihash.code !== defaultHasher.code ||
298
- parsed.multihash.digest.byteLength !== 32 ||
299
- stringifyCid(parsed) !== cid
300
- ) {
301
- throw new Error(
302
- "Authority EntryV0 direct parents must use canonical CIDv1/raw/sha2-256",
303
- );
304
- }
305
- if (seen.has(cid)) {
306
- throw new Error("Authority EntryV0 direct parents must be unique");
307
- }
308
- seen.add(cid);
309
- return {
310
- cid,
311
- digest: copyUint8ArrayV2(parsed.multihash.digest),
312
- };
313
- });
314
- };
315
-
316
- /**
317
- * Authenticate one bounded raw EntryV0 authority envelope. This establishes
318
- * canonical bytes, authority signature, and structural facts only. It does not
319
- * establish policy acceptance, causal ancestry, freshness, or durability.
320
- */
321
- export const authenticateAuthorityEntryV0V2 = async (
210
+ export const captureAuthorityEntryV0BytesV2 = (
322
211
  entryBytes: Uint8Array,
323
- descriptor: NetworkDescriptorV2,
324
- limits: AuthorityEntryV0LimitsV2,
325
- ): Promise<AuthenticatedAuthorityEntryV0V2> => {
326
- validateLimitsV2(limits);
212
+ maximumEntryBytes: number,
213
+ ): Uint8Array => {
214
+ validateMaximumEntryBytesV2(maximumEntryBytes);
327
215
  let byteLength: number;
328
216
  try {
329
217
  byteLength = exactUint8ArrayByteLengthV2(entryBytes);
330
218
  } catch {
331
219
  throw new Error("Authority entry must use canonical EntryV0 bytes");
332
220
  }
333
- if (byteLength < 1 || byteLength > limits.maximumEntryBytes) {
221
+ if (byteLength < 1 || byteLength > maximumEntryBytes) {
334
222
  throw new Error(
335
- `Authority EntryV0 must contain 1-${limits.maximumEntryBytes} bytes`,
223
+ `Authority EntryV0 must contain 1-${maximumEntryBytes} bytes`,
336
224
  );
337
225
  }
338
- const capturedEntryBytes = copyUint8ArrayWithLengthV2(entryBytes, byteLength);
226
+ return copyUint8ArrayWithLengthV2(entryBytes, byteLength);
227
+ };
339
228
 
229
+ /**
230
+ * Authenticate one bounded raw EntryV0 authority envelope. This establishes
231
+ * canonical bytes and the sole public authority signature only. It does not
232
+ * establish a concrete record profile, policy acceptance, causal ancestry,
233
+ * freshness, or durability.
234
+ */
235
+ export const authenticateCapturedAuthorityEntryV0V2 = async (
236
+ capturedEntryBytes: Uint8Array,
237
+ descriptor: NetworkDescriptorV2,
238
+ assertProfile?: AuthorityEntryV0ProfileV2,
239
+ ): Promise<AuthenticatedAuthorityEntryV0V2> => {
340
240
  assertNetworkDescriptorV2(descriptor);
341
241
  const authorityBytes = serialize(descriptor.policyAuthority);
342
- const scanned = scanAuthorityEntryV0(
343
- capturedEntryBytes,
344
- authorityBytes,
345
- limits,
346
- );
242
+ const scanned = scanEntryV0StructureV2(capturedEntryBytes);
347
243
  const capturedDescriptor = deserialize(
348
244
  serialize(descriptor),
349
245
  NetworkDescriptorV2,
@@ -374,37 +270,28 @@ export const authenticateAuthorityEntryV0V2 = async (
374
270
  }
375
271
 
376
272
  entry.init({ encoding: NO_ENCODING });
377
- const meta = entry.meta;
378
273
  const payload = entry.payload;
379
274
  const signatures = entry.signatures;
380
- if (
381
- !equals(scanned.metaBytes, serialize(meta)) ||
382
- !equals(scanned.payloadBytes, serialize(payload)) ||
383
- !equals(scanned.signatureBytes, serialize(signatures[0]!))
384
- ) {
385
- throw new Error("Authority EntryV0 nested encoding is not canonical");
386
- }
387
- if (
388
- meta.type !== EntryType.APPEND ||
389
- meta.next.length > limits.maximumDirectParents ||
390
- !equals(meta.clock.id, authorityBytes)
391
- ) {
392
- throw new Error("Authority EntryV0 metadata does not match its profile");
393
- }
394
275
  if (
395
276
  signatures.length !== 1 ||
396
277
  !equals(serialize(signatures[0]!.publicKey), authorityBytes)
397
278
  ) {
398
279
  throw new Error("Authority EntryV0 signer is not the policy authority");
399
280
  }
400
- if (
401
- limits.exactPayloadBytes !== undefined &&
402
- payload.data.byteLength !== limits.exactPayloadBytes
403
- ) {
404
- throw new Error(
405
- `Authority EntryV0 payload must contain exactly ${limits.exactPayloadBytes} bytes`,
406
- );
281
+ if (!equals(scanned.payloadBytes, payload.data)) {
282
+ throw new Error("Authority EntryV0 payload framing is inconsistent");
407
283
  }
284
+ const authenticated: AuthenticatedAuthorityEntryV0V2 = {
285
+ descriptor: capturedDescriptor,
286
+ entryBytes: capturedEntryBytes,
287
+ entry,
288
+ metaBytes: scanned.metaBytes,
289
+ payloadBytes: payload.data,
290
+ reservedBytes: scanned.reservedBytes,
291
+ hasHash: scanned.hasHash,
292
+ directParentCount: scanned.directParentCount,
293
+ };
294
+ assertProfile?.(authenticated);
408
295
 
409
296
  let signatureIsValid = false;
410
297
  try {
@@ -416,15 +303,17 @@ export const authenticateAuthorityEntryV0V2 = async (
416
303
  throw new Error("Authority EntryV0 signature is invalid");
417
304
  }
418
305
 
419
- const prepared = await calculateRawCid(capturedEntryBytes);
420
- return {
421
- descriptor: capturedDescriptor,
422
- entryBytes: capturedEntryBytes,
423
- entryCid: prepared.cid,
424
- entryDigest: copyUint8ArrayV2(prepared.block.cid.multihash.digest),
425
- gid: meta.gid,
426
- metaData: meta.data === undefined ? undefined : copyUint8ArrayV2(meta.data),
427
- directParents: canonicalDirectParentsV2(meta.next),
428
- payloadBytes: copyUint8ArrayV2(payload.data),
429
- };
306
+ return authenticated;
430
307
  };
308
+
309
+ export const authenticateAuthorityEntryV0V2 = async (
310
+ entryBytes: Uint8Array,
311
+ descriptor: NetworkDescriptorV2,
312
+ maximumEntryBytes: number,
313
+ assertProfile?: AuthorityEntryV0ProfileV2,
314
+ ): Promise<AuthenticatedAuthorityEntryV0V2> =>
315
+ authenticateCapturedAuthorityEntryV0V2(
316
+ captureAuthorityEntryV0BytesV2(entryBytes, maximumEntryBytes),
317
+ descriptor,
318
+ assertProfile,
319
+ );
@@ -1,7 +1,7 @@
1
1
  import { deserialize, serialize } from "@dao-xyz/borsh";
2
- import { DecryptedThing, PublicSignKey, verify } from "@peerbit/crypto";
3
- import { Entry, EntryV0, NO_ENCODING } from "@peerbit/log";
2
+ import { PublicSignKey } from "@peerbit/crypto";
4
3
  import { compare, equals } from "uint8arrays";
4
+ import { authenticateCapturedAuthorityEntryV0V2 } from "./v2-authority-entry.js";
5
5
  import {
6
6
  NetworkDescriptorV2,
7
7
  PolicySnapshotBodyV2,
@@ -304,58 +304,11 @@ const authenticateCapturedPolicySnapshotEntryV2 = async (
304
304
  canonicalEntryBytes: Uint8Array,
305
305
  descriptor: NetworkDescriptorV2,
306
306
  ): Promise<ValidatedPolicySnapshotV2> => {
307
- const authenticatedEntry = deserialize(canonicalEntryBytes, Entry);
308
- if (!(authenticatedEntry instanceof EntryV0)) {
309
- throw new Error("Policy snapshot must use EntryV0");
310
- }
311
- if (!equals(canonicalEntryBytes, serialize(authenticatedEntry))) {
312
- throw new Error("Policy snapshot entry encoding is not canonical");
313
- }
314
- if (!(authenticatedEntry._meta instanceof DecryptedThing)) {
315
- throw new Error("Policy snapshot metadata must be public");
316
- }
317
- if (!(authenticatedEntry._payload instanceof DecryptedThing)) {
318
- throw new Error("Policy snapshot payload must be public");
319
- }
320
- if (
321
- authenticatedEntry._signatures === undefined ||
322
- authenticatedEntry._signatures.signatures.length !== 1
323
- ) {
324
- throw new Error("Policy snapshot must contain exactly one signature");
325
- }
326
- if (
327
- !(authenticatedEntry._signatures.signatures[0] instanceof DecryptedThing)
328
- ) {
329
- throw new Error("Policy snapshot signature must be public");
330
- }
331
- authenticatedEntry.init({ encoding: NO_ENCODING });
332
-
333
- const signatures = await authenticatedEntry.getSignatures();
334
- if (signatures.length !== 1) {
335
- throw new Error("Policy snapshot must resolve exactly one signature");
336
- }
337
- const signature = signatures[0]!;
338
- if (
339
- !equals(
340
- serialize(signature.publicKey),
341
- serialize(descriptor.policyAuthority),
342
- )
343
- ) {
344
- throw new Error("Policy snapshot signer is not the policy authority");
345
- }
346
- if (!(await verify(signature, authenticatedEntry.getSignableBytes()))) {
347
- throw new Error("Policy snapshot authority signature is invalid");
348
- }
349
-
350
- const payload = await authenticatedEntry.getPayloadValue();
351
- let canonicalPayload: Uint8Array;
352
- try {
353
- canonicalPayload = copyBytes(payload as Uint8Array);
354
- } catch {
355
- throw new Error(
356
- "Policy snapshot payload must contain canonical body bytes",
357
- );
358
- }
307
+ const authenticated = await authenticateCapturedAuthorityEntryV0V2(
308
+ canonicalEntryBytes,
309
+ descriptor,
310
+ );
311
+ const canonicalPayload = authenticated.payloadBytes;
359
312
  const body = decodePolicySnapshotBodyV2(canonicalPayload, descriptor);
360
313
  const digest = digestPolicySnapshotBodyV2(body);
361
314
  return {