#[cfg(feature = "mocks")]
pub mod mocks;
use super::{PrivateKey, PublicKey, Signature as Ed25519Signature};
use crate::{
BatchEntry, BatchVerifier as _, Signer as _, Verifier as _,
certificate::{AssemblyError, Attestation, Namespace, Scheme, Signers, Subject, Verification},
};
#[cfg(not(feature = "std"))]
use alloc::{collections::BTreeSet, vec::Vec};
use bytes::{BufMut, Bytes};
use commonware_codec::{
Buf, EncodeSize, Error, FixedSize, Read, ReadRangeExt, Write, types::lazy::Lazy,
};
use commonware_parallel::Strategy;
use commonware_utils::{
Participant, Widen,
iter::NonEmpty,
ordered::{Quorum, Set},
};
use rand_core::CryptoRng;
#[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 signer_index(&self) -> Option<Participant> {
self.signer.as_ref().map(|(index, _)| *index)
}
pub fn sign<S>(&self, subject: impl Subject<Namespace = N>) -> Option<Attestation<S>>
where
S: Scheme<Signature = Ed25519Signature>,
{
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<S>(
&self,
subject: impl Subject<Namespace = N>,
attestation: &Attestation<S>,
) -> bool
where
S: Scheme<Signature = Ed25519Signature>,
{
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,
)
}
pub fn verify_attestations<S, R, I>(
&self,
rng: &mut R,
subject: impl Subject<Namespace = N>,
attestations: I,
strategy: &impl Strategy,
) -> Verification<S>
where
S: Scheme<Signature = Ed25519Signature>,
R: CryptoRng,
I: IntoIterator<Item = Attestation<S>>,
{
let namespace = subject.namespace(&self.namespace);
let message = subject.message();
let attestations = attestations.into_iter();
let mut invalid = BTreeSet::new();
let mut candidates = Vec::with_capacity(attestations.size_hint().0);
for attestation in attestations {
let Some(public_key) = self.participants.key(attestation.signer) else {
invalid.insert(attestation.signer);
continue;
};
if attestation.signature.get().is_none() {
invalid.insert(attestation.signer);
continue;
}
candidates.push((attestation, public_key));
}
if !candidates.is_empty()
&& !PublicKey::verify_batch(
rng,
&candidates,
|_, (attestation, public_key)| BatchEntry {
namespace,
message: &message,
public_key,
signature: attestation.signature.get().expect("validated signature"),
},
strategy,
)
{
for (attestation, public_key) in &candidates {
let signature = attestation.signature.get().expect("validated signature");
if !public_key.verify(namespace, &message, signature) {
invalid.insert(attestation.signer);
}
}
}
let verified = candidates
.into_iter()
.filter(|(attestation, _)| !invalid.contains(&attestation.signer))
.map(|(attestation, _)| attestation)
.collect();
Verification::new(verified, invalid.into_iter().collect())
}
pub fn assemble<S, I>(&self, attestations: NonEmpty<I>) -> Result<Certificate, AssemblyError>
where
S: Scheme<Signature = Ed25519Signature>,
I: Iterator<Item = Attestation<S>>,
{
let mut entries = Vec::new();
for Attestation { signer, signature } in attestations {
self.participants
.key(signer)
.ok_or(AssemblyError::UnknownSigner(signer))?;
let signature = signature
.get()
.cloned()
.ok_or(AssemblyError::MalformedSignature(signer))?;
entries.push((signer, signature));
}
entries.sort_by_key(|(signer, _)| *signer);
let (signer, signatures): (Vec<Participant>, Vec<_>) = entries.into_iter().unzip();
let signers = Signers::try_from((&self.participants, signer))?
.require(self.participants.quorum::<S::Faults>())?;
let signatures = signatures.into_iter().map(Lazy::from).collect();
Ok(Certificate {
signers,
signatures,
})
}
fn stage_certificate<'a, S: Scheme>(
&'a self,
subject: impl Subject<Namespace = N>,
certificate: &'a Certificate,
mut stage: impl FnMut(&'a PublicKey, &'a Ed25519Signature),
) -> Option<(&'a [u8], Bytes)> {
if certificate.signers.len() != self.participants.len()
|| certificate.signers.count() != certificate.signatures.len()
|| certificate.signers.count() < Widen::widen(self.participants.quorum::<S::Faults>())
{
return None;
}
for (signer, signature) in certificate.signers.iter().zip(&certificate.signatures) {
stage(self.participants.key(signer)?, signature.get()?);
}
Some((subject.namespace(&self.namespace), subject.message()))
}
pub fn verify_certificate<S, R>(
&self,
rng: &mut R,
subject: impl Subject<Namespace = N>,
certificate: &Certificate,
strategy: &impl Strategy,
) -> bool
where
S: Scheme,
R: CryptoRng,
{
let mut entries = Vec::with_capacity(certificate.signatures.len());
let Some((namespace, message)) =
self.stage_certificate::<S>(subject, certificate, |public_key, signature| {
entries.push((public_key, signature));
})
else {
return false;
};
PublicKey::verify_batch(
rng,
&entries,
|_, (public_key, signature)| BatchEntry {
namespace,
message: &message,
public_key,
signature,
},
strategy,
)
}
pub fn verify_certificates<'a, S, R, T, I>(
&self,
rng: &mut R,
certificates: NonEmpty<I>,
strategy: &impl Strategy,
) -> bool
where
S: Scheme,
T: Subject<Namespace = N>,
R: CryptoRng,
I: Iterator<Item = (T, &'a Certificate)>,
{
let per_certificate = self.participants.len();
let certificates = certificates.into_iter();
let mut messages = Vec::with_capacity(certificates.size_hint().0);
let mut entries =
Vec::with_capacity(certificates.size_hint().0.saturating_mul(per_certificate));
for (subject, certificate) in certificates {
let index = messages.len();
let Some(message) =
self.stage_certificate::<S>(subject, certificate, |public_key, signature| {
entries.push((index, public_key, signature));
})
else {
return false;
};
messages.push(message);
}
PublicKey::verify_batch(
rng,
&entries,
|_, (index, public_key, signature)| BatchEntry {
namespace: messages[*index].0,
message: &messages[*index].1,
public_key,
signature,
},
strategy,
)
}
pub const fn is_attributable() -> bool {
true
}
pub const fn is_batchable() -> bool {
true
}
pub fn max_certificate_size(participants: u32) -> usize {
crate::certificate::max_individual_certificate_size(participants, Ed25519Signature::SIZE)
}
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, $faults: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_core::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>,
member: Option<commonware_utils::Participant>,
}
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,
)?,
member: None,
})
}
pub fn verifier(
namespace: &[u8],
participants: commonware_utils::ordered::Set<$crate::ed25519::PublicKey>,
) -> Self {
Self {
generic: $crate::ed25519::certificate::Generic::verifier(
namespace,
participants,
),
member: None,
}
}
pub fn member(
namespace: &[u8],
participants: commonware_utils::ordered::Set<$crate::ed25519::PublicKey>,
public_key: &$crate::ed25519::PublicKey,
) -> Option<Self> {
let member = commonware_utils::ordered::Quorum::index(&participants, public_key)?;
Some(Self {
generic: $crate::ed25519::certificate::Generic::verifier(
namespace,
participants,
),
member: Some(member),
})
}
}
impl $crate::certificate::Verifier for Scheme {
type Subject<'a, D: $crate::Digest> = $subject;
type Faults = $faults;
type PublicKey = $crate::ed25519::PublicKey;
type Certificate = $crate::ed25519::certificate::Certificate;
fn verify_certificate<R, D>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
certificate: &Self::Certificate,
strategy: &impl commonware_parallel::Strategy,
) -> bool
where
R: rand_core::CryptoRng,
D: $crate::Digest,
{
self.generic
.verify_certificate::<Self, _>(rng, subject, certificate, strategy)
}
fn verify_certificates<'a, R, D, I>(
&self,
rng: &mut R,
certificates: commonware_utils::iter::NonEmpty<I>,
strategy: &impl commonware_parallel::Strategy,
) -> bool
where
R: rand_core::CryptoRng,
D: $crate::Digest,
I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
{
self.generic
.verify_certificates::<Self, _, _, _>(rng, certificates, strategy)
}
fn is_batchable() -> bool {
$crate::ed25519::certificate::Generic::<$namespace>::is_batchable()
}
fn max_certificate_size(participants: u32) -> usize {
$crate::ed25519::certificate::Generic::<$namespace>::max_certificate_size(participants)
}
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()
}
}
impl $crate::certificate::Scheme for Scheme {
type Signature = $crate::ed25519::Signature;
fn me(&self) -> Option<$crate::certificate::Membership> {
self.generic
.signer_index()
.map($crate::certificate::Membership::Signer)
.or(self.member.map($crate::certificate::Membership::Member))
}
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(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::CryptoRng,
D: $crate::Digest,
{
self.generic
.verify_attestation(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::CryptoRng,
D: $crate::Digest,
I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
{
self.generic
.verify_attestations(rng, subject, attestations, strategy)
}
fn optimistic_assemble<'a, R, D, I, J>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
pending: I,
verified: J,
strategy: &impl commonware_parallel::Strategy,
) -> Result<Self::Certificate, $crate::certificate::Verification<Self>>
where
R: rand_core::CryptoRng,
D: $crate::Digest,
I: IntoIterator<Item = $crate::certificate::Attestation<Self>>,
I::IntoIter: ExactSizeIterator + Send,
J: IntoIterator<Item = &'a $crate::certificate::Attestation<Self>>,
J::IntoIter: Send,
{
$crate::certificate::verify_then_assemble::<Self, _, D, _, _>(
self, rng, subject, pending, verified, strategy,
)
}
fn assemble<I>(
&self,
attestations: commonware_utils::iter::NonEmpty<I>,
_strategy: &impl commonware_parallel::Strategy,
) -> Result<Self::Certificate, $crate::certificate::AssemblyError>
where
I: Iterator<Item = $crate::certificate::Attestation<Self>> + Send,
{
self.generic.assemble::<Self, _>(attestations)
}
fn is_attributable() -> bool {
$crate::ed25519::certificate::Generic::<$namespace>::is_attributable()
}
}
};
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
certificate::{Membership, Scheme as _, Verifier as _},
sha256::Digest as Sha256Digest,
};
use bytes::Bytes;
use commonware_codec::{Decode, Encode, varint::MAX_U32_VARINT_SIZE};
use commonware_math::algebra::Random;
use commonware_parallel::{Rayon, Sequential};
use commonware_utils::{
Faults, N3f1, NZUsize, Participant, TestRng, TryCollect, non_empty, ordered::Set, test_rng,
};
use rand_core::Rng;
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>, N3f1);
fn setup_signers(rng: &mut impl CryptoRng, 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]
fn test_is_batchable() {
assert!(Generic::<Vec<u8>>::is_batchable());
assert!(Scheme::is_batchable());
}
#[test]
fn test_optimistic_assemble_reuses_verified_attestations() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 5);
let quorum = N3f1::quorum(schemes.len()) as usize;
let subject = TestSubject {
message: Bytes::from_static(MESSAGE),
};
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|scheme| scheme.sign::<Sha256Digest>(subject.clone()).unwrap())
.collect();
let strategy = Rayon::new(NZUsize!(1))
.unwrap()
.with_parallelism(NZUsize!(quorum));
for pending_len in [quorum, 1, 0] {
let (prior, pending) = attestations.split_at(quorum - pending_len);
let verified = verifier.verify_attestations::<_, Sha256Digest, _>(
&mut rng,
subject.clone(),
prior.iter().cloned(),
&Sequential,
);
assert!(verified.invalid.is_empty());
let mut expected_rng = TestRng::new(0);
let expected = verifier.verify_attestations::<_, Sha256Digest, _>(
&mut expected_rng,
subject.clone(),
pending.iter().cloned(),
&strategy,
);
assert!(expected.invalid.is_empty());
let mut actual_rng = TestRng::new(0);
let certificate = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut actual_rng,
subject.clone(),
pending.iter().cloned(),
&verified.verified,
&strategy,
)
.ok()
.expect("verified quorum must certify");
assert_eq!(
actual_rng.next_u64(),
expected_rng.next_u64(),
"retained votes must not enter the verification batch"
);
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
subject.clone(),
&certificate,
&Sequential,
));
}
}
#[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_member_reports_membership_without_signing() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let participants = verifier.participants().clone();
let subject = TestSubject {
message: Bytes::from_static(MESSAGE),
};
let outsider = PrivateKey::random(&mut rng).public_key();
assert!(Scheme::member(NAMESPACE, participants.clone(), &outsider).is_none());
let index = Participant::new(2);
let key = participants.key(index).unwrap().clone();
let member = Scheme::member(NAMESPACE, participants, &key).unwrap();
assert_eq!(member.me(), Some(Membership::Member(index)));
assert_eq!(member.me().unwrap().signer(), None);
assert!(member.sign::<Sha256Digest>(subject.clone()).is_none());
assert_eq!(verifier.me(), None);
for scheme in &schemes {
let Some(Membership::Signer(signer)) = scheme.me() else {
panic!("signer must report signing membership");
};
let attestation = scheme.sign::<Sha256Digest>(subject.clone()).unwrap();
assert_eq!(attestation.signer, signer);
}
let attestations: Vec<_> = schemes
.iter()
.take(Widen::widen(N3f1::quorum(schemes.len())))
.map(|s| s.sign::<Sha256Digest>(subject.clone()).unwrap())
.collect();
let certificate = schemes[0]
.assemble(non_empty![@attestations], &Sequential)
.unwrap();
assert!(member.verify_certificate::<_, Sha256Digest>(
&mut rng,
subject,
&certificate,
&Sequential,
));
}
#[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);
let subject = TestSubject {
message: Bytes::from_static(MESSAGE),
};
let mut malformed = schemes[0].sign::<Sha256Digest>(subject.clone()).unwrap();
let mut truncated = Bytes::from_static(&[0u8; 3]);
malformed.signature = Lazy::deferred(&mut truncated, ());
let mut unknown = schemes[1].sign::<Sha256Digest>(subject.clone()).unwrap();
unknown.signer = Participant::new(999);
for attestations in [Vec::new(), vec![malformed, unknown]] {
let expected_invalid = attestations.len();
let mut actual_rng = TestRng::new(0);
let mut expected_rng = TestRng::new(0);
let result = schemes[0].verify_attestations::<_, Sha256Digest, _>(
&mut actual_rng,
subject.clone(),
attestations,
&Sequential,
);
assert!(result.verified.is_empty());
assert_eq!(result.invalid.len(), expected_invalid);
assert_eq!(actual_rng.next_u64(), expected_rng.next_u64());
}
}
#[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(non_empty![@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().index(), 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(non_empty![@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(non_empty![@attestations], &Sequential)
.unwrap();
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&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(non_empty![@attestations], &Sequential)
.unwrap();
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&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>(
&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(non_empty![@attestations], &Sequential)
.unwrap();
let encoded = certificate.encode();
assert!(
encoded.len() <= Scheme::max_certificate_size(u32::try_from(schemes.len()).unwrap())
);
let decoded = Certificate::decode_cfg(encoded, &schemes.len()).expect("decode certificate");
assert_eq!(decoded, certificate);
}
#[test]
fn test_max_certificate_size() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 128);
let attestations = schemes.iter().map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
});
let certificate = schemes[0]
.assemble(non_empty![@attestations], &Sequential)
.unwrap();
let prefix_slack = MAX_U32_VARINT_SIZE - schemes.len().encode_size();
assert_eq!(
certificate.encode().len() + prefix_slack,
Scheme::max_certificate_size(u32::try_from(schemes.len()).unwrap())
);
}
#[test]
fn test_certificate_rejects_sub_quorum() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let expected = N3f1::quorum(schemes.len());
let found = expected - 1;
let found_count = usize::try_from(found).expect("quorum exceeds usize::MAX");
let attestations: Vec<_> = schemes
.iter()
.take(found_count)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
assert_eq!(
schemes[0].assemble(non_empty![@attestations], &Sequential),
Err(AssemblyError::InsufficientAttestations(expected, found))
);
}
#[test]
fn test_certificate_rejects_invalid_signer() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let quorum =
usize::try_from(N3f1::quorum(schemes.len())).expect("quorum exceeds usize::MAX");
let mut attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let unknown = Participant::new(999);
attestations[0].signer = unknown;
assert_eq!(
schemes[0].assemble(non_empty![@attestations], &Sequential),
Err(AssemblyError::UnknownSigner(unknown))
);
}
#[test]
fn test_certificate_rejects_malformed_signature() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let quorum =
usize::try_from(N3f1::quorum(schemes.len())).expect("quorum exceeds usize::MAX");
let mut attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| {
s.sign::<Sha256Digest>(TestSubject {
message: Bytes::from_static(MESSAGE),
})
.unwrap()
})
.collect();
let valid = schemes[0]
.assemble(non_empty![@attestations.clone()], &Sequential)
.unwrap();
let signer = attestations[0].signer;
let mut truncated = Bytes::from_static(&[0u8; 3]);
attestations[0].signature = Lazy::deferred(&mut truncated, ());
assert_eq!(
schemes[0].assemble(non_empty![@attestations], &Sequential),
Err(AssemblyError::MalformedSignature(signer))
);
let subject = TestSubject {
message: Bytes::from_static(MESSAGE),
};
let mut invalid = valid.clone();
let mut truncated = Bytes::from_static(&[0u8; 3]);
*invalid.signatures.last_mut().unwrap() = Lazy::deferred(&mut truncated, ());
let mut actual_rng = TestRng::new(0);
let mut expected_rng = TestRng::new(0);
assert!(!verifier.verify_certificate::<_, Sha256Digest>(
&mut actual_rng,
subject.clone(),
&invalid,
&Sequential,
));
assert_eq!(actual_rng.next_u64(), expected_rng.next_u64());
let mut actual_rng = TestRng::new(0);
let mut expected_rng = TestRng::new(0);
assert!(!verifier.verify_certificates::<_, Sha256Digest, _>(
&mut actual_rng,
non_empty![(subject.clone(), &valid), (subject, &invalid)],
&Sequential,
));
assert_eq!(actual_rng.next_u64(), expected_rng.next_u64());
}
#[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(non_empty![@attestations], &Sequential)
.unwrap();
let mut signers: Vec<Participant> = certificate.signers.iter().collect();
signers.pop();
certificate.signers = Signers::new(participants_len.try_into().unwrap(), signers).unwrap();
certificate.signatures.pop();
assert!(!verifier.verify_certificate::<_, Sha256Digest>(
&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(non_empty![@attestations], &Sequential)
.unwrap();
certificate.signatures.pop();
assert!(!verifier.verify_certificate::<_, Sha256Digest>(
&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::from_static)
.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(non_empty![@attestations], &Sequential)
.unwrap(),
);
}
let certs_iter = messages.iter().zip(&certificates).map(|(msg, cert)| {
(
TestSubject {
message: msg.clone(),
},
cert,
)
});
let mut actual_rng = TestRng::new(0);
let mut expected_rng = TestRng::new(0);
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut expected_rng,
TestSubject {
message: messages[0].clone(),
},
&certificates[0],
&Sequential,
));
assert!(verifier.verify_certificates::<_, Sha256Digest, _>(
&mut actual_rng,
non_empty![@certs_iter],
&Sequential
));
assert_eq!(actual_rng.next_u64(), expected_rng.next_u64());
}
#[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::from_static)
.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(non_empty![@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, _>(
&mut rng,
non_empty![@certs_iter],
&Sequential
));
}
#[test]
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();
let duplicate = attestations.last().unwrap().clone();
let signer = duplicate.signer;
attestations.push(duplicate);
assert_eq!(
schemes[0].assemble(non_empty![@attestations], &Sequential),
Err(AssemblyError::DuplicateSigner(signer))
);
}
#[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 participant_count =
u32::try_from(participants_len).expect("participant count exceeds u32::MAX");
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(non_empty![@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::new(participant_count, std::iter::empty::<Participant>()).unwrap(),
signatures: Vec::new(),
};
assert!(Certificate::decode_cfg(empty.encode(), &participants_len).is_err());
let mismatched = Certificate {
signers: Signers::new(participant_count, [0u32, 1].map(Participant::new)).unwrap(),
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::new(participant_count + 1, signers).unwrap(),
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(non_empty![@attestations], &Sequential)
.unwrap();
let mut signers: Vec<Participant> = certificate.signers.iter().collect();
signers.push(Participant::from_usize(participants_len));
certificate.signers =
Signers::new((participants_len + 1).try_into().unwrap(), signers).unwrap();
certificate
.signatures
.push(certificate.signatures[0].clone());
assert!(!verifier.verify_certificate::<_, Sha256Digest>(
&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(non_empty![@attestations], &Sequential)
.unwrap();
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
TestSubject {
message: Bytes::from_static(MESSAGE),
},
&certificate,
&Sequential,
));
let signers: Vec<Participant> = certificate.signers.iter().collect();
certificate.signers =
Signers::new((participants_len + 1).try_into().unwrap(), signers).unwrap();
assert!(!verifier.verify_certificate::<_, Sha256Digest>(
&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(non_empty![@attestations], &Sequential)
.unwrap();
let signers: Vec<Participant> = certificate.signers.iter().collect();
certificate.signers =
Signers::new((participants_len + 1).try_into().unwrap(), signers).unwrap();
certificate
.signatures
.push(certificate.signatures[0].clone());
assert!(!verifier.verify_certificate::<_, Sha256Digest>(
&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>,
}
}
}