#[cfg(feature = "mocks")]
pub mod mocks;
use crate::{
Signer as _, Verifier as _,
certificate::{AssemblyError, Attestation, Namespace, Scheme, Signers, Subject, Verification},
secp256r1::standard::{PrivateKey, PublicKey, Signature as Secp256r1Signature},
};
#[cfg(not(feature = "std"))]
use alloc::{collections::BTreeSet, vec::Vec};
use bytes::BufMut;
use commonware_codec::{
Buf, EncodeSize, Error, FixedSize, Read, ReadRangeExt, Write, types::lazy::Lazy,
};
use commonware_utils::{
Participant, Widen,
iter::NonEmpty,
ordered::{BiMap, Quorum, Set},
};
use rand_core::CryptoRng;
#[cfg(feature = "std")]
use std::collections::BTreeSet;
#[derive(Clone, Debug)]
pub struct Generic<P: crate::PublicKey, N: Namespace> {
pub participants: BiMap<P, PublicKey>,
pub signer: Option<(Participant, PrivateKey)>,
pub namespace: N,
}
impl<P: crate::PublicKey, N: Namespace> Generic<P, N> {
pub fn signer(
namespace: &[u8],
participants: BiMap<P, PublicKey>,
private_key: PrivateKey,
) -> Option<Self> {
let public_key = private_key.public_key();
let signer = participants
.values()
.iter()
.position(|p| p == &public_key)
.map(|index| (Participant::from_usize(index), private_key))?;
Some(Self {
participants,
signer: Some(signer),
namespace: N::derive(namespace),
})
}
pub fn verifier(namespace: &[u8], participants: BiMap<P, PublicKey>) -> Self {
Self {
participants,
signer: None,
namespace: N::derive(namespace),
}
}
pub const fn participants(&self) -> &Set<P> {
self.participants.keys()
}
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 = Secp256r1Signature>,
{
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 = Secp256r1Signature>,
{
let Some(public_key) = self.participants.value(attestation.signer.into()) 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,
) -> Verification<S>
where
S: Scheme<Signature = Secp256r1Signature>,
R: CryptoRng,
I: IntoIterator<Item = Attestation<S>>,
{
let namespace = subject.namespace(&self.namespace);
let message = subject.message();
let mut invalid = BTreeSet::new();
let mut verified = Vec::new();
for attestation in attestations.into_iter() {
let Some(public_key) = self.participants.value(attestation.signer.into()) 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);
}
}
Verification::new(verified, invalid.into_iter().collect())
}
pub fn assemble<S, I>(&self, attestations: NonEmpty<I>) -> Result<Certificate, AssemblyError>
where
S: Scheme<Signature = Secp256r1Signature>,
I: Iterator<Item = Attestation<S>>,
{
let mut entries = Vec::new();
for Attestation { signer, signature } in attestations {
self.participants
.value(signer.into())
.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.keys(), signer))?
.require(self.participants.quorum::<S::Faults>())?;
let signatures = signatures.into_iter().map(Lazy::from).collect();
Ok(Certificate {
signers,
signatures,
})
}
pub fn verify_certificate<S, R>(
&self,
_rng: &mut R,
subject: impl Subject<Namespace = N>,
certificate: &Certificate,
) -> bool
where
S: Scheme,
R: CryptoRng,
{
if certificate.signers.len() != self.participants.len() {
return false;
}
if certificate.signers.count() != certificate.signatures.len() {
return false;
}
if certificate.signers.count() < Widen::widen(self.participants.quorum::<S::Faults>()) {
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.value(signer.into()) else {
return false;
};
let Some(signature) = signature.get() else {
return false;
};
if !public_key.verify(namespace, &message, signature) {
return false;
}
}
true
}
pub const fn is_attributable() -> bool {
true
}
pub const fn is_batchable() -> bool {
false
}
pub fn max_certificate_size(participants: u32) -> usize {
crate::certificate::max_individual_certificate_size(participants, Secp256r1Signature::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<Secp256r1Signature>>,
}
#[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::<Secp256r1Signature>().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::secp256r1::certificate::Certificate",
"Certificate contains no signers",
));
}
let signatures = Vec::<Lazy<Secp256r1Signature>>::read_range(reader, ..=*participants)?;
if signers.count() != signatures.len() {
return Err(Error::Invalid(
"cryptography::secp256r1::certificate::Certificate",
"Signers and signatures counts differ",
));
}
Ok(Self {
signers,
signatures,
})
}
}
#[macro_export]
macro_rules! impl_certificate_secp256r1 {
($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<$crate::ed25519::PublicKey>>
where
R: rand_core::CryptoRng,
{
$crate::secp256r1::certificate::mocks::fixture(
rng,
namespace,
n,
Scheme::signer,
Scheme::verifier,
)
}
#[derive(Clone, Debug)]
pub struct Scheme<P: $crate::PublicKey> {
generic: $crate::secp256r1::certificate::Generic<P, $namespace>,
member: Option<commonware_utils::Participant>,
}
impl<P: $crate::PublicKey> Scheme<P> {
pub fn signer(
namespace: &[u8],
participants: commonware_utils::ordered::BiMap<P, $crate::secp256r1::standard::PublicKey>,
private_key: $crate::secp256r1::standard::PrivateKey,
) -> Option<Self> {
Some(Self {
generic: $crate::secp256r1::certificate::Generic::signer(
namespace,
participants,
private_key,
)?,
member: None,
})
}
pub fn verifier(
namespace: &[u8],
participants: commonware_utils::ordered::BiMap<P, $crate::secp256r1::standard::PublicKey>,
) -> Self {
Self {
generic: $crate::secp256r1::certificate::Generic::verifier(
namespace,
participants,
),
member: None,
}
}
pub fn member(
namespace: &[u8],
participants: commonware_utils::ordered::BiMap<P, $crate::secp256r1::standard::PublicKey>,
public_key: &P,
) -> Option<Self> {
let member =
commonware_utils::ordered::Quorum::index(participants.keys(), public_key)?;
Some(Self {
generic: $crate::secp256r1::certificate::Generic::verifier(
namespace,
participants,
),
member: Some(member),
})
}
}
impl<P: $crate::PublicKey> $crate::certificate::Verifier for Scheme<P> {
type Subject<'a, D: $crate::Digest> = $subject;
type Faults = $faults;
type PublicKey = P;
type Certificate = $crate::secp256r1::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,
)
}
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)>,
{
for (subject, certificate) in certificates {
if !self.generic.verify_certificate::<Self, _>(rng, subject, certificate) {
return false;
}
}
true
}
fn is_batchable() -> bool {
$crate::secp256r1::certificate::Generic::<P, $namespace>::is_batchable()
}
fn max_certificate_size(participants: u32) -> usize {
$crate::secp256r1::certificate::Generic::<P, $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::secp256r1::certificate::Generic::<P, $namespace>::certificate_codec_config_unbounded()
}
}
impl<P: $crate::PublicKey> $crate::certificate::Scheme for Scheme<P> {
type Signature = $crate::secp256r1::standard::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,
)
}
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::secp256r1::certificate::Generic::<P, $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::Sequential;
use commonware_utils::{Faults, N3f1, TryCollect, non_empty, ordered::BiMap, test_rng};
use rand_core::CryptoRng;
use std::sync::{
Arc,
atomic::{AtomicUsize, Ordering},
};
const NAMESPACE: &[u8] = b"test-secp256r1";
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_secp256r1!(TestSubject, Vec<u8>, N3f1);
fn setup_signers(
rng: &mut impl CryptoRng,
n: u32,
) -> (Vec<Scheme<PublicKey>>, Scheme<PublicKey>) {
let private_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut *rng)).collect();
let participants: BiMap<PublicKey, PublicKey> = private_keys
.iter()
.map(|sk| {
let pk = sk.public_key();
(pk.clone(), pk)
})
.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_member_reports_membership_without_signing() {
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 4);
let participants = verifier.generic.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.keys().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_member_uses_identity_keys() {
let mut rng = test_rng();
let (_, verifier) = setup_signers(&mut rng, 4);
let identities = verifier.participants().clone();
let signing_keys: Vec<_> = (0..identities.len())
.map(|_| PrivateKey::random(&mut rng).public_key())
.collect();
let participants: BiMap<PublicKey, PublicKey> = identities
.iter()
.cloned()
.zip(signing_keys.iter().cloned())
.try_collect()
.unwrap();
for (position, identity) in identities.iter().enumerate() {
let member = Scheme::member(NAMESPACE, participants.clone(), identity).unwrap();
assert_eq!(
member.me(),
Some(Membership::Member(Participant::from_usize(position)))
);
}
for signing_key in &signing_keys {
assert!(Scheme::member(NAMESPACE, participants.clone(), signing_key).is_none());
}
}
#[allow(dead_code)]
#[test]
fn test_optimistic_assemble_verifies_pending_once() {
#[derive(Clone, Debug)]
pub struct CountingSubject {
message: Bytes,
calls: Arc<AtomicUsize>,
}
impl Subject for CountingSubject {
type Namespace = Vec<u8>;
fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
derived
}
fn message(&self) -> Bytes {
self.calls.fetch_add(1, Ordering::Relaxed);
self.message.clone()
}
}
impl_certificate_secp256r1!(CountingSubject, Vec<u8>, N3f1);
let mut rng = test_rng();
let (schemes, verifier) = setup_signers(&mut rng, 5);
let participants = verifier.generic.participants;
let mut schemes: Vec<_> = schemes
.into_iter()
.map(|scheme| {
Scheme::signer(
NAMESPACE,
participants.clone(),
scheme.generic.signer.unwrap().1,
)
.unwrap()
})
.collect();
schemes.sort_by_key(|scheme| scheme.me().unwrap().index());
let quorum = N3f1::quorum(schemes.len()) as usize;
let verifier = Scheme::verifier(NAMESPACE, participants);
let calls = Arc::new(AtomicUsize::new(0));
let subject = CountingSubject {
message: Bytes::from_static(MESSAGE),
calls: Arc::clone(&calls),
};
let sign = |scheme: &Scheme<PublicKey>, message| {
scheme
.sign::<Sha256Digest>(CountingSubject {
message,
calls: Arc::clone(&calls),
})
.unwrap()
};
let prior = verifier.verify_attestations::<_, Sha256Digest, _>(
&mut rng,
subject.clone(),
schemes[..quorum - 1]
.iter()
.map(|scheme| sign(scheme, Bytes::from_static(MESSAGE))),
&Sequential,
);
assert!(prior.invalid.is_empty());
assert_eq!(prior.verified.len(), quorum - 1);
let rejected = sign(
schemes.last().unwrap(),
Bytes::from_static(b"other subject"),
);
let invalid = rejected.signer;
calls.store(0, Ordering::Relaxed);
let result = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
subject.clone(),
[rejected],
&prior.verified,
&Sequential,
)
.expect_err("invalid vote must be reported");
assert!(result.verified.is_empty());
assert_eq!(result.invalid, vec![invalid]);
assert_eq!(calls.load(Ordering::Relaxed), 1);
let replacement = sign(&schemes[quorum - 1], Bytes::from_static(MESSAGE));
let certificate = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
subject.clone(),
[replacement.clone()],
&prior.verified,
&Sequential,
)
.ok()
.expect("valid replacement must certify");
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
subject.clone(),
&certificate,
&Sequential,
));
let mut prior = prior.verified;
prior.push(replacement);
calls.store(0, Ordering::Relaxed);
let certificate = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
subject.clone(),
[],
&prior,
&Sequential,
)
.ok()
.expect("verified quorum must certify");
assert_eq!(calls.load(Ordering::Relaxed), 0);
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
subject.clone(),
&certificate,
&Sequential,
));
assert!(!verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
CountingSubject {
message: Bytes::from_static(b"other subject"),
..subject
},
&certificate,
&Sequential,
));
}
#[test]
fn test_is_attributable() {
assert!(Generic::<PublicKey, Vec<u8>>::is_attributable());
assert!(Scheme::<PublicKey>::is_attributable());
}
#[test]
fn test_is_not_batchable() {
assert!(!Generic::<PublicKey, Vec<u8>>::is_batchable());
assert!(!Scheme::<PublicKey>::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;
let first_signer = attestations_corrupted[0].signer;
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, vec![first_signer]);
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(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::<PublicKey>::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::<PublicKey>::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_assemble_certificate_rejects_malformed_signature() {
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 malformed_signer = attestations[0].signer;
let mut truncated = Bytes::from_static(&[0u8]);
attestations[0].signature = Lazy::deferred(&mut truncated, ());
assert_eq!(
schemes[0].assemble(non_empty![@attestations], &Sequential),
Err(AssemblyError::MalformedSignature(malformed_signer))
);
}
#[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::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(non_empty![@attestations], &Sequential)
.unwrap(),
);
}
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_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(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 duplicate_signer = duplicate.signer;
attestations.push(duplicate);
assert_eq!(
schemes[0].assemble(non_empty![@attestations], &Sequential),
Err(AssemblyError::DuplicateSigner(duplicate_signer))
);
}
#[test]
fn test_scheme_clone_and_verifier() {
let mut rng = test_rng();
let (schemes, _) = setup_signers(&mut rng, 4);
let participants = schemes[0].generic.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,
[Participant::new(0), Participant::new(1)],
)
.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> => 1024,
}
}
}