use crate::{account_address::AccountAddress, on_chain_config::ValidatorSet};
use aptos_crypto::{bls12381, hash::CryptoHash, Signature, VerifyingKey};
use serde::{Deserialize, Deserializer, Serialize};
use std::{
collections::{BTreeMap, HashMap},
fmt,
};
use thiserror::Error;
use crate::multi_signature::{MultiSignature, PartialSignatures};
#[cfg(any(test, feature = "fuzzing"))]
use crate::validator_signer::ValidatorSigner;
use anyhow::{ensure, Result};
use aptos_crypto::bls12381::PublicKey;
#[cfg(any(test, feature = "fuzzing"))]
use proptest_derive::Arbitrary;
#[derive(Debug, Error, PartialEq)]
pub enum VerifyError {
#[error("Author is unknown")]
UnknownAuthor,
#[error(
"The voting power ({}) is less than expected voting power ({})",
voting_power,
expected_voting_power
)]
TooLittleVotingPower {
voting_power: u128,
expected_voting_power: u128,
},
#[error(
"The number of voters ({}) is greater than total number of authors ({})",
num_of_voters,
num_of_authors
)]
TooManyVoters {
num_of_voters: usize,
num_of_authors: usize,
},
#[error("Signature is empty")]
EmptySignature,
#[error("Signature is invalid")]
InvalidSignature,
#[error("Inconsistent Block Info")]
InconsistentBlockInfo,
#[error("Failed to aggregate public keys")]
FailedToAggregatePubKey,
#[error("Failed to aggregate signatures")]
FailedToAggregateSignature,
#[error("Failed to verify multi-signature")]
FailedToVerifyMultiSignature,
}
#[derive(Clone, Debug, Deserialize, Eq, PartialEq, Serialize)]
#[cfg_attr(any(test, feature = "fuzzing"), derive(Arbitrary))]
pub struct ValidatorConsensusInfo {
address: AccountAddress,
public_key: PublicKey,
voting_power: u64,
}
impl ValidatorConsensusInfo {
pub fn new(address: AccountAddress, public_key: PublicKey, voting_power: u64) -> Self {
ValidatorConsensusInfo {
address,
public_key,
voting_power,
}
}
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
}
#[derive(Clone, Debug, Eq, PartialEq, Serialize)]
pub struct ValidatorVerifier {
validator_infos: Vec<ValidatorConsensusInfo>,
#[serde(skip)]
quorum_voting_power: u128,
#[serde(skip)]
total_voting_power: u128,
#[serde(skip)]
address_to_validator_index: HashMap<AccountAddress, usize>,
}
impl<'de> Deserialize<'de> for ValidatorVerifier {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
#[derive(Deserialize)]
#[serde(rename = "ValidatorVerifier")]
struct RawValidatorVerifier {
validator_infos: Vec<ValidatorConsensusInfo>,
}
let RawValidatorVerifier { validator_infos } =
RawValidatorVerifier::deserialize(deserializer)?;
Ok(ValidatorVerifier::new(validator_infos))
}
}
impl ValidatorVerifier {
fn build_index(
validator_infos: Vec<ValidatorConsensusInfo>,
quorum_voting_power: u128,
total_voting_power: u128,
) -> Self {
let address_to_validator_index = validator_infos
.iter()
.enumerate()
.map(|(index, info)| (info.address, index))
.collect();
Self {
validator_infos,
quorum_voting_power,
total_voting_power,
address_to_validator_index,
}
}
pub fn new(validator_infos: Vec<ValidatorConsensusInfo>) -> Self {
let total_voting_power = sum_voting_power(&validator_infos);
let quorum_voting_power = if validator_infos.is_empty() {
0
} else {
total_voting_power * 2 / 3 + 1
};
Self::build_index(validator_infos, quorum_voting_power, total_voting_power)
}
pub fn new_with_quorum_voting_power(
validator_infos: Vec<ValidatorConsensusInfo>,
quorum_voting_power: u128,
) -> Result<Self> {
let total_voting_power = sum_voting_power(&validator_infos);
ensure!(
quorum_voting_power <= total_voting_power,
"Quorum voting power is greater than the sum of all voting power of authors: {}, \
quorum_size: {}.",
quorum_voting_power,
total_voting_power
);
Ok(Self::build_index(
validator_infos,
quorum_voting_power,
total_voting_power,
))
}
pub fn new_single(author: AccountAddress, public_key: PublicKey) -> Self {
let validator_infos = vec![ValidatorConsensusInfo::new(author, public_key, 1)];
Self::new(validator_infos)
}
pub fn verify<T: Serialize + CryptoHash>(
&self,
author: AccountAddress,
message: &T,
signature: &bls12381::Signature,
) -> std::result::Result<(), VerifyError> {
match self.get_public_key(&author) {
Some(public_key) => public_key
.verify_struct_signature(message, signature)
.map_err(|_| VerifyError::InvalidSignature),
None => Err(VerifyError::UnknownAuthor),
}
}
pub fn aggregate_multi_signature(
&self,
partial_signatures: &PartialSignatures,
) -> Result<(MultiSignature, PublicKey), VerifyError> {
let mut pub_keys = vec![];
let mut sigs = vec![];
let mut masks = vec![false; self.validator_infos.len()];
for (addr, sig) in partial_signatures.signatures() {
let index = *self
.address_to_validator_index
.get(addr)
.ok_or(VerifyError::UnknownAuthor)?;
masks[index] = true;
pub_keys.push(self.validator_infos[index].public_key());
sigs.push(sig.clone());
}
let aggregated_sig = bls12381::Signature::aggregate(sigs)
.map_err(|_| VerifyError::FailedToAggregateSignature)?;
let aggregated_key =
PublicKey::aggregate(pub_keys).map_err(|_| VerifyError::FailedToAggregatePubKey)?;
Ok((
MultiSignature::new(masks, Some(aggregated_sig)),
aggregated_key,
))
}
pub fn aggregate_and_verify_multi_signature<T: CryptoHash + Serialize>(
&self,
partial_signatures: &PartialSignatures,
message: &T,
) -> Result<MultiSignature, VerifyError> {
let (aggregated_sig, aggregated_key) =
self.aggregate_multi_signature(partial_signatures)?;
aggregated_sig
.multi_sig()
.as_ref()
.expect("Failed to get multi signature")
.verify(message, &aggregated_key)
.map_err(|_| VerifyError::FailedToVerifyMultiSignature)?;
Ok(aggregated_sig)
}
pub fn verify_multi_signatures<T: CryptoHash + Serialize>(
&self,
message: &T,
multi_signature: &MultiSignature,
) -> std::result::Result<(), VerifyError> {
self.check_num_of_voters(multi_signature)?;
let mut pub_keys = vec![];
let mut authors = vec![];
for (index, exist) in multi_signature.get_voters_bitmap().iter().enumerate() {
if *exist {
authors.push(self.validator_infos[index].address);
pub_keys.push(self.validator_infos[index].public_key());
}
}
self.check_voting_power(authors.iter())?;
#[cfg(any(test, feature = "fuzzing"))]
{
if self.quorum_voting_power == 0 {
return Ok(());
}
}
let multi_sig = multi_signature
.multi_sig()
.as_ref()
.ok_or(VerifyError::EmptySignature)?;
let aggregated_key =
PublicKey::aggregate(pub_keys).map_err(|_| VerifyError::FailedToAggregatePubKey)?;
multi_sig
.verify(message, &aggregated_key)
.map_err(|_| VerifyError::InvalidSignature)?;
Ok(())
}
fn check_num_of_voters(
&self,
multi_signature: &MultiSignature,
) -> std::result::Result<(), VerifyError> {
let num_of_voters = multi_signature.get_num_voters();
if num_of_voters > self.len() {
return Err(VerifyError::TooManyVoters {
num_of_voters,
num_of_authors: self.len(),
});
}
Ok(())
}
pub fn check_voting_power<'a>(
&self,
authors: impl Iterator<Item = &'a AccountAddress>,
) -> std::result::Result<(), VerifyError> {
let mut aggregated_voting_power = 0;
for account_address in authors {
match self.get_voting_power(account_address) {
Some(voting_power) => aggregated_voting_power += voting_power as u128,
None => return Err(VerifyError::UnknownAuthor),
}
}
if aggregated_voting_power < self.quorum_voting_power {
return Err(VerifyError::TooLittleVotingPower {
voting_power: aggregated_voting_power,
expected_voting_power: self.quorum_voting_power,
});
}
Ok(())
}
pub fn get_public_key(&self, author: &AccountAddress) -> Option<PublicKey> {
self.address_to_validator_index
.get(author)
.map(|index| self.validator_infos[*index].public_key().clone())
}
pub fn get_voting_power(&self, author: &AccountAddress) -> Option<u64> {
self.address_to_validator_index
.get(author)
.map(|index| self.validator_infos[*index].voting_power)
}
pub fn get_ordered_account_addresses_iter(&self) -> impl Iterator<Item = AccountAddress> + '_ {
self.validator_infos.iter().map(|info| info.address)
}
pub fn len(&self) -> usize {
self.validator_infos.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn quorum_voting_power(&self) -> u128 {
self.quorum_voting_power
}
pub fn total_voting_power(&self) -> u128 {
self.total_voting_power
}
}
fn sum_voting_power(address_to_validator_info: &[ValidatorConsensusInfo]) -> u128 {
address_to_validator_info.iter().fold(0, |sum, x| {
sum.checked_add(x.voting_power as u128)
.expect("sum of all voting power is greater than u64::max")
})
}
impl fmt::Display for ValidatorVerifier {
fn fmt(&self, f: &mut fmt::Formatter) -> std::fmt::Result {
write!(f, "ValidatorSet: [")?;
for info in &self.validator_infos {
write!(
f,
"{}: {}, ",
info.address.short_str_lossless(),
info.voting_power
)?;
}
write!(f, "]")
}
}
impl From<&ValidatorSet> for ValidatorVerifier {
fn from(validator_set: &ValidatorSet) -> Self {
let sorted_validator_infos: BTreeMap<u64, ValidatorConsensusInfo> = validator_set
.payload()
.map(|info| {
(
info.config().validator_index,
ValidatorConsensusInfo::new(
info.account_address,
info.consensus_public_key().clone(),
info.consensus_voting_power(),
),
)
})
.collect();
let validator_infos: Vec<_> = sorted_validator_infos.values().cloned().collect();
for info in validator_set.payload() {
assert_eq!(
validator_infos[info.config().validator_index as usize].address,
info.account_address
);
}
ValidatorVerifier::new(validator_infos)
}
}
#[cfg(any(test, feature = "fuzzing"))]
impl From<&ValidatorVerifier> for ValidatorSet {
fn from(verifier: &ValidatorVerifier) -> Self {
ValidatorSet::new(
verifier
.get_ordered_account_addresses_iter()
.enumerate()
.map(|(index, addr)| {
crate::validator_info::ValidatorInfo::new_with_test_network_keys(
addr,
verifier.get_public_key(&addr).unwrap(),
verifier.get_voting_power(&addr).unwrap(),
index as u64,
)
})
.collect(),
)
}
}
#[cfg(any(test, feature = "fuzzing"))]
pub fn generate_validator_verifier(validators: &[ValidatorSigner]) -> ValidatorVerifier {
let validator_consensus_info = validators
.iter()
.map(|signer| ValidatorConsensusInfo::new(signer.author(), signer.public_key(), 1))
.collect();
ValidatorVerifier::new_with_quorum_voting_power(
validator_consensus_info,
validators.len() as u128 / 2,
)
.expect("Incorrect quorum size.")
}
#[cfg(any(test, feature = "fuzzing"))]
pub fn random_validator_verifier(
count: usize,
custom_voting_power_quorum: Option<u128>,
pseudo_random_account_address: bool,
) -> (Vec<ValidatorSigner>, ValidatorVerifier) {
let mut signers = Vec::new();
let mut validator_infos = vec![];
for i in 0..count {
let random_signer = if pseudo_random_account_address {
ValidatorSigner::from_int(i as u8)
} else {
ValidatorSigner::random([i as u8; 32])
};
validator_infos.push(ValidatorConsensusInfo::new(
random_signer.author(),
random_signer.public_key(),
1,
));
signers.push(random_signer);
}
(
signers,
match custom_voting_power_quorum {
Some(custom_voting_power_quorum) => ValidatorVerifier::new_with_quorum_voting_power(
validator_infos,
custom_voting_power_quorum,
)
.expect("Unable to create testing validator verifier"),
None => ValidatorVerifier::new(validator_infos),
},
)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::validator_signer::ValidatorSigner;
use aptos_crypto::test_utils::{TestAptosCrypto, TEST_SEED};
use std::collections::{BTreeMap, HashMap};
#[test]
fn test_check_voting_power() {
let (validator_signers, validator_verifier) = random_validator_verifier(2, None, false);
let mut author_to_signature_map = BTreeMap::new();
assert_eq!(
validator_verifier
.check_voting_power(author_to_signature_map.keys())
.unwrap_err(),
VerifyError::TooLittleVotingPower {
voting_power: 0,
expected_voting_power: 2,
}
);
let dummy_struct = TestAptosCrypto("Hello, World".to_string());
for validator in validator_signers.iter() {
author_to_signature_map.insert(validator.author(), validator.sign(&dummy_struct));
}
assert_eq!(
validator_verifier.check_voting_power(author_to_signature_map.keys()),
Ok(())
);
}
#[test]
fn test_validator() {
let validator_signer = ValidatorSigner::random(TEST_SEED);
let dummy_struct = TestAptosCrypto("Hello, World".to_string());
let signature = validator_signer.sign(&dummy_struct);
let validator =
ValidatorVerifier::new_single(validator_signer.author(), validator_signer.public_key());
assert_eq!(
validator.verify(validator_signer.author(), &dummy_struct, &signature),
Ok(())
);
let unknown_validator_signer = ValidatorSigner::random([1; 32]);
let unknown_signature = unknown_validator_signer.sign(&dummy_struct);
assert_eq!(
validator.verify(
unknown_validator_signer.author(),
&dummy_struct,
&unknown_signature
),
Err(VerifyError::UnknownAuthor)
);
assert_eq!(
validator.verify(validator_signer.author(), &dummy_struct, &unknown_signature),
Err(VerifyError::InvalidSignature)
);
}
#[test]
fn test_invalid_multi_signatures() {
let validator_signer = ValidatorSigner::random(TEST_SEED);
let dummy_struct = TestAptosCrypto("Hello, World".to_string());
let validator =
ValidatorVerifier::new_single(validator_signer.author(), validator_signer.public_key());
let unknown_validator_signer = ValidatorSigner::random([1; 32]);
let unknown_signature = unknown_validator_signer.sign(&dummy_struct);
let unknown_validator = ValidatorVerifier::new_single(
unknown_validator_signer.author(),
unknown_validator_signer.public_key(),
);
let mut partial_sig = PartialSignatures::empty();
partial_sig.add_signature(unknown_validator_signer.author(), unknown_signature);
let (multi_sig, _) = unknown_validator
.aggregate_multi_signature(&partial_sig)
.unwrap();
assert_eq!(
validator.verify_multi_signatures(&dummy_struct, &multi_sig),
Err(VerifyError::InvalidSignature)
);
}
#[test]
fn test_verify_empty_signature() {
let validator_signer = ValidatorSigner::random(TEST_SEED);
let dummy_struct = TestAptosCrypto("Hello, World".to_string());
let validator =
ValidatorVerifier::new_single(validator_signer.author(), validator_signer.public_key());
assert_eq!(
validator
.verify_multi_signatures(&dummy_struct, &MultiSignature::new(vec![true], None)),
Err(VerifyError::EmptySignature)
);
}
#[test]
fn test_insufficient_voting_power() {
let validator_signer = ValidatorSigner::random(TEST_SEED);
let dummy_struct = TestAptosCrypto("Hello, World".to_string());
let validator =
ValidatorVerifier::new_single(validator_signer.author(), validator_signer.public_key());
assert_eq!(
validator.verify_multi_signatures(&dummy_struct, &MultiSignature::empty()),
Err(VerifyError::TooLittleVotingPower {
voting_power: 0,
expected_voting_power: 1
})
);
}
#[test]
fn test_equal_vote_quorum_validators() {
const NUM_SIGNERS: u8 = 7;
let validator_signers: Vec<ValidatorSigner> = (0..NUM_SIGNERS)
.map(|i| ValidatorSigner::random([i; 32]))
.collect();
let dummy_struct = TestAptosCrypto("Hello, World".to_string());
let mut validator_infos = vec![];
for validator in validator_signers.iter() {
validator_infos.push(ValidatorConsensusInfo::new(
validator.author(),
validator.public_key(),
1,
));
}
let mut partial_signature = PartialSignatures::new(HashMap::new());
for validator in validator_signers.iter() {
partial_signature.add_signature(validator.author(), validator.sign(&dummy_struct));
}
let validator_verifier =
ValidatorVerifier::new_with_quorum_voting_power(validator_infos, 5)
.expect("Incorrect quorum size.");
let mut aggregated_signature = validator_verifier
.aggregate_multi_signature(&partial_signature)
.unwrap()
.0;
assert_eq!(
validator_verifier.verify_multi_signatures(&dummy_struct, &aggregated_signature),
Ok(())
);
let unknown_validator_signer = ValidatorSigner::random([NUM_SIGNERS + 1; 32]);
let unknown_signature = unknown_validator_signer.sign(&dummy_struct);
partial_signature
.add_signature(unknown_validator_signer.author(), unknown_signature.clone());
assert_eq!(
validator_verifier.aggregate_multi_signature(&partial_signature),
Err(VerifyError::UnknownAuthor)
);
partial_signature = PartialSignatures::new(HashMap::new());
for validator in validator_signers.iter().take(5) {
partial_signature.add_signature(validator.author(), validator.sign(&dummy_struct));
}
aggregated_signature = validator_verifier
.aggregate_multi_signature(&partial_signature)
.unwrap()
.0;
assert_eq!(
validator_verifier.verify_multi_signatures(&dummy_struct, &aggregated_signature),
Ok(())
);
partial_signature
.add_signature(unknown_validator_signer.author(), unknown_signature.clone());
assert_eq!(
validator_verifier.aggregate_multi_signature(&partial_signature),
Err(VerifyError::UnknownAuthor)
);
partial_signature = PartialSignatures::new(HashMap::new());
for validator in validator_signers.iter().take(4) {
partial_signature.add_signature(validator.author(), validator.sign(&dummy_struct));
}
aggregated_signature = validator_verifier
.aggregate_multi_signature(&partial_signature)
.unwrap()
.0;
assert_eq!(
validator_verifier.verify_multi_signatures(&dummy_struct, &aggregated_signature),
Err(VerifyError::TooLittleVotingPower {
voting_power: 4,
expected_voting_power: 5
})
);
partial_signature.add_signature(unknown_validator_signer.author(), unknown_signature);
assert_eq!(
validator_verifier.aggregate_multi_signature(&partial_signature),
Err(VerifyError::UnknownAuthor)
);
}
#[test]
fn test_unequal_vote_quorum_validators() {
const NUM_SIGNERS: u8 = 4;
let validator_signers: Vec<ValidatorSigner> = (0..NUM_SIGNERS)
.map(|i| ValidatorSigner::random([i; 32]))
.collect();
let dummy_struct = TestAptosCrypto("Hello, World".to_string());
let mut validator_infos = vec![];
let mut partial_signature = PartialSignatures::new(HashMap::new());
for (i, validator_signer) in validator_signers.iter().enumerate() {
validator_infos.push(ValidatorConsensusInfo::new(
validator_signer.author(),
validator_signer.public_key(),
i as u64,
));
partial_signature.add_signature(
validator_signer.author(),
validator_signer.sign(&dummy_struct),
);
}
let validator_verifier =
ValidatorVerifier::new_with_quorum_voting_power(validator_infos, 5)
.expect("Incorrect quorum size.");
let mut aggregated_signature = validator_verifier
.aggregate_multi_signature(&partial_signature)
.unwrap()
.0;
assert_eq!(
validator_verifier.verify_multi_signatures(&dummy_struct, &aggregated_signature),
Ok(())
);
let unknown_validator_signer = ValidatorSigner::random([NUM_SIGNERS + 1; 32]);
let unknown_signature = unknown_validator_signer.sign(&dummy_struct);
partial_signature
.add_signature(unknown_validator_signer.author(), unknown_signature.clone());
assert_eq!(
validator_verifier.aggregate_multi_signature(&partial_signature),
Err(VerifyError::UnknownAuthor)
);
let mut partial_signature = PartialSignatures::new(HashMap::new());
for validator in validator_signers.iter().skip(2) {
partial_signature.add_signature(validator.author(), validator.sign(&dummy_struct));
}
aggregated_signature = validator_verifier
.aggregate_multi_signature(&partial_signature)
.unwrap()
.0;
assert_eq!(
validator_verifier.verify_multi_signatures(&dummy_struct, &aggregated_signature),
Ok(())
);
partial_signature
.add_signature(unknown_validator_signer.author(), unknown_signature.clone());
assert_eq!(
validator_verifier.aggregate_multi_signature(&partial_signature),
Err(VerifyError::UnknownAuthor)
);
let mut partial_signature = PartialSignatures::new(HashMap::new());
for validator in validator_signers.iter().take(3) {
partial_signature.add_signature(validator.author(), validator.sign(&dummy_struct));
}
aggregated_signature = validator_verifier
.aggregate_multi_signature(&partial_signature)
.unwrap()
.0;
assert_eq!(
validator_verifier.verify_multi_signatures(&dummy_struct, &aggregated_signature),
Err(VerifyError::TooLittleVotingPower {
voting_power: 3,
expected_voting_power: 5
})
);
partial_signature.add_signature(unknown_validator_signer.author(), unknown_signature);
assert_eq!(
validator_verifier.aggregate_multi_signature(&partial_signature),
Err(VerifyError::UnknownAuthor)
);
}
}