#![cfg_attr(
feature = "bls12381",
doc = "
- [`bls12381_multisig`]: Attributable signatures with aggregated verification. Signatures
can be aggregated into a single multi-signature for compact certificates while preserving
attribution (signer indices are stored alongside the aggregated signature). Callers must verify
a proof of possession for every BLS signing key before constructing the scheme.
- [`bls12381_threshold`]: Non-attributable threshold signatures. Produces succinct
certificates that are constant-size regardless of committee size. Requires a trusted
setup (distributed key generation) and cannot attribute signatures to individual signers.
"
)]
#![cfg_attr(
feature = "bls12381",
doc = "Some cryptographic schemes are only performant when used in batch verification (like
[`bls12381_multisig`]) and/or are refreshed frequently (like [`bls12381_threshold`])."
)]
#[cfg(feature = "bls12381")]
pub use crate::bls12381::certificate::{
multisig as bls12381_multisig, threshold as bls12381_threshold,
};
pub use crate::ed25519::certificate as ed25519;
#[commonware_macros::stability(ALPHA)]
pub use crate::secp256r1::certificate as secp256r1;
use crate::{Digest, PublicKey};
#[cfg(not(feature = "std"))]
use alloc::{collections::BTreeSet, sync::Arc, vec, vec::Vec};
use bytes::{BufMut, Bytes};
use commonware_codec::{
Buf, Codec, CodecFixed, EncodeSize, Error as CodecError, FixedSize, MaxEncodeSize, Read,
ReadExt, Write, types::lazy::Lazy, util::max_items_size,
};
use commonware_parallel::Strategy;
use commonware_utils::{Faults, Participant, Widen, bitmap::BitMap, iter::NonEmpty, ordered::Set};
use core::{fmt::Debug, hash::Hash};
use rand_chacha::ChaCha20Rng;
use rand_core::{CryptoRng, SeedableRng};
#[cfg(feature = "std")]
use std::{collections::BTreeSet, sync::Arc, vec::Vec};
use thiserror::Error;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Membership {
Signer(Participant),
Member(Participant),
}
impl Membership {
pub const fn index(&self) -> Participant {
match self {
Self::Signer(index) | Self::Member(index) => *index,
}
}
pub const fn signer(&self) -> Option<Participant> {
match self {
Self::Signer(index) => Some(*index),
Self::Member(_) => None,
}
}
}
#[derive(Clone, Debug)]
pub struct Attestation<S: Scheme> {
pub signer: Participant,
pub signature: Lazy<S::Signature>,
}
impl<S: Scheme> PartialEq for Attestation<S> {
fn eq(&self, other: &Self) -> bool {
self.signer == other.signer && self.signature == other.signature
}
}
impl<S: Scheme> Eq for Attestation<S> {}
impl<S: Scheme> Hash for Attestation<S> {
fn hash<H: core::hash::Hasher>(&self, state: &mut H) {
self.signer.hash(state);
self.signature.hash(state);
}
}
impl<S: Scheme> Write for Attestation<S> {
fn write(&self, writer: &mut impl BufMut) {
self.signer.write(writer);
self.signature.write(writer);
}
}
impl<S: Scheme> EncodeSize for Attestation<S> {
fn encode_size(&self) -> usize {
self.signer.encode_size() + self.signature.encode_size()
}
}
impl<S: Scheme> MaxEncodeSize for Attestation<S> {
const MAX_ENCODE_SIZE: usize = Participant::MAX_ENCODE_SIZE + S::Signature::SIZE;
}
impl<S: Scheme> Read for Attestation<S> {
type Cfg = ();
fn read_cfg(reader: &mut impl Buf, _: &()) -> Result<Self, CodecError> {
let signer = Participant::read(reader)?;
let signature = ReadExt::read(reader)?;
Ok(Self { signer, signature })
}
}
#[cfg(feature = "arbitrary")]
impl<S: Scheme> arbitrary::Arbitrary<'_> for Attestation<S>
where
S::Signature: for<'a> arbitrary::Arbitrary<'a>,
{
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let signer = Participant::arbitrary(u)?;
let signature = S::Signature::arbitrary(u)?;
Ok(Self {
signer,
signature: signature.into(),
})
}
}
pub struct Verification<S: Scheme> {
pub verified: Vec<Attestation<S>>,
pub invalid: Vec<Participant>,
}
impl<S: Scheme> Verification<S> {
pub const fn new(verified: Vec<Attestation<S>>, invalid: Vec<Participant>) -> Self {
Self { verified, invalid }
}
}
#[derive(Debug, Error, PartialEq, Eq)]
pub enum AssemblyError {
#[error("insufficient attestations: expected={0}, found={1}")]
InsufficientAttestations(u32, u32),
#[error("unknown signer: {0}")]
UnknownSigner(Participant),
#[error("duplicate signer: {0}")]
DuplicateSigner(Participant),
#[error("malformed signature from signer: {0}")]
MalformedSignature(Participant),
#[error("certificate recovery failed")]
RecoveryFailed,
}
pub trait Namespace: Clone + Send + Sync {
fn derive(namespace: &[u8]) -> Self;
}
impl Namespace for Vec<u8> {
fn derive(namespace: &[u8]) -> Self {
namespace.to_vec()
}
}
pub trait Subject: Clone + Debug + Send + Sync {
type Namespace: Namespace;
fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8];
fn message(&self) -> Bytes;
}
pub trait Verifier: Clone + Debug + Send + Sync + 'static {
type Subject<'a, D: Digest>: Subject;
type Faults: Faults;
type PublicKey: PublicKey;
type Certificate: Clone + Debug + Eq + Hash + Send + Sync + Codec;
fn verify_certificate<R, D>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
certificate: &Self::Certificate,
strategy: &impl Strategy,
) -> bool
where
R: CryptoRng,
D: Digest;
fn verify_certificates<'a, R, D, I>(
&self,
rng: &mut R,
certificates: NonEmpty<I>,
strategy: &impl Strategy,
) -> bool
where
R: CryptoRng,
D: Digest,
I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
{
for (subject, certificate) in certificates {
if !self.verify_certificate(rng, subject, certificate, strategy) {
return false;
}
}
true
}
fn verify_certificates_bisect<'a, R, D>(
&self,
rng: &mut R,
certificates: &[(Self::Subject<'a, D>, &'a Self::Certificate)],
strategy: &impl Strategy,
) -> Vec<bool>
where
R: CryptoRng,
D: Digest,
Self::Subject<'a, D>: Copy,
{
let len = certificates.len();
let mut verified = vec![false; len];
if len == 0 {
return verified;
}
if !Self::is_batchable() {
for (i, (subject, certificate)) in certificates.iter().enumerate() {
verified[i] = self.verify_certificate(rng, *subject, certificate, strategy);
}
return verified;
}
let mut stack = vec![(0, len)];
while let Some((start, end)) = stack.pop() {
let (first, rest) = certificates[start..end]
.split_first()
.expect("bisection ranges are non-empty");
let certificates = NonEmpty::new(*first, rest.iter().copied());
if self.verify_certificates(rng, certificates, strategy) {
verified[start..end].fill(true);
} else if end - start > 1 {
let mid = start + (end - start) / 2;
stack.push((mid, end));
stack.push((start, mid));
}
}
verified
}
fn is_batchable() -> bool;
fn max_certificate_size(participants: u32) -> usize;
fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg;
fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg;
}
pub trait Scheme: Verifier {
type Signature: Clone + Debug + Eq + Hash + Send + Sync + CodecFixed<Cfg = ()>;
fn me(&self) -> Option<Membership>;
fn participants(&self) -> &Set<Self::PublicKey>;
fn sign<D: Digest>(&self, subject: Self::Subject<'_, D>) -> Option<Attestation<Self>>;
fn verify_attestation<R, D>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
attestation: &Attestation<Self>,
strategy: &impl Strategy,
) -> bool
where
R: CryptoRng,
D: Digest;
fn verify_attestations<R, D, I>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
attestations: I,
strategy: &impl Strategy,
) -> Verification<Self>
where
R: CryptoRng,
D: Digest,
I: IntoIterator<Item = Attestation<Self>>,
I::IntoIter: Send,
{
let mut invalid = BTreeSet::new();
let verified = attestations.into_iter().filter_map(|attestation| {
if self.verify_attestation(&mut *rng, subject.clone(), &attestation, strategy) {
Some(attestation)
} else {
invalid.insert(attestation.signer);
None
}
});
Verification::new(verified.collect(), invalid.into_iter().collect())
}
fn optimistic_assemble<'a, R, D, I, J>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
pending: I,
verified: J,
strategy: &impl Strategy,
) -> Result<Self::Certificate, Verification<Self>>
where
R: CryptoRng,
D: Digest,
I: IntoIterator<Item = Attestation<Self>>,
I::IntoIter: ExactSizeIterator + Send,
J: IntoIterator<Item = &'a Attestation<Self>>,
J::IntoIter: Send;
fn assemble<I>(
&self,
attestations: NonEmpty<I>,
strategy: &impl Strategy,
) -> Result<Self::Certificate, AssemblyError>
where
I: Iterator<Item = Attestation<Self>> + Send;
fn is_attributable() -> bool;
}
pub fn verify_then_assemble<'a, S, R, D, I, J>(
scheme: &S,
rng: &mut R,
subject: S::Subject<'_, D>,
pending: I,
verified: J,
strategy: &impl Strategy,
) -> Result<S::Certificate, Verification<S>>
where
S: Scheme,
R: CryptoRng,
D: Digest,
I: IntoIterator<Item = Attestation<S>>,
I::IntoIter: Send,
J: IntoIterator<Item = &'a Attestation<S>>,
J::IntoIter: Send,
{
let result = NonEmpty::try_new(pending.into_iter()).map_or_else(
|| Verification::new(Vec::new(), Vec::new()),
|pending| scheme.verify_attestations::<_, D, _>(rng, subject, pending, strategy),
);
if !result.invalid.is_empty() {
return Err(result);
}
let Some(attestations) = NonEmpty::try_new(
result
.verified
.iter()
.cloned()
.chain(verified.into_iter().cloned()),
) else {
return Err(result);
};
scheme.assemble(attestations, strategy).map_err(|_| result)
}
pub fn optimistic_assemble<'a, S, R, D, I, J>(
scheme: &S,
rng: &mut R,
subject: S::Subject<'_, D>,
pending: I,
verified: J,
strategy: &impl Strategy,
) -> Result<S::Certificate, Verification<S>>
where
S: Scheme,
R: CryptoRng,
D: Digest,
I: IntoIterator<Item = Attestation<S>>,
I::IntoIter: ExactSizeIterator + Send,
J: IntoIterator<Item = &'a Attestation<S>>,
J::IntoIter: Send,
{
let pending = pending.into_iter();
let verified = verified.into_iter();
if pending.len() == 0 {
return verify_then_assemble::<_, _, D, _, _>(
scheme, rng, subject, pending, verified, strategy,
);
}
let mut pending = strategy.map_collect_vec(pending, |attestation| {
let _ = attestation.signature.get();
attestation
});
let attestations = pending.iter().cloned().chain(verified.cloned());
if let Some(attestations) = NonEmpty::try_new(attestations)
&& let Ok(certificate) = scheme.assemble(attestations, strategy)
&& scheme.verify_certificate::<_, D>(rng, subject.clone(), &certificate, strategy)
{
return Ok(certificate);
}
if pending.len() == 1 {
return Err(scheme.verify_attestations::<_, D, _>(rng, subject, pending, strategy));
}
let rest = pending.split_off(pending.len().div_ceil(2));
let mut first_rng = ChaCha20Rng::from_rng(&mut *rng);
let mut rest_rng = ChaCha20Rng::from_rng(rng);
let first_subject = subject.clone();
let (mut result, rest) = strategy.join(
|| scheme.verify_attestations::<_, D, _>(&mut first_rng, first_subject, pending, strategy),
|| scheme.verify_attestations::<_, D, _>(&mut rest_rng, subject, rest, strategy),
);
result.verified.extend(rest.verified);
result.invalid.extend(rest.invalid);
Err(result)
}
#[derive(Clone, Debug)]
pub struct Scoped<S: Scheme> {
scheme: Arc<S>,
can_sign: bool,
}
impl<S: Scheme> Scoped<S> {
pub const fn verifier(scheme: Arc<S>) -> Self {
Self {
scheme,
can_sign: false,
}
}
pub const fn scheme(scheme: Arc<S>) -> Self {
Self {
scheme,
can_sign: true,
}
}
pub fn into_scheme(self) -> Option<Arc<S>> {
self.can_sign.then_some(self.scheme)
}
}
impl<S: Scheme> Verifier for Scoped<S> {
type Subject<'a, D: Digest> = S::Subject<'a, D>;
type Faults = S::Faults;
type PublicKey = S::PublicKey;
type Certificate = S::Certificate;
fn verify_certificate<R, D>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
certificate: &Self::Certificate,
strategy: &impl Strategy,
) -> bool
where
R: CryptoRng,
D: Digest,
{
self.scheme
.verify_certificate(rng, subject, certificate, strategy)
}
fn verify_certificates<'a, R, D, I>(
&self,
rng: &mut R,
certificates: NonEmpty<I>,
strategy: &impl Strategy,
) -> bool
where
R: CryptoRng,
D: Digest,
I: Iterator<Item = (Self::Subject<'a, D>, &'a Self::Certificate)>,
{
self.scheme.verify_certificates(rng, certificates, strategy)
}
fn is_batchable() -> bool {
S::is_batchable()
}
fn max_certificate_size(participants: u32) -> usize {
S::max_certificate_size(participants)
}
fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg {
self.scheme.certificate_codec_config()
}
fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg {
S::certificate_codec_config_unbounded()
}
}
pub trait Provider: Clone + Send + Sync + 'static {
type Scope: Clone + Send + Sync + 'static;
type Scheme: Scheme;
fn scoped(&self, scope: Self::Scope) -> Option<Scoped<Self::Scheme>>;
fn scheme(&self, scope: Self::Scope) -> Option<Arc<Self::Scheme>> {
self.scoped(scope).and_then(Scoped::into_scheme)
}
}
#[derive(Clone, Debug, PartialEq, Eq, Hash)]
pub struct Signers {
bitmap: BitMap<1>,
}
impl Signers {
pub(crate) fn max_encode_size(participants: u32) -> usize {
BitMap::<1>::max_encode_size(u64::from(participants))
}
pub fn new(
participants: u32,
signers: impl IntoIterator<Item = Participant>,
) -> Result<Self, AssemblyError> {
let mut bitmap = BitMap::zeroes(u64::from(participants));
for signer in signers.into_iter() {
let index = u64::from(signer.get());
if index >= bitmap.len() {
return Err(AssemblyError::UnknownSigner(signer));
}
if bitmap.get(index) {
return Err(AssemblyError::DuplicateSigner(signer));
}
bitmap.set(index, true);
}
Ok(Self { bitmap })
}
pub(crate) fn require(self, required: u32) -> Result<Self, AssemblyError> {
let found = u32::try_from(self.count()).expect("signer count exceeds u32::MAX");
if found < required {
return Err(AssemblyError::InsufficientAttestations(required, found));
}
Ok(self)
}
#[allow(clippy::len_without_is_empty)]
pub const fn len(&self) -> usize {
self.bitmap.len() as usize
}
pub fn count(&self) -> usize {
self.bitmap.count_ones() as usize
}
pub fn iter(&self) -> impl Iterator<Item = Participant> + '_ {
self.bitmap
.ones_iter()
.map(|index| Participant::from_usize(index as usize))
}
}
pub(crate) fn max_individual_certificate_size(participants: u32, signature_size: usize) -> usize {
Signers::max_encode_size(participants)
+ max_items_size(Widen::widen(participants), signature_size)
}
impl<'a, P, I> TryFrom<(&'a Set<P>, I)> for Signers
where
I: IntoIterator<Item = Participant>,
{
type Error = AssemblyError;
fn try_from((participants, signers): (&'a Set<P>, I)) -> Result<Self, Self::Error> {
let total = u32::try_from(participants.len()).expect("participant count exceeds u32::MAX");
Self::new(total, signers)
}
}
impl Write for Signers {
fn write(&self, writer: &mut impl BufMut) {
self.bitmap.write(writer);
}
}
impl EncodeSize for Signers {
fn encode_size(&self) -> usize {
self.bitmap.encode_size()
}
}
impl Read for Signers {
type Cfg = usize;
fn read_cfg(reader: &mut impl Buf, max_participants: &usize) -> Result<Self, CodecError> {
let bitmap = BitMap::read_cfg(reader, &(..=*max_participants as u64).into())?;
Ok(Self { bitmap })
}
}
#[cfg(feature = "arbitrary")]
impl arbitrary::Arbitrary<'_> for Signers {
fn arbitrary(u: &mut arbitrary::Unstructured<'_>) -> arbitrary::Result<Self> {
let participants = u.arbitrary_len::<u8>()? % 10;
let signer_count = u.arbitrary_len::<u8>()?.min(participants);
let signers = (0..u32::try_from(signer_count).expect("signer count exceeds u32::MAX"))
.map(Participant::new)
.collect::<Vec<_>>();
Ok(Self::new(participants.try_into().unwrap(), signers)
.expect("indices are unique and in range"))
}
}
#[derive(Clone, Debug)]
pub struct ConstantProvider<S: Scheme, Sc = ()> {
scheme: Arc<S>,
_scope: core::marker::PhantomData<Sc>,
}
impl<S: Scheme, Sc> ConstantProvider<S, Sc> {
pub fn new(scheme: S) -> Self {
Self {
scheme: Arc::new(scheme),
_scope: core::marker::PhantomData,
}
}
}
impl<S: Scheme, Sc: Clone + Send + Sync + 'static> crate::certificate::Provider
for ConstantProvider<S, Sc>
{
type Scope = Sc;
type Scheme = S;
fn scoped(&self, _: Sc) -> Option<Scoped<S>> {
Some(Scoped::scheme(self.scheme.clone()))
}
}
#[cfg(feature = "mocks")]
pub mod mocks;
#[cfg(test)]
mod tests {
use super::*;
#[cfg(feature = "bls12381")]
use crate::bls12381::{
dkg::feldman_desmedt as dkg,
primitives::{
group::Private as BlsPrivate,
ops::{compute_public, sign_proof_of_possession, verify_proof_of_possession},
sharing::Mode,
variant::{MinPk, MinSig, Variant},
},
};
use crate::{
Signer as _,
ed25519::{PrivateKey, Signature},
sha256::Digest as Sha256Digest,
};
use commonware_codec::{Decode, Encode, varint::MAX_U32_VARINT_SIZE};
use commonware_math::algebra::Random;
use commonware_parallel::Sequential;
use commonware_utils::{
N3f1, TryCollect, Widen, non_empty,
ordered::{Quorum as _, Set},
test_rng,
};
#[cfg(feature = "bls12381")]
use commonware_utils::{NZU32, ordered::BiMap, sync::Mutex};
#[cfg(feature = "bls12381")]
use core::sync::atomic::{AtomicUsize, Ordering};
use ed25519_fixture::{Scheme as Ed25519Scheme, TestSubject};
#[test]
fn test_max_individual_certificate_size() {
let mut previous = 0;
for n in [0, 1, 7, 8, 9, 127, 128] {
let signers = Signers::new(n, (0..n).map(Participant::new)).unwrap();
let signatures = vec![[0u8; Signature::SIZE]; Widen::widen(n)];
assert_eq!(Signers::max_encode_size(n), signers.encode().len());
let size = max_individual_certificate_size(n, Signature::SIZE);
assert_eq!(
size,
signers.encode().len() + MAX_U32_VARINT_SIZE + signatures.len() * Signature::SIZE
);
assert!(signers.encode().len() + signatures.encode().len() <= size);
assert!(size >= previous);
previous = size;
}
let n: usize = Widen::widen(u32::MAX);
assert_eq!(
max_individual_certificate_size(u32::MAX, Signature::SIZE),
u64::SIZE + n.div_ceil(8) + MAX_U32_VARINT_SIZE + n * Signature::SIZE,
);
}
#[test]
fn test_new_signers() {
let signers = Signers::new(6, [0, 3, 5].map(Participant::new)).unwrap();
let collected: Vec<_> = signers.iter().collect();
assert_eq!(
collected,
vec![0, 3, 5]
.into_iter()
.map(Participant::new)
.collect::<Vec<_>>()
);
assert_eq!(signers.count(), 3);
}
#[test]
fn test_new_out_of_bounds() {
assert_eq!(
Signers::new(4, [0, 4].map(Participant::new)),
Err(AssemblyError::UnknownSigner(Participant::new(4)))
);
}
#[test]
fn test_new_duplicate() {
assert_eq!(
Signers::new(4, [0, 0, 1].map(Participant::new)),
Err(AssemblyError::DuplicateSigner(Participant::new(0)))
);
}
#[test]
fn test_new_not_increasing() {
assert!(Signers::new(4, [2, 1].map(Participant::new)).is_ok());
}
#[test]
fn test_try_from_set_and_require() {
let participants = Set::from_iter_dedup(0..4);
let signers = Signers::try_from((&participants, [0, 2, 3].map(Participant::new)))
.unwrap()
.require(3)
.unwrap();
assert_eq!(signers.count(), 3);
assert_eq!(
Signers::try_from((&participants, [0, 2].map(Participant::new)))
.unwrap()
.require(3),
Err(AssemblyError::InsufficientAttestations(3, 2))
);
}
#[test]
fn test_try_from_set_checks_signer_bounds() {
let participants = Set::<u8>::default();
let signer = Participant::new(0);
assert_eq!(
Signers::try_from((&participants, [signer])),
Err(AssemblyError::UnknownSigner(signer))
);
}
#[test]
fn test_codec_round_trip() {
let signers = Signers::new(9, [1, 6].map(Participant::new)).unwrap();
let encoded = signers.encode();
let decoded = Signers::decode_cfg(encoded, &9).unwrap();
assert_eq!(decoded, signers);
}
#[test]
fn test_decode_respects_participant_limit() {
let signers = Signers::new(8, [0, 3, 7].map(Participant::new)).unwrap();
let encoded = signers.encode();
assert!(Signers::decode_cfg(encoded.clone(), &2).is_err());
assert!(Signers::decode_cfg(encoded.clone(), &8).is_ok());
assert!(Signers::decode_cfg(encoded, &10).is_ok());
}
mod ed25519_fixture {
use crate::{certificate::Subject, impl_certificate_ed25519};
use commonware_utils::N3f1;
#[derive(Copy, Clone, Debug)]
pub struct TestSubject {
pub message: &'static [u8],
}
impl Subject for TestSubject {
type Namespace = Vec<u8>;
fn namespace<'a>(&self, derived: &'a Self::Namespace) -> &'a [u8] {
derived
}
fn message(&self) -> bytes::Bytes {
bytes::Bytes::from_static(self.message)
}
}
impl_certificate_ed25519!(TestSubject, Vec<u8>, N3f1);
}
const NAMESPACE: &[u8] = b"test-bisect";
const MESSAGE: &[u8] = b"good message";
const BAD_MESSAGE: &[u8] = b"bad message";
fn make_certificate(
schemes: &[Ed25519Scheme],
message: &'static [u8],
) -> <Ed25519Scheme as Verifier>::Certificate {
let attestations: Vec<_> = schemes
.iter()
.filter_map(|s| s.sign::<Sha256Digest>(TestSubject { message }))
.collect();
schemes[0]
.assemble(non_empty![@attestations], &Sequential)
.expect("assembly failed")
}
fn setup_ed25519(n: u32) -> (Vec<Ed25519Scheme>, Ed25519Scheme) {
let mut rng = test_rng();
let private_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut rng)).collect();
let participants: Set<crate::ed25519::PublicKey> = private_keys
.iter()
.map(|sk| sk.public_key())
.try_collect()
.unwrap();
let signers: Vec<_> = private_keys
.into_iter()
.map(|sk| Ed25519Scheme::signer(NAMESPACE, participants.clone(), sk).unwrap())
.collect();
let verifier = Ed25519Scheme::verifier(NAMESPACE, participants);
(signers, verifier)
}
#[test]
fn membership_reports_index_and_signing() {
let (signers, verifier) = setup_ed25519(4);
let participants = verifier.participants().clone();
let signer = signers[0].me().unwrap();
let index = signer.index();
let key = participants.key(index).unwrap().clone();
let member = Ed25519Scheme::member(NAMESPACE, participants, &key).unwrap();
assert_eq!(member.me(), Some(Membership::Member(index)));
assert_eq!(member.me().unwrap().index(), index);
assert_eq!(member.me().unwrap().signer(), None);
assert_eq!(signer, Membership::Signer(index));
assert_eq!(signer.signer(), Some(index));
assert_eq!(verifier.me(), None);
}
#[test]
fn test_bisect_empty() {
let mut rng = test_rng();
let (_, verifier) = setup_ed25519(4);
let result =
verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &[], &Sequential);
assert!(result.is_empty());
}
#[test]
fn test_bisect_all_valid() {
let mut rng = test_rng();
let (schemes, verifier) = setup_ed25519(4);
let cert = make_certificate(&schemes, MESSAGE);
let good = TestSubject { message: MESSAGE };
let pairs: Vec<_> = (0..5).map(|_| (good, &cert)).collect();
let result =
verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
assert_eq!(result, vec![true; 5]);
}
#[test]
fn test_bisect_mixed() {
let mut rng = test_rng();
let (schemes, verifier) = setup_ed25519(4);
let cert = make_certificate(&schemes, MESSAGE);
let good = TestSubject { message: MESSAGE };
let bad = TestSubject {
message: BAD_MESSAGE,
};
let pairs = vec![
(good, &cert),
(bad, &cert),
(good, &cert),
(bad, &cert),
(good, &cert),
(good, &cert),
(bad, &cert),
(bad, &cert),
];
let expected = vec![true, false, true, false, true, true, false, false];
let result =
verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
assert_eq!(result, expected);
}
#[test]
fn test_bisect_all_invalid() {
let mut rng = test_rng();
let (schemes, verifier) = setup_ed25519(4);
let cert = make_certificate(&schemes, MESSAGE);
let bad = TestSubject {
message: BAD_MESSAGE,
};
let pairs: Vec<_> = (0..4).map(|_| (bad, &cert)).collect();
let result =
verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
assert_eq!(result, vec![false; 4]);
}
#[test]
fn test_bisect_single_valid() {
let mut rng = test_rng();
let (schemes, verifier) = setup_ed25519(4);
let cert = make_certificate(&schemes, MESSAGE);
let pairs = vec![(TestSubject { message: MESSAGE }, &cert)];
let result =
verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
assert_eq!(result, vec![true]);
}
#[test]
fn test_bisect_single_invalid() {
let mut rng = test_rng();
let (schemes, verifier) = setup_ed25519(4);
let cert = make_certificate(&schemes, MESSAGE);
let pairs = vec![(
TestSubject {
message: BAD_MESSAGE,
},
&cert,
)];
let result =
verifier.verify_certificates_bisect::<_, Sha256Digest>(&mut rng, &pairs, &Sequential);
assert_eq!(result, vec![false]);
}
#[test]
fn test_scoped_verifies_and_into_scheme() {
let mut rng = test_rng();
let (schemes, verifier) = setup_ed25519(4);
let cert = make_certificate(&schemes, MESSAGE);
let subject = TestSubject { message: MESSAGE };
let as_verifier = Scoped::verifier(Arc::new(verifier));
let as_scheme = Scoped::scheme(Arc::new(schemes[0].clone()));
assert!(as_verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
subject,
&cert,
&Sequential,
));
assert!(as_scheme.verify_certificate::<_, Sha256Digest>(
&mut rng,
subject,
&cert,
&Sequential,
));
let pairs = [(subject, &cert)];
assert!(as_verifier.verify_certificates::<_, Sha256Digest, _>(
&mut rng,
non_empty![@pairs.iter().copied()],
&Sequential,
));
assert_eq!(
<Scoped<Ed25519Scheme> as Verifier>::is_batchable(),
<Ed25519Scheme as Verifier>::is_batchable(),
);
assert_eq!(
as_verifier.certificate_codec_config(),
schemes[0].certificate_codec_config(),
);
for participants in [0, 4, 128] {
assert_eq!(
<Scoped<Ed25519Scheme> as Verifier>::max_certificate_size(participants),
<Ed25519Scheme as Verifier>::max_certificate_size(participants),
);
}
let _ = <Scoped<Ed25519Scheme> as Verifier>::certificate_codec_config_unbounded();
assert!(as_verifier.into_scheme().is_none());
assert!(as_scheme.into_scheme().is_some());
let provider = ConstantProvider::<_, ()>::new(schemes[0].clone());
assert!(provider.scoped(()).is_some());
assert!(provider.scheme(()).is_some());
}
#[cfg(feature = "bls12381")]
#[allow(dead_code)]
mod threshold {
use super::TestSubject;
use crate::impl_certificate_bls12381_threshold;
use commonware_utils::N3f1;
impl_certificate_bls12381_threshold!(TestSubject, Vec<u8>, N3f1);
}
#[cfg(feature = "bls12381")]
#[allow(dead_code)]
mod multisig {
use super::TestSubject;
use crate::impl_certificate_bls12381_multisig;
use commonware_utils::N3f1;
impl_certificate_bls12381_multisig!(TestSubject, Vec<u8>, N3f1);
}
const SUBJECT: TestSubject = TestSubject {
message: b"subject",
};
const OTHER_SUBJECT: TestSubject = TestSubject {
message: b"other-subject",
};
#[cfg(feature = "bls12381")]
const THRESHOLD_NAMESPACE: &[u8] =
b"_COMMONWARE_CRYPTOGRAPHY_CERTIFICATE_THRESHOLD_OPTIMISTIC_ASSEMBLE";
#[cfg(feature = "bls12381")]
const MULTISIG_NAMESPACE: &[u8] =
b"_COMMONWARE_CRYPTOGRAPHY_CERTIFICATE_MULTISIG_OPTIMISTIC_ASSEMBLE";
#[cfg(feature = "bls12381")]
const MULTISIG_POP_NAMESPACE: &[u8] =
b"_COMMONWARE_CRYPTOGRAPHY_CERTIFICATE_MULTISIG_OPTIMISTIC_ASSEMBLE_POP";
#[cfg(feature = "bls12381")]
fn threshold_signers<V: Variant>(
rng: &mut impl CryptoRng,
n: u32,
) -> Vec<threshold::Scheme<crate::ed25519::PublicKey, V>> {
let participants: Set<crate::ed25519::PublicKey> = (0..n)
.map(|_| PrivateKey::random(&mut *rng).public_key())
.try_collect()
.unwrap();
let (polynomial, shares) =
dkg::deal_anonymous::<V, N3f1>(&mut *rng, Mode::NonZeroCounter, NZU32!(n));
shares
.into_iter()
.map(|share| {
threshold::Scheme::signer(
THRESHOLD_NAMESPACE,
participants.clone(),
polynomial.clone(),
share,
)
.unwrap()
})
.collect()
}
#[cfg(feature = "bls12381")]
fn multisig_signers<V: Variant>(
rng: &mut impl CryptoRng,
n: u32,
) -> Vec<multisig::Scheme<crate::ed25519::PublicKey, V>> {
let identity_keys: Vec<_> = (0..n).map(|_| PrivateKey::random(&mut *rng)).collect();
let signing_keys: Vec<_> = (0..n).map(|_| BlsPrivate::random(&mut *rng)).collect();
let signing_publics: Vec<_> = signing_keys.iter().map(compute_public::<V>).collect();
for (private, public) in signing_keys.iter().zip(&signing_publics) {
let proof = sign_proof_of_possession::<V>(private, MULTISIG_POP_NAMESPACE);
verify_proof_of_possession::<V>(public, MULTISIG_POP_NAMESPACE, &proof)
.expect("proof of possession must verify");
}
let participants: BiMap<_, _> = identity_keys
.iter()
.map(|key| key.public_key())
.zip(signing_publics)
.try_collect()
.unwrap();
signing_keys
.into_iter()
.map(|private| {
multisig::Scheme::signer(MULTISIG_NAMESPACE, participants.clone(), private)
.expect("signer must be a participant")
})
.collect()
}
#[cfg(feature = "bls12381")]
fn threshold_success<V: Variant>() {
let mut rng = test_rng();
let schemes = threshold_signers::<V>(&mut rng, 5);
let quorum = N3f1::quorum(schemes.len()) as usize;
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|scheme| scheme.sign::<Sha256Digest>(SUBJECT).unwrap())
.collect();
let expected = schemes[0]
.assemble(non_empty![@attestations.clone()], &Sequential)
.unwrap();
let prior: Vec<_> = attestations[..1]
.iter()
.cloned()
.map(Recording::outer)
.collect();
let calls = Arc::new(RecordingCalls::default());
let verifier = Recording {
inner: schemes[0].clone(),
calls: Arc::clone(&calls),
};
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
attestations[1..].iter().cloned().map(Recording::outer),
&prior,
&Sequential,
);
let certificate = result.ok().expect("valid quorum must certify");
assert_eq!(certificate, expected);
assert!(calls.attestations.lock().is_empty());
assert!(schemes[0].verify_certificate::<_, Sha256Digest>(
&mut rng,
SUBJECT,
&certificate,
&Sequential,
));
assert!(!schemes[0].verify_certificate::<_, Sha256Digest>(
&mut rng,
OTHER_SUBJECT,
&certificate,
&Sequential,
));
let unused_bad = schemes[quorum].sign::<Sha256Digest>(OTHER_SUBJECT).unwrap();
let pending: Vec<_> = attestations
.into_iter()
.chain([unused_bad])
.map(Recording::outer)
.collect();
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
std::iter::empty(),
&Sequential,
);
assert_eq!(result.ok(), Some(expected));
assert!(calls.attestations.lock().is_empty());
}
#[cfg(feature = "bls12381")]
#[test]
fn test_threshold_recovers_exact_certificate_with_verified_attestations() {
threshold_success::<MinPk>();
threshold_success::<MinSig>();
}
#[cfg(feature = "bls12381")]
fn multisig_optimistic_assemble_uses_one_certificate_check<V: Variant>() {
let mut rng = test_rng();
let schemes = multisig_signers::<V>(&mut rng, 5);
let quorum = N3f1::quorum(schemes.len()) as usize;
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|scheme| scheme.sign::<Sha256Digest>(SUBJECT).unwrap())
.collect();
let prior: Vec<_> = attestations[..1]
.iter()
.cloned()
.map(Recording::outer)
.collect();
let calls = Arc::new(RecordingCalls::default());
let verifier = Recording {
inner: schemes[0].clone(),
calls: Arc::clone(&calls),
};
let certificate = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
attestations[1..].iter().cloned().map(Recording::outer),
&prior,
&Sequential,
)
.ok()
.expect("valid mixed quorum must certify");
assert_eq!(calls.certificates.load(Ordering::Relaxed), 1);
assert!(calls.attestations.lock().is_empty());
assert!(schemes[0].verify_certificate::<_, Sha256Digest>(
&mut rng,
SUBJECT,
&certificate,
&Sequential,
));
assert!(!schemes[0].verify_certificate::<_, Sha256Digest>(
&mut rng,
OTHER_SUBJECT,
&certificate,
&Sequential,
));
}
#[cfg(feature = "bls12381")]
#[test]
fn test_multisig_optimistic_assemble_uses_one_certificate_check() {
multisig_optimistic_assemble_uses_one_certificate_check::<MinPk>();
multisig_optimistic_assemble_uses_one_certificate_check::<MinSig>();
}
#[test]
fn test_optimistic_assemble_individual_inputs() {
let mut rng = test_rng();
let (schemes, verifier) = setup_ed25519(5);
let empty = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
Vec::new(),
std::iter::empty(),
&Sequential,
);
let verification = empty.unwrap_err();
assert!(verification.verified.is_empty());
assert!(verification.invalid.is_empty());
let pending: Vec<_> = schemes
.iter()
.map(|scheme| scheme.sign::<Sha256Digest>(SUBJECT).unwrap())
.collect();
let certificate = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
std::iter::empty(),
&Sequential,
)
.ok()
.expect("valid quorum must certify");
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
SUBJECT,
&certificate,
&Sequential,
));
let pending: Vec<_> = schemes
.iter()
.enumerate()
.map(|(i, scheme)| {
scheme
.sign::<Sha256Digest>(if i == 0 { OTHER_SUBJECT } else { SUBJECT })
.unwrap()
})
.collect();
let signers: Vec<_> = pending.iter().map(|a| a.signer).collect();
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
std::iter::empty(),
&Sequential,
);
let verification = result.unwrap_err();
assert_eq!(verification.invalid, vec![signers[0]]);
assert_eq!(
verification
.verified
.iter()
.map(|a| a.signer)
.collect::<Vec<_>>(),
signers[1..]
);
let certificate = verifier
.assemble(non_empty![@verification.verified], &Sequential)
.unwrap();
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
SUBJECT,
&certificate,
&Sequential,
));
}
#[test]
fn test_optimistic_assemble_individual_quorum() {
let mut rng = test_rng();
let (schemes, verifier) = setup_ed25519(5);
let quorum = N3f1::quorum(schemes.len()) as usize;
let attestations: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| s.sign::<Sha256Digest>(SUBJECT).unwrap())
.collect();
for count in [0, quorum - 1, quorum] {
for pending in [0, count] {
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
attestations[..pending].iter().cloned(),
&attestations[pending..count],
&Sequential,
);
assert_eq!(result.is_ok(), count == quorum);
match result {
Ok(certificate) => assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
SUBJECT,
&certificate,
&Sequential,
)),
Err(verification) => {
assert_eq!(verification.verified, attestations[..pending]);
assert!(verification.invalid.is_empty());
}
}
}
}
}
#[test]
fn test_optimistic_assemble_empty_committee() {
let mut rng = test_rng();
let (_, verifier) = setup_ed25519(0);
let (schemes, _) = setup_ed25519(1);
let unknown = schemes[0].sign::<Sha256Digest>(SUBJECT).unwrap();
for pending in [Vec::new(), vec![unknown]] {
let expected: Vec<_> = pending.iter().map(|a| a.signer).collect();
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
std::iter::empty(),
&Sequential,
);
let verification = result.unwrap_err();
assert!(verification.verified.is_empty());
assert_eq!(verification.invalid, expected);
}
}
#[cfg(feature = "bls12381")]
#[derive(Debug, Default)]
struct RecordingCalls {
attestations: Mutex<Vec<Vec<Participant>>>,
assemblies: AtomicUsize,
certificates: AtomicUsize,
}
#[cfg(feature = "bls12381")]
#[derive(Clone, Debug)]
struct Recording<S> {
inner: S,
calls: Arc<RecordingCalls>,
}
#[cfg(feature = "bls12381")]
impl<S: Scheme> Recording<S> {
fn inner(attestation: Attestation<Self>) -> Attestation<S> {
Attestation {
signer: attestation.signer,
signature: attestation.signature,
}
}
fn outer(attestation: Attestation<S>) -> Attestation<Self> {
Attestation {
signer: attestation.signer,
signature: attestation.signature,
}
}
}
#[cfg(feature = "bls12381")]
impl<S: Scheme> Verifier for Recording<S> {
type Subject<'a, D: Digest> = S::Subject<'a, D>;
type Faults = S::Faults;
type PublicKey = S::PublicKey;
type Certificate = S::Certificate;
fn verify_certificate<R, D>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
certificate: &Self::Certificate,
strategy: &impl Strategy,
) -> bool
where
R: CryptoRng,
D: Digest,
{
self.calls.certificates.fetch_add(1, Ordering::Relaxed);
self.inner
.verify_certificate(rng, subject, certificate, strategy)
}
fn is_batchable() -> bool {
S::is_batchable()
}
fn max_certificate_size(participants: u32) -> usize {
S::max_certificate_size(participants)
}
fn certificate_codec_config(&self) -> <Self::Certificate as Read>::Cfg {
self.inner.certificate_codec_config()
}
fn certificate_codec_config_unbounded() -> <Self::Certificate as Read>::Cfg {
S::certificate_codec_config_unbounded()
}
}
#[cfg(feature = "bls12381")]
impl<S: Scheme> Scheme for Recording<S> {
type Signature = S::Signature;
fn me(&self) -> Option<Membership> {
self.inner.me()
}
fn participants(&self) -> &Set<Self::PublicKey> {
self.inner.participants()
}
fn sign<D: Digest>(&self, subject: Self::Subject<'_, D>) -> Option<Attestation<Self>> {
self.inner.sign(subject).map(Self::outer)
}
fn verify_attestation<R, D>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
attestation: &Attestation<Self>,
strategy: &impl Strategy,
) -> bool
where
R: CryptoRng,
D: Digest,
{
self.calls
.attestations
.lock()
.push(vec![attestation.signer]);
self.inner
.verify_attestation(rng, subject, &Self::inner(attestation.clone()), strategy)
}
fn verify_attestations<R, D, I>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
attestations: I,
strategy: &impl Strategy,
) -> Verification<Self>
where
R: CryptoRng,
D: Digest,
I: IntoIterator<Item = Attestation<Self>>,
I::IntoIter: Send,
{
let attestations: Vec<_> = attestations.into_iter().collect();
self.calls
.attestations
.lock()
.push(attestations.iter().map(|a| a.signer).collect());
let result = self.inner.verify_attestations(
rng,
subject,
attestations.into_iter().map(Self::inner),
strategy,
);
Verification::new(
result.verified.into_iter().map(Self::outer).collect(),
result.invalid,
)
}
fn optimistic_assemble<'a, R, D, I, J>(
&self,
rng: &mut R,
subject: Self::Subject<'_, D>,
pending: I,
verified: J,
strategy: &impl Strategy,
) -> Result<Self::Certificate, Verification<Self>>
where
R: CryptoRng,
D: Digest,
I: IntoIterator<Item = Attestation<Self>>,
I::IntoIter: ExactSizeIterator + Send,
J: IntoIterator<Item = &'a Attestation<Self>>,
J::IntoIter: Send,
{
crate::certificate::optimistic_assemble::<Self, _, D, _, _>(
self, rng, subject, pending, verified, strategy,
)
}
fn assemble<I>(
&self,
attestations: NonEmpty<I>,
strategy: &impl Strategy,
) -> Result<Self::Certificate, AssemblyError>
where
I: Iterator<Item = Attestation<Self>> + Send,
{
self.calls.assemblies.fetch_add(1, Ordering::Relaxed);
self.inner.assemble(
non_empty![@attestations.into_iter().map(Self::inner)],
strategy,
)
}
fn is_attributable() -> bool {
S::is_attributable()
}
}
#[cfg(feature = "bls12381")]
fn expected_halves(len: usize) -> Vec<Vec<Participant>> {
let middle = len.div_ceil(2);
let first = (0..middle).map(Participant::from_usize).collect();
if middle == len {
vec![first]
} else {
vec![first, (middle..len).map(Participant::from_usize).collect()]
}
}
#[cfg(feature = "bls12381")]
#[test]
fn test_rejected_certificate_verifies_pending_halves_directly() {
for n in [1, 5, 7] {
let mut rng = test_rng();
let schemes = threshold_signers::<MinPk>(&mut rng, n);
let calls = Arc::new(RecordingCalls::default());
let verifier = Recording {
inner: schemes[0].clone(),
calls: Arc::clone(&calls),
};
let quorum = N3f1::quorum(n) as usize;
let pending: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| Recording::outer(s.sign::<Sha256Digest>(OTHER_SUBJECT).unwrap()))
.collect();
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
std::iter::empty(),
&Sequential,
);
let verification = result.unwrap_err();
assert!(verification.verified.is_empty());
assert_eq!(
verification.invalid,
(0..quorum).map(Participant::from_usize).collect::<Vec<_>>()
);
assert_eq!(*calls.attestations.lock(), expected_halves(quorum));
}
}
#[cfg(feature = "bls12381")]
#[test]
fn test_malformed_assembly_falls_back_to_pending_halves() {
let mut rng = test_rng();
let schemes = threshold_signers::<MinPk>(&mut rng, 5);
let quorum = N3f1::quorum(schemes.len()) as usize;
for count in [quorum, schemes.len()] {
let calls = Arc::new(RecordingCalls::default());
let verifier = Recording {
inner: schemes[0].clone(),
calls: Arc::clone(&calls),
};
let mut pending: Vec<_> = schemes
.iter()
.take(count)
.map(|s| Recording::outer(s.sign::<Sha256Digest>(SUBJECT).unwrap()))
.collect();
let mut malformed = Bytes::from_static(&[0]);
pending[0].signature = Lazy::deferred(&mut malformed, ());
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
std::iter::empty(),
&Sequential,
);
let verification = result.unwrap_err();
assert_eq!(verification.verified.len(), count - 1);
assert_eq!(verification.invalid, vec![Participant::new(0)]);
assert_eq!(*calls.attestations.lock(), expected_halves(count));
if count > quorum {
let certificate = verifier
.assemble(non_empty![@verification.verified], &Sequential)
.unwrap();
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
SUBJECT,
&certificate,
&Sequential,
));
}
}
}
#[cfg(feature = "bls12381")]
#[test]
fn test_optimistic_assemble_retains_verified_inputs() {
let mut rng = test_rng();
let schemes = threshold_signers::<MinPk>(&mut rng, 5);
let mut prior: Vec<_> = schemes[..2]
.iter()
.map(|s| Recording::outer(s.sign::<Sha256Digest>(SUBJECT).unwrap()))
.collect();
let accepted = Recording::outer(schemes[2].sign::<Sha256Digest>(SUBJECT).unwrap());
let rejected = Recording::outer(schemes[3].sign::<Sha256Digest>(OTHER_SUBJECT).unwrap());
let invalid = rejected.signer;
let calls = Arc::new(RecordingCalls::default());
let verifier = Recording {
inner: schemes[0].clone(),
calls: Arc::clone(&calls),
};
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
vec![accepted.clone(), rejected],
&prior,
&Sequential,
);
let verification = result.unwrap_err();
assert_eq!(verification.verified, vec![accepted.clone()]);
assert_eq!(verification.invalid, vec![invalid]);
assert_eq!(
*calls.attestations.lock(),
vec![vec![accepted.signer], vec![invalid]]
);
prior.extend(verification.verified);
calls.attestations.lock().clear();
let replacement = Recording::outer(schemes[4].sign::<Sha256Digest>(SUBJECT).unwrap());
let signer = replacement.signer;
let verification = verifier.verify_attestations::<_, Sha256Digest, _>(
&mut rng,
SUBJECT,
vec![replacement.clone()],
&Sequential,
);
assert_eq!(verification.verified, vec![replacement]);
assert!(verification.invalid.is_empty());
assert_eq!(*calls.attestations.lock(), vec![vec![signer]]);
prior.extend(verification.verified);
let certificate = verifier.assemble(non_empty![@prior], &Sequential).unwrap();
assert!(verifier.verify_certificate::<_, Sha256Digest>(
&mut rng,
SUBJECT,
&certificate,
&Sequential,
));
}
#[cfg(feature = "bls12381")]
#[test]
fn test_optimistic_assemble_with_verified_votes() {
let mut rng = test_rng();
let schemes = threshold_signers::<MinPk>(&mut rng, 5);
let quorum = N3f1::quorum(schemes.len()) as usize;
let attestations: Vec<_> = schemes[..quorum]
.iter()
.map(|scheme| scheme.sign::<Sha256Digest>(SUBJECT).unwrap())
.collect();
for pending_len in [0, 1, 2] {
let (prior, pending) = attestations.split_at(quorum - pending_len);
let prior = schemes[0].verify_attestations::<_, Sha256Digest, _>(
&mut rng,
SUBJECT,
prior.iter().cloned(),
&Sequential,
);
assert!(prior.invalid.is_empty());
let prior: Vec<_> = prior.verified.into_iter().map(Recording::outer).collect();
let calls = Arc::new(RecordingCalls::default());
let verifier = Recording {
inner: schemes[0].clone(),
calls: Arc::clone(&calls),
};
let certificate = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending.iter().cloned().map(Recording::outer),
&prior,
&Sequential,
)
.ok()
.expect("verified quorum must certify");
assert_eq!(calls.assemblies.load(Ordering::Relaxed), 1);
assert_eq!(
calls.certificates.load(Ordering::Relaxed),
usize::from(pending_len != 0)
);
assert!(calls.attestations.lock().is_empty());
assert!(schemes[0].verify_certificate::<_, Sha256Digest>(
&mut rng,
SUBJECT,
&certificate,
&Sequential,
));
}
}
#[cfg(feature = "bls12381")]
#[test]
fn test_optimistic_assemble_retries_with_replacements() {
let mut rng = test_rng();
let schemes = threshold_signers::<MinPk>(&mut rng, 10);
let quorum = N3f1::quorum(schemes.len()) as usize;
for failures in [1, 2] {
let calls = Arc::new(RecordingCalls::default());
let verifier = Recording {
inner: schemes[0].clone(),
calls: Arc::clone(&calls),
};
let own = schemes[0].verify_attestations::<_, Sha256Digest, _>(
&mut rng,
SUBJECT,
[schemes[0].sign::<Sha256Digest>(SUBJECT).unwrap()],
&Sequential,
);
assert!(own.invalid.is_empty());
let mut prior: Vec<_> = own.verified.into_iter().map(Recording::outer).collect();
let pending: Vec<_> = schemes[1..quorum]
.iter()
.enumerate()
.map(|(index, scheme)| {
let subject = if index + 1 >= quorum - failures {
OTHER_SUBJECT
} else {
SUBJECT
};
Recording::outer(scheme.sign::<Sha256Digest>(subject).unwrap())
})
.collect();
let result = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
&prior,
&Sequential,
)
.unwrap_err();
assert_eq!(result.invalid.len(), failures);
prior.extend(result.verified);
assert_eq!(prior.len(), quorum - failures);
let pending: Vec<_> = schemes[quorum..quorum + failures]
.iter()
.enumerate()
.map(|(index, scheme)| {
let subject = if index + 1 == failures {
OTHER_SUBJECT
} else {
SUBJECT
};
Recording::outer(scheme.sign::<Sha256Digest>(subject).unwrap())
})
.collect();
let signers: Vec<_> = pending.iter().map(|vote| vote.signer).collect();
let invalid = *signers.last().unwrap();
calls.attestations.lock().clear();
calls.assemblies.store(0, Ordering::Relaxed);
calls.certificates.store(0, Ordering::Relaxed);
let result = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
&prior,
&Sequential,
)
.unwrap_err();
assert_eq!(result.invalid, vec![invalid]);
assert_eq!(result.verified.len(), failures - 1);
assert_eq!(calls.assemblies.load(Ordering::Relaxed), 1);
assert_eq!(calls.certificates.load(Ordering::Relaxed), 1);
assert_eq!(
*calls.attestations.lock(),
signers
.into_iter()
.map(|signer| vec![signer])
.collect::<Vec<_>>()
);
prior.extend(result.verified);
assert_eq!(prior.len(), quorum - 1);
let replacement = Recording::outer(
schemes[quorum + failures]
.sign::<Sha256Digest>(SUBJECT)
.unwrap(),
);
calls.attestations.lock().clear();
calls.assemblies.store(0, Ordering::Relaxed);
calls.certificates.store(0, Ordering::Relaxed);
let certificate = verifier
.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
[replacement],
&prior,
&Sequential,
)
.ok()
.expect("valid replacement must certify");
assert_eq!(calls.assemblies.load(Ordering::Relaxed), 1);
assert_eq!(calls.certificates.load(Ordering::Relaxed), 1);
assert!(calls.attestations.lock().is_empty());
assert!(schemes[0].verify_certificate::<_, Sha256Digest>(
&mut rng,
SUBJECT,
&certificate,
&Sequential,
));
assert!(!schemes[0].verify_certificate::<_, Sha256Digest>(
&mut rng,
OTHER_SUBJECT,
&certificate,
&Sequential,
));
}
}
#[cfg(feature = "bls12381")]
#[test]
fn test_optimistic_assemble_below_quorum() {
let mut rng = test_rng();
let schemes = threshold_signers::<MinPk>(&mut rng, 5);
let pending: Vec<_> = schemes[..2]
.iter()
.map(|s| Recording::outer(s.sign::<Sha256Digest>(SUBJECT).unwrap()))
.collect();
let expected = pending.clone();
let calls = Arc::new(RecordingCalls::default());
let verifier = Recording {
inner: schemes[0].clone(),
calls: Arc::clone(&calls),
};
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
std::iter::empty(),
&Sequential,
);
let verification = result.unwrap_err();
assert_eq!(verification.verified, expected);
assert!(verification.invalid.is_empty());
assert_eq!(*calls.attestations.lock(), expected_halves(expected.len()));
}
#[cfg(feature = "bls12381")]
#[test]
fn test_optimistic_assemble_unknown_pending_signer() {
let mut rng = test_rng();
let schemes = threshold_signers::<MinPk>(&mut rng, 5);
let quorum = N3f1::quorum(schemes.len()) as usize;
let mut pending: Vec<_> = schemes
.iter()
.take(quorum)
.map(|s| Recording::outer(s.sign::<Sha256Digest>(SUBJECT).unwrap()))
.collect();
pending[0].signer = Participant::new(999);
let calls = Arc::new(RecordingCalls::default());
let verifier = Recording {
inner: schemes[0].clone(),
calls: Arc::clone(&calls),
};
let result = verifier.optimistic_assemble::<_, Sha256Digest, _, _>(
&mut rng,
SUBJECT,
pending,
std::iter::empty(),
&Sequential,
);
let verification = result.unwrap_err();
assert_eq!(verification.verified.len(), quorum - 1);
assert_eq!(verification.invalid, vec![Participant::new(999)]);
assert_eq!(
*calls.attestations.lock(),
vec![
vec![Participant::new(999), Participant::new(1)],
vec![Participant::new(2), Participant::new(3)],
]
);
}
#[cfg(feature = "arbitrary")]
mod conformance {
use super::{ed25519_fixture::Scheme, *};
use commonware_codec::conformance::CodecConformance;
commonware_conformance::conformance_tests! {
CodecConformance<Signers>,
CodecConformance<Attestation<Scheme>>,
}
}
}