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}