1use alloc::collections::BTreeSet;
11use alloc::vec::Vec;
12use core::convert::TryInto;
13
14use crate::{
15 EndpointIdentity, EndpointSigner, Error, FederationId, ML_DSA_65_PUBLIC_KEY_BYTES,
16 ML_DSA_65_SIGNATURE_BYTES,
17};
18
19pub const FEDERATION_CONFIG_MARKER: [u8; 4] = *b"FCFG";
21pub const FEDERATION_CONFIG_VERSION: u8 = 1;
23pub const MAX_FEDERATION_MEMBERS: usize = 1_024;
25
26const IDENTITY_BYTES: usize = 32 + ML_DSA_65_PUBLIC_KEY_BYTES;
27const MEMBER_BYTES: usize = 32 + IDENTITY_BYTES;
28const FIXED_HEADER_BYTES: usize = 4 + 1 + 32 + IDENTITY_BYTES + 8 + 2;
29const SIGNATURE_BYTES: usize = 64 + ML_DSA_65_SIGNATURE_BYTES;
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
37pub struct FederationMember {
38 pub cfr_identity: [u8; 32],
40 pub endpoint: EndpointIdentity,
42}
43
44#[derive(Debug, Clone, PartialEq, Eq)]
46pub struct FederationConfiguration {
47 pub federation: FederationId,
49 pub authority: EndpointIdentity,
51 pub epoch: u64,
53 pub members: Vec<FederationMember>,
55}
56
57impl FederationConfiguration {
58 pub fn new(
60 federation: FederationId,
61 authority: EndpointIdentity,
62 epoch: u64,
63 members: Vec<FederationMember>,
64 ) -> Result<Self, Error> {
65 let configuration = Self {
66 federation,
67 authority,
68 epoch,
69 members,
70 };
71 configuration.validate()?;
72 Ok(configuration)
73 }
74
75 fn validate(&self) -> Result<(), Error> {
76 if self.members.len() > MAX_FEDERATION_MEMBERS {
77 return Err(Error::TooManyFederationMembers);
78 }
79 let mut identities = BTreeSet::new();
80 let mut endpoints = BTreeSet::new();
81 for member in &self.members {
82 if !identities.insert(member.cfr_identity) || !endpoints.insert(member.endpoint) {
83 return Err(Error::DuplicateFederationMember);
84 }
85 }
86 Ok(())
87 }
88
89 fn signed_bytes(&self) -> Result<Vec<u8>, Error> {
90 self.validate()?;
91 let member_count =
92 u16::try_from(self.members.len()).map_err(|_| Error::TooManyFederationMembers)?;
93 let mut bytes = Vec::with_capacity(FIXED_HEADER_BYTES + self.members.len() * MEMBER_BYTES);
94 bytes.extend_from_slice(&FEDERATION_CONFIG_MARKER);
95 bytes.push(FEDERATION_CONFIG_VERSION);
96 bytes.extend_from_slice(self.federation.as_bytes());
97 write_identity(&mut bytes, &self.authority);
98 bytes.extend_from_slice(&self.epoch.to_be_bytes());
99 bytes.extend_from_slice(&member_count.to_be_bytes());
100 for member in &self.members {
101 bytes.extend_from_slice(&member.cfr_identity);
102 write_identity(&mut bytes, &member.endpoint);
103 }
104 Ok(bytes)
105 }
106}
107
108#[derive(Debug, Clone, PartialEq, Eq)]
112pub struct SignedFederationConfiguration {
113 pub configuration: FederationConfiguration,
115 classical_signature: [u8; 64],
116 post_quantum_signature: [u8; ML_DSA_65_SIGNATURE_BYTES],
117}
118
119impl SignedFederationConfiguration {
120 pub fn sign<S: EndpointSigner>(
122 configuration: FederationConfiguration,
123 signer: &S,
124 ) -> Result<Self, Error> {
125 if configuration.authority != signer.endpoint() {
126 return Err(Error::WrongConfigurationAuthority);
127 }
128 let transcript = configuration.signed_bytes()?;
129 Ok(Self {
130 configuration,
131 classical_signature: signer.sign_classical(&transcript),
132 post_quantum_signature: signer.sign_post_quantum(&transcript),
133 })
134 }
135
136 pub fn verify(&self) -> Result<(), Error> {
138 let transcript = self.configuration.signed_bytes()?;
139 self.configuration
140 .authority
141 .verify_classical(&transcript, &self.classical_signature)
142 .map_err(map_configuration_classical_error)?;
143 self.configuration
144 .authority
145 .verify_post_quantum(&transcript, &self.post_quantum_signature)
146 .map_err(map_configuration_pq_error)
147 }
148
149 pub fn encode(&self) -> Result<Vec<u8>, Error> {
151 let mut wire = self.configuration.signed_bytes()?;
152 wire.extend_from_slice(&self.classical_signature);
153 wire.extend_from_slice(&self.post_quantum_signature);
154 Ok(wire)
155 }
156
157 pub fn decode_verified(wire: &[u8]) -> Result<Self, Error> {
159 let parsed = parse_wire(wire)?;
160 let authority = parsed.authority;
161 let classical_signature = wire[parsed.signature_offset..parsed.signature_offset + 64]
162 .try_into()
163 .map_err(|_| Error::Truncated)?;
164 let post_quantum_signature = wire[parsed.signature_offset + 64..]
165 .try_into()
166 .map_err(|_| Error::Truncated)?;
167 authority
168 .verify_classical(&wire[..parsed.signature_offset], &classical_signature)
169 .map_err(map_configuration_classical_error)?;
170 authority
171 .verify_post_quantum(&wire[..parsed.signature_offset], &post_quantum_signature)
172 .map_err(map_configuration_pq_error)?;
173 let members = parsed
174 .member_bytes
175 .chunks_exact(MEMBER_BYTES)
176 .map(parse_member)
177 .collect::<Result<Vec<_>, _>>()?;
178 let configuration =
179 FederationConfiguration::new(parsed.federation, authority, parsed.epoch, members)?;
180 Ok(Self {
181 configuration,
182 classical_signature,
183 post_quantum_signature,
184 })
185 }
186}
187
188fn map_configuration_classical_error(error: Error) -> Error {
189 match error {
190 Error::BadSenderKey => Error::BadConfigurationAuthority,
191 Error::BadSignature => Error::BadConfigurationSignature,
192 other => other,
193 }
194}
195
196fn map_configuration_pq_error(error: Error) -> Error {
197 match error {
198 Error::BadPostQuantumKey => Error::BadConfigurationAuthority,
199 Error::BadPostQuantumSignature | Error::BadPostQuantumSignatureEncoding => {
200 Error::BadConfigurationSignature
201 }
202 other => other,
203 }
204}
205
206fn write_identity(bytes: &mut Vec<u8>, identity: &EndpointIdentity) {
207 bytes.extend_from_slice(identity.classical.as_bytes());
208 bytes.extend_from_slice(&identity.post_quantum);
209}
210
211fn read_identity(bytes: &[u8]) -> Result<EndpointIdentity, Error> {
212 if bytes.len() != IDENTITY_BYTES {
213 return Err(Error::Truncated);
214 }
215 Ok(EndpointIdentity::new(
216 crate::EndpointKey::from_bytes(bytes[..32].try_into().map_err(|_| Error::Truncated)?),
217 bytes[32..].try_into().map_err(|_| Error::Truncated)?,
218 ))
219}
220
221fn parse_member(bytes: &[u8]) -> Result<FederationMember, Error> {
222 if bytes.len() != MEMBER_BYTES {
223 return Err(Error::Truncated);
224 }
225 Ok(FederationMember {
226 cfr_identity: bytes[..32].try_into().map_err(|_| Error::Truncated)?,
227 endpoint: read_identity(&bytes[32..])?,
228 })
229}
230
231struct ParsedConfiguration<'a> {
232 federation: FederationId,
233 authority: EndpointIdentity,
234 epoch: u64,
235 member_bytes: &'a [u8],
236 signature_offset: usize,
237}
238
239fn parse_wire(wire: &[u8]) -> Result<ParsedConfiguration<'_>, Error> {
240 if wire.len() < FIXED_HEADER_BYTES + SIGNATURE_BYTES {
241 return Err(Error::Truncated);
242 }
243 if wire[..4] != FEDERATION_CONFIG_MARKER {
244 return Err(Error::BadConfigurationMarker);
245 }
246 if wire[4] != FEDERATION_CONFIG_VERSION {
247 return Err(Error::UnsupportedConfigurationVersion);
248 }
249 let federation =
250 FederationId::from_bytes(wire[5..37].try_into().map_err(|_| Error::Truncated)?);
251 let authority = read_identity(&wire[37..37 + IDENTITY_BYTES])?;
252 let epoch_start = 37 + IDENTITY_BYTES;
253 let epoch = u64::from_be_bytes(
254 wire[epoch_start..epoch_start + 8]
255 .try_into()
256 .map_err(|_| Error::Truncated)?,
257 );
258 let count_start = epoch_start + 8;
259 let count = usize::from(u16::from_be_bytes(
260 wire[count_start..count_start + 2]
261 .try_into()
262 .map_err(|_| Error::Truncated)?,
263 ));
264 if count > MAX_FEDERATION_MEMBERS {
265 return Err(Error::TooManyFederationMembers);
266 }
267 let member_bytes_len = count
268 .checked_mul(MEMBER_BYTES)
269 .ok_or(Error::TooManyFederationMembers)?;
270 let signature_offset = FIXED_HEADER_BYTES
271 .checked_add(member_bytes_len)
272 .ok_or(Error::TooManyFederationMembers)?;
273 let expected = signature_offset
274 .checked_add(SIGNATURE_BYTES)
275 .ok_or(Error::TooManyFederationMembers)?;
276 if wire.len() < expected {
277 return Err(Error::Truncated);
278 }
279 if wire.len() > expected {
280 return Err(Error::TrailingBytes);
281 }
282 Ok(ParsedConfiguration {
283 federation,
284 authority,
285 epoch,
286 member_bytes: &wire[FIXED_HEADER_BYTES..signature_offset],
287 signature_offset,
288 })
289}