#[cfg(feature = "mocks")]
pub mod mocks;
#[cfg(feature = "std")]
use super::Batch;
use super::{PrivateKey, PublicKey, Signature as Ed25519Signature};
#[cfg(feature = "std")]
use crate::{certificate::Verification, BatchVerifier};
use crate::{
certificate::{Attestation, Namespace, Scheme, Signers, Subject},
Digest, Signer as _, Verifier as _,
};
#[cfg(not(feature = "std"))]
use alloc::vec::Vec;
use bytes::{Buf, BufMut};
use commonware_codec::{types::lazy::Lazy, EncodeSize, Error, Read, ReadRangeExt, Write};
use commonware_utils::{
ordered::{Quorum, Set},
Faults, Participant,
};
use rand_core::CryptoRngCore;
#[cfg(feature = "std")]
use std::collections::BTreeSet;
#[derive(Clone, Debug)]
pub struct Generic<N: Namespace> {
pub participants: Set<PublicKey>,
pub signer: Option<(Participant, PrivateKey)>,
pub namespace: N,
}
impl<N: Namespace> Generic<N> {
pub fn signer(
namespace: &[u8],
participants: Set<PublicKey>,
private_key: PrivateKey,
) -> Option<Self> {
let signer = participants
.index(&private_key.public_key())
.map(|index| (index, private_key))?;
Some(Self {
participants,
signer: Some(signer),
namespace: N::derive(namespace),
})
}
pub fn verifier(namespace: &[u8], participants: Set<PublicKey>) -> Self {
Self {
participants,
signer: None,
namespace: N::derive(namespace),
}
}
pub fn me(&self) -> Option<Participant> {
self.signer.as_ref().map(|(index, _)| *index)
}
pub fn sign<'a, S, D>(&self, subject: S::Subject<'a, D>) -> Option<Attestation<S>>
where
S: Scheme<Signature = Ed25519Signature>,
S::Subject<'a, D>: Subject<Namespace = N>,
D: Digest,
{
let (index, private_key) = self.signer.as_ref()?;
let signature = private_key.sign(subject.namespace(&self.namespace), &subject.message());
Some(Attestation {
signer: *index,
signature: signature.into(),
})
}
pub fn verify_attestation<'a, S, D>(
&self,
subject: S::Subject<'a, D>,
attestation: &Attestation<S>,
) -> bool
where
S: Scheme<Signature = Ed25519Signature>,
S::Subject<'a, D>: Subject<Namespace = N>,
D: Digest,
{
let Some(public_key) = self.participants.key(attestation.signer) else {
return false;
};
let Some(signature) = attestation.signature.get() else {
return false;
};
public_key.verify(
subject.namespace(&self.namespace),
&subject.message(),
signature,
)
}
#[cfg(feature = "std")]
pub fn verify_attestations<'a, S, R, D, I>(
&self,
rng: &mut R,
subject: S::Subject<'a, D>,
attestations: I,
) -> Verification<S>
where
S: Scheme<Signature = Ed25519Signature>,
S::Subject<'a, D>: Subject<Namespace = N>,
R: CryptoRngCore,
D: Digest,
I: IntoIterator<Item = Attestation<S>>,
{
let namespace = subject.namespace(&self.namespace);
let message = subject.message();
let mut invalid = BTreeSet::new();
let mut candidates = Vec::new();
let mut batch = Batch::new();
for attestation in attestations.into_iter() {
let Some(public_key) = self.participants.key(attestation.signer) else {
invalid.insert(attestation.signer);
continue;
};
let Some(signature) = attestation.signature.get() else {
invalid.insert(attestation.signer);
continue;
};
batch.add(namespace, &message, public_key, signature);
candidates.push((attestation, public_key));
}
if !candidates.is_empty() && !batch.verify(rng) {
for (attestation, public_key) in &candidates {
let Some(signature) = attestation.signature.get() else {
invalid.insert(attestation.signer);
continue;
};
if !public_key.verify(namespace, &message, signature) {
invalid.insert(attestation.signer);
}
}
}
let verified = candidates
.into_iter()
.filter_map(|(attestation, _)| {
if invalid.contains(&attestation.signer) {
None
} else {
Some(attestation)
}
})
.collect();
Verification::new(verified, invalid.into_iter().collect())
}
#[cfg(not(feature = "std"))]
pub fn verify_attestations<'a, S, R, D, I>(
&self,
_rng: &mut R,
subject: S::Subject<'a, D>,
attestations: I,
) -> crate::certificate::Verification<S>
where
S: Scheme<Signature = Ed25519Signature>,
S::Subject<'a, D>: Subject<Namespace = N>,
R: CryptoRngCore,
D: Digest,
I: IntoIterator<Item = Attestation<S>>,
{
let namespace = subject.namespace(&self.namespace);
let message = subject.message();
let mut invalid = alloc::collections::BTreeSet::new();
let mut verified = Vec::new();
for attestation in attestations.into_iter() {
let Some(public_key) = self.participants.key(attestation.signer) else {
invalid.insert(attestation.signer);
continue;
};
let Some(signature) = attestation.signature.get() else {
invalid.insert(attestation.signer);
continue;
};
if public_key.verify(namespace, &message, signature) {
verified.push(attestation);
} else {
invalid.insert(attestation.signer);
}
}
crate::certificate::Verification::new(verified, invalid.into_iter().collect())
}
pub fn assemble<S, I, M>(&self, attestations: I) -> Option<Certificate>
where
S: Scheme<Signature = Ed25519Signature>,
I: IntoIterator<Item = Attestation<S>>,
M: Faults,
{
let mut entries = Vec::new();
for Attestation { signer, signature } in attestations {
if usize::from(signer) >= self.participants.len() {
return None;
}
let signature = signature.get().cloned()?;
entries.push((signer, signature));
}
if entries.len() < self.participants.quorum::<M>() as usize {
return None;
}
entries.sort_by_key(|(signer, _)| *signer);
let (signer, signatures): (Vec<Participant>, Vec<_>) = entries.into_iter().unzip();
let signers = Signers::from(self.participants.len(), signer);
let signatures = signatures.into_iter().map(Lazy::from).collect();
Some(Certificate {
signers,
signatures,
})
}
#[cfg(feature = "std")]
fn batch_verify_certificate<'a, S, D, M>(
&self,
batch: &mut Batch,
subject: S::Subject<'a, D>,
certificate: &Certificate,
) -> bool
where
S: Scheme,
S::Subject<'a, D>: Subject<Namespace = N>,
D: Digest,
M: Faults,
{
if certificate.signers.len() != self.participants.len() {
return false;
}
if certificate.signers.count() != certificate.signatures.len() {
return false;
}
if certificate.signers.count() < self.participants.quorum::<M>() as usize {
return false;
}
let namespace = subject.namespace(&self.namespace);
let message = subject.message();
for (signer, signature) in certificate.signers.iter().zip(&certificate.signatures) {
let Some(public_key) = self.participants.key(signer) else {
return false;
};
let Some(signature) = signature.get() else {
return false;
};
batch.add(namespace, &message, public_key, signature);
}
true
}
#[cfg(feature = "std")]
pub fn verify_certificate<'a, S, R, D, M>(
&self,
rng: &mut R,
subject: S::Subject<'a, D>,
certificate: &Certificate,
) -> bool
where
S: Scheme,
S::Subject<'a, D>: Subject<Namespace = N>,
R: CryptoRngCore,
D: Digest,
M: Faults,
{
let mut batch = Batch::new();
if !self.batch_verify_certificate::<S, D, M>(&mut batch, subject, certificate) {
return false;
}
batch.verify(rng)
}
#[cfg(not(feature = "std"))]
pub fn verify_certificate<'a, S, R, D, M>(
&self,
_rng: &mut R,
subject: S::Subject<'a, D>,
certificate: &Certificate,
) -> bool
where
S: Scheme,
S::Subject<'a, D>: Subject<Namespace = N>,
R: CryptoRngCore,
D: Digest,
M: Faults,
{
if certificate.signers.len() != self.participants.len() {
return false;
}
if certificate.signers.count() != certificate.signatures.len() {
return false;
}
if certificate.signers.count() < self.participants.quorum::<M>() as usize {
return false;
}
let namespace = subject.namespace(&self.namespace);
let message = subject.message();
for (signer, signature) in certificate.signers.iter().zip(&certificate.signatures) {
let Some(public_key) = self.participants.key(signer) else {
return false;
};
let Some(signature) = signature.get() else {
return false;
};
if !public_key.verify(namespace, &message, signature) {
return false;
}
}
true
}
#[cfg(feature = "std")]
pub fn verify_certificates<'a, S, R, D, I, M>(&self, rng: &mut R, certificates: I) -> bool
where
S: Scheme,
S::Subject<'a, D>: Subject<Namespace = N>,
R: CryptoRngCore,
D: Digest,
I: Iterator<Item = (S::Subject<'a, D>, &'a Certificate)>,
M: Faults,
{
let mut batch = Batch::new();
for (subject, certificate) in certificates {
if !self.batch_verify_certificate::<S, D, M>(&mut batch, subject, certificate) {
return false;
}
}
batch.verify(rng)
}
#[cfg(not(feature = "std"))]
pub fn verify_certificates<'a, S, R, D, I, M>(&self, rng: &mut R, certificates: I) -> bool
where
S: Scheme,
S::Subject<'a, D>: Subject<Namespace = N>,
R: CryptoRngCore,
D: Digest,
I: Iterator<Item = (S::Subject<'a, D>, &'a Certificate)>,
M: Faults,
{
for (subject, certificate) in certificates {
if !self.verify_certificate::<S, _, D, M>(rng, subject, certificate) {
return false;
}
}
true
}
pub const fn is_attributable() -> bool {
true
}
pub const fn is_batchable() -> bool {
cfg!(feature = "std")
}
pub const fn certificate_codec_config(&self) -> <Certificate as commonware_codec::Read>::Cfg {
self.participants.len()
}
pub const fn certificate_codec_config_unbounded() -> <Certificate as commonware_codec::Read>::Cfg
{
u32::MAX as usize
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Certificate {
pub signers: Signers,
pub signatures: Vec<Lazy<Ed25519Signature>>,
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Certificate {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let signers = Signers::arbitrary(u)?;
let signatures = (0..signers.count())
.map(|_| u.arbitrary::<Ed25519Signature>().map(Lazy::from))
.collect::<arbitrary::Result<Vec<_>>>()?;
Ok(Self {
signers,
signatures,
})
}
}
impl Write for Certificate {
fn write(&self, writer: &mut impl BufMut) {
self.signers.write(writer);
self.signatures.write(writer);
}
}
impl EncodeSize for Certificate {
fn encode_size(&self) -> usize {
self.signers.encode_size() + self.signatures.encode_size()
}
}
impl Read for Certificate {
type Cfg = usize;
fn read_cfg(reader: &mut impl Buf, participants: &usize) -> Result<Self, Error> {
let signers = Signers::read_cfg(reader, participants)?;
if signers.count() == 0 {
return Err(Error::Invalid(
"cryptography::ed25519::certificate::Certificate",
"Certificate contains no signers",
));
}
let signatures = Vec::<Lazy<Ed25519Signature>>::read_range(reader, ..=*participants)?;
if signers.count() != signatures.len() {
return Err(Error::Invalid(
"cryptography::ed25519::certificate::Certificate",
"Signers and signatures counts differ",
));
}
Ok(Self {
signers,
signatures,
})
}
}
#[macro_export]
macro_rules! impl_certificate_ed25519 {
($subject:ty, $namespace:ty) => {
#[cfg(feature = "mocks")]
#[allow(dead_code)]
pub fn fixture<R>(
rng: &mut R,
namespace: &[u8],
n: u32,
) -> $crate::certificate::mocks::Fixture<Scheme>
where
R: rand::RngCore + rand::CryptoRng,
{
$crate::ed25519::certificate::mocks::fixture(
rng,
namespace,
n,
Scheme::signer,
Scheme::verifier,
)
}
#[derive(Clone, Debug)]
pub struct Scheme {
generic: $crate::ed25519::certificate::Generic<$namespace>,
}
impl Scheme {
pub fn signer(
namespace: &[u8],
participants: commonware_utils::ordered::Set<$crate::ed25519::PublicKey>,
private_key: $crate::ed25519::PrivateKey,
) -> Option<Self> {
Some(Self {
generic: $crate::ed25519::certificate::Generic::signer(
namespace,
participants,
private_key,
)?,
})
}
pub fn verifier(
namespace: &[u8],
participants: commonware_utils::ordered::Set<$crate::ed25519::PublicKey>,
) -> Self {
Self {
generic: $crate::ed25519::certificate::Generic::verifier(
namespace,
participants,
),
}
}
}
impl $crate::certificate::Scheme for Scheme {
type Subject<'a, D: $crate::Digest> = $subject;
type PublicKey = $crate::ed25519::PublicKey;
type Signature = $crate::ed25519::Signature;
type Certificate = $crate::ed25519::certificate::Certificate;
fn me(&self) -> Option<commonware_utils::Participant> {
self.generic.me()
}
fn participants(&self) -> &commonware_utils::ordered::Set<Self::PublicKey> {
&self.generic.participants
}
fn sign<D: $crate::Digest>(
&self,
subject: Self::Subject<'_, D>,
) -> Option<$crate::certificate::Attestation<Self>> {
self.generic.sign::<_, D>(subject)
}
fn verify_attestation<R, D>(
&self,
_rng: &mut R,
subject: Self::Subject<'_, D>,
attestation: &$crate::certificate::Attestation<Self>,
_strategy: &impl commonware_parallel::Strategy,
) -> bool
where
R: rand_core::CryptoRngCore,
D: $crate::Digest,
{
self.generic
.verify_attestation::<_, D>(subject, attestation)
}
fn verify_attestations<R, D, I>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
attestations: I,
_strategy: &impl commonware_parallel::Strategy,
) -> $crate::certificate::Verification<Self>
where
R: rand_core::CryptoRngCore,
D: $crate::Digest,
I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
{
self.generic
.verify_attestations::<_, _, D, _>(rng, subject, attestations)
}
fn assemble<I, M>(
&self,
attestations: I,
_strategy: &impl commonware_parallel::Strategy,
) -> Option<Self::Certificate>
where
I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
M: commonware_utils::Faults,
{
self.generic.assemble::<Self, _, M>(attestations)
}
fn verify_certificate<R, D, M>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
certificate: &Self::Certificate,
_strategy: &impl commonware_parallel::Strategy,
) -> bool
where
R: rand_core::CryptoRngCore,
D: $crate::Digest,
M: commonware_utils::Faults,
{
self.generic
.verify_certificate::<Self, _, D, M>(rng, subject, certificate)
}
fn verify_certificates<'a, R, D, I, M>(
&self,
rng: &mut R,
certificates: I,
_strategy: &impl commonware_parallel::Strategy,
) -> bool
where
R: rand::Rng + rand::CryptoRng,
D: $crate::Digest,
I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
M: commonware_utils::Faults,
{
self.generic
.verify_certificates::<Self, _, D, _, M>(rng, certificates)
}
fn is_attributable() -> bool {
$crate::ed25519::certificate::Generic::<$namespace>::is_attributable()
}
fn is_batchable() -> bool {
$crate::ed25519::certificate::Generic::<$namespace>::is_batchable()
}
fn certificate_codec_config(
&self,
) -> <Self::Certificate as commonware_codec::Read>::Cfg {
self.generic.certificate_codec_config()
}
fn certificate_codec_config_unbounded(
) -> <Self::Certificate as commonware_codec::Read>::Cfg {
$crate::ed25519::certificate::Generic::<$namespace>::certificate_codec_config_unbounded()
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{certificate::Scheme as _, sha256::Digest as Sha256Digest};
use bytes::Bytes;
use commonware_codec::{Decode, Encode};
use commonware_math::algebra::Random;
use commonware_parallel::Sequential;
use commonware_utils::{ordered::Set, test_rng, Faults, N3f1, Participant, TryCollect};
const NAMESPACE: &[u8] = b"test-ed25519";
const MESSAGE: &[u8] = b"test message";
#[derive(Clone, Debug)]
pub struct TestSubject {
pub message: Bytes,
}
impl Subject for TestSubject {
type Namespace = Vec<u8>;
fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
derived.as_ref()
}
fn message(&self) -> Bytes {
self.message.clone()
}
}
impl_certificate_ed25519!(TestSubject, Vec<u8>);
fn setup_signers(rng: &mut impl CryptoRngCore, n: u32) -> (Vec<Scheme>, Scheme) {
let private_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut *rng)).collect();
let participants: Set<PublicKey> = private_keys
.iter()
.map(|sk| sk.public_key())
.try_collect()
.unwrap();
let signers = private_keys
.into_iter()
.map(|sk| Scheme::signer(NAMESPACE, participants.clone(), sk).unwrap())
.collect();
let verifier = Scheme::verifier(NAMESPACE, participants);
(signers, verifier)
}
#[test]
fn test_is_attributable() {
assert!(Generic::<Vec<u8>>::is_attributable());
assert!(Scheme::is_attributable());
}
#[test]
#[cfg(feature = "std")]
fn test_is_batchable() {
assert!(Generic::<Vec<u8>>::is_batchable());
assert!(Scheme::is_batchable());
}
#[test]
#[cfg(not(feature = "std"))]
fn test_is_not_batchable() {
assert!(!Generic::is_batchable());
assert!(!Scheme::is_batchable());
}
#[test]
fn test_sign_vote_roundtrip() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let scheme = &schemes[0];
let attestation = scheme
.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap();
assert!(scheme.verify_attestation::<_, Sha256Digest>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&attestation,
&Sequential,
));
}
#[test]
fn test_verifier_cannot_sign() {
let mut rng = test_rng();
let (_, verifier) = setup_signers(&mut rng, 4);
assert!(verifier
.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE)
})
.is_none());
}
#[test]
fn test_verify_attestations_filters_invalid() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 5);
let quorum = N3f1::quorum(schemes.len()) as usize;
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
attestations.clone(),
&Sequential,
);
assert!(result.invalid.is_empty());
assert_eq!(result.verified.len(), quorum);
let mut attestations_corrupted = attestations.clone();
attestations_corrupted[0].signer = Participant::new(999);
let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
attestations_corrupted,
&Sequential,
);
assert_eq!(result.invalid, vec![Participant::new(999)]);
assert_eq!(result.verified.len(), quorum - 1);
let mut attestations_corrupted = attestations;
attestations_corrupted[0].signature = attestations_corrupted[1].signature.clone();
let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
attestations_corrupted,
&Sequential,
);
assert_eq!(result.invalid.len(), 1);
assert_eq!(result.verified.len(), quorum - 1);
}
#[test]
fn test_assemble_certificate() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let quorum = N3f1::quorum(schemes.len()) as usize;
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
assert_eq!(certificate.signers.count(), quorum);
assert_eq!(certificate.signatures.len(), quorum);
}
#[test]
fn test_assemble_certificate_sorts_signers() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let mut indexed: Vec<_> = (0..3).map(|i| (schemes[i].me().unwrap(), i)).collect();
indexed.sort_by_key(|(idx, _)| *idx);
let attestations = vec![
schemes[indexed[2].1]
.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap(),
schemes[indexed[1].1]
.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap(),
schemes[indexed[0].1]
.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap(),
];
let certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
let expected: Vec<_> = indexed.iter().map(|(idx, _)| *idx).collect();
assert_eq!(certificate.signers.iter().collect::<Vec<_>>(), expected);
}
#[test]
fn test_verify_certificate() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let quorum = N3f1::quorum(schemes.len()) as usize;
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE)
},
&certificate,
&Sequential,
));
}
#[test]
fn test_verify_certificate_detects_corruption() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let quorum = N3f1::quorum(schemes.len()) as usize;
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&certificate,
&Sequential,
));
let mut corrupted = certificate;
corrupted.signatures[0] = corrupted.signatures[1].clone();
assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&corrupted,
&Sequential,
));
}
#[test]
fn test_certificate_codec_roundtrip() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let quorum = N3f1::quorum(schemes.len()) as usize;
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
let encoded = certificate.encode();
let decoded = Certificate::decode_cfg(encoded, &schemes.len()).expect("decode certificate");
assert_eq!(decoded, certificate);
}
#[test]
fn test_certificate_rejects_sub_quorum() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let sub_quorum = 2;
let attestations: Vec<_> = schemes
.iter()
.take(sub_quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
assert!(schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.is_none());
}
#[test]
fn test_certificate_rejects_invalid_signer() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let quorum = N3f1::quorum(schemes.len()) as usize;
let mut attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
attestations[0].signer = Participant::new(999);
assert!(schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.is_none());
}
#[test]
fn test_verify_certificate_rejects_sub_quorum() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let participants_len = schemes.len();
let attestations: Vec<_> = schemes
.iter()
.take(3)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let mut certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
let mut signers: Vec<Participant> = certificate.signers.iter().collect();
signers.pop();
certificate.signers = Signers::from(participants_len, signers);
certificate.signatures.pop();
assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&certificate,
&Sequential,
));
}
#[test]
fn test_verify_certificate_rejects_mismatched_signature_count() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let attestations: Vec<_> = schemes
.iter()
.take(3)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let mut certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
certificate.signatures.pop();
assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&certificate,
&Sequential,
));
}
#[test]
fn test_verify_certificates_batch() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let quorum = N3f1::quorum(schemes.len()) as usize;
let messages: Vec<Bytes> = [b"msg1".as_slice(), b"msg2".as_slice(), b"msg3".as_slice()]
.into_iter()
.map(Bytes::copy_from_slice)
.collect();
let mut certificates = Vec::new();
for msg in &messages {
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: msg.clone(),
})
.unwrap()
})
.collect();
certificates.push(
schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap(),
);
}
let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
(
TestSubject {
message: msg.clone(),
},
cert,
)
});
assert!(verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
&mut rng,
certs_iter,
&Sequential
));
}
#[test]
fn test_verify_certificates_batch_detects_failure() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let quorum = N3f1::quorum(schemes.len()) as usize;
let messages: Vec<Bytes> = [b"msg1".as_slice(), b"msg2".as_slice()]
.into_iter()
.map(Bytes::copy_from_slice)
.collect();
let mut certificates = Vec::new();
for msg in &messages {
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: msg.clone(),
})
.unwrap()
})
.collect();
certificates.push(
schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap(),
);
}
certificates[1].signatures[0] = certificates[1].signatures[1].clone();
let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
(
TestSubject {
message: msg.clone(),
},
cert,
)
});
assert!(!verifier.verify_certificates::<_, Sha256Digest, _, N3f1>(
&mut rng,
certs_iter,
&Sequential
));
}
#[test]
#[should_panic(expected = "duplicate signer index")]
fn test_assemble_certificate_rejects_duplicate_signers() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let mut attestations: Vec<_> = schemes
.iter()
.take(3)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
attestations.push(attestations.last().unwrap().clone());
schemes[0].assemble::<_, N3f1>(attestations, &Sequential);
}
#[test]
fn test_scheme_clone_and_verifier() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let participants = schemes[0].participants().clone();
let signer = schemes[0].clone();
assert!(
signer
.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.is_some(),
"signer should produce votes"
);
let verifier = Scheme::verifier(NAMESPACE, participants);
assert!(
verifier
.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.is_none(),
"verifier should not produce votes"
);
}
#[test]
fn test_certificate_decode_validation() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let participants_len = schemes.len();
let attestations: Vec<_> = schemes
.iter()
.take(3)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
let encoded = certificate.encode();
let decoded =
Certificate::decode_cfg(encoded, &participants_len).expect("decode certificate");
assert_eq!(decoded, certificate);
let empty = Certificate {
signers: Signers::from(participants_len, std::iter::empty::<Participant>()),
signatures: Vec::new(),
};
assert!(Certificate::decode_cfg(empty.encode(), &participants_len).is_err());
let mismatched = Certificate {
signers: Signers::from(participants_len, [0u32, 1].map(Participant::new)),
signatures: vec![certificate.signatures[0].clone()],
};
assert!(Certificate::decode_cfg(mismatched.encode(), &participants_len).is_err());
let mut signers = certificate.signers.iter().collect::<Vec<_>>();
signers.push(Participant::from_usize(participants_len));
let mut sigs = certificate.signatures.clone();
sigs.push(certificate.signatures[0].clone());
let extended = Certificate {
signers: Signers::from(participants_len + 1, signers),
signatures: sigs,
};
assert!(Certificate::decode_cfg(extended.encode(), &participants_len).is_err());
}
#[test]
fn test_verify_certificate_rejects_unknown_signer() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let participants_len = schemes.len();
let attestations: Vec<_> = schemes
.iter()
.take(3)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let mut certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
let mut signers: Vec<Participant> = certificate.signers.iter().collect();
signers.push(Participant::from_usize(participants_len));
certificate.signers = Signers::from(participants_len + 1, signers);
certificate
.signatures
.push(certificate.signatures[0].clone());
assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&certificate,
&Sequential,
));
}
#[test]
fn test_verify_certificate_rejects_invalid_certificate_signers_size() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let participants_len = schemes.len();
let attestations: Vec<_> = schemes
.iter()
.take(3)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let mut certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
assert!(verifier.verify_certificate::<_, Sha256Digest, N3f1>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&certificate,
&Sequential,
));
let signers: Vec<Participant> = certificate.signers.iter().collect();
certificate.signers = Signers::from(participants_len + 1, signers);
assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&certificate,
&Sequential,
));
}
#[test]
fn test_verify_certificate_rejects_signers_size_mismatch() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let participants_len = schemes.len();
let attestations: Vec<_> = schemes
.iter()
.take(3)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let mut certificate = schemes[0]
.assemble::<_, N3f1>(attestations, &Sequential)
.unwrap();
let signers: Vec<Participant> = certificate.signers.iter().collect();
certificate.signers = Signers::from(participants_len + 1, signers);
certificate
.signatures
.push(certificate.signatures[0].clone());
assert!(!verifier.verify_certificate::<_, Sha256Digest, N3f1>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&certificate,
&Sequential,
));
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::*;
use commonware_codec::conformance::CodecConformance;
commonware_conformance::conformance_tests! {
CodecConformance<Certificate>,
}
}
}