lib/machine/src/world/canon.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

  1 const checkpoint = @import("../checkpoint/root.zig");
  2 const fabric = @import("../fabric/root.zig");
  3 const os = @import("os");
  4 const std = @import("std");
  5 const types = @import("types.zig");
  6 
  7 const Sha256 = std.crypto.hash.sha2.Sha256;
  8 const root_domain = "TINYMACHINEWORLDROOT1\x00";
  9 
 10 /// The rejections of cut material. Each variant reports which check failed, whether
 11 /// on a field's value, on the order of entries, or on a digest.
 12 pub const Error = error{
 13     ContractMismatch,
 14     CutInvalid,
 15     InvalidMachineRoot,
 16     InvalidNode,
 17     NodeCountMismatch,
 18     NodesNotCanonical,
 19     RootMismatch,
 20 };
 21 
 22 /// Builds a canonical cut from a ledger root together with nodes in strict order,
 23 /// so a caller turns a ledger position and a node list into the one cut that stands
 24 /// for them. The cut root digest covers every node together with the contract and
 25 /// the ledger root. Node identities must ascend strictly and none may be all zero,
 26 /// and a node count of zero or above the node limit rejects. Unused slots are filled
 27 /// with the padding value.
 28 pub fn prepare(
 29     fabric_root: fabric.Root,
 30     nodes: []const types.Node,
 31 ) Error!types.Cut {
 32     try validateMaterial(fabric_root, nodes);
 33     var cut: types.Cut = .{
 34         .root = undefined,
 35         .nodes = @splat(emptyNode()),
 36     };
 37     @memcpy(cut.nodes[0..nodes.len], nodes);
 38     cut.root = rootFor(fabric_root, nodes);
 39     return cut;
 40 }
 41 
 42 /// Hashes a cut's material again and refuses the cut when the result differs from
 43 /// the stored root. A node slot holding no node carries the empty padding value.
 44 /// The cut's own contract must equal the contract its ledger root carries.
 45 pub fn verify(cut: types.Cut) Error!void {
 46     const count = cut.root.node_count;
 47     if (count == 0 or count > types.node_limit) {
 48         return error.NodeCountMismatch;
 49     }
 50     for (cut.nodes[count..]) |node| {
 51         if (!std.meta.eql(node, emptyNode())) return error.CutInvalid;
 52     }
 53     const nodes = cut.nodes[0..count];
 54     try validateMaterial(cut.root.fabric, nodes);
 55     if (cut.root.dialect != .connected_world_v1 or
 56         !std.meta.eql(
 57             cut.root.machine_contract,
 58             cut.root.fabric.machine_contract,
 59         ))
 60     {
 61         return error.CutInvalid;
 62     }
 63     const expected = rootFor(cut.root.fabric, nodes);
 64     if (!std.meta.eql(expected, cut.root)) return error.RootMismatch;
 65 }
 66 
 67 /// The padding value for unused cut slots. A caller uses this to fill the slots
 68 /// of a cut it is building by hand. Every field of it is zero.
 69 pub fn emptyNode() types.Node {
 70     return .{
 71         .id = .{ .bytes = @splat(0) },
 72         .machine = .{
 73             .digest = @splat(0),
 74             .profile = .{ .digest = @splat(0) },
 75             .state = .{ .digest = @splat(0) },
 76         },
 77     };
 78 }
 79 
 80 fn validateMaterial(
 81     fabric_root: fabric.Root,
 82     nodes: []const types.Node,
 83 ) Error!void {
 84     if (nodes.len == 0 or nodes.len > types.node_limit) {
 85         return error.NodeCountMismatch;
 86     }
 87     try validateFabricRoot(fabric_root);
 88     var previous: ?fabric.NodeId = null;
 89     for (nodes) |node| {
 90         if (os.abi.wire.allZero(&node.id.bytes)) return error.InvalidNode;
 91         if (previous) |id| {
 92             if (std.mem.order(u8, &id.bytes, &node.id.bytes) != .lt) {
 93                 return error.NodesNotCanonical;
 94             }
 95         }
 96         try validateMachineRoot(node.machine);
 97         previous = node.id;
 98     }
 99 }
100 
101 fn validateFabricRoot(value: fabric.Root) Error!void {
102     os.abi.wire.validateDigest(value.digest) catch return error.CutInvalid;
103     os.abi.wire.validateDigest(value.machine_contract.digest) catch
104         return error.CutInvalid;
105     if (value.dialect != .ordered_effect_fabric_v3 or
106         value.entry_frontier < value.admission_frontier or
107         value.fault_frontier > value.admission_frontier)
108     {
109         return error.CutInvalid;
110     }
111 }
112 
113 fn validateMachineRoot(value: checkpoint.Root) Error!void {
114     os.abi.wire.validateDigest(value.digest) catch
115         return error.InvalidMachineRoot;
116     os.abi.wire.validateDigest(value.profile.digest) catch
117         return error.InvalidMachineRoot;
118     os.abi.wire.validateDigest(value.state.digest) catch
119         return error.InvalidMachineRoot;
120 }
121 
122 fn rootFor(fabric_root: fabric.Root, nodes: []const types.Node) types.Root {
123     var hasher = Sha256.init(.{});
124     hasher.update(root_domain);
125     hashInteger(&hasher, @backingInt(types.Dialect.connected_world_v1));
126     hasher.update(&fabric_root.machine_contract.digest);
127     hashFabricRoot(&hasher, fabric_root);
128     hashInteger(&hasher, @as(u8, @intCast(nodes.len)));
129     for (nodes) |node| {
130         hasher.update(&node.id.bytes);
131         hashMachineRoot(&hasher, node.machine);
132     }
133     var digest: os.abi.Digest = undefined;
134     hasher.final(&digest);
135     return .{
136         .digest = digest,
137         .dialect = .connected_world_v1,
138         .machine_contract = fabric_root.machine_contract,
139         .fabric = fabric_root,
140         .node_count = @intCast(nodes.len),
141     };
142 }
143 
144 fn hashFabricRoot(hasher: *Sha256, value: fabric.Root) void {
145     hasher.update(&value.digest);
146     hashInteger(hasher, @backingInt(value.dialect));
147     hasher.update(&value.machine_contract.digest);
148     hashInteger(hasher, value.entry_frontier);
149     hashInteger(hasher, value.admission_frontier);
150     hashInteger(hasher, value.fault_frontier);
151 }
152 
153 fn hashMachineRoot(hasher: *Sha256, value: checkpoint.Root) void {
154     hasher.update(&value.digest);
155     hasher.update(&value.profile.digest);
156     hasher.update(&value.state.digest);
157 }
158 
159 fn hashInteger(hasher: *Sha256, value: anytype) void {
160     var encoded: [@sizeOf(@TypeOf(value))]u8 = undefined;
161     std.mem.writeInt(@TypeOf(value), &encoded, value, .little);
162     hasher.update(&encoded);
163 }