lib/machine/src/fault/canon.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const os = @import("os");
2 const std = @import("std");
3 const types = @import("types.zig");
4
5 const Sha256 = std.crypto.hash.sha2.Sha256;
6 const chain_domain = "TINYMACHINEFAULTCHAIN1\x00";
7 const decision_domain = "TINYMACHINEFAULTDECISION1\x00";
8 const point_domain = "TINYMACHINEFAULTPOINT1\x00";
9
10 /// Rejections of fault material.
11 /// A caller matches on these to tell which piece failed, because every variant names
12 /// the digest or the encoding version that failed recomputation: the basis, the
13 /// chain, the decision, the point, or the subject.
14 pub const Error = error{
15 InvalidFaultBasis,
16 InvalidFaultChain,
17 InvalidFaultDecision,
18 InvalidFaultPoint,
19 InvalidFaultSubject,
20 };
21
22 /// Assembles a fault point, one place a fault may occur, from its material and computes
23 /// the id that authenticates it, so that the same place in two runs comes out as
24 /// the same value.
25 /// An all-zero basis digest and an all-zero subject digest are both rejected before
26 /// the id is computed.
27 /// The id is a SHA-256 digest over the encoding version, the basis, the kind, and
28 /// the subject, under the point domain tag.
29 /// A domain tag is a distinct constant string hashed before a value, so digests
30 /// of different kinds never collide.
31 pub fn prepare(
32 basis: os.abi.Digest,
33 kind: types.Kind,
34 subject: os.abi.Digest,
35 ) Error!types.Point {
36 try validateDigest(basis, error.InvalidFaultBasis);
37 try validateDigest(subject, error.InvalidFaultSubject);
38 var point: types.Point = .{
39 .id = undefined,
40 .dialect = .deterministic_fault_v1,
41 .basis = basis,
42 .kind = kind,
43 .subject = subject,
44 };
45 point.id = pointDigest(point);
46 return point;
47 }
48
49 /// Recomputes a point's id from its fields and rejects any drift, proving the stored
50 /// record unchanged.
51 /// A point carrying an unknown encoding version is rejected first.
52 pub fn verifyPoint(point: types.Point) Error!void {
53 if (point.dialect != .deterministic_fault_v1) {
54 return error.InvalidFaultPoint;
55 }
56 try validateDigest(point.basis, error.InvalidFaultBasis);
57 try validateDigest(point.subject, error.InvalidFaultSubject);
58 if (!std.mem.eql(u8, &point.id, &pointDigest(point))) {
59 return error.InvalidFaultPoint;
60 }
61 }
62
63 /// Pairs a point it has verified with one choice, the outcome recorded at a fault
64 /// point, then hashes that pair into the decision digest.
65 /// The point is verified before the choice is bound to it.
66 pub fn decide(point: types.Point, choice: types.Choice) Error!types.Decision {
67 try verifyPoint(point);
68 var decision: types.Decision = .{
69 .point = point,
70 .choice = choice,
71 .digest = undefined,
72 };
73 decision.digest = decisionDigest(decision);
74 return decision;
75 }
76
77 /// Verifies both halves of a stored decision to prove the record unchanged, checking
78 /// from the bottom up with the point id first and then the decision digest over
79 /// it.
80 pub fn verifyDecision(decision: types.Decision) Error!void {
81 try verifyPoint(decision.point);
82 if (!std.mem.eql(u8, &decision.digest, &decisionDigest(decision))) {
83 return error.InvalidFaultDecision;
84 }
85 }
86
87 /// Extends the running fault-chain digest by one decision it has verified.
88 /// Equal chain digests prove equal fault histories.
89 /// An all-zero previous digest is rejected, and the decision is verified before
90 /// it folds in.
91 pub fn advance(
92 previous: os.abi.Digest,
93 decision: types.Decision,
94 ) Error!os.abi.Digest {
95 try validateDigest(previous, error.InvalidFaultChain);
96 try verifyDecision(decision);
97 var hasher = Sha256.init(.{});
98 hasher.update(chain_domain);
99 hasher.update(&previous);
100 hasher.update(&decision.digest);
101 var digest: os.abi.Digest = undefined;
102 hasher.final(&digest);
103 return digest;
104 }
105
106 fn pointDigest(point: types.Point) os.abi.Digest {
107 var hasher = Sha256.init(.{});
108 hasher.update(point_domain);
109 hashInteger(&hasher, @backingInt(point.dialect));
110 hasher.update(&point.basis);
111 hashInteger(&hasher, @backingInt(point.kind));
112 hasher.update(&point.subject);
113 var digest: os.abi.Digest = undefined;
114 hasher.final(&digest);
115 return digest;
116 }
117
118 fn decisionDigest(decision: types.Decision) os.abi.Digest {
119 var hasher = Sha256.init(.{});
120 hasher.update(decision_domain);
121 hasher.update(&decision.point.id);
122 hashInteger(&hasher, @backingInt(decision.choice));
123 var digest: os.abi.Digest = undefined;
124 hasher.final(&digest);
125 return digest;
126 }
127
128 fn validateDigest(value: os.abi.Digest, failure: Error) Error!void {
129 os.abi.wire.validateDigest(value) catch return failure;
130 }
131
132 fn hashInteger(hasher: *Sha256, value: anytype) void {
133 var encoded: [@sizeOf(@TypeOf(value))]u8 = undefined;
134 std.mem.writeInt(@TypeOf(value), &encoded, value, .little);
135 hasher.update(&encoded);
136 }