#![allow(clippy::type_complexity)]
use crate::{
error::{blst_err_to_atms, AtmsError},
merkle_tree::{BatchPath, MerkleTree, MerkleTreeCommitment},
multi_sig::{PublicKey, PublicKeyPoP, Signature},
};
use blake2::Digest;
use digest::FixedOutput;
use std::convert::TryFrom;
use std::{
collections::{HashMap, HashSet},
fmt::Debug,
};
#[derive(Debug)]
pub struct Avk<H>
where
H: Digest,
{
aggregate_key: PublicKey,
mt_commitment: MerkleTreeCommitment<H>,
nr_parties: usize,
}
impl<H: Digest> PartialEq for Avk<H> {
fn eq(&self, other: &Self) -> bool {
self.nr_parties == other.nr_parties
&& self.mt_commitment.value == other.mt_commitment.value
&& self.aggregate_key == other.aggregate_key
}
}
impl<H: Digest> Eq for Avk<H> {}
impl<H> Avk<H>
where
H: Digest + FixedOutput,
{
pub fn check(&self, keys: &[PublicKeyPoP]) -> Result<(), AtmsError> {
let akey2: Registration<H> = Registration::new(keys)?;
if self == &akey2.to_avk() {
return Ok(());
}
Err(AtmsError::InvalidAggregation)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut result = Vec::with_capacity(48 + 4 + H::output_size());
result.extend_from_slice(&self.aggregate_key.to_bytes());
result.extend_from_slice(
&u32::try_from(self.nr_parties)
.expect("Length must fit in u32")
.to_be_bytes(),
);
result.extend_from_slice(&self.mt_commitment.to_bytes());
result
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AtmsError> {
let mut nr_bytes = [0u8; 4];
nr_bytes.copy_from_slice(&bytes[48..52]);
let aggregate_key = PublicKey::from_bytes(bytes)?;
let nr_parties = usize::try_from(u32::from_be_bytes(nr_bytes))
.expect("Library should be built in 32 bit targets or higher");
let mt_commitment = MerkleTreeCommitment::from_bytes(&bytes[52..])?;
Ok(Self {
aggregate_key,
mt_commitment,
nr_parties,
})
}
}
#[derive(Debug)]
pub struct Registration<H>
where
H: Digest + FixedOutput,
{
aggregate_key: PublicKey,
tree: MerkleTree<H>,
leaf_map: HashMap<usize, PublicKey>,
}
#[derive(Debug)]
pub struct AggregateSig<H>
where
H: Digest + FixedOutput,
{
aggregate: Signature,
pub keys: Vec<PublicKey>,
pub batch_proof: Option<BatchPath<H>>,
}
impl<H> Registration<H>
where
H: Digest + FixedOutput,
{
pub fn new(keys_pop: &[PublicKeyPoP]) -> Result<Self, AtmsError> {
let mut checked_keys: Vec<PublicKey> = Vec::with_capacity(keys_pop.len());
for key_pop in keys_pop {
checked_keys.push(key_pop.verify()?);
}
let aggregate_key = checked_keys.iter().sum();
checked_keys.sort();
let mut tree_vec = Vec::with_capacity(keys_pop.len());
let mut leaf_map = HashMap::new();
for (index, &key) in checked_keys.iter().enumerate() {
if leaf_map.insert(index, key).is_some() {
return Err(AtmsError::RegisterExistingKey(key));
}
tree_vec.push(key.0.compress().to_vec());
}
Ok(Registration {
aggregate_key,
tree: MerkleTree::create(&tree_vec),
leaf_map,
})
}
pub fn get_index(&self, pk: &PublicKey) -> Vec<usize> {
let mut indices = Vec::with_capacity(self.leaf_map.len());
for (&idx, ®_pk) in self.leaf_map.iter() {
if reg_pk == *pk {
indices.push(idx);
}
}
indices
}
pub fn to_avk(&self) -> Avk<H> {
Avk {
aggregate_key: self.aggregate_key,
mt_commitment: self.tree.to_commitment(),
nr_parties: self.leaf_map.len(),
}
}
#[allow(dead_code)]
fn to_bytes(&self) -> Vec<u8> {
let mut result = Vec::with_capacity(
48 + 4 + self.leaf_map.len() * (48 + 4) + 4 + self.tree.nodes.len() * H::output_size(),
);
result.extend_from_slice(&self.aggregate_key.to_bytes());
let len = u32::try_from(self.leaf_map.len()).expect("Length must fit in u32");
result.extend_from_slice(&len.to_be_bytes());
for (&index, pk) in &self.leaf_map {
result.extend_from_slice(&pk.to_bytes());
result.extend_from_slice(
&u32::try_from(index)
.expect("Index must fit in u32")
.to_be_bytes(),
);
}
result.extend_from_slice(&self.tree.to_bytes());
result
}
#[allow(dead_code)]
fn from_bytes(bytes: &[u8]) -> Result<Self, AtmsError> {
let aggregate_key = PublicKey::from_bytes(bytes)?;
let mut len_bytes = [0u8; 4];
len_bytes.copy_from_slice(&bytes[48..52]);
let nr_parties = usize::try_from(u32::from_be_bytes(len_bytes))
.expect("Library should be build in 32 bit targets or higher");
let hm_element_size = 48 + 4; let hm_offset = 52;
let mut leaf_map = HashMap::new();
for i in 0..nr_parties {
let mut idx_bytes = [0u8; 4];
idx_bytes.copy_from_slice(
&bytes[hm_offset + hm_element_size * i + 48..hm_offset + hm_element_size * i + 52],
);
leaf_map.insert(
usize::try_from(u32::from_be_bytes(idx_bytes))
.expect("Library should be build in 32 bit targets or higher"),
PublicKey::from_bytes(&bytes[hm_offset + hm_element_size * i..])?,
);
}
let mt_offset = hm_offset + hm_element_size * nr_parties;
let tree = MerkleTree::from_bytes(&bytes[mt_offset..])?;
Ok(Self {
aggregate_key,
tree,
leaf_map,
})
}
}
impl<H> AggregateSig<H>
where
H: Digest + FixedOutput,
{
pub fn new(
registration: &Registration<H>,
sigs: &[(usize, Signature)],
msg: &[u8],
) -> Result<Self, AtmsError> {
let mut unique_sigs = sigs.to_vec();
unique_sigs.sort_unstable();
unique_sigs.dedup();
let signers = unique_sigs.iter().map(|(k, _)| k).collect::<HashSet<_>>();
let mut non_signer_indices = Vec::with_capacity(registration.tree.n - unique_sigs.len());
let mut keys = (0..registration.tree.n)
.into_iter()
.filter_map(|k| {
if !signers.contains(&k) {
non_signer_indices.push(k);
Some(*registration.leaf_map.get(&k)?)
} else {
None
}
})
.collect::<Vec<_>>();
let aggregate: Signature = unique_sigs
.iter()
.map(|&(index, s)| {
if let Some(pk) = registration.leaf_map.get(&index) {
s.verify(pk, msg)?;
Ok(s)
} else {
Err(AtmsError::NonRegisteredParticipant)
}
})
.collect::<Result<Vec<Signature>, AtmsError>>()?
.iter()
.sum();
let batch_proof = if keys.is_empty() {
None
} else {
keys.sort_unstable();
non_signer_indices.sort_unstable();
Some(registration.tree.get_batched_path(non_signer_indices))
};
Ok(Self {
aggregate,
keys,
batch_proof,
})
}
pub fn verify(&self, msg: &[u8], avk: &Avk<H>, threshold: usize) -> Result<(), AtmsError> {
assert!(threshold > (avk.nr_parties / 2));
let mut unique_non_signers = HashSet::new();
let mut non_signing_size = 0;
if !self.keys.is_empty() {
if let Some(proof) = &self.batch_proof {
let compressed_keys = self
.keys
.iter()
.map(|k| k.0.compress().to_vec())
.collect::<Vec<_>>();
avk.mt_commitment.check_batched(&compressed_keys, proof)?;
for non_signer in &self.keys {
non_signing_size += 1;
if !unique_non_signers.insert(non_signer) {
return Err(AtmsError::FoundDuplicates(*non_signer));
}
}
}
if non_signing_size > avk.nr_parties - threshold {
return Err(AtmsError::TooMuchOutstandingSigners(non_signing_size));
}
}
let final_key = avk.aggregate_key - unique_non_signers.into_iter().sum();
blst_err_to_atms(
self.aggregate
.0
.verify(false, msg, &[], &[], &final_key.0, false),
)
}
pub fn to_bytes(&self) -> Vec<u8> {
let mut aggregate_sig_bytes = Vec::new();
let nr_non_signers = u32::try_from(self.keys.len()).expect("Length must fit in u32");
aggregate_sig_bytes.extend_from_slice(&nr_non_signers.to_be_bytes());
aggregate_sig_bytes.extend_from_slice(&self.aggregate.to_bytes());
for key in &self.keys {
aggregate_sig_bytes.extend_from_slice(&key.to_bytes());
}
if let Some(proof) = &self.batch_proof {
aggregate_sig_bytes.extend_from_slice(&proof.to_bytes());
}
aggregate_sig_bytes
}
pub fn from_bytes(bytes: &[u8]) -> Result<Self, AtmsError> {
let mut u64_bytes = [0u8; 4];
u64_bytes.copy_from_slice(&bytes[..4]);
let non_signers = usize::try_from(u32::from_be_bytes(u64_bytes))
.expect("Library should be built in 32 bit targets or higher");
let aggregate = Signature::from_bytes(&bytes[4..])?;
let mut keys: Vec<PublicKey> = Vec::with_capacity(non_signers);
for i in 0..non_signers {
let pk_offset = 100 + i * 48;
let pk = PublicKey::from_bytes(&bytes[pk_offset..])?;
keys.push(pk);
}
let batch_proof = if non_signers > 0 {
let proof_offset = 100 + non_signers * 48;
Some(BatchPath::from_bytes(&bytes[proof_offset..])?)
} else {
None
};
Ok(Self {
aggregate,
keys,
batch_proof,
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::multi_sig::{PublicKey, PublicKeyPoP, Signature, SigningKey};
use blake2::Blake2b;
use proptest::prelude::*;
use rand::seq::SliceRandom;
use rand_chacha::ChaCha20Rng;
use rand_core::SeedableRng;
proptest! {
#![proptest_config(ProptestConfig::with_cases(1000))]
#[test]
fn test_aggregate_sig(msg in prop::collection::vec(any::<u8>(), 1..128),
num_sigs in 1..16usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sk_pks = Vec::with_capacity(num_sigs);
let mut pkpops = Vec::with_capacity(num_sigs);
let mut sigs = Vec::with_capacity(num_sigs);
for _ in 0..num_sigs {
let sk = SigningKey::gen(&mut rng);
let pk = PublicKey::from(&sk);
let pkpop = PublicKeyPoP::from(&sk);
pkpops.push(pkpop);
sk_pks.push((sk, pk));
}
let registration = Registration::<Blake2b>::new(&pkpops).expect("Registration should pass with valid keys");
for (sk, pk) in sk_pks {
let sig = sk.sign(&msg);
assert!(sig.verify(&pk, &msg).is_ok());
let indices = registration.get_index(&pk);
for j in indices {
sigs.push((j, sig));
}
}
let mu = AggregateSig::new(®istration, &sigs, &msg).expect("Signatures should be valid");
assert!(mu.verify(&msg, ®istration.to_avk(), num_sigs).is_ok());
}
#[test]
fn test_get_index(num_pks in 1..16usize,
num_eligible_signers in 1..20usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sks = Vec::with_capacity(num_pks);
let mut pks = Vec::with_capacity(num_eligible_signers);
let mut pkpops = Vec::with_capacity(num_eligible_signers);
for _ in 0..num_pks {
let sk = SigningKey::gen(&mut rng);
sks.push(sk);
}
for i in 0..num_eligible_signers {
let pk = PublicKey::from(&sks[i % num_pks]);
pks.push(pk);
let pkpop = PublicKeyPoP::from(&sks[i % num_pks]);
pkpops.push(pkpop);
}
pks.sort();
let registration = Registration::<Blake2b>::new(&pkpops).expect("Registration should pass with valid keys");
for (index, pk) in pks.iter().enumerate() {
let indices = registration.get_index(pk);
if indices.is_empty() {
panic!();
} else {
assert!(indices.contains(&index));
}
}
}
#[test]
fn test_aggregate_sig_repeaded_keys(msg in prop::collection::vec(any::<u8>(), 1..128),
num_sigs in 1..16usize,
num_pks in 1..8usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sk_pks = Vec::with_capacity(num_pks);
let mut pkpops = Vec::with_capacity(num_sigs);
let mut sigs = Vec::with_capacity(num_sigs);
for _ in 0..num_pks {
let sk = SigningKey::gen(&mut rng);
let pk = PublicKey::from(&sk);
sk_pks.push((sk, pk));
}
for i in 0..num_sigs {
pkpops.push(PublicKeyPoP::from(&sk_pks[i % num_pks].0));
}
let registration = Registration::<Blake2b>::new(&pkpops).expect("Registration should pass with valid keys");
for (sk, pk) in sk_pks {
let sig = sk.sign(&msg);
assert!(sig.verify(&pk, &msg).is_ok());
let indices = registration.get_index(&pk);
for j in indices {
sigs.push((j, sig));
}
}
let mu = AggregateSig::new(®istration, &sigs, &msg).expect("Signatures should be valid");
assert!(mu.verify(&msg, ®istration.to_avk(), num_sigs).is_ok());
}
#[test]
fn test_aggregate_sig_serde(msg in prop::collection::vec(any::<u8>(), 1..128),
num_sigs in 1..16usize,
num_pks in 1..16usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sk_pks = Vec::with_capacity(num_pks);
let mut pkpops = Vec::with_capacity(num_pks);
let mut sigs = Vec::with_capacity(num_sigs);
for _ in 0..num_pks {
let sk = SigningKey::gen(&mut rng);
let pk = PublicKey::from(&sk);
let pkpop = PublicKeyPoP::from(&sk);
pkpops.push(pkpop);
sk_pks.push((sk, pk));
}
let registration = Registration::<Blake2b>::new(&pkpops).expect("Registration should pass with valid keys");
for i in 0..num_sigs {
let (sk, pk) = &sk_pks[i % num_pks];
let sig = sk.sign(&msg);
assert!(sig.verify(pk, &msg).is_ok());
let indices = registration.get_index(pk);
for j in indices {
sigs.push((j, sig));
}
}
let mu = AggregateSig::new(®istration, &sigs, &msg).expect("Signatures should be valid");
let bytes = mu.to_bytes();
let recovered = AggregateSig::<Blake2b>::from_bytes(&bytes).unwrap();
match recovered.verify(&msg, ®istration.to_avk(), num_pks - (num_pks - 1) / 3) {
Ok(_) => {
assert!(num_sigs >= num_pks - (num_pks - 1) / 3);
},
Err(AtmsError::TooMuchOutstandingSigners(n)) => {
assert_eq!(n, num_pks - num_sigs);
assert!(n >= (num_pks - 1) / 3);
}
Err(err) => {
unreachable!("{:?}", err);
}
}
}
#[test]
fn test_aggregate_shuffled_sig(msg in prop::collection::vec(any::<u8>(), 1..128),
num_sigs in 1..16usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sk_pks = Vec::with_capacity(num_sigs);
let mut pkpops = Vec::with_capacity(num_sigs);
let mut sigs = Vec::with_capacity(num_sigs);
for _ in 0..num_sigs {
let sk = SigningKey::gen(&mut rng);
let pk = PublicKey::from(&sk);
let pkpop = PublicKeyPoP::from(&sk);
pkpops.push(pkpop);
sk_pks.push((sk, pk));
}
let registration = Registration::<Blake2b>::new(&pkpops).expect("Registration should pass with valid keys");
for (sk, pk) in sk_pks {
let sig = sk.sign(&msg);
assert!(sig.verify(&pk, &msg).is_ok());
let indices = registration.get_index(&pk);
for j in indices {
sigs.push((j, sig));
}
}
sigs.shuffle(&mut rng);
let mu = AggregateSig::new(®istration, &sigs, &msg).expect("Signatures should be valid");
assert!(mu.verify(&msg, ®istration.to_avk(), num_sigs).is_ok());
}
#[test]
fn test_registration_serde(num_sigs in 1..16usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut pkpops = Vec::with_capacity(num_sigs);
for _ in 0..num_sigs {
let sk = SigningKey::gen(&mut rng);
let pkpop = PublicKeyPoP::from(&sk);
pkpops.push(pkpop);
}
let registration = Registration::<Blake2b>::new(&pkpops).expect("Registration should pass with valid keys");
let bytes = registration.to_bytes();
let test = Registration::<Blake2b>::from_bytes(&bytes).unwrap();
assert!(test.to_avk().check(&pkpops).is_ok());
}
#[test]
fn test_deaggregate_pks(msg in prop::collection::vec(any::<u8>(), 1..128),
num_pks in 1..16usize,
num_sigs in 1..5usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sks = Vec::with_capacity(num_pks);
let mut pks = Vec::with_capacity(num_pks);
for _ in 0..num_pks {
let sk = SigningKey::gen(&mut rng);
let pk = PublicKey::from(&sk);
sks.push(sk);
pks.push(pk);
}
let mut aggr_pk = pks.iter().sum();
let mut sigs = Vec::with_capacity(num_sigs);
for sk in sks.iter().take(num_sigs as usize) {
sigs.push(sk.sign(&msg));
}
for pk in pks.iter().skip(num_sigs as usize) {
aggr_pk = aggr_pk - *pk;
}
let aggr_sig: Signature = sigs.iter().sum();
assert!(aggr_sig.verify(&aggr_pk, &msg).is_ok());
}
#[test]
fn test_correct_avk(num_pks in 1..16usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sks = Vec::with_capacity(num_pks);
let mut pks = Vec::with_capacity(num_pks);
for _ in 0..num_pks {
let sk = SigningKey::gen(&mut rng);
let pk = PublicKeyPoP::from(&sk);
sks.push(sk);
pks.push(pk);
}
let registration = Registration::<Blake2b>::new(&pks).expect("Valid keys should have a valid registration.");
assert!(registration.to_avk().check(&pks).is_ok());
}
#[test]
fn test_avk_serde(num_pks in 1..16usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sks = Vec::with_capacity(num_pks);
let mut pks = Vec::with_capacity(num_pks);
for _ in 0..num_pks {
let sk = SigningKey::gen(&mut rng);
let pk = PublicKeyPoP::from(&sk);
sks.push(sk);
pks.push(pk);
}
let avk = Registration::<Blake2b>::new(&pks).expect("Valid keys should have a valid registration.").to_avk();
let bytes = avk.to_bytes();
let test = Avk::<Blake2b>::from_bytes(&bytes).unwrap();
assert!(test.check(&pks).is_ok());
}
#[test]
fn shuffle_keys_same_avk(num_pks in 1..16usize,
seed in any::<[u8;32]>(),
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sks = Vec::with_capacity(num_pks);
let mut pks = Vec::with_capacity(num_pks);
for _ in 0..num_pks {
let sk = SigningKey::gen(&mut rng);
let pk = PublicKeyPoP::from(&sk);
sks.push(sk);
pks.push(pk);
}
let registration = Registration::<Blake2b>::new(&pks).expect("Valid keys should have a valid registration.");
pks.shuffle(&mut rng);
let shuffled_reg = Registration::<Blake2b>::new(&pks).expect("Shufled keys should have a correct registration.");
assert_eq!(registration.to_avk(), shuffled_reg.to_avk());
assert!(registration.to_avk().check(&pks).is_ok());
}
#[test]
fn test_atms_registration(n in 5..=32_usize,
seed in any::<[u8; 32]>(),
invalid_pop in 0..1,
repeated_reg in 0..1,
) {
let mut rng = ChaCha20Rng::from_seed(seed);
let mut keyspop: Vec<PublicKeyPoP> = Vec::with_capacity(n);
for _ in 0..n {
let sk = SigningKey::gen(&mut rng);
let pkpop = PublicKeyPoP::from(&sk);
keyspop.push(pkpop);
}
if repeated_reg == 1 {
keyspop.push(keyspop[0]);
}
if invalid_pop == 1 {
let sk = SigningKey::gen(&mut rng);
let false_pkpop = PublicKeyPoP::from(&sk);
keyspop[0] = false_pkpop;
}
match Registration::<Blake2b>::new(&keyspop) {
Ok(_) => {
assert_eq!(0, invalid_pop);
assert_eq!(0, repeated_reg);
},
Err(AtmsError::RegisterExistingKey(key)) => {
assert_eq!(key, keyspop[0].0);
assert_eq!(repeated_reg, 1);
},
Err(AtmsError::InvalidPoP) => {
assert_eq!(invalid_pop, 1);
},
Err(err) => {
unreachable!("{:?}", err);
}
};
}
#[test]
fn test_atms_aggregation(n in 5..=32_usize,
subset_is in prop::collection::vec(1..=32_usize, 1..=32_usize),
false_mk_proof in 0..32usize,
msg in any::<[u8; 16]>(),
seed in any::<[u8; 32]>(),
repeate_non_signer in 0..1,
) {
let threshold: usize = n - ((n - 1) / 3);
let mut rng = ChaCha20Rng::from_seed(seed);
let mut sk_pks = Vec::with_capacity(n);
let mut pkpops = Vec::with_capacity(n);
let mut sigs = Vec::with_capacity(n);
for _ in 0..n {
let sk = SigningKey::gen(&mut rng);
let pk = PublicKey::from(&sk);
let pkpop = PublicKeyPoP::from(&sk);
pkpops.push(pkpop);
sk_pks.push((sk, pk));
}
let registration = Registration::<Blake2b>::new(&pkpops).expect("Re\
gistration should pass with valid keys");
for (sk, pk) in sk_pks {
let sig = sk.sign(&msg);
assert!(sig.verify(&pk, &msg).is_ok());
let indices = registration.get_index(&pk);
for j in indices {
sigs.push((j, sig));
}
}
let mu = AggregateSig::new(®istration, &sigs, &msg).expect("Signatures should be valid");
assert!(mu.verify(&msg, ®istration.to_avk(), n).is_ok());
let subset = subset_is
.iter()
.map(|i| {
sigs[i % n]
})
.collect::<Vec<_>>();
let mut aggr_sig = AggregateSig::new(®istration, &subset, &msg).expect("Signatures should be valid");
let mut false_susbset = subset.clone();
false_susbset[0] = sigs[false_mk_proof % n];
let false_aggr = AggregateSig::new(®istration, &false_susbset, &msg).expect("Signatures should be valid");
if aggr_sig.keys.len() == false_aggr.keys.len() {
aggr_sig.batch_proof = false_aggr.batch_proof.clone();
}
if aggr_sig.keys.len() == false_aggr.keys.len() && aggr_sig.keys.len() > 1 && repeate_non_signer == 1 {
aggr_sig.keys[0] = false_aggr.keys[1];
aggr_sig.batch_proof = false_aggr.batch_proof.clone();
}
let avk = registration.to_avk();
match aggr_sig.verify(&msg, &avk, threshold) {
Ok(()) => {
assert!(subset.len() >= threshold);
if aggr_sig.keys.len() > 1 && repeate_non_signer == 1 {
assert_eq!(aggr_sig.keys[0], false_aggr.keys[1]);
}
},
Err(AtmsError::TooMuchOutstandingSigners(d)) => {
assert!(d >= avk.nr_parties - threshold);
}
Err(AtmsError::InvalidMerkleProof) => {
assert!(false_susbset.to_vec() != subset || false_mk_proof % n != subset_is[0] || repeate_non_signer == 1);
}
Err(AtmsError::FoundDuplicates(pk)) => {
assert_eq!(repeate_non_signer, 1);
assert_eq!(pk, aggr_sig.keys[0]);
}
Err(err) => unreachable!("{:?}", err)
}
}
}
}