1use ed25519_dalek::{Signature, VerifyingKey};
4use prost::Message;
5use sha2::{Digest, Sha256};
6
7#[derive(Debug, Clone, Copy, PartialEq, Eq, thiserror::Error)]
8pub enum Reject {
9 #[error("unsupported format")]
10 Version,
11 #[error("noncanonical or missing field")]
12 Canonical,
13 #[error("size or count bound exceeded")]
14 Bounds,
15 #[error("invalid signature")]
16 Signature,
17 #[error("independent authority/root mismatch")]
18 Root,
19 #[error("semantically invalid record")]
20 Semantic,
21 #[error("generation rollback or equivocation")]
22 HighWater,
23 #[error("illegal signer lifecycle transition")]
24 Transition,
25 #[error("job key cannot be a witness")]
26 JobAsWitness,
27 #[error("key roles overlap")]
28 KeyRole,
29 #[error("authority is not valid at verification time")]
30 Expired,
31 #[error("delegation scope violation")]
32 Scope,
33 #[error("prepared frozen fields changed")]
34 PreparedFields,
35 #[error("delegation window exceeds prepared host bounds")]
36 ValidityBounds,
37 #[error("genesis binding does not match prepared branch")]
38 GenesisBinding,
39 #[error("missing typed owner import permission")]
40 ImportPermission,
41 #[error("renewal forks the logical job or widens remaining scope")]
42 RenewalFork,
43 #[error("committed manifest changed before renewal")]
44 StaleManifest,
45 #[error("replacement includes an already committed slot")]
46 CommittedSlot,
47 #[error("context is stale at mutation boundary")]
48 StaleContext,
49 #[error("missing or invalid exact retirement inclusion proof")]
50 Proof,
51 #[error("witness is revoked")]
52 Revoked,
53 #[error("committed slot conflict")]
54 SlotConflict,
55 #[error("missing or mismatched exact boundary acceptance binding")]
56 BoundaryAcceptance,
57 #[error("incompatible peer")]
58 Protocol,
59}
60
61pub trait Canonical {
62 fn write(&self, out: &mut Vec<u8>) -> Result<(), Reject>;
63}
64pub fn canonical<T: Canonical>(value: &T) -> Result<Vec<u8>, Reject> {
65 let mut out = Vec::new();
66 value.write(&mut out)?;
67 if out.len() > 1024 * 1024 {
68 return Err(Reject::Bounds);
69 }
70 Ok(out)
71}
72pub fn counted(out: &mut Vec<u8>, value: &[u8]) -> Result<(), Reject> {
73 if value.len() > 1024 * 1024
74 || out.len().saturating_add(value.len()).saturating_add(4) > 1024 * 1024
75 {
76 return Err(Reject::Bounds);
77 }
78 out.extend_from_slice(&(value.len() as u32).to_be_bytes());
79 out.extend_from_slice(value);
80 Ok(())
81}
82pub fn hash(parts: &[&[u8]]) -> Vec<u8> {
83 let mut hash = Sha256::new();
84 for part in parts {
85 hash.update(part);
86 }
87 hash.finalize().to_vec()
88}
89pub fn signing_digest<T: Canonical>(domain: &str, value: &T) -> Result<Vec<u8>, Reject> {
90 Ok(hash(&[domain.as_bytes(), &canonical(value)?]))
91}
92pub fn key_id(public_key: &[u8]) -> Vec<u8> {
93 hash(&[b"heddle-key-v1", &1_u32.to_be_bytes(), public_key])
94}
95pub fn width(value: &[u8], expected: usize) -> Result<(), Reject> {
96 if value.len() != expected {
97 return Err(Reject::Canonical);
98 }
99 Ok(())
100}
101pub fn verify(public_key: &[u8], input: &[u8], signature: &[u8]) -> Result<(), Reject> {
102 let key: &[u8; 32] = public_key.try_into().map_err(|_| Reject::Canonical)?;
103 let signature: &[u8; 64] = signature.try_into().map_err(|_| Reject::Canonical)?;
104 VerifyingKey::from_bytes(key)
105 .map_err(|_| Reject::Signature)?
106 .verify_strict(input, &Signature::from_bytes(signature))
107 .map_err(|_| Reject::Signature)
108}
109pub fn strict_decode<T: Message + Default>(bytes: &[u8], max: usize) -> Result<T, Reject> {
113 if bytes.is_empty() || bytes.len() > max {
114 return Err(Reject::Bounds);
115 }
116 let value = T::decode(bytes).map_err(|_| Reject::Canonical)?;
117 if value.encode_to_vec() != bytes {
118 return Err(Reject::Canonical);
119 }
120 Ok(value)
121}
122
123macro_rules! field {
124 ($out:ident, $v:expr, b) => {
125 $crate::hybrid_codec::counted($out, &$v)?
126 };
127 ($out:ident, $v:expr, s) => {
128 $crate::hybrid_codec::counted($out, $v.as_bytes())?
129 };
130 ($out:ident, $v:expr, u) => {
131 $out.extend_from_slice(&$v.to_be_bytes())
132 };
133 ($out:ident, $v:expr, e) => {
134 $out.extend_from_slice(
135 &u32::try_from($v)
136 .map_err(|_| $crate::hybrid_codec::Reject::Canonical)?
137 .to_be_bytes(),
138 )
139 };
140 ($out:ident, $v:expr, m) => {
141 $crate::hybrid_codec::Canonical::write(
142 $v.as_ref().ok_or($crate::hybrid_codec::Reject::Canonical)?,
143 $out,
144 )?
145 };
146 ($out:ident, $v:expr, o) => {
147 $out.extend_from_slice(&u32::from($v.is_some()).to_be_bytes());
148 if let Some(value) = &$v {
149 $crate::hybrid_codec::Canonical::write(value, $out)?;
150 }
151 };
152 ($out:ident, $v:expr, l) => {
153 if $v.len() > 4096 {
154 return Err($crate::hybrid_codec::Reject::Bounds);
155 }
156 $out.extend_from_slice(&($v.len() as u32).to_be_bytes());
157 for value in &$v {
158 $crate::hybrid_codec::Canonical::write(value, $out)?;
159 }
160 };
161 ($out:ident, $v:expr, p) => {
162 if $v.len() > 4 {
163 return Err($crate::hybrid_codec::Reject::Bounds);
164 }
165 $out.extend_from_slice(&($v.len() as u32).to_be_bytes());
166 for value in &$v {
167 $out.extend_from_slice(
168 &u32::try_from(*value)
169 .map_err(|_| $crate::hybrid_codec::Reject::Canonical)?
170 .to_be_bytes(),
171 );
172 }
173 };
174 ($out:ident, $v:expr, h) => {
175 if $v.len() > 128 {
176 return Err($crate::hybrid_codec::Reject::Bounds);
177 }
178 $out.extend_from_slice(&($v.len() as u32).to_be_bytes());
179 for value in &$v {
180 $crate::hybrid_codec::counted($out, value)?;
181 }
182 };
183 ($out:ident, $v:expr, q) => {
184 if $v.len() > 64 {
185 return Err($crate::hybrid_codec::Reject::Bounds);
186 }
187 $out.extend_from_slice(&($v.len() as u32).to_be_bytes());
188 for value in &$v {
189 $crate::hybrid_codec::counted($out, value)?;
190 }
191 };
192}
193macro_rules! record {
194 ($ty:ty, $($field:ident : $kind:ident),+ $(,)?) => {
195 impl $crate::hybrid_codec::Canonical for $ty {
196 fn write(&self, out: &mut Vec<u8>) -> Result<(), $crate::hybrid_codec::Reject> {
197 $(field!(out, self.$field, $kind);
198 if out.len() > 1024 * 1024 { return Err($crate::hybrid_codec::Reject::Bounds); })+
199 Ok(())
200 }
201 }
202 };
203}
204pub(crate) use {field, record};