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 }