use alloc::collections::BTreeSet;
use alloc::vec::Vec;
use core::convert::TryInto;
use crate::{
EndpointIdentity, EndpointSigner, Error, FederationId, ML_DSA_65_PUBLIC_KEY_BYTES,
ML_DSA_65_SIGNATURE_BYTES,
};
pub const FEDERATION_CONFIG_MARKER: [u8; 4] = *b"FCFG";
pub const FEDERATION_CONFIG_VERSION: u8 = 1;
pub const MAX_FEDERATION_MEMBERS: usize = 1_024;
const IDENTITY_BYTES: usize = 32 + ML_DSA_65_PUBLIC_KEY_BYTES;
const MEMBER_BYTES: usize = 32 + IDENTITY_BYTES;
const FIXED_HEADER_BYTES: usize = 4 + 1 + 32 + IDENTITY_BYTES + 8 + 2;
const SIGNATURE_BYTES: usize = 64 + ML_DSA_65_SIGNATURE_BYTES;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct FederationMember {
pub cfr_identity: [u8; 32],
pub endpoint: EndpointIdentity,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct FederationConfiguration {
pub federation: FederationId,
pub authority: EndpointIdentity,
pub epoch: u64,
pub members: Vec<FederationMember>,
}
impl FederationConfiguration {
pub fn new(
federation: FederationId,
authority: EndpointIdentity,
epoch: u64,
members: Vec<FederationMember>,
) -> Result<Self, Error> {
let configuration = Self {
federation,
authority,
epoch,
members,
};
configuration.validate()?;
Ok(configuration)
}
fn validate(&self) -> Result<(), Error> {
if self.members.len() > MAX_FEDERATION_MEMBERS {
return Err(Error::TooManyFederationMembers);
}
let mut identities = BTreeSet::new();
let mut endpoints = BTreeSet::new();
for member in &self.members {
if !identities.insert(member.cfr_identity) || !endpoints.insert(member.endpoint) {
return Err(Error::DuplicateFederationMember);
}
}
Ok(())
}
fn signed_bytes(&self) -> Result<Vec<u8>, Error> {
self.validate()?;
let member_count =
u16::try_from(self.members.len()).map_err(|_| Error::TooManyFederationMembers)?;
let mut bytes = Vec::with_capacity(FIXED_HEADER_BYTES + self.members.len() * MEMBER_BYTES);
bytes.extend_from_slice(&FEDERATION_CONFIG_MARKER);
bytes.push(FEDERATION_CONFIG_VERSION);
bytes.extend_from_slice(self.federation.as_bytes());
write_identity(&mut bytes, &self.authority);
bytes.extend_from_slice(&self.epoch.to_be_bytes());
bytes.extend_from_slice(&member_count.to_be_bytes());
for member in &self.members {
bytes.extend_from_slice(&member.cfr_identity);
write_identity(&mut bytes, &member.endpoint);
}
Ok(bytes)
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SignedFederationConfiguration {
pub configuration: FederationConfiguration,
classical_signature: [u8; 64],
post_quantum_signature: [u8; ML_DSA_65_SIGNATURE_BYTES],
}
impl SignedFederationConfiguration {
pub fn sign<S: EndpointSigner>(
configuration: FederationConfiguration,
signer: &S,
) -> Result<Self, Error> {
if configuration.authority != signer.endpoint() {
return Err(Error::WrongConfigurationAuthority);
}
let transcript = configuration.signed_bytes()?;
Ok(Self {
configuration,
classical_signature: signer.sign_classical(&transcript),
post_quantum_signature: signer.sign_post_quantum(&transcript),
})
}
pub fn verify(&self) -> Result<(), Error> {
let transcript = self.configuration.signed_bytes()?;
self.configuration
.authority
.verify_classical(&transcript, &self.classical_signature)
.map_err(map_configuration_classical_error)?;
self.configuration
.authority
.verify_post_quantum(&transcript, &self.post_quantum_signature)
.map_err(map_configuration_pq_error)
}
pub fn encode(&self) -> Result<Vec<u8>, Error> {
let mut wire = self.configuration.signed_bytes()?;
wire.extend_from_slice(&self.classical_signature);
wire.extend_from_slice(&self.post_quantum_signature);
Ok(wire)
}
pub fn decode_verified(wire: &[u8]) -> Result<Self, Error> {
let parsed = parse_wire(wire)?;
let authority = parsed.authority;
let classical_signature = wire[parsed.signature_offset..parsed.signature_offset + 64]
.try_into()
.map_err(|_| Error::Truncated)?;
let post_quantum_signature = wire[parsed.signature_offset + 64..]
.try_into()
.map_err(|_| Error::Truncated)?;
authority
.verify_classical(&wire[..parsed.signature_offset], &classical_signature)
.map_err(map_configuration_classical_error)?;
authority
.verify_post_quantum(&wire[..parsed.signature_offset], &post_quantum_signature)
.map_err(map_configuration_pq_error)?;
let members = parsed
.member_bytes
.chunks_exact(MEMBER_BYTES)
.map(parse_member)
.collect::<Result<Vec<_>, _>>()?;
let configuration =
FederationConfiguration::new(parsed.federation, authority, parsed.epoch, members)?;
Ok(Self {
configuration,
classical_signature,
post_quantum_signature,
})
}
}
fn map_configuration_classical_error(error: Error) -> Error {
match error {
Error::BadSenderKey => Error::BadConfigurationAuthority,
Error::BadSignature => Error::BadConfigurationSignature,
other => other,
}
}
fn map_configuration_pq_error(error: Error) -> Error {
match error {
Error::BadPostQuantumKey => Error::BadConfigurationAuthority,
Error::BadPostQuantumSignature | Error::BadPostQuantumSignatureEncoding => {
Error::BadConfigurationSignature
}
other => other,
}
}
fn write_identity(bytes: &mut Vec<u8>, identity: &EndpointIdentity) {
bytes.extend_from_slice(identity.classical.as_bytes());
bytes.extend_from_slice(&identity.post_quantum);
}
fn read_identity(bytes: &[u8]) -> Result<EndpointIdentity, Error> {
if bytes.len() != IDENTITY_BYTES {
return Err(Error::Truncated);
}
Ok(EndpointIdentity::new(
crate::EndpointKey::from_bytes(bytes[..32].try_into().map_err(|_| Error::Truncated)?),
bytes[32..].try_into().map_err(|_| Error::Truncated)?,
))
}
fn parse_member(bytes: &[u8]) -> Result<FederationMember, Error> {
if bytes.len() != MEMBER_BYTES {
return Err(Error::Truncated);
}
Ok(FederationMember {
cfr_identity: bytes[..32].try_into().map_err(|_| Error::Truncated)?,
endpoint: read_identity(&bytes[32..])?,
})
}
struct ParsedConfiguration<'a> {
federation: FederationId,
authority: EndpointIdentity,
epoch: u64,
member_bytes: &'a [u8],
signature_offset: usize,
}
fn parse_wire(wire: &[u8]) -> Result<ParsedConfiguration<'_>, Error> {
if wire.len() < FIXED_HEADER_BYTES + SIGNATURE_BYTES {
return Err(Error::Truncated);
}
if wire[..4] != FEDERATION_CONFIG_MARKER {
return Err(Error::BadConfigurationMarker);
}
if wire[4] != FEDERATION_CONFIG_VERSION {
return Err(Error::UnsupportedConfigurationVersion);
}
let federation =
FederationId::from_bytes(wire[5..37].try_into().map_err(|_| Error::Truncated)?);
let authority = read_identity(&wire[37..37 + IDENTITY_BYTES])?;
let epoch_start = 37 + IDENTITY_BYTES;
let epoch = u64::from_be_bytes(
wire[epoch_start..epoch_start + 8]
.try_into()
.map_err(|_| Error::Truncated)?,
);
let count_start = epoch_start + 8;
let count = usize::from(u16::from_be_bytes(
wire[count_start..count_start + 2]
.try_into()
.map_err(|_| Error::Truncated)?,
));
if count > MAX_FEDERATION_MEMBERS {
return Err(Error::TooManyFederationMembers);
}
let member_bytes_len = count
.checked_mul(MEMBER_BYTES)
.ok_or(Error::TooManyFederationMembers)?;
let signature_offset = FIXED_HEADER_BYTES
.checked_add(member_bytes_len)
.ok_or(Error::TooManyFederationMembers)?;
let expected = signature_offset
.checked_add(SIGNATURE_BYTES)
.ok_or(Error::TooManyFederationMembers)?;
if wire.len() < expected {
return Err(Error::Truncated);
}
if wire.len() > expected {
return Err(Error::TrailingBytes);
}
Ok(ParsedConfiguration {
federation,
authority,
epoch,
member_bytes: &wire[FIXED_HEADER_BYTES..signature_offset],
signature_offset,
})
}