use alloc::string::ToString;
use alloc::vec::Vec;
use crate::crypto::SequentialCommit;
use crate::crypto::dsa::ecdsa_k256_keccak::PublicKey;
use crate::errors::ValidatorConfigError;
use crate::utils::serde::{
ByteReader,
ByteWriter,
Deserializable,
DeserializationError,
Serializable,
};
use crate::{Felt, WORD_SIZE, Word, ZERO};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ValidatorConfig {
keys: Vec<PublicKey>,
quorum: u16,
}
impl ValidatorConfig {
pub const MAX_VALIDATORS: usize = 5;
pub fn new(mut keys: Vec<PublicKey>, quorum: u16) -> Result<Self, ValidatorConfigError> {
if keys.is_empty() {
return Err(ValidatorConfigError::EmptySet);
}
if keys.len() > Self::MAX_VALIDATORS {
return Err(ValidatorConfigError::TooManyKeys { count: keys.len() });
}
if usize::from(quorum) != keys.len() {
return Err(ValidatorConfigError::QuorumMustEqualValidatorCount {
quorum,
count: keys.len(),
});
}
keys.sort_by_key(|key| key.to_bytes());
if keys.windows(2).any(|pair| pair[0] == pair[1]) {
return Err(ValidatorConfigError::DuplicateKey);
}
Ok(Self { keys, quorum })
}
pub fn keys(&self) -> &[PublicKey] {
&self.keys
}
pub fn len(&self) -> usize {
self.keys.len()
}
pub fn is_empty(&self) -> bool {
false
}
pub fn quorum(&self) -> u16 {
self.quorum
}
pub fn to_commitment(&self) -> Word {
<Self as SequentialCommit>::to_commitment(self)
}
pub fn to_elements(&self) -> Vec<Felt> {
<Self as SequentialCommit>::to_elements(self)
}
}
impl SequentialCommit for ValidatorConfig {
type Commitment = Word;
fn to_elements(&self) -> Vec<Felt> {
let mut elements: Vec<Felt> = Vec::with_capacity((self.keys.len() + 1) * WORD_SIZE);
elements.extend([Felt::from(self.quorum), ZERO, ZERO, ZERO]);
for key in &self.keys {
elements.extend_from_slice(key.to_commitment().as_elements());
}
elements
}
}
impl Serializable for ValidatorConfig {
fn write_into<W: ByteWriter>(&self, target: &mut W) {
let Self { keys, quorum } = self;
let num_keys =
u8::try_from(keys.len()).expect("constructor should validate num keys fits in u8");
quorum.write_into(target);
num_keys.write_into(target);
target.write_many(keys);
}
}
impl Deserializable for ValidatorConfig {
fn read_from<R: ByteReader>(source: &mut R) -> Result<Self, DeserializationError> {
let quorum = u16::read_from(source)?;
let num_keys: u8 = source.read()?;
let keys = source
.read_many_iter(num_keys as usize)?
.collect::<Result<Vec<PublicKey>, _>>()?;
Self::new(keys, quorum).map_err(|err| DeserializationError::InvalidValue(err.to_string()))
}
}
#[cfg(test)]
mod tests {
use assert_matches::assert_matches;
use super::*;
use crate::testing::random_secret_key::random_secret_key;
fn random_keys(count: usize) -> Vec<PublicKey> {
(0..count).map(|_| random_secret_key().public_key()).collect()
}
#[test]
fn new_rejects_empty_set() {
let result = ValidatorConfig::new(Vec::new(), 1);
assert_matches!(result, Err(ValidatorConfigError::EmptySet));
}
#[test]
fn new_accepts_single_validator() -> anyhow::Result<()> {
let config = ValidatorConfig::new(random_keys(1), 1)?;
assert_eq!(config.len(), 1);
assert_eq!(config.quorum(), 1);
Ok(())
}
#[test]
fn new_accepts_max_validators() -> anyhow::Result<()> {
let max_validators = ValidatorConfig::MAX_VALIDATORS;
let config = ValidatorConfig::new(random_keys(max_validators), max_validators as u16)?;
assert_eq!(config.len(), max_validators);
Ok(())
}
#[test]
fn new_rejects_too_many_keys() {
let result = ValidatorConfig::new(random_keys(ValidatorConfig::MAX_VALIDATORS + 1), 1);
assert_matches!(
result,
Err(ValidatorConfigError::TooManyKeys { count }) if count == ValidatorConfig::MAX_VALIDATORS + 1
);
}
#[test]
fn new_rejects_duplicate_keys() {
let mut keys = random_keys(3);
keys[1] = keys[0].clone();
let result = ValidatorConfig::new(keys, 3);
assert_matches!(result, Err(ValidatorConfigError::DuplicateKey));
}
#[rstest::rstest]
#[case::zero_quorum(0)]
#[case::quorum_below_validator_count(2)]
#[case::quorum_above_validator_count(4)]
fn new_rejects_quorum_other_than_validator_count(#[case] quorum: u16) {
let result = ValidatorConfig::new(random_keys(3), quorum);
assert_matches!(
result,
Err(ValidatorConfigError::QuorumMustEqualValidatorCount { quorum: actual, count: 3 })
if actual == quorum
);
}
#[test]
fn new_sorts_into_canonical_order() -> anyhow::Result<()> {
let keys = random_keys(5);
let forward = ValidatorConfig::new(keys.clone(), 5)?;
let mut reversed = keys;
reversed.reverse();
let backward = ValidatorConfig::new(reversed, 5)?;
assert_eq!(forward.keys(), backward.keys());
assert_eq!(forward.to_commitment(), backward.to_commitment());
Ok(())
}
#[test]
fn commitment_binds_the_quorum() -> anyhow::Result<()> {
let config = ValidatorConfig::new(random_keys(3), 3)?;
assert_eq!(config.to_elements()[0], Felt::from(config.quorum()));
Ok(())
}
#[test]
fn serde_round_trip() -> anyhow::Result<()> {
let config = ValidatorConfig::new(random_keys(4), 4)?;
let deserialized = ValidatorConfig::read_from_bytes(&config.to_bytes())?;
assert_eq!(config, deserialized);
Ok(())
}
}