lib/machine/src/instance/receipt/owner.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const admission = @import("../../admission/root.zig");
2 const os = @import("os");
3 const std = @import("std");
4 const types = @import("types.zig");
5
6 const Sha256 = std.crypto.hash.sha2.Sha256;
7 const receipt_domain = "TINYMACHINEQUIESCENCE2\x00";
8 const semantic_domain = "TINYMACHINEQUIESCENCESTATE1\x00";
9 const transcript_domain = "TINYMACHINEEVENTS1\x00";
10 const semantic_transcript_domain = "TINYMACHINESEMANTICEVENTS1\x00";
11 const canonical_fence: os.abi.ActivationFence = .{
12 .world = @splat(0x51),
13 .generation = 1,
14 .token = @splat(0x52),
15 };
16
17 /// Every check in this file fails in the same single way, so this error reports
18 /// the one failure a receipt check encounters. The error means the value
19 /// disagrees with itself or with one of the digests bound into it.
20 pub const Error = error{InvalidQuiescenceReceipt};
21
22 const EventBatchOwnerError = error{
23 EventBatchCountInvalid,
24 EventBatchFenceMismatch,
25 EventTranscriptMismatch,
26 };
27
28 /// Checking a batch can go wrong in more ways than checking a receipt, so this
29 /// error set reports the failures coming from decoding the bytes, matching the
30 /// fence, checking the receipt, bounding the count, or comparing the
31 /// transcript.
32 pub const EventBatchError = os.abi.message.Error ||
33 os.abi.wire.FenceError ||
34 Error ||
35 EventBatchOwnerError;
36
37 /// Issuing a receipt takes the whole boundary at once, so this structure
38 /// gathers everything needed to issue the authenticated evidence for one
39 /// acknowledged quiescent turn, or quiescence receipt. Each field is copied
40 /// into the value the call returns, once the checks pass.
41 pub const Material = struct {
42 fence: os.abi.ActivationFence,
43 delivery: admission.DeliveryReceipt,
44 admission_receipt: admission.Receipt,
45 basis: admission.Basis,
46 image_digest: os.abi.Digest,
47 execution_fingerprint: types.ExecutionFingerprint,
48 block_root: os.abi.BlockRoot,
49 boundary: os.abi.Quiescence,
50 settled: types.SettledTransport,
51 k0: types.K0State,
52 event_transcript_digest: os.abi.Digest,
53 semantic_transcript_digest: os.abi.Digest,
54 };
55
56 /// Checks a settled boundary, then hashes it into the digest inside a
57 /// quiescence receipt that commits every one of its fields, activation
58 /// authority included, as a fenced digest, so a caller holds the evidence a
59 /// turn leaves behind to check again later. Material that disagrees with itself
60 /// comes back as `InvalidQuiescenceReceipt`, and no digest is produced.
61 pub fn issue(material: Material) Error!types.QuiescenceReceipt {
62 var value: types.QuiescenceReceipt = .{
63 .fenced_digest = undefined,
64 .fence = material.fence,
65 .delivery = material.delivery,
66 .admission_receipt = material.admission_receipt,
67 .basis = material.basis,
68 .image_digest = material.image_digest,
69 .execution_fingerprint = material.execution_fingerprint,
70 .block_root = material.block_root,
71 .boundary = material.boundary,
72 .settled = material.settled,
73 .k0 = material.k0,
74 .event_transcript_digest = material.event_transcript_digest,
75 .semantic_transcript_digest = material.semantic_transcript_digest,
76 };
77 try validateMaterial(value);
78 value.fenced_digest = digest(value);
79 return value;
80 }
81
82 /// Whoever holds the bytes can check them without the machine that made them,
83 /// so the call runs every invariant again and recomputes the fenced digest to
84 /// compare.
85 pub fn verify(value: types.QuiescenceReceipt) Error!void {
86 try validateMaterial(value);
87 const expected = digest(value);
88 if (!std.mem.eql(u8, &expected, &value.fenced_digest)) {
89 return error.InvalidQuiescenceReceipt;
90 }
91 }
92
93 /// A valid receipt from some other turn is worse than none, so the call checks
94 /// the receipt and then requires its world, generation, and token to match the
95 /// ones handed in.
96 pub fn verifyForFence(
97 value: types.QuiescenceReceipt,
98 expected_fence: os.abi.ActivationFence,
99 ) Error!void {
100 try verify(value);
101 if (!os.abi.wire.equalFence(value.fence, expected_fence)) {
102 return error.InvalidQuiescenceReceipt;
103 }
104 }
105
106 /// The events a turn produced have to belong to that turn, so the call decodes
107 /// each filled slot and requires the handed-in fence on every encoded message
108 /// in the kernel wire form, or frame. A fence that fails its own check, a count
109 /// past the slots, or a frame that will not decode all come back as
110 /// `EventBatchError`.
111 pub fn verifyEventBatch(
112 batch: *const types.EventBatch,
113 expected_fence: os.abi.ActivationFence,
114 ) EventBatchError!void {
115 try os.abi.wire.validateFence(expected_fence);
116 if (batch.count > batch.storage.len) {
117 return error.EventBatchCountInvalid;
118 }
119 for (batch.storage[0..batch.count]) |*frame| {
120 const decoded = try os.abi.decodeEvent(frame);
121 if (!os.abi.wire.equalFence(decoded.header.fence, expected_fence)) {
122 return error.EventBatchFenceMismatch;
123 }
124 }
125 }
126
127 /// One call ties a batch, a receipt, and a fence together, so the call checks
128 /// the receipt against the fence, compares the batch's raw transcript digest
129 /// with the one the receipt carries, and then checks every frame.
130 pub fn verifyEventBatchReceipt(
131 batch: *const types.EventBatch,
132 value: types.QuiescenceReceipt,
133 expected_fence: os.abi.ActivationFence,
134 ) EventBatchError!void {
135 try verifyForFence(value, expected_fence);
136 if (batch.count == 0 or batch.count > batch.storage.len) {
137 return error.EventBatchCountInvalid;
138 }
139 if (!std.mem.eql(
140 u8,
141 &transcriptDigest(batch),
142 &value.event_transcript_digest,
143 )) {
144 return error.EventTranscriptMismatch;
145 }
146 try verifyEventBatch(batch, expected_fence);
147 }
148
149 /// Two receipts whose retained evidence is equal reach the same digest whatever
150 /// activation authority each carries, so the call checks the receipt first,
151 /// then hashes the form of it that leaves out activation authority. Matching
152 /// guest memory alone does not give equal digests.
153 pub fn semanticDigest(value: types.QuiescenceReceipt) Error!os.abi.Digest {
154 try verify(value);
155 return semanticReceiptHash(semanticReceipt(value));
156 }
157
158 /// A checkpoint keeps the authority-free form, so the call checks the receipt
159 /// and copies out the form checkpoints work from.
160 pub fn projectSemantic(
161 value: types.QuiescenceReceipt,
162 ) Error!types.SemanticReceipt {
163 try verify(value);
164 return semanticReceipt(value);
165 }
166
167 /// A semantic receipt read back out of storage has to stand on its own, so the
168 /// call runs every invariant the authority-free form has. A value that
169 /// disagrees with itself comes back as `InvalidQuiescenceReceipt`.
170 pub fn verifySemantic(value: types.SemanticReceipt) Error!void {
171 try validateSemantic(value);
172 }
173
174 /// Naming a stored guest state takes its digest, so the call checks the value,
175 /// then hashes its canonical fields.
176 pub fn semanticReceiptDigest(
177 value: types.SemanticReceipt,
178 ) Error!os.abi.Digest {
179 try validateSemantic(value);
180 return semanticReceiptHash(value);
181 }
182
183 fn semanticReceiptHash(value: types.SemanticReceipt) os.abi.Digest {
184 var hasher = Sha256.init(.{});
185 hasher.update(semantic_domain);
186 hasher.update(&value.admission_receipt.digest);
187 hasher.update(&value.basis.contract.digest);
188 hasher.update(&value.basis.source_root);
189 hashFrontiers(&hasher, value.basis.frontiers);
190 hashInteger(&hasher, @as(u8, 0));
191 hasher.update(&value.image_digest);
192 hasher.update(&value.execution_fingerprint.digest);
193 hashInteger(&hasher, value.block_root.generation);
194 hasher.update(&value.block_root.digest);
195 hashInteger(&hasher, value.boundary.request_sequence);
196 hashInteger(&hasher, value.boundary.semantic_frontier);
197 hashInteger(&hasher, value.boundary.effect_frontier);
198 hashInteger(&hasher, value.boundary.terminal_offset);
199 hashInteger(&hasher, value.boundary.virtual_time_tick);
200 hashInteger(&hasher, value.boundary.entropy_generation);
201 hashInteger(&hasher, value.boundary.request_consumed);
202 hashInteger(&hasher, value.boundary.request_produced);
203 hashInteger(&hasher, value.boundary.event_consumed);
204 hashInteger(&hasher, value.boundary.event_produced);
205 hashInteger(&hasher, value.boundary.unresolved_effects);
206 hashInteger(&hasher, @backingInt(value.boundary.scheduler));
207 hasher.update(&value.boundary.block_root);
208 hashInteger(&hasher, value.boundary.input_frontier);
209 hashInteger(&hasher, value.boundary.terminal_input_offset);
210 hashInteger(&hasher, value.settled.request_cursor);
211 hashInteger(&hasher, value.settled.event_cursor);
212 hashInteger(&hasher, value.k0.counter);
213 hasher.update(&value.semantic_transcript_digest);
214 var output: os.abi.Digest = undefined;
215 hasher.final(&output);
216 return output;
217 }
218
219 fn semanticReceipt(value: types.QuiescenceReceipt) types.SemanticReceipt {
220 return .{
221 .admission_receipt = value.admission_receipt,
222 .basis = value.basis,
223 .image_digest = value.image_digest,
224 .execution_fingerprint = value.execution_fingerprint,
225 .block_root = value.block_root,
226 .boundary = .{
227 .request_sequence = value.boundary.request_sequence,
228 .semantic_frontier = value.boundary.semantic_frontier,
229 .effect_frontier = value.boundary.effect_frontier,
230 .terminal_offset = value.boundary.terminal_offset,
231 .virtual_time_tick = value.boundary.virtual_time_tick,
232 .entropy_generation = value.boundary.entropy_generation,
233 .request_consumed = value.boundary.request_consumed,
234 .request_produced = value.boundary.request_produced,
235 .event_consumed = value.boundary.event_consumed,
236 .event_produced = value.boundary.event_produced,
237 .unresolved_effects = value.boundary.unresolved_effects,
238 .scheduler = value.boundary.scheduler,
239 .block_root = value.boundary.block_root,
240 .input_frontier = value.boundary.input_frontier,
241 .terminal_input_offset = value.boundary.terminal_input_offset,
242 },
243 .settled = value.settled,
244 .k0 = value.k0,
245 .semantic_transcript_digest = value.semantic_transcript_digest,
246 };
247 }
248
249 /// A receipt binds itself to the events a turn produced, so the call hashes the
250 /// filled slots as raw bytes, together with how many there are and the order
251 /// they sit in. The count has to be at least one and no more than
252 /// `event_batch_max`, which the call asserts.
253 pub fn transcriptDigest(batch: *const types.EventBatch) os.abi.Digest {
254 std.debug.assert(batch.count > 0);
255 std.debug.assert(batch.count <= batch.storage.len);
256 var hasher = Sha256.init(.{});
257 hasher.update(transcript_domain);
258 hashInteger(&hasher, batch.count);
259 for (batch.frames()) |*frame| hasher.update(frame);
260 var output: os.abi.Digest = undefined;
261 hasher.final(&output);
262 return output;
263 }
264
265 /// The same events under a later fence have to reach the same value, so the
266 /// call decodes each event, puts the fixed world, generation, and token of the
267 /// canonical fence in place of each event header's authority, and hashes the
268 /// result. The call also replaces a quiescent event's `capability_generation`
269 /// field with that same fixed generation, and keeps each event's sequence
270 /// number, its correlation, and the rest of its payload as they were. The count
271 /// has to be at least one and no more than `event_batch_max`, which the call
272 /// asserts. Bytes that will not decode come back as a message error.
273 pub fn semanticTranscriptDigest(
274 batch: *const types.EventBatch,
275 ) os.abi.message.Error!os.abi.Digest {
276 std.debug.assert(batch.count > 0);
277 std.debug.assert(batch.count <= batch.storage.len);
278 var hasher = Sha256.init(.{});
279 hasher.update(semantic_transcript_domain);
280 hashInteger(&hasher, batch.count);
281 for (batch.frames()) |*frame| {
282 const decoded = try os.abi.decodeEvent(frame);
283 var value = decoded.value;
284 if (std.meta.activeTag(value) == .quiescent) {
285 value.quiescent.capability_generation = canonical_fence.generation;
286 }
287 var canonical: os.abi.MessageWire = undefined;
288 try os.abi.encodeEvent(
289 canonical_fence,
290 decoded.header.sequence,
291 decoded.header.correlation,
292 value,
293 &canonical,
294 );
295 hasher.update(&canonical);
296 }
297 var output: os.abi.Digest = undefined;
298 hasher.final(&output);
299 return output;
300 }
301
302 fn validateMaterial(value: types.QuiescenceReceipt) Error!void {
303 os.abi.wire.validateFence(value.fence) catch
304 return error.InvalidQuiescenceReceipt;
305 validateDigest(value.delivery.digest) catch
306 return error.InvalidQuiescenceReceipt;
307 validateDigest(value.event_transcript_digest) catch
308 return error.InvalidQuiescenceReceipt;
309 try validateSemantic(semanticReceipt(value));
310 if (value.boundary.capability_generation != value.fence.generation) {
311 return error.InvalidQuiescenceReceipt;
312 }
313 }
314
315 fn validateSemantic(value: types.SemanticReceipt) Error!void {
316 validateDigest(value.admission_receipt.digest) catch
317 return error.InvalidQuiescenceReceipt;
318 admission.validateBasis(value.basis) catch
319 return error.InvalidQuiescenceReceipt;
320 if (value.basis.outstanding_effect != null) {
321 return error.InvalidQuiescenceReceipt;
322 }
323 validateDigest(value.image_digest) catch
324 return error.InvalidQuiescenceReceipt;
325 validateDigest(value.execution_fingerprint.digest) catch
326 return error.InvalidQuiescenceReceipt;
327 validateDigest(value.block_root.digest) catch
328 return error.InvalidQuiescenceReceipt;
329 validateDigest(value.semantic_transcript_digest) catch
330 return error.InvalidQuiescenceReceipt;
331 const boundary: os.abi.Quiescence = .{
332 .request_sequence = value.boundary.request_sequence,
333 .semantic_frontier = value.boundary.semantic_frontier,
334 .effect_frontier = value.boundary.effect_frontier,
335 .terminal_offset = value.boundary.terminal_offset,
336 .virtual_time_tick = value.boundary.virtual_time_tick,
337 .entropy_generation = value.boundary.entropy_generation,
338 .request_consumed = value.boundary.request_consumed,
339 .request_produced = value.boundary.request_produced,
340 .event_consumed = value.boundary.event_consumed,
341 .event_produced = value.boundary.event_produced,
342 .capability_generation = canonical_fence.generation,
343 .unresolved_effects = value.boundary.unresolved_effects,
344 .scheduler = value.boundary.scheduler,
345 .block_root = value.boundary.block_root,
346 .input_frontier = value.boundary.input_frontier,
347 .terminal_input_offset = value.boundary.terminal_input_offset,
348 };
349 var frame: os.abi.MessageWire = undefined;
350 os.abi.encodeQuiescentEvent(
351 canonical_fence,
352 boundary.event_produced,
353 boundary,
354 &frame,
355 ) catch return error.InvalidQuiescenceReceipt;
356 if (value.block_root.generation == 0 or
357 value.block_root.generation > std.math.maxInt(u32) or
358 value.boundary.semantic_frontier >= std.math.maxInt(u32) or
359 value.block_root.generation != value.boundary.semantic_frontier + 1 or
360 !std.mem.eql(
361 u8,
362 &value.boundary.block_root,
363 &value.block_root.digest,
364 ) or
365 value.basis.frontiers.input != value.boundary.input_frontier or
366 value.basis.frontiers.terminal_input_offset !=
367 value.boundary.terminal_input_offset or
368 value.basis.frontiers.virtual_time_tick !=
369 value.boundary.virtual_time_tick or
370 value.basis.frontiers.entropy_generation !=
371 value.boundary.entropy_generation or
372 value.basis.frontiers.effect != value.boundary.effect_frontier or
373 value.settled.request_cursor != value.boundary.request_consumed or
374 value.settled.request_cursor != value.boundary.request_produced or
375 value.settled.event_cursor != value.boundary.event_produced or
376 value.k0.counter > value.boundary.semantic_frontier)
377 {
378 return error.InvalidQuiescenceReceipt;
379 }
380 }
381
382 fn digest(value: types.QuiescenceReceipt) os.abi.Digest {
383 var frame: os.abi.MessageWire = undefined;
384 os.abi.encodeQuiescentEvent(
385 value.fence,
386 value.boundary.event_produced,
387 value.boundary,
388 &frame,
389 ) catch unreachable;
390 var hasher = Sha256.init(.{});
391 hasher.update(receipt_domain);
392 hasher.update(&frame);
393 hasher.update(&value.delivery.digest);
394 hasher.update(&value.admission_receipt.digest);
395 hasher.update(&value.basis.contract.digest);
396 hasher.update(&value.basis.source_root);
397 hashFrontiers(&hasher, value.basis.frontiers);
398 hashInteger(&hasher, @as(u8, 0));
399 hasher.update(&value.image_digest);
400 hasher.update(&value.execution_fingerprint.digest);
401 hashInteger(&hasher, value.block_root.generation);
402 hasher.update(&value.block_root.digest);
403 hashInteger(&hasher, value.settled.request_cursor);
404 hashInteger(&hasher, value.settled.event_cursor);
405 hashInteger(&hasher, value.k0.counter);
406 hasher.update(&value.event_transcript_digest);
407 hasher.update(&value.semantic_transcript_digest);
408 var output: os.abi.Digest = undefined;
409 hasher.final(&output);
410 return output;
411 }
412
413 fn hashFrontiers(hasher: *Sha256, value: admission.Frontiers) void {
414 hashInteger(hasher, value.input);
415 hashInteger(hasher, value.terminal_input_offset);
416 hashInteger(hasher, value.virtual_time_tick);
417 hashInteger(hasher, value.entropy_generation);
418 hashInteger(hasher, value.effect);
419 }
420
421 fn hashInteger(hasher: *Sha256, value: anytype) void {
422 var encoded: [@sizeOf(@TypeOf(value))]u8 = undefined;
423 std.mem.writeInt(@TypeOf(value), &encoded, value, .little);
424 hasher.update(&encoded);
425 }
426
427 fn validateDigest(value: os.abi.Digest) os.abi.wire.DigestError!void {
428 try os.abi.wire.validateDigest(value);
429 }
430
431 test "quiescence receipt has one canonical digest" {
432 const value = try issue(exampleMaterial());
433 try verify(value);
434 const actual = std.fmt.bytesToHex(value.fenced_digest, .lower);
435 try std.testing.expectEqualStrings(
436 "77d309ec469f7bafb27041504f5f29df5276c81c377e376d144a925a5e0c0e71",
437 &actual,
438 );
439 }
440
441 test "semantic receipt commits canonical evidence and excludes activation authority" {
442 const first_material = exampleMaterial();
443 const first = try issue(first_material);
444 const expected = try semanticDigest(first);
445
446 var changed = first_material;
447 changed.admission_receipt.digest[0] ^= 1;
448 try expectDifferentSemantic(expected, try issue(changed));
449 changed = first_material;
450 changed.semantic_transcript_digest[0] ^= 1;
451 try expectDifferentSemantic(expected, try issue(changed));
452 changed = first_material;
453 changed.boundary.request_sequence += 1;
454 changed.boundary.request_consumed += 1;
455 changed.boundary.request_produced += 1;
456 changed.settled.request_cursor += 1;
457 try expectDifferentSemantic(expected, try issue(changed));
458 changed = first_material;
459 changed.boundary.event_consumed += 1;
460 try expectDifferentSemantic(expected, try issue(changed));
461
462 changed = first_material;
463 changed.fence.world[0] ^= 1;
464 changed.fence.generation += 1;
465 changed.fence.token[0] ^= 1;
466 changed.delivery.digest[0] ^= 1;
467 changed.boundary.capability_generation = changed.fence.generation;
468 const reframed = try issue(changed);
469 try std.testing.expectEqualSlices(
470 u8,
471 &expected,
472 &(try semanticDigest(reframed)),
473 );
474 }
475
476 test "quiescence receipt rejects changed authority and basis bindings" {
477 const value = try issue(exampleMaterial());
478 var changed = value;
479 changed.fenced_digest[0] ^= 1;
480 try expectRejected(changed);
481 changed = value;
482 changed.fence.world[0] ^= 1;
483 try expectRejected(changed);
484 changed = value;
485 changed.fence.generation += 1;
486 try expectRejected(changed);
487 changed = value;
488 changed.fence.token[0] ^= 1;
489 try expectRejected(changed);
490 changed = value;
491 changed.delivery.digest[0] ^= 1;
492 try expectRejected(changed);
493 changed = value;
494 changed.admission_receipt.digest[0] ^= 1;
495 try expectRejected(changed);
496 changed = value;
497 changed.basis.contract.digest[0] ^= 1;
498 try expectRejected(changed);
499 changed = value;
500 changed.basis.source_root[0] ^= 1;
501 try expectRejected(changed);
502 changed = value;
503 changed.basis.frontiers.input += 1;
504 try expectRejected(changed);
505 changed = value;
506 changed.basis.frontiers.terminal_input_offset += 1;
507 try expectRejected(changed);
508 changed = value;
509 changed.basis.frontiers.virtual_time_tick += 1;
510 try expectRejected(changed);
511 changed = value;
512 changed.basis.frontiers.entropy_generation += 1;
513 try expectRejected(changed);
514 changed = value;
515 changed.basis.frontiers.effect += 1;
516 try expectRejected(changed);
517 changed = value;
518 changed.basis.outstanding_effect = .{
519 .receipt = .{ .digest = @splat(0x88) },
520 .correlation = changed.basis.frontiers.effect + 1,
521 };
522 try expectRejected(changed);
523 changed = value;
524 changed.image_digest[0] ^= 1;
525 try expectRejected(changed);
526 changed = value;
527 changed.execution_fingerprint.digest[0] ^= 1;
528 try expectRejected(changed);
529 changed = value;
530 changed.block_root.generation += 1;
531 try expectRejected(changed);
532 changed = value;
533 changed.block_root.digest[0] ^= 1;
534 try expectRejected(changed);
535 }
536
537 test "quiescence receipt rejects changed boundary and settlement bindings" {
538 const value = try issue(exampleMaterial());
539 var changed = value;
540 changed.boundary.request_sequence += 1;
541 try expectRejected(changed);
542 changed = value;
543 changed.boundary.semantic_frontier += 1;
544 try expectRejected(changed);
545 changed = value;
546 changed.boundary.effect_frontier += 1;
547 try expectRejected(changed);
548 changed = value;
549 changed.boundary.terminal_offset += 1;
550 try expectRejected(changed);
551 changed = value;
552 changed.boundary.virtual_time_tick += 1;
553 try expectRejected(changed);
554 changed = value;
555 changed.boundary.entropy_generation += 1;
556 try expectRejected(changed);
557 changed = value;
558 changed.boundary.request_consumed += 1;
559 try expectRejected(changed);
560 changed = value;
561 changed.boundary.request_produced += 1;
562 try expectRejected(changed);
563 changed = value;
564 changed.boundary.event_consumed += 1;
565 try expectRejected(changed);
566 changed = value;
567 changed.boundary.event_produced += 1;
568 try expectRejected(changed);
569 changed = value;
570 changed.boundary.capability_generation += 1;
571 try expectRejected(changed);
572 changed = value;
573 changed.boundary.unresolved_effects += 1;
574 try expectRejected(changed);
575 changed = value;
576 changed.boundary.block_root[0] ^= 1;
577 try expectRejected(changed);
578 changed = value;
579 changed.boundary.input_frontier += 1;
580 try expectRejected(changed);
581 changed = value;
582 changed.boundary.terminal_input_offset += 1;
583 try expectRejected(changed);
584 changed = value;
585 changed.settled.request_cursor += 1;
586 try expectRejected(changed);
587 changed = value;
588 changed.settled.event_cursor += 1;
589 try expectRejected(changed);
590 changed = value;
591 changed.k0.counter += 1;
592 try expectRejected(changed);
593 changed = value;
594 changed.event_transcript_digest[0] ^= 1;
595 try expectRejected(changed);
596 changed = value;
597 changed.semantic_transcript_digest[0] ^= 1;
598 try expectRejected(changed);
599 }
600
601 test "quiescence receipt creation rejects inconsistent stopped cuts" {
602 const value = exampleMaterial();
603 var changed = value;
604 changed.boundary.capability_generation += 1;
605 try expectMaterialRejected(changed);
606 changed = value;
607 changed.basis.frontiers.input += 1;
608 try expectMaterialRejected(changed);
609 changed = value;
610 changed.block_root.digest[0] ^= 1;
611 try expectMaterialRejected(changed);
612 changed = value;
613 changed.settled.request_cursor += 1;
614 try expectMaterialRejected(changed);
615 changed = value;
616 changed.basis.outstanding_effect = .{
617 .receipt = .{ .digest = @splat(0x88) },
618 .correlation = changed.basis.frontiers.effect + 1,
619 };
620 try expectMaterialRejected(changed);
621 changed = value;
622 changed.block_root.generation = 0;
623 try expectMaterialRejected(changed);
624 changed = value;
625 changed.block_root.generation = @as(u64, std.math.maxInt(u32)) + 1;
626 try expectMaterialRejected(changed);
627 changed = value;
628 changed.block_root.generation += 1;
629 try expectMaterialRejected(changed);
630 changed = value;
631 changed.boundary.semantic_frontier = std.math.maxInt(u32);
632 try expectMaterialRejected(changed);
633 changed = value;
634 changed.k0.counter = @intCast(changed.boundary.semantic_frontier + 1);
635 try expectMaterialRejected(changed);
636 changed = value;
637 changed.boundary.unresolved_effects = 1;
638 try expectMaterialRejected(changed);
639 }
640
641 test "event transcript binds count order and every byte" {
642 var batch: types.EventBatch = .{
643 .count = 2,
644 .storage = @splat(@splat(0)),
645 };
646 batch.storage[0] = @splat(0x11);
647 batch.storage[1] = @splat(0x22);
648 const expected = transcriptDigest(&batch);
649
650 batch.storage[1][255] ^= 1;
651 try std.testing.expect(!std.mem.eql(u8, &expected, &transcriptDigest(&batch)));
652 batch.storage[1][255] ^= 1;
653 const first = batch.storage[0];
654 batch.storage[0] = batch.storage[1];
655 batch.storage[1] = first;
656 try std.testing.expect(!std.mem.eql(u8, &expected, &transcriptDigest(&batch)));
657 batch.storage[0] = first;
658 batch.storage[1] = @splat(0x22);
659 batch.count = 1;
660 try std.testing.expect(!std.mem.eql(u8, &expected, &transcriptDigest(&batch)));
661 }
662
663 test "semantic event transcript removes only activation authority" {
664 const first_fence: os.abi.ActivationFence = .{
665 .world = @splat(0x61),
666 .generation = 7,
667 .token = @splat(0x62),
668 };
669 const second_fence: os.abi.ActivationFence = .{
670 .world = @splat(0x71),
671 .generation = 11,
672 .token = @splat(0x72),
673 };
674 const semantic: os.abi.Semantic = .{
675 .interface_id = @splat(0x31),
676 .event_id = 4,
677 .position = 9,
678 .bytes = "state",
679 };
680 var first: types.EventBatch = .{
681 .count = 1,
682 .storage = @splat(@splat(0)),
683 };
684 var second = first;
685 try os.abi.encodeSemanticEvent(first_fence, 3, semantic, &first.storage[0]);
686 try os.abi.encodeSemanticEvent(second_fence, 3, semantic, &second.storage[0]);
687 try std.testing.expect(!std.mem.eql(
688 u8,
689 &transcriptDigest(&first),
690 &transcriptDigest(&second),
691 ));
692 try std.testing.expectEqualSlices(
693 u8,
694 &(try semanticTranscriptDigest(&first)),
695 &(try semanticTranscriptDigest(&second)),
696 );
697
698 try os.abi.encodeSemanticEvent(
699 second_fence,
700 3,
701 .{
702 .interface_id = semantic.interface_id,
703 .event_id = semantic.event_id + 1,
704 .position = semantic.position,
705 .bytes = semantic.bytes,
706 },
707 &second.storage[0],
708 );
709 try std.testing.expect(!std.mem.eql(
710 u8,
711 &(try semanticTranscriptDigest(&first)),
712 &(try semanticTranscriptDigest(&second)),
713 ));
714 }
715
716 fn exampleMaterial() Material {
717 const root: os.abi.Digest = @splat(0x55);
718 return .{
719 .fence = .{
720 .world = @splat(0x11),
721 .generation = 7,
722 .token = @splat(0x22),
723 },
724 .delivery = .{ .digest = @splat(0x33) },
725 .admission_receipt = .{ .digest = @splat(0x34) },
726 .basis = .{
727 .contract = .{ .digest = @splat(0x44) },
728 .source_root = @splat(0x45),
729 .frontiers = .{
730 .input = 3,
731 .terminal_input_offset = 9,
732 .virtual_time_tick = 21,
733 .entropy_generation = 2,
734 .effect = 1,
735 },
736 .outstanding_effect = null,
737 },
738 .image_digest = @splat(0x46),
739 .execution_fingerprint = .{ .digest = @splat(0x47) },
740 .block_root = .{ .generation = 3, .digest = root },
741 .boundary = .{
742 .request_sequence = 3,
743 .semantic_frontier = 2,
744 .effect_frontier = 1,
745 .terminal_offset = 19,
746 .virtual_time_tick = 21,
747 .entropy_generation = 2,
748 .request_consumed = 3,
749 .request_produced = 3,
750 .event_consumed = 2,
751 .event_produced = 6,
752 .capability_generation = 7,
753 .unresolved_effects = 0,
754 .scheduler = .idle,
755 .block_root = root,
756 .input_frontier = 3,
757 .terminal_input_offset = 9,
758 },
759 .settled = .{ .request_cursor = 3, .event_cursor = 6 },
760 .k0 = .{ .counter = 1 },
761 .event_transcript_digest = @splat(0x48),
762 .semantic_transcript_digest = @splat(0x49),
763 };
764 }
765
766 fn expectRejected(value: types.QuiescenceReceipt) !void {
767 try std.testing.expectError(error.InvalidQuiescenceReceipt, verify(value));
768 }
769
770 fn expectMaterialRejected(value: Material) !void {
771 try std.testing.expectError(error.InvalidQuiescenceReceipt, issue(value));
772 }
773
774 fn expectDifferentSemantic(
775 expected: os.abi.Digest,
776 value: types.QuiescenceReceipt,
777 ) !void {
778 try std.testing.expect(!std.mem.eql(
779 u8,
780 &expected,
781 &(try semanticDigest(value)),
782 ));
783 }