@peerbit/trusted-network 6.0.112 → 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.
@@ -0,0 +1,319 @@
1
+ import { deserialize, serialize } from "@dao-xyz/borsh";
2
+ import { DecryptedThing, verify } from "@peerbit/crypto";
3
+ import { Entry, EntryV0, NO_ENCODING } from "@peerbit/log";
4
+ import { equals } from "uint8arrays";
5
+ import {
6
+ NetworkDescriptorV2,
7
+ assertNetworkDescriptorV2,
8
+ copyUint8ArrayWithLengthV2,
9
+ exactUint8ArrayByteLengthV2,
10
+ } from "./v2.js";
11
+
12
+ export type AuthenticatedAuthorityEntryV0V2 = {
13
+ descriptor: NetworkDescriptorV2;
14
+ entryBytes: Uint8Array;
15
+ entry: EntryV0<Uint8Array>;
16
+ metaBytes: Uint8Array;
17
+ payloadBytes: Uint8Array;
18
+ reservedBytes: Uint8Array;
19
+ hasHash: boolean;
20
+ directParentCount: number;
21
+ };
22
+
23
+ export type AuthorityEntryV0ProfileV2 = (
24
+ entry: AuthenticatedAuthorityEntryV0V2,
25
+ ) => void;
26
+
27
+ type ScannedEntryV0StructureV2 = {
28
+ metaBytes: Uint8Array;
29
+ payloadBytes: Uint8Array;
30
+ signableBytes: Uint8Array;
31
+ reservedBytes: Uint8Array;
32
+ hasHash: boolean;
33
+ directParentCount: number;
34
+ };
35
+
36
+ class BoundsReaderV2 {
37
+ private offset = 0;
38
+ private readonly view: DataView;
39
+
40
+ constructor(private readonly bytes: Uint8Array) {
41
+ this.view = new DataView(bytes.buffer, bytes.byteOffset, bytes.byteLength);
42
+ }
43
+
44
+ get position(): number {
45
+ return this.offset;
46
+ }
47
+
48
+ get remaining(): number {
49
+ return this.bytes.byteLength - this.offset;
50
+ }
51
+
52
+ readU8(label: string): number {
53
+ return this.readExact(1, label)[0]!;
54
+ }
55
+
56
+ readU32(label: string): number {
57
+ this.requireRemaining(4, label);
58
+ const value = this.view.getUint32(this.offset, true);
59
+ this.offset += 4;
60
+ return value;
61
+ }
62
+
63
+ readBytes(label: string): Uint8Array {
64
+ return this.readExact(this.readU32(`${label} length`), label);
65
+ }
66
+
67
+ readExact(byteLength: number, label: string): Uint8Array {
68
+ this.requireRemaining(byteLength, label);
69
+ const start = this.offset;
70
+ this.offset += byteLength;
71
+ return this.bytes.subarray(start, this.offset);
72
+ }
73
+
74
+ expectU8(expected: number, label: string): void {
75
+ if (this.readU8(label) !== expected) {
76
+ throw new Error(`Authority EntryV0 has invalid ${label}`);
77
+ }
78
+ }
79
+
80
+ expectDone(label: string): void {
81
+ if (this.offset !== this.bytes.byteLength) {
82
+ throw new Error(`Authority EntryV0 has trailing ${label} bytes`);
83
+ }
84
+ }
85
+
86
+ private requireRemaining(byteLength: number, label: string): void {
87
+ if (
88
+ !Number.isSafeInteger(byteLength) ||
89
+ byteLength < 0 ||
90
+ byteLength > this.bytes.byteLength - this.offset
91
+ ) {
92
+ throw new Error(`Authority EntryV0 has truncated ${label}`);
93
+ }
94
+ }
95
+ }
96
+
97
+ const readPublicWrapperV2 = (
98
+ reader: BoundsReaderV2,
99
+ label: string,
100
+ ): Uint8Array => {
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
+ }
107
+ return reader.readBytes(label);
108
+ };
109
+
110
+ const scanMetaStructureV2 = (metaBytes: Uint8Array): number => {
111
+ const reader = new BoundsReaderV2(metaBytes);
112
+ reader.expectU8(0, "metadata variant");
113
+ reader.expectU8(0, "clock variant");
114
+ reader.readBytes("clock id");
115
+ reader.expectU8(0, "timestamp variant");
116
+ reader.readExact(8, "timestamp wall time");
117
+ reader.readExact(4, "timestamp logical time");
118
+ reader.readBytes("gid");
119
+
120
+ const directParentCount = reader.readU32("direct-parent count");
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");
125
+ }
126
+ for (let i = 0; i < directParentCount; i++) {
127
+ reader.readBytes("direct parent");
128
+ }
129
+
130
+ reader.readU8("entry type");
131
+ const metaDataOption = reader.readU8("metadata data option");
132
+ if (metaDataOption === 1) {
133
+ reader.readBytes("metadata data");
134
+ } else if (metaDataOption !== 0) {
135
+ throw new Error("Authority EntryV0 has invalid metadata data option");
136
+ }
137
+ reader.expectDone("metadata");
138
+ return directParentCount;
139
+ };
140
+
141
+ const scanPayloadStructureV2 = (payloadBytes: Uint8Array): Uint8Array => {
142
+ const reader = new BoundsReaderV2(payloadBytes);
143
+ reader.expectU8(0, "payload variant");
144
+ const data = reader.readBytes("payload data");
145
+ reader.expectDone("payload");
146
+ return data;
147
+ };
148
+
149
+ const scanSignatureStructureV2 = (signatureBytes: Uint8Array): void => {
150
+ const reader = new BoundsReaderV2(signatureBytes);
151
+ reader.expectU8(0, "signature variant");
152
+ reader.readBytes("signature data");
153
+ const publicKeyVariant = reader.readU8("signature public-key variant");
154
+ const publicKeyLength =
155
+ publicKeyVariant === 0 ? 32 : publicKeyVariant === 1 ? 33 : undefined;
156
+ if (publicKeyLength === undefined) {
157
+ throw new Error("Authority EntryV0 uses an unsupported signing key");
158
+ }
159
+ reader.readExact(publicKeyLength, "signature public key");
160
+ reader.readU8("signature prehash");
161
+ reader.expectDone("signature");
162
+ };
163
+
164
+ const scanEntryV0StructureV2 = (
165
+ entryBytes: Uint8Array,
166
+ ): ScannedEntryV0StructureV2 => {
167
+ const reader = new BoundsReaderV2(entryBytes);
168
+ reader.expectU8(0, "entry variant");
169
+ const metaBytes = readPublicWrapperV2(reader, "metadata");
170
+ const payloadContainerBytes = readPublicWrapperV2(reader, "payload");
171
+ const reservedBytes = reader.readExact(4, "reserved bytes");
172
+ const signablePrefixLength = reader.position;
173
+ if (reader.readU8("signatures option") !== 1) {
174
+ throw new Error("Authority EntryV0 must contain exactly one signature");
175
+ }
176
+ reader.expectU8(0, "signatures variant");
177
+ if (reader.readU32("signature count") !== 1) {
178
+ throw new Error("Authority EntryV0 must contain exactly one signature");
179
+ }
180
+ const signatureBytes = readPublicWrapperV2(reader, "signature");
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
+ }
187
+ reader.expectDone("storage");
188
+
189
+ const directParentCount = scanMetaStructureV2(metaBytes);
190
+ const payloadBytes = scanPayloadStructureV2(payloadContainerBytes);
191
+ scanSignatureStructureV2(signatureBytes);
192
+ const signableBytes = new Uint8Array(signablePrefixLength + 2);
193
+ signableBytes.set(entryBytes.subarray(0, signablePrefixLength));
194
+ return {
195
+ metaBytes,
196
+ payloadBytes,
197
+ signableBytes,
198
+ reservedBytes,
199
+ hasHash: hashOption === 1,
200
+ directParentCount,
201
+ };
202
+ };
203
+
204
+ const validateMaximumEntryBytesV2 = (maximumEntryBytes: number): void => {
205
+ if (!Number.isSafeInteger(maximumEntryBytes) || maximumEntryBytes < 1) {
206
+ throw new Error("Invalid internal authority EntryV0 byte limit");
207
+ }
208
+ };
209
+
210
+ export const captureAuthorityEntryV0BytesV2 = (
211
+ entryBytes: Uint8Array,
212
+ maximumEntryBytes: number,
213
+ ): Uint8Array => {
214
+ validateMaximumEntryBytesV2(maximumEntryBytes);
215
+ let byteLength: number;
216
+ try {
217
+ byteLength = exactUint8ArrayByteLengthV2(entryBytes);
218
+ } catch {
219
+ throw new Error("Authority entry must use canonical EntryV0 bytes");
220
+ }
221
+ if (byteLength < 1 || byteLength > maximumEntryBytes) {
222
+ throw new Error(
223
+ `Authority EntryV0 must contain 1-${maximumEntryBytes} bytes`,
224
+ );
225
+ }
226
+ return copyUint8ArrayWithLengthV2(entryBytes, byteLength);
227
+ };
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> => {
240
+ assertNetworkDescriptorV2(descriptor);
241
+ const authorityBytes = serialize(descriptor.policyAuthority);
242
+ const scanned = scanEntryV0StructureV2(capturedEntryBytes);
243
+ const capturedDescriptor = deserialize(
244
+ serialize(descriptor),
245
+ NetworkDescriptorV2,
246
+ );
247
+ assertNetworkDescriptorV2(capturedDescriptor);
248
+ if (!equals(serialize(capturedDescriptor.policyAuthority), authorityBytes)) {
249
+ throw new Error("Network descriptor changed during capture");
250
+ }
251
+ const entry = deserialize(capturedEntryBytes, Entry);
252
+ if (!(entry instanceof EntryV0)) {
253
+ throw new Error("Authority entry must use EntryV0");
254
+ }
255
+ if (!equals(capturedEntryBytes, serialize(entry))) {
256
+ throw new Error("Authority EntryV0 storage is not canonical");
257
+ }
258
+ if (!(entry._meta instanceof DecryptedThing)) {
259
+ throw new Error("Authority EntryV0 metadata must be public");
260
+ }
261
+ if (!(entry._payload instanceof DecryptedThing)) {
262
+ throw new Error("Authority EntryV0 payload must be public");
263
+ }
264
+ if (
265
+ entry._signatures === undefined ||
266
+ entry._signatures.signatures.length !== 1 ||
267
+ !(entry._signatures.signatures[0] instanceof DecryptedThing)
268
+ ) {
269
+ throw new Error("Authority EntryV0 must contain one public signature");
270
+ }
271
+
272
+ entry.init({ encoding: NO_ENCODING });
273
+ const payload = entry.payload;
274
+ const signatures = entry.signatures;
275
+ if (
276
+ signatures.length !== 1 ||
277
+ !equals(serialize(signatures[0]!.publicKey), authorityBytes)
278
+ ) {
279
+ throw new Error("Authority EntryV0 signer is not the policy authority");
280
+ }
281
+ if (!equals(scanned.payloadBytes, payload.data)) {
282
+ throw new Error("Authority EntryV0 payload framing is inconsistent");
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);
295
+
296
+ let signatureIsValid = false;
297
+ try {
298
+ signatureIsValid = await verify(signatures[0]!, scanned.signableBytes);
299
+ } catch {
300
+ // Unsupported prehashes and malformed signatures fail closed.
301
+ }
302
+ if (!signatureIsValid) {
303
+ throw new Error("Authority EntryV0 signature is invalid");
304
+ }
305
+
306
+ return authenticated;
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 {
@@ -0,0 +1,267 @@
1
+ import { serialize } from "@dao-xyz/borsh";
2
+ import {
3
+ calculateRawCid,
4
+ cidifyString,
5
+ codecMap,
6
+ defaultHasher,
7
+ stringifyCid,
8
+ } from "@peerbit/blocks-interface";
9
+ import { EntryType, type Meta } from "@peerbit/log";
10
+ import { equals } from "uint8arrays";
11
+ import {
12
+ type AuthenticatedAuthorityEntryV0V2,
13
+ authenticateAuthorityEntryV0V2,
14
+ } from "./v2-authority-entry.js";
15
+ import {
16
+ NetworkDescriptorV2,
17
+ ResourceFenceV2,
18
+ TRUSTED_NETWORK_V2_MAX_RESOURCE_FENCE_DIRECT_PARENTS,
19
+ TRUSTED_NETWORK_V2_MAX_RESOURCE_FENCE_ENTRY_BYTES,
20
+ TRUSTED_NETWORK_V2_RESOURCE_FENCE_BODY_BYTES,
21
+ copyUint8ArrayWithLengthV2,
22
+ decodeResourceFenceV2,
23
+ exactUint8ArrayByteLengthV2,
24
+ } from "./v2.js";
25
+
26
+ const RESOURCE_FENCE_AUTHORITY_LIMITS_V2 = Object.freeze({
27
+ maximumEntryBytes: TRUSTED_NETWORK_V2_MAX_RESOURCE_FENCE_ENTRY_BYTES,
28
+ });
29
+
30
+ const SECP256K1_SIGNATURE_TEXT_BYTES = 132;
31
+ const SECP256K1_LOW_S_MAX_HEX =
32
+ "7fffffffffffffffffffffffffffffff5d576e7357a4501ddfe92f46681b20a0";
33
+
34
+ const isLowerHexByteV2 = (value: number): boolean =>
35
+ (value >= 0x30 && value <= 0x39) || (value >= 0x61 && value <= 0x66);
36
+
37
+ const assertCanonicalSecp256k1SignatureV2 = (signature: Uint8Array): void => {
38
+ if (
39
+ signature.byteLength !== SECP256K1_SIGNATURE_TEXT_BYTES ||
40
+ signature[0] !== 0x30 ||
41
+ signature[1] !== 0x78
42
+ ) {
43
+ throw new Error("Authority EntryV0 secp256k1 signature is not canonical");
44
+ }
45
+ for (let i = 2; i < signature.byteLength; i++) {
46
+ if (!isLowerHexByteV2(signature[i]!)) {
47
+ throw new Error("Authority EntryV0 secp256k1 signature is not canonical");
48
+ }
49
+ }
50
+ if (
51
+ signature[130] !== 0x31 ||
52
+ (signature[131] !== 0x62 && signature[131] !== 0x63)
53
+ ) {
54
+ throw new Error("Authority EntryV0 secp256k1 signature is not canonical");
55
+ }
56
+ for (let i = 0; i < SECP256K1_LOW_S_MAX_HEX.length; i++) {
57
+ const actual = signature[66 + i]!;
58
+ const maximum = SECP256K1_LOW_S_MAX_HEX.charCodeAt(i);
59
+ if (actual < maximum) break;
60
+ if (actual > maximum) {
61
+ throw new Error("Authority EntryV0 secp256k1 signature is not canonical");
62
+ }
63
+ }
64
+ };
65
+
66
+ const canonicalDirectParentsV2 = (
67
+ parents: string[],
68
+ ): Array<{ cid: string; digest: Uint8Array }> => {
69
+ const seen = new Set<string>();
70
+ return parents.map((cid) => {
71
+ let parsed: ReturnType<typeof cidifyString>;
72
+ try {
73
+ parsed = cidifyString(cid);
74
+ } catch {
75
+ throw new Error(
76
+ "Authority EntryV0 direct parents must use canonical CIDv1/raw/sha2-256",
77
+ );
78
+ }
79
+ if (
80
+ !cid ||
81
+ parsed.version !== 1 ||
82
+ parsed.code !== codecMap.raw.code ||
83
+ parsed.multihash.code !== defaultHasher.code ||
84
+ parsed.multihash.digest.byteLength !== 32 ||
85
+ stringifyCid(parsed) !== cid
86
+ ) {
87
+ throw new Error(
88
+ "Authority EntryV0 direct parents must use canonical CIDv1/raw/sha2-256",
89
+ );
90
+ }
91
+ if (seen.has(cid)) {
92
+ throw new Error("Authority EntryV0 direct parents must be unique");
93
+ }
94
+ seen.add(cid);
95
+ return {
96
+ cid,
97
+ digest: copyUint8ArrayWithLengthV2(
98
+ parsed.multihash.digest,
99
+ parsed.multihash.digest.byteLength,
100
+ ),
101
+ };
102
+ });
103
+ };
104
+
105
+ type ResourceFenceEntryV0ProfileV2 = {
106
+ meta: Meta;
107
+ directParents: Array<{ cid: string; digest: Uint8Array }>;
108
+ };
109
+
110
+ const assertResourceFenceEntryV0ProfileV2 = (
111
+ authenticated: AuthenticatedAuthorityEntryV0V2,
112
+ ): ResourceFenceEntryV0ProfileV2 => {
113
+ if (authenticated.hasHash) {
114
+ throw new Error(
115
+ "Authority EntryV0 has invalid resource hash option: embedded hash",
116
+ );
117
+ }
118
+ if (
119
+ authenticated.reservedBytes[0] !== 0 ||
120
+ authenticated.reservedBytes[1] !== 0 ||
121
+ authenticated.reservedBytes[2] !== 0 ||
122
+ authenticated.reservedBytes[3] !== 0
123
+ ) {
124
+ throw new Error("Authority EntryV0 reserved bytes must be zero");
125
+ }
126
+ if (
127
+ authenticated.directParentCount >
128
+ TRUSTED_NETWORK_V2_MAX_RESOURCE_FENCE_DIRECT_PARENTS
129
+ ) {
130
+ throw new Error(
131
+ `Authority EntryV0 may contain at most ${TRUSTED_NETWORK_V2_MAX_RESOURCE_FENCE_DIRECT_PARENTS} direct parents`,
132
+ );
133
+ }
134
+ if (
135
+ authenticated.payloadBytes.byteLength !==
136
+ TRUSTED_NETWORK_V2_RESOURCE_FENCE_BODY_BYTES
137
+ ) {
138
+ throw new Error(
139
+ `Authority EntryV0 payload must contain exactly ${TRUSTED_NETWORK_V2_RESOURCE_FENCE_BODY_BYTES} bytes`,
140
+ );
141
+ }
142
+
143
+ const meta = authenticated.entry.meta;
144
+ if (!equals(authenticated.metaBytes, serialize(meta))) {
145
+ throw new Error("Authority EntryV0 nested encoding is not canonical");
146
+ }
147
+ const authorityBytes = serialize(authenticated.descriptor.policyAuthority);
148
+ if (meta.type !== EntryType.APPEND) {
149
+ throw new Error("Authority EntryV0 must be an APPEND entry");
150
+ }
151
+ if (meta.next.length !== authenticated.directParentCount) {
152
+ throw new Error("Authority EntryV0 direct-parent framing is inconsistent");
153
+ }
154
+ if (!equals(meta.clock.id, authorityBytes)) {
155
+ throw new Error("Authority EntryV0 clock id is not the policy authority");
156
+ }
157
+ const signature = authenticated.entry.signatures[0]!;
158
+ if (serialize(signature.publicKey)[0] === 1) {
159
+ assertCanonicalSecp256k1SignatureV2(signature.signature);
160
+ }
161
+ return { meta, directParents: canonicalDirectParentsV2(meta.next) };
162
+ };
163
+
164
+ export type AuthenticatedResourceFenceEntryV2 = Readonly<{
165
+ entryBytes: Uint8Array;
166
+ entryCid: string;
167
+ digest: Uint8Array;
168
+ body: ResourceFenceV2;
169
+ gid: string;
170
+ metaData?: Uint8Array;
171
+ directParents: ReadonlyArray<Readonly<{ cid: string; digest: Uint8Array }>>;
172
+ }>;
173
+
174
+ export type AuthenticateResourceFenceEntryV2Properties = Readonly<{
175
+ entryBytes: Uint8Array;
176
+ descriptor: NetworkDescriptorV2;
177
+ expectedResourceId: Uint8Array;
178
+ expectedGid: string;
179
+ }>;
180
+
181
+ const captureResourceIdV2 = (resourceId: Uint8Array): Uint8Array => {
182
+ let byteLength: number;
183
+ try {
184
+ byteLength = exactUint8ArrayByteLengthV2(resourceId);
185
+ } catch {
186
+ throw new Error("Expected resource id must contain exactly 32 bytes");
187
+ }
188
+ if (byteLength !== 32) {
189
+ throw new Error("Expected resource id must contain exactly 32 bytes");
190
+ }
191
+ return copyUint8ArrayWithLengthV2(resourceId, byteLength);
192
+ };
193
+
194
+ const toResourceFenceTokenV2 = (
195
+ authenticated: AuthenticatedAuthorityEntryV0V2,
196
+ body: ResourceFenceV2,
197
+ entryCid: string,
198
+ digest: Uint8Array,
199
+ gid: string,
200
+ metaData: Uint8Array | undefined,
201
+ directParents: Array<{ cid: string; digest: Uint8Array }>,
202
+ ): AuthenticatedResourceFenceEntryV2 => ({
203
+ entryBytes: authenticated.entryBytes,
204
+ entryCid,
205
+ digest,
206
+ body,
207
+ gid,
208
+ metaData,
209
+ directParents,
210
+ });
211
+
212
+ /**
213
+ * Authenticate a raw resource-fence EntryV0 against immutable resource
214
+ * context. The returned token does not prove policy acceptance, causal
215
+ * ancestry, fork freedom, freshness, or durability.
216
+ */
217
+ export const authenticateResourceFenceEntryV2 = async ({
218
+ entryBytes,
219
+ descriptor,
220
+ expectedResourceId,
221
+ expectedGid,
222
+ }: AuthenticateResourceFenceEntryV2Properties): Promise<AuthenticatedResourceFenceEntryV2> => {
223
+ if (typeof expectedGid !== "string") {
224
+ throw new Error("Expected resource gid must be a string");
225
+ }
226
+ const capturedResourceId = captureResourceIdV2(expectedResourceId);
227
+ let profile: ResourceFenceEntryV0ProfileV2 | undefined;
228
+ const authenticated = await authenticateAuthorityEntryV0V2(
229
+ entryBytes,
230
+ descriptor,
231
+ RESOURCE_FENCE_AUTHORITY_LIMITS_V2.maximumEntryBytes,
232
+ (candidate) => {
233
+ profile = assertResourceFenceEntryV0ProfileV2(candidate);
234
+ },
235
+ );
236
+ if (profile === undefined) {
237
+ throw new Error("Resource fence EntryV0 profile was not applied");
238
+ }
239
+ if (profile.meta.gid !== expectedGid) {
240
+ throw new Error("Resource fence belongs to another resource log");
241
+ }
242
+ const body = decodeResourceFenceV2(
243
+ authenticated.payloadBytes,
244
+ authenticated.descriptor,
245
+ );
246
+ if (!equals(body.resourceId, capturedResourceId)) {
247
+ throw new Error("Resource fence belongs to another resource");
248
+ }
249
+ const prepared = await calculateRawCid(authenticated.entryBytes);
250
+ return toResourceFenceTokenV2(
251
+ authenticated,
252
+ body,
253
+ prepared.cid,
254
+ copyUint8ArrayWithLengthV2(
255
+ prepared.block.cid.multihash.digest,
256
+ prepared.block.cid.multihash.digest.byteLength,
257
+ ),
258
+ profile.meta.gid,
259
+ profile.meta.data === undefined
260
+ ? undefined
261
+ : copyUint8ArrayWithLengthV2(
262
+ profile.meta.data,
263
+ profile.meta.data.byteLength,
264
+ ),
265
+ profile.directParents,
266
+ );
267
+ };