Skip to main content

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}