macroonz_compiler/closure/encode.rs
1//! The canonical bytes the proof's transcript is taken over, and the bytes one closure issue is.
2//!
3//! Every posture byte rides ahead of the material it governs, and every variable-length member is framed through the identity home's one framing, so no two values can be cut at another boundary and produce one byte string.
4
5use super::prove::{count_under, under};
6use super::{CarriedTokens, ClosureIssue, PartitionCargo, PartitionedEmission};
7use crate::identity::{encode_bytes, encode_length};
8use crate::kind::{Destination, Role};
9use crate::plan::Membership;
10use crate::render::RenderedProjection;
11
12/// The posture byte a delivery nothing was planned into carries.
13const NOTHING_PLANNED: u8 = 0;
14
15/// The posture byte a delivery carrying proved tokens carries.
16const CARRIED: u8 = 1;
17
18/// The posture byte a delivery that is never joined carries.
19///
20/// A third posture rather than the first: the publication delivery carrying artifacts and the test carrier carrying nothing are different facts about different deliveries.
21const NOT_JOINED: u8 = 2;
22
23/// The complete closure claim, as the bytes its identity is derived over.
24///
25/// Three members: the plan's whole declared membership in roster order, the roster's own length followed by what stood under each seat, and the joined deliveries.
26///
27/// A rendered unit is written as its own identity and its digest, and the rest of what it answers — the semantic key, the origin, the profile, the address — is not missing.
28/// Those are the reconstruction's members, the reconstruction was proved equal to the membership written first, and one fact spelled twice in one preimage is how a preimage drifts.
29pub(super) fn claim<R: Role>(
30 planned: &Membership<R>,
31 rendered: &RenderedProjection<R>,
32 emission: &PartitionedEmission,
33) -> Vec<u8> {
34 let mut material = Vec::new();
35 planned.encode_into(&mut material);
36 encode_length(R::ALL.len(), &mut material);
37 for role in R::ALL {
38 material.extend_from_slice(&role.slot().to_be_bytes());
39 encode_length(count_under(rendered, *role), &mut material);
40 if let Some(unit) = under(rendered, *role) {
41 encode_bytes(unit.identity().as_bytes(), &mut material);
42 encode_bytes(unit.digest().as_bytes(), &mut material);
43 }
44 }
45 emission.encode_into(&mut material);
46 material
47}
48
49impl PartitionedEmission {
50 /// Appends this emission's canonical bytes: every delivery of the roster, in roster order, each written as its declared name and then its cargo.
51 ///
52 /// The published artifacts are not written here and are not missing: the claim already commits to every rendered unit's identity and digest, and to the membership that names each unit's address, so an artifact written elsewhere is already a different closure.
53 fn encode_into(&self, into: &mut Vec<u8>) {
54 encode_length(Destination::ALL.len(), into);
55 for destination in Destination::ALL {
56 encode_bytes(destination.name().as_bytes(), into);
57 if let Some(cargo) = self.joined(*destination) {
58 cargo.encode_into(into);
59 } else {
60 into.push(NOT_JOINED);
61 encode_bytes(&[], into);
62 }
63 }
64 }
65}
66
67impl PartitionCargo {
68 /// Appends this cargo's canonical bytes: the posture, then the digest where tokens are carried.
69 ///
70 /// The posture rides ahead of the material, so a delivery nothing was planned into never encodes as one that carries bytes.
71 fn encode_into(&self, into: &mut Vec<u8>) {
72 match self {
73 Self::NothingPlanned => {
74 into.push(NOTHING_PLANNED);
75 encode_bytes(&[], into);
76 }
77 Self::Carried(carried) => {
78 into.push(CARRIED);
79 carried.encode_into(into);
80 }
81 }
82 }
83}
84
85impl CarriedTokens {
86 /// Appends these tokens' canonical bytes: the digest, at full width.
87 ///
88 /// The tokens themselves are not written and do not need to be: the digest is derived over them at full width, so a byte that changed changes the digest and therefore this encoding.
89 fn encode_into(&self, into: &mut Vec<u8>) {
90 encode_bytes(self.digest().as_bytes(), into);
91 }
92}
93
94impl<R: Role> ClosureIssue<R> {
95 /// This issue's canonical bytes on their own, for the related identity a diagnostic derives over it.
96 #[must_use]
97 pub fn canonical_bytes(&self) -> Vec<u8> {
98 let mut bytes = Vec::new();
99 self.encode_into(&mut bytes);
100 bytes
101 }
102
103 /// Appends this issue's canonical bytes: the row's position in the declared roster, then the typed material that row carries, framed.
104 ///
105 /// Exhaustive over the roster on purpose: an issue added to [`ClosureIssue`] stops compiling HERE until somebody says what of it a preimage commits to.
106 pub fn encode_into(&self, into: &mut Vec<u8>) {
107 into.push(self.slot());
108 let mut material = Vec::new();
109 self.material_into(&mut material);
110 encode_bytes(&material, into);
111 }
112
113 /// The typed material one issue carries, through each value's own declared spelling.
114 fn material_into(&self, into: &mut Vec<u8>) {
115 match self {
116 Self::MemberMissing { role }
117 | Self::MemberUnplanned { role }
118 | Self::OriginOrphan { role }
119 | Self::DigestMismatch { role }
120 | Self::SemanticKeyMismatch { role }
121 | Self::MaterializationMismatch { role }
122 | Self::MembershipDisagreement { role }
123 | Self::ArtifactAddressAbsent { role } => seat_into(*role, into),
124 Self::MemberDuplicated { role, observed }
125 | Self::MemberPlannedTwice { role, observed } => {
126 seat_into(*role, into);
127 into.extend_from_slice(&observed.to_be_bytes());
128 }
129 Self::ReconstructionEmpty => {}
130 Self::ReconstructionUndeclarable { observed } => {
131 into.extend_from_slice(&observed.to_be_bytes());
132 }
133 Self::JoinedTreeUnbounded { destination } => {
134 encode_bytes(destination.name().as_bytes(), into);
135 }
136 Self::ArtifactAddressDoubled { role, address } => {
137 seat_into(*role, into);
138 encode_bytes(&address.citation_bytes(), into);
139 }
140 }
141 }
142}
143
144/// Appends one seat's roster position, in two big-endian bytes.
145fn seat_into<R: Role>(role: R, into: &mut Vec<u8>) {
146 into.extend_from_slice(&role.slot().to_be_bytes());
147}