@blockcast/mmt-manifest-verify 0.1.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.
@@ -0,0 +1,376 @@
1
+ import { decodeSourceAuthManifestFrame, sourceAuthMerkleRoot, sourceAuthObjectDigest, sourceAuthSignaturePreimage, } from './source-auth.js';
2
+ const FAILURE_REASONS = [
3
+ 'sigInvalid',
4
+ 'digestMismatch',
5
+ 'manifestLate',
6
+ 'manifestAbsent',
7
+ 'verifierError',
8
+ ];
9
+ const equalBytes = (left, right) => {
10
+ if (left.byteLength !== right.byteLength)
11
+ return false;
12
+ let diff = 0;
13
+ for (let index = 0; index < left.byteLength; index++)
14
+ diff |= left[index] ^ right[index];
15
+ return diff === 0;
16
+ };
17
+ export class ManifestVerifier {
18
+ #config;
19
+ #verifySignature;
20
+ #onFailure;
21
+ #tracks = new Map();
22
+ #stats = {
23
+ verified: 0,
24
+ failures: Object.fromEntries(FAILURE_REASONS.map((reason) => [reason, 0])),
25
+ quarantinedBytes: 0,
26
+ };
27
+ constructor(options) {
28
+ this.#config = { ...options.manifestAuth };
29
+ this.#verifySignature = options.verifySignature;
30
+ this.#onFailure = options.onFailure;
31
+ }
32
+ get config() {
33
+ return this.#config;
34
+ }
35
+ get stats() {
36
+ return {
37
+ verified: this.#stats.verified,
38
+ failures: { ...this.#stats.failures },
39
+ quarantinedBytes: this.#stats.quarantinedBytes,
40
+ };
41
+ }
42
+ setMode(mode) {
43
+ this.#config = { ...this.#config, mode };
44
+ if (mode !== 'enforce')
45
+ this.#releaseAllQuarantine(false);
46
+ }
47
+ registerTrack(context) {
48
+ if (this.#tracks.has(context.trackName)) {
49
+ throw new Error(`manifest track ${context.trackName} is already registered`);
50
+ }
51
+ if (!Number.isFinite(context.bitrate) || context.bitrate <= 0) {
52
+ throw new Error('manifest track bitrate must be > 0');
53
+ }
54
+ if (!Number.isFinite(context.groupDurationMs) || context.groupDurationMs <= 0) {
55
+ throw new Error('manifest track groupDurationMs must be > 0');
56
+ }
57
+ if (context.authScope.byteLength !== 32)
58
+ throw new Error('manifest authScope must be 32 bytes');
59
+ if (context.publicKey.byteLength !== 32)
60
+ throw new Error('manifest publicKey must be 32 bytes');
61
+ for (const [name, value] of Object.entries({
62
+ broadcastNamespaceId: context.broadcastNamespaceId,
63
+ trackName: context.trackName,
64
+ codec: context.codec,
65
+ timing: context.timing,
66
+ fecGeometry: context.fecGeometry,
67
+ })) {
68
+ if (value.length === 0)
69
+ throw new Error(`manifest ${name} must not be empty`);
70
+ }
71
+ this.#tracks.set(context.trackName, {
72
+ context,
73
+ groups: new Map(),
74
+ quarantine: [],
75
+ quarantinedBytes: 0,
76
+ capBytes: Math.ceil((context.bitrate * context.groupDurationMs) / 8000),
77
+ manifestEntries: 0,
78
+ maxManifestEntries: Math.ceil((context.bitrate * context.groupDurationMs) / (8000 * 52)),
79
+ maxGroups: context.mapper.d + 1,
80
+ });
81
+ }
82
+ ingestManifestFrame(trackName, frameBytes) {
83
+ const track = this.#requireTrack(trackName);
84
+ try {
85
+ const frame = decodeSourceAuthManifestFrame(frameBytes);
86
+ const groupId = frame.groupId;
87
+ const group = this.#group(track, groupId);
88
+ if (frame.kind === 'object') {
89
+ if (group.close || group.failed) {
90
+ this.#failure({ trackName, groupId, objectId: frame.objectId }, 'verifierError');
91
+ return;
92
+ }
93
+ if (group.entries.has(frame.objectId)) {
94
+ this.#failure({ trackName, groupId, objectId: frame.objectId }, 'verifierError');
95
+ return;
96
+ }
97
+ if (group.entries.size >= 4096 || track.manifestEntries >= track.maxManifestEntries) {
98
+ this.#failure({ trackName, groupId, objectId: frame.objectId }, 'verifierError');
99
+ return;
100
+ }
101
+ group.entries.set(frame.objectId, frame);
102
+ track.manifestEntries++;
103
+ const pending = group.pending.get(frame.objectId);
104
+ if (pending) {
105
+ this.#failure(pending.identity, 'manifestLate');
106
+ this.#resolvePending(track, group, pending, frame);
107
+ }
108
+ if (group.pendingClose && group.entries.size === group.pendingClose.objectCount) {
109
+ const close = group.pendingClose;
110
+ group.pendingClose = undefined;
111
+ this.#closeGroup(track, groupId, group, close);
112
+ }
113
+ return;
114
+ }
115
+ this.#closeGroup(track, groupId, group, frame);
116
+ }
117
+ catch {
118
+ this.#failure({ trackName, groupId: 0, objectId: 0 }, 'verifierError');
119
+ }
120
+ }
121
+ admitObject(identity, data, release) {
122
+ if (this.#config.mode === 'off') {
123
+ release();
124
+ return { action: 'play', verified: false };
125
+ }
126
+ const track = this.#tracks.get(identity.trackName);
127
+ if (!track) {
128
+ release();
129
+ this.#failure(identity, 'verifierError');
130
+ return { action: 'play', verified: false, reason: 'verifierError' };
131
+ }
132
+ try {
133
+ if (!this.#validateIdentity(track, identity)) {
134
+ return this.#rejectOrShadow(identity, release, 'digestMismatch');
135
+ }
136
+ const group = this.#group(track, BigInt(identity.groupId));
137
+ if (group.failed === 'sigInvalid' || group.failed === 'digestMismatch') {
138
+ return this.#rejectOrShadow(identity, release, group.failed);
139
+ }
140
+ const entry = group.entries.get(identity.objectId);
141
+ if (entry)
142
+ return this.#admitAgainstEntry(track, group, identity, data, release, entry);
143
+ if (this.#config.mode === 'shadow' || this.#config.onMissingManifest === 'fallback-shadow') {
144
+ release();
145
+ this.#failure(identity, 'manifestAbsent');
146
+ return { action: 'play', verified: false, reason: 'manifestAbsent' };
147
+ }
148
+ if (!this.#quarantine(track, group, { identity, data, release, bytes: data.byteLength })) {
149
+ return { action: 'drop', verified: false };
150
+ }
151
+ return { action: 'stall', verified: false };
152
+ }
153
+ catch {
154
+ release();
155
+ this.#failure(identity, 'verifierError');
156
+ return { action: 'play', verified: false, reason: 'verifierError' };
157
+ }
158
+ }
159
+ dispose() {
160
+ this.#releaseAllQuarantine(false);
161
+ this.#tracks.clear();
162
+ }
163
+ #requireTrack(trackName) {
164
+ const track = this.#tracks.get(trackName);
165
+ if (!track)
166
+ throw new Error(`unknown manifest track ${trackName}`);
167
+ return track;
168
+ }
169
+ #group(track, groupId) {
170
+ let group = track.groups.get(groupId);
171
+ if (!group) {
172
+ group = { entries: new Map(), pending: new Map(), rootVerified: false };
173
+ track.groups.set(groupId, group);
174
+ while (track.groups.size > track.maxGroups) {
175
+ const oldest = track.groups.entries().next().value;
176
+ if (!oldest)
177
+ break;
178
+ const [oldestId, oldestGroup] = oldest;
179
+ for (const pending of [...oldestGroup.pending.values()]) {
180
+ this.#removePending(track, oldestGroup, pending);
181
+ pending.release();
182
+ this.#failure(pending.identity, 'verifierError');
183
+ }
184
+ track.manifestEntries -= oldestGroup.entries.size;
185
+ track.groups.delete(oldestId);
186
+ }
187
+ }
188
+ return group;
189
+ }
190
+ #validateIdentity(track, identity) {
191
+ const coordinates = track.context.mapper.derive(Number(identity.groupId), identity.objectId);
192
+ return identity.ssId === undefined || coordinates.ssId === identity.ssId;
193
+ }
194
+ #admitAgainstEntry(track, group, identity, data, release, entry) {
195
+ if (entry.trueLength !== data.byteLength) {
196
+ return this.#rejectOrShadow(identity, release, 'digestMismatch');
197
+ }
198
+ const digestMatches = equalBytes(sourceAuthObjectDigest(data.subarray(0, entry.trueLength)), entry.digest);
199
+ if (!digestMatches)
200
+ return this.#rejectOrShadow(identity, release, 'digestMismatch');
201
+ if (!track.context.manifestChannelAuthenticated && !group.rootVerified) {
202
+ if (this.#config.mode === 'shadow') {
203
+ release();
204
+ return { action: 'play', verified: false };
205
+ }
206
+ if (!this.#quarantine(track, group, { identity, data, release, bytes: data.byteLength })) {
207
+ return { action: 'drop', verified: false };
208
+ }
209
+ return { action: 'stall', verified: false };
210
+ }
211
+ release();
212
+ this.#stats.verified++;
213
+ return { action: 'play', verified: true };
214
+ }
215
+ #rejectOrShadow(identity, release, reason) {
216
+ this.#failure(identity, reason);
217
+ if (this.#config.mode === 'enforce') {
218
+ console.warn(JSON.stringify({ event: 'manifest-enforce-drop', track: identity.trackName, group: String(identity.groupId), object: identity.objectId, reason }));
219
+ return { action: 'drop', verified: false, reason };
220
+ }
221
+ release();
222
+ return { action: 'play', verified: false, reason };
223
+ }
224
+ #resolvePending(track, group, pending, entry) {
225
+ this.#removePending(track, group, pending);
226
+ this.#admitAgainstEntry(track, group, pending.identity, pending.data, pending.release, entry);
227
+ }
228
+ #closeGroup(track, groupId, group, close) {
229
+ if (group.close)
230
+ return;
231
+ const entries = [...group.entries.values()];
232
+ if (entries.length !== close.objectCount) {
233
+ // Manifest-channel reordering: the close frame beat some object entries.
234
+ // Stash it (latest-wins on duplicates) so the object-entry path can
235
+ // re-attempt the close once the count matches; quarantined objects stay
236
+ // quarantined, and manifestAbsent below is a signal, not an eviction.
237
+ group.pendingClose = close;
238
+ const hadPending = group.pending.size > 0;
239
+ if (this.#config.onMissingManifest === 'fallback-shadow' || this.#config.mode === 'shadow') {
240
+ for (const pending of [...group.pending.values()]) {
241
+ this.#removePending(track, group, pending);
242
+ pending.release();
243
+ this.#failure(pending.identity, 'manifestAbsent');
244
+ }
245
+ }
246
+ else {
247
+ for (const pending of group.pending.values()) {
248
+ this.#failure(pending.identity, 'manifestAbsent');
249
+ }
250
+ }
251
+ if (!hadPending) {
252
+ this.#failure({ trackName: track.context.trackName, groupId, objectId: 0 }, 'manifestAbsent');
253
+ }
254
+ return;
255
+ }
256
+ if (!equalBytes(sourceAuthMerkleRoot(entries), close.root)) {
257
+ group.failed = 'digestMismatch';
258
+ this.#failGroup(track, groupId, group, 'digestMismatch');
259
+ return;
260
+ }
261
+ const preimage = sourceAuthSignaturePreimage(track.context.authScope, close.groupId, close.objectCount, close.root);
262
+ let signatureValid;
263
+ try {
264
+ signatureValid = this.#verifySignature(track.context.publicKey, preimage, close.signature);
265
+ }
266
+ catch {
267
+ this.#failOpenGroup(track, groupId, group);
268
+ return;
269
+ }
270
+ if (!signatureValid) {
271
+ group.failed = 'sigInvalid';
272
+ this.#failGroup(track, groupId, group, 'sigInvalid');
273
+ return;
274
+ }
275
+ group.failed = undefined;
276
+ group.close = close;
277
+ group.pendingClose = undefined;
278
+ group.rootVerified = true;
279
+ for (const pending of [...group.pending.values()]) {
280
+ const entry = group.entries.get(pending.identity.objectId);
281
+ if (entry)
282
+ this.#resolvePending(track, group, pending, entry);
283
+ else if (this.#config.onMissingManifest === 'fallback-shadow') {
284
+ this.#removePending(track, group, pending);
285
+ pending.release();
286
+ this.#failure(pending.identity, 'manifestAbsent');
287
+ }
288
+ }
289
+ }
290
+ #failGroup(track, groupId, group, reason) {
291
+ if (group.pending.size === 0) {
292
+ this.#failure({ trackName: track.context.trackName, groupId, objectId: 0 }, reason);
293
+ return;
294
+ }
295
+ for (const pending of [...group.pending.values()]) {
296
+ this.#removePending(track, group, pending);
297
+ this.#rejectOrShadow(pending.identity, pending.release, reason);
298
+ }
299
+ }
300
+ #removePending(track, group, pending) {
301
+ if (group.pending.get(pending.identity.objectId) === pending) {
302
+ group.pending.delete(pending.identity.objectId);
303
+ }
304
+ const index = track.quarantine.indexOf(pending);
305
+ if (index >= 0)
306
+ track.quarantine.splice(index, 1);
307
+ track.quarantinedBytes -= pending.bytes;
308
+ this.#stats.quarantinedBytes -= pending.bytes;
309
+ }
310
+ #quarantine(track, group, pending) {
311
+ if (group.pending.has(pending.identity.objectId))
312
+ return false;
313
+ group.pending.set(pending.identity.objectId, pending);
314
+ track.quarantine.push(pending);
315
+ track.quarantinedBytes += pending.bytes;
316
+ this.#stats.quarantinedBytes += pending.bytes;
317
+ this.#enforceCap(track);
318
+ return true;
319
+ }
320
+ #enforceCap(track) {
321
+ while (track.quarantinedBytes > track.capBytes && track.quarantine.length > 0) {
322
+ const pending = track.quarantine.shift();
323
+ const group = track.groups.get(BigInt(pending.identity.groupId));
324
+ if (group)
325
+ group.pending.delete(pending.identity.objectId);
326
+ track.quarantinedBytes -= pending.bytes;
327
+ this.#stats.quarantinedBytes -= pending.bytes;
328
+ pending.release();
329
+ this.#failure(pending.identity, 'verifierError');
330
+ console.warn(`[ManifestVerifier] quarantine cap eviction fail-open track=${pending.identity.trackName} group=${pending.identity.groupId} object=${pending.identity.objectId}`);
331
+ }
332
+ }
333
+ #failOpenTrack(track, reason) {
334
+ for (const pending of [...track.quarantine]) {
335
+ const group = track.groups.get(BigInt(pending.identity.groupId));
336
+ if (group)
337
+ this.#removePending(track, group, pending);
338
+ pending.release();
339
+ this.#failure(pending.identity, reason);
340
+ }
341
+ }
342
+ #failOpenGroup(track, groupId, group) {
343
+ const pending = [...group.pending.values()];
344
+ if (pending.length === 0) {
345
+ this.#failure({ trackName: track.context.trackName, groupId, objectId: 0 }, 'verifierError');
346
+ return;
347
+ }
348
+ for (const object of pending) {
349
+ this.#removePending(track, group, object);
350
+ object.release();
351
+ this.#failure(object.identity, 'verifierError');
352
+ }
353
+ }
354
+ #releaseAllQuarantine(countFailure) {
355
+ for (const track of this.#tracks.values()) {
356
+ for (const pending of [...track.quarantine]) {
357
+ const group = track.groups.get(BigInt(pending.identity.groupId));
358
+ if (group)
359
+ this.#removePending(track, group, pending);
360
+ pending.release();
361
+ if (countFailure)
362
+ this.#failure(pending.identity, 'verifierError');
363
+ }
364
+ }
365
+ }
366
+ #failure(identity, reason) {
367
+ this.#stats.failures[reason]++;
368
+ this.#onFailure?.({
369
+ trackName: identity.trackName,
370
+ groupId: identity.groupId,
371
+ objectId: identity.objectId,
372
+ reason,
373
+ });
374
+ }
375
+ }
376
+ //# sourceMappingURL=verifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"verifier.js","sourceRoot":"","sources":["../src/verifier.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "@blockcast/mmt-manifest-verify",
3
+ "version": "0.1.0",
4
+ "description": "Canonical live MMT source-auth manifest verifier",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "types": "./dist/index.d.ts"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "src"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsc",
20
+ "dev": "tsc --watch",
21
+ "clean": "rm -rf dist",
22
+ "typecheck": "tsc --noEmit",
23
+ "test": "vitest run",
24
+ "test:watch": "vitest"
25
+ },
26
+ "dependencies": {
27
+ "@noble/hashes": "^1.8.0"
28
+ },
29
+ "devDependencies": {
30
+ "@blockcast/mmt-fec": "file:../fec",
31
+ "typescript": "^5.3.0",
32
+ "vitest": "^1.0.0"
33
+ },
34
+ "license": "Apache-2.0",
35
+ "repository": {
36
+ "type": "git",
37
+ "url": "https://github.com/Blockcast/libmmt.git",
38
+ "directory": "packages/mmt-manifest-verify"
39
+ }
40
+ }
@@ -0,0 +1,171 @@
1
+ import {
2
+ sourceAuthMerkleRoot,
3
+ sourceAuthObjectDigest,
4
+ sourceAuthSignaturePreimage,
5
+ trimRecoveredSourceAuthObject,
6
+ type SourceAuthGroupClose,
7
+ type SourceAuthObjectEntry,
8
+ } from './source-auth.js'
9
+
10
+ export type SourceAuthAdmissionState =
11
+ | 'pending-entry'
12
+ | 'pending-object'
13
+ | 'provisional'
14
+ | 'authenticated'
15
+
16
+ export interface SourceAuthObjectAdmission {
17
+ groupId: bigint
18
+ objectId: number
19
+ recovered: boolean
20
+ state: Exclude<SourceAuthAdmissionState, 'pending-object'>
21
+ }
22
+
23
+ interface PendingSourceAuthObject {
24
+ bytes: Uint8Array
25
+ recovered: boolean
26
+ }
27
+
28
+ function requireU64(name: string, value: bigint): void {
29
+ if (value < 0n || value > 0xffff_ffff_ffff_ffffn) {
30
+ throw new Error(`${name} must be an unsigned 64-bit integer`)
31
+ }
32
+ }
33
+
34
+ function equalBytes(left: Uint8Array, right: Uint8Array): boolean {
35
+ if (left.byteLength !== right.byteLength) return false
36
+ return left.every((value, index) => value === right[index])
37
+ }
38
+
39
+ /**
40
+ * Bounded media/manifest rendezvous for the browser player receive path.
41
+ * Object digest admission is synchronous; only the group-close signature is deferred.
42
+ */
43
+ export class SourceAuthAdmission {
44
+ readonly #manifestAuthenticated: boolean
45
+ readonly #entries = new Map<number, SourceAuthObjectEntry>()
46
+ readonly #pendingObjects = new Map<number, PendingSourceAuthObject>()
47
+ readonly #entriesAwaitingObjects = new Set<number>()
48
+ readonly #admissions = new Map<number, SourceAuthObjectAdmission>()
49
+ #groupId: bigint | null = null
50
+ #closed = false
51
+
52
+ constructor(options: { manifestAuthenticated: boolean }) {
53
+ this.#manifestAuthenticated = options.manifestAuthenticated
54
+ }
55
+
56
+ admitObject(input: {
57
+ groupId: bigint
58
+ objectId: number
59
+ bytes: Uint8Array
60
+ recovered?: boolean
61
+ }): SourceAuthObjectAdmission {
62
+ this.#requireGroup(input.groupId)
63
+ if (this.#closed) throw new Error('source-auth group is already closed')
64
+ if (this.#admissions.has(input.objectId) || this.#pendingObjects.has(input.objectId)) {
65
+ throw new Error(`duplicate source-auth object ${input.objectId}`)
66
+ }
67
+ const entry = this.#entries.get(input.objectId)
68
+ if (entry) {
69
+ this.#entriesAwaitingObjects.delete(input.objectId)
70
+ return this.#verifyObject(entry, input.bytes, input.recovered ?? false)
71
+ }
72
+ if (this.#pendingObjects.size >= 1) {
73
+ throw new Error('source-auth media exceeds one-object manifest skew')
74
+ }
75
+ this.#pendingObjects.set(input.objectId, {
76
+ bytes: input.bytes,
77
+ recovered: input.recovered ?? false,
78
+ })
79
+ return {
80
+ groupId: input.groupId,
81
+ objectId: input.objectId,
82
+ recovered: input.recovered ?? false,
83
+ state: 'pending-entry',
84
+ }
85
+ }
86
+
87
+ admitEntry(entry: SourceAuthObjectEntry): SourceAuthObjectAdmission | SourceAuthAdmissionState {
88
+ this.#requireGroup(entry.groupId)
89
+ if (this.#closed) throw new Error('source-auth group is already closed')
90
+ if (this.#entries.has(entry.objectId)) {
91
+ throw new Error(`duplicate source-auth manifest entry ${entry.objectId}`)
92
+ }
93
+ const pending = this.#pendingObjects.get(entry.objectId)
94
+ if (!pending && this.#entriesAwaitingObjects.size >= 1) {
95
+ throw new Error('source-auth manifest exceeds one-object media skew')
96
+ }
97
+ this.#entries.set(entry.objectId, entry)
98
+ if (!pending) {
99
+ this.#entriesAwaitingObjects.add(entry.objectId)
100
+ return 'pending-object'
101
+ }
102
+ this.#pendingObjects.delete(entry.objectId)
103
+ return this.#verifyObject(entry, pending.bytes, pending.recovered)
104
+ }
105
+
106
+ objectState(objectId: number): SourceAuthAdmissionState | undefined {
107
+ if (this.#pendingObjects.has(objectId)) return 'pending-entry'
108
+ if (this.#entriesAwaitingObjects.has(objectId)) return 'pending-object'
109
+ if (this.#closed && this.#admissions.has(objectId)) return 'authenticated'
110
+ return this.#admissions.get(objectId)?.state
111
+ }
112
+
113
+ async verifyClose(
114
+ close: SourceAuthGroupClose,
115
+ authScope: Uint8Array,
116
+ publicKey: CryptoKey,
117
+ subtle: SubtleCrypto = globalThis.crypto.subtle,
118
+ ): Promise<void> {
119
+ this.#requireGroup(close.groupId)
120
+ if (this.#closed) throw new Error('source-auth group is already closed')
121
+ if (this.#pendingObjects.size !== 0 || this.#entriesAwaitingObjects.size !== 0) {
122
+ throw new Error('source-auth group closed with unmatched objects or entries')
123
+ }
124
+ if (close.objectCount !== this.#entries.size || this.#admissions.size !== this.#entries.size) {
125
+ throw new Error('source-auth group close object count mismatch')
126
+ }
127
+ const root = sourceAuthMerkleRoot([...this.#entries.values()])
128
+ if (!equalBytes(root, close.root)) throw new Error('source-auth group root mismatch')
129
+ const verified = await subtle.verify(
130
+ 'Ed25519',
131
+ publicKey,
132
+ new Uint8Array(close.signature).buffer,
133
+ new Uint8Array(
134
+ sourceAuthSignaturePreimage(authScope, close.groupId, close.objectCount, root),
135
+ ).buffer,
136
+ )
137
+ if (!verified) throw new Error('source-auth group close signature failed')
138
+ for (const admission of this.#admissions.values()) admission.state = 'authenticated'
139
+ this.#closed = true
140
+ }
141
+
142
+ #requireGroup(groupId: bigint): void {
143
+ requireU64('groupId', groupId)
144
+ if (this.#groupId === null) this.#groupId = groupId
145
+ else if (this.#groupId !== groupId) throw new Error('source-auth group identity mismatch')
146
+ }
147
+
148
+ #verifyObject(
149
+ entry: SourceAuthObjectEntry,
150
+ bytes: Uint8Array,
151
+ recovered: boolean,
152
+ ): SourceAuthObjectAdmission {
153
+ const authenticated = recovered
154
+ ? trimRecoveredSourceAuthObject(bytes, entry.trueLength)
155
+ : bytes
156
+ if (!recovered && bytes.byteLength !== entry.trueLength) {
157
+ throw new Error(`direct source-auth object length mismatch for ${entry.objectId}`)
158
+ }
159
+ if (!equalBytes(sourceAuthObjectDigest(authenticated), entry.digest)) {
160
+ throw new Error(`source-auth object digest mismatch for ${entry.objectId}`)
161
+ }
162
+ const admission: SourceAuthObjectAdmission = {
163
+ groupId: entry.groupId,
164
+ objectId: entry.objectId,
165
+ recovered,
166
+ state: this.#manifestAuthenticated ? 'authenticated' : 'provisional',
167
+ }
168
+ this.#admissions.set(entry.objectId, admission)
169
+ return admission
170
+ }
171
+ }
package/src/index.ts ADDED
@@ -0,0 +1,3 @@
1
+ export * from './source-auth.js'
2
+ export * from './admission.js'
3
+ export * from './verifier.js'