Skip to main content

macroonz_compiler/plan/
encode.rs

1//! The canonical bytes a plan's transcript is taken over, and the bytes one planning issue is.
2//!
3//! Every row's discriminant 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//! Declared SETS are canonicalized here: each member is encoded, the ENCODINGS are sorted, and the sorted sequence is written.
5
6use super::{
7    Account, Context, ContradictionPair, DigestContract, InvalidationTrigger, Membership,
8    PlanIssue, PlannedMember, PlannedOutput,
9};
10use crate::identity::{self, Identity, encode_bytes, encode_length};
11use crate::kind::{Kind, Role};
12
13/// Appends one captured commitment's canonical bytes, at full width.
14fn capture_into(captured: &Identity<identity::CapturedDeclaration>, into: &mut Vec<u8>) {
15    encode_bytes(captured.as_bytes(), into);
16}
17
18impl<K: Kind> Account<K> {
19    /// The intent's canonical bytes on their own — the exact preimage [`Account::intent`](super::Account::intent) is derived over.
20    ///
21    /// Written from the pair and never read back off the identity: a preimage road that spelled the digest would hand back thirty-two bytes nobody can re-derive anything from, and the derivation it feeds would be defined in terms of its own output.
22    #[must_use]
23    pub fn intent_bytes(&self) -> Vec<u8> {
24        let mut bytes = Vec::new();
25        self.intent_into(&mut bytes);
26        bytes
27    }
28
29    /// Appends this account's canonical bytes: the intent preimage, then the dependency set, canonicalized.
30    ///
31    /// The account's bytes therefore BEGIN with exactly the intent's preimage, through the same road [`Account::intent_bytes`] hands back, rather than through a second spelling that happens to agree today.
32    pub fn encode_into(&self, into: &mut Vec<u8>) {
33        self.intent_into(into);
34        encode_set(self.dependencies().iter(), capture_into, into);
35    }
36
37    /// Appends the intent preimage: the owner-qualified kind, then the kind-specific content commitment at full width.
38    fn intent_into(&self, into: &mut Vec<u8>) {
39        encode_bytes(self.kind().as_bytes(), into);
40        encode_bytes(self.content_commitment().as_bytes(), into);
41    }
42}
43
44impl Context {
45    /// Appends this context's canonical bytes: the profile, then the generator identity at full width.
46    ///
47    /// What a plan was planned OVER is not written here and is not missing: it is the account's fact, written ahead of this by the account's own road, so no byte of a plan transcript states the content twice.
48    pub fn encode_into(&self, into: &mut Vec<u8>) {
49        self.profile().encode_into(into);
50        encode_bytes(self.generator().as_bytes(), into);
51    }
52}
53
54impl InvalidationTrigger {
55    /// Appends this trigger's canonical bytes: the row's discriminant, then what it watches.
56    ///
57    /// Two rows watching one thing never encode alike, because the discriminant rides ahead of the material.
58    pub fn encode_into(&self, into: &mut Vec<u8>) {
59        into.push(self.slot());
60        match self {
61            Self::CapturedDeclaration { watched } => encode_bytes(watched.as_bytes(), into),
62            Self::Profile { watched } => watched.encode_into(into),
63            Self::Generator { watched } => encode_bytes(watched.as_bytes(), into),
64            Self::ProjectionContent { watched } => encode_bytes(watched.as_bytes(), into),
65            Self::Declared { name, watched } => {
66                encode_bytes(name.as_bytes(), into);
67                encode_bytes(&watched.citation_bytes(), into);
68            }
69        }
70    }
71}
72
73impl DigestContract {
74    /// Appends this contract's canonical bytes: the member identity the digest must be anchored to.
75    pub fn encode_into(&self, into: &mut Vec<u8>) {
76        encode_bytes(self.anchored_to.as_bytes(), into);
77    }
78}
79
80impl PlannedOutput {
81    /// Appends this output's canonical bytes: the semantic key, the origin trail in walk order, the expected profile, the publication address where one is named, and the digest contract.
82    ///
83    /// Everything a plan states about one member, and no rendered byte, because a plan has none.
84    /// The member's delivery is not written and is not missing: it is the seat's own answer, and the seat is written by the member's own road.
85    pub fn encode_into(&self, into: &mut Vec<u8>) {
86        encode_bytes(self.semantic_key.as_bytes(), into);
87        self.origin.encode_into(into);
88        self.expected_profile.encode_into(into);
89        match self.address {
90            None => {
91                into.push(0);
92                encode_bytes(&[], into);
93            }
94            Some(address) => {
95                into.push(1);
96                encode_bytes(&address.citation_bytes(), into);
97            }
98        }
99        self.digest_contract.encode_into(into);
100    }
101}
102
103impl<R: Role> PlannedMember<R> {
104    /// Appends this member's canonical bytes: the seat's roster position in two big-endian bytes, then the output planned there.
105    pub fn encode_into(&self, into: &mut Vec<u8>) {
106        into.extend_from_slice(&self.role.slot().to_be_bytes());
107        self.output.encode_into(into);
108    }
109}
110
111impl<R: Role> Membership<R> {
112    /// Appends this membership's canonical bytes, in the kind's declared ROSTER order.
113    ///
114    /// Roster order and never declaration order: a declared output set is order-insensitive, so the same members declared in another order must encode identically.
115    /// Every member standing under a seat is written rather than only the first, so a membership that doubled a seat encodes differently from one that did not — that is a defect closure reports, and the encoding must not hide it before the check runs.
116    pub fn encode_into(&self, into: &mut Vec<u8>) {
117        encode_length(R::ALL.len(), into);
118        for role in R::ALL {
119            into.extend_from_slice(&role.slot().to_be_bytes());
120            let under: Vec<&PlannedMember<R>> = self.members_under(*role).collect();
121            encode_length(under.len(), into);
122            for member in under {
123                member.encode_into(into);
124            }
125        }
126    }
127}
128
129impl ContradictionPair {
130    /// Appends this pair's canonical bytes: the left citation, then the right.
131    ///
132    /// # Ordering
133    ///
134    /// Written in the order it is held and NOT canonicalized as a set: the two seats are named seats rather than members of a collection, so a spelling that sorted them would answer a question this type deliberately does not ask.
135    fn encode_into(&self, into: &mut Vec<u8>) {
136        encode_bytes(&self.left.citation_bytes(), into);
137        encode_bytes(&self.right.citation_bytes(), into);
138    }
139}
140
141impl PlanIssue {
142    /// This issue's canonical bytes on their own, for the related identity a diagnostic derives over it.
143    #[must_use]
144    pub fn canonical_bytes(&self) -> Vec<u8> {
145        let mut bytes = Vec::new();
146        self.encode_into(&mut bytes);
147        bytes
148    }
149
150    /// Appends this issue's canonical bytes: the row's position in the declared roster, then the typed material that row carries, framed.
151    ///
152    /// Exhaustive over the roster on purpose: an issue added to [`PlanIssue`] stops compiling HERE until somebody says what of it a preimage commits to, so no issue can be admitted and left out of every identity derived over a refusal that carries it.
153    pub fn encode_into(&self, into: &mut Vec<u8>) {
154        into.push(self.slot());
155        let mut material = Vec::new();
156        self.material_into(&mut material);
157        encode_bytes(&material, into);
158    }
159
160    /// The typed material one issue carries, through each value's own declared spelling.
161    fn material_into(&self, into: &mut Vec<u8>) {
162        match self {
163            Self::ContradictoryFacts { between } => between.encode_into(into),
164            Self::UnknownKind { named } => encode_bytes(named.as_bytes(), into),
165            Self::ProfileUnsupported { profile } => profile.encode_into(into),
166            Self::BoundExceeded {
167                axis,
168                bound,
169                observed,
170            } => {
171                into.push(axis.slot());
172                into.extend_from_slice(&bound.to_be_bytes());
173                into.extend_from_slice(&observed.to_be_bytes());
174            }
175            Self::MembershipIncomplete { absent } => encode_bytes(absent.as_bytes(), into),
176            Self::OrphanGeneratedNode { node } => encode_bytes(node.as_bytes(), into),
177            Self::MembershipDoubled {
178                role_slot,
179                observed,
180            } => {
181                into.extend_from_slice(&role_slot.to_be_bytes());
182                into.extend_from_slice(&observed.to_be_bytes());
183            }
184            Self::TrailDiscontinuous { at } => into.extend_from_slice(&at.to_be_bytes()),
185            Self::CauseSetUnwatchable { named, watchable } => {
186                into.extend_from_slice(&named.to_be_bytes());
187                into.extend_from_slice(&watchable.to_be_bytes());
188            }
189            Self::MembershipForeign { seat } | Self::AddressInert { seat } => {
190                encode_bytes(seat.as_bytes(), into);
191            }
192        }
193    }
194}
195
196/// Appends one declared SET's canonical bytes: every member encoded, the encodings sorted, the sorted sequence written behind its count.
197///
198/// # Ordering
199///
200/// Sorting the ENCODINGS rather than the members is what canonicalizes a set without an `Ord` this compiler declares for nobody: a byte order over finished encodings is a spelling rule for a collection whose order carries no meaning, not a ranking of the values.
201pub(super) fn encode_set<'member, T: 'member, Encode>(
202    members: impl Iterator<Item = &'member T>,
203    encode: Encode,
204    into: &mut Vec<u8>,
205) where
206    Encode: Fn(&T, &mut Vec<u8>),
207{
208    let mut encoded: Vec<Vec<u8>> = members
209        .map(|member| {
210            let mut bytes = Vec::new();
211            encode(member, &mut bytes);
212            bytes
213        })
214        .collect();
215    encoded.sort_unstable();
216    encode_length(encoded.len(), into);
217    for member in &encoded {
218        encode_bytes(member, into);
219    }
220}