use crate::account_address::AccountAddress;
use failure::prelude::*;
use solana_libra_crypto::*;
use solana_libra_logger::prelude::*;
use std::collections::HashMap;
#[derive(Debug, Fail, PartialEq)]
pub enum VerifyError {
#[fail(display = "Author is unknown")]
UnknownAuthor,
#[fail(
display = "The voting power ({}) is less than quorum voting power ({})",
voting_power, quorum_voting_power
)]
TooLittleVotingPower {
voting_power: u64,
quorum_voting_power: u64,
},
#[fail(
display = "The number of signatures ({}) is greater than total number of authors ({})",
num_of_signatures, num_of_authors
)]
TooManySignatures {
num_of_signatures: usize,
num_of_authors: usize,
},
#[fail(display = "Signature is invalid")]
InvalidSignature,
}
#[derive(Clone)]
pub struct ValidatorInfo<PublicKey> {
public_key: PublicKey,
voting_power: u64,
}
impl<PublicKey: VerifyingKey> ValidatorInfo<PublicKey> {
pub fn new(public_key: PublicKey, voting_power: u64) -> Self {
ValidatorInfo {
public_key,
voting_power,
}
}
pub fn public_key(&self) -> &PublicKey {
&self.public_key
}
pub fn voting_power(&self) -> u64 {
self.voting_power
}
}
#[derive(Clone)]
pub struct ValidatorVerifier<PublicKey> {
address_to_validator_info: HashMap<AccountAddress, ValidatorInfo<PublicKey>>,
quorum_voting_power: u64,
total_voting_power: u64,
}
impl<PublicKey: VerifyingKey> ValidatorVerifier<PublicKey> {
pub fn new(
address_to_validator_info: HashMap<AccountAddress, ValidatorInfo<PublicKey>>,
) -> Self {
let total_voting_power = address_to_validator_info
.values()
.map(|x| x.voting_power)
.sum();
let quorum_voting_power = if address_to_validator_info.is_empty() {
0
} else {
total_voting_power * 2 / 3 + 1
};
ValidatorVerifier {
address_to_validator_info,
quorum_voting_power,
total_voting_power,
}
}
pub fn new_with_quorum_voting_power(
address_to_validator_info: HashMap<AccountAddress, ValidatorInfo<PublicKey>>,
quorum_voting_power: u64,
) -> Result<Self> {
let total_voting_power = address_to_validator_info
.values()
.fold(0, |sum, x| sum + x.voting_power);
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(ValidatorVerifier {
address_to_validator_info,
quorum_voting_power,
total_voting_power,
})
}
pub fn new_single(author: AccountAddress, public_key: PublicKey) -> Self {
let mut author_to_validator_info = HashMap::new();
author_to_validator_info.insert(author, ValidatorInfo::new(public_key, 1));
Self::new(author_to_validator_info)
}
pub fn verify_signature(
&self,
author: AccountAddress,
hash: HashValue,
signature: &PublicKey::SignatureMaterial,
) -> std::result::Result<(), VerifyError> {
match self.get_public_key(&author) {
Some(public_key) => {
if public_key.verify_signature(&hash, signature).is_err() {
Err(VerifyError::InvalidSignature)
} else {
Ok(())
}
}
None => Err(VerifyError::UnknownAuthor),
}
}
pub fn verify_aggregated_signature<T>(
&self,
hash: HashValue,
aggregated_signature: &HashMap<AccountAddress, T>,
) -> std::result::Result<(), VerifyError>
where
T: Into<PublicKey::SignatureMaterial> + Clone,
{
self.check_num_of_signatures(&aggregated_signature)?;
self.check_voting_power(aggregated_signature.keys())?;
for (author, signature) in aggregated_signature {
self.verify_signature(*author, hash, &signature.clone().into())?;
}
Ok(())
}
pub fn batch_verify_aggregated_signature<T>(
&self,
hash: HashValue,
aggregated_signature: &HashMap<AccountAddress, T>,
) -> std::result::Result<(), VerifyError>
where
T: Into<PublicKey::SignatureMaterial> + Clone,
{
self.check_num_of_signatures(&aggregated_signature)?;
self.check_voting_power(aggregated_signature.keys())?;
let keys_and_signatures: Vec<(PublicKey, PublicKey::SignatureMaterial)> =
aggregated_signature
.iter()
.flat_map(|(address, signature)| {
let sig: PublicKey::SignatureMaterial = signature.clone().into();
self.get_public_key(&address)
.map(|pub_key| (pub_key.clone(), sig))
})
.collect();
if PublicKey::batch_verify_signatures(&hash, keys_and_signatures).is_err() {
match self.verify_aggregated_signature(hash, aggregated_signature) {
Ok(_) => warn!(
"Inconsistency between batch and iterative signature verification detected! \
Batch verification failed, while iterative passed."
),
Err(err) => return Err(err),
}
}
Ok(())
}
fn check_num_of_signatures<T>(
&self,
aggregated_signature: &HashMap<AccountAddress, T>,
) -> std::result::Result<(), VerifyError>
where
T: Into<PublicKey::SignatureMaterial> + Clone,
{
let num_of_signatures = aggregated_signature.len();
if num_of_signatures > self.len() {
return Err(VerifyError::TooManySignatures {
num_of_signatures,
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,
None => return Err(VerifyError::UnknownAuthor),
}
}
if aggregated_voting_power < self.quorum_voting_power {
return Err(VerifyError::TooLittleVotingPower {
voting_power: aggregated_voting_power,
quorum_voting_power: self.quorum_voting_power,
});
}
Ok(())
}
pub fn get_public_key(&self, author: &AccountAddress) -> Option<PublicKey> {
self.address_to_validator_info
.get(&author)
.map(|validator_info| validator_info.public_key.clone())
}
pub fn get_voting_power(&self, author: &AccountAddress) -> Option<u64> {
self.address_to_validator_info
.get(&author)
.map(|validator_info| validator_info.voting_power)
}
pub fn get_ordered_account_addresses(&self) -> Vec<AccountAddress> {
let mut account_addresses: Vec<AccountAddress> =
self.address_to_validator_info.keys().cloned().collect();
account_addresses.sort();
account_addresses
}
pub fn len(&self) -> usize {
self.address_to_validator_info.len()
}
pub fn is_empty(&self) -> bool {
self.len() == 0
}
pub fn quorum_voting_power(&self) -> u64 {
self.quorum_voting_power
}
}
#[cfg(test)]
mod tests {
use crate::crypto_proxies::random_validator_verifier;
use crate::validator_verifier::VerifyError::TooLittleVotingPower;
use crate::{
account_address::AccountAddress,
validator_signer::ValidatorSigner,
validator_verifier::{ValidatorInfo, ValidatorVerifier, VerifyError},
};
use solana_libra_crypto::{ed25519::*, test_utils::TEST_SEED, HashValue};
use std::collections::HashMap;
#[test]
fn test_check_voting_power() {
let (validator_signers, validator_verifier) = random_validator_verifier(2, None, false);
let mut author_to_signature_map: HashMap<AccountAddress, Ed25519Signature> = HashMap::new();
assert_eq!(
validator_verifier
.check_voting_power(author_to_signature_map.keys())
.unwrap_err(),
TooLittleVotingPower {
voting_power: 0,
quorum_voting_power: 2,
}
);
let random_hash = HashValue::random();
for validator in validator_signers.iter() {
author_to_signature_map.insert(
validator.author(),
validator.sign_message(random_hash).unwrap(),
);
}
assert_eq!(
validator_verifier.check_voting_power(author_to_signature_map.keys()),
Ok(())
);
}
#[test]
fn test_validator() {
let validator_signer = ValidatorSigner::<Ed25519PrivateKey>::random(TEST_SEED);
let random_hash = HashValue::random();
let signature = validator_signer.sign_message(random_hash).unwrap();
let validator =
ValidatorVerifier::new_single(validator_signer.author(), validator_signer.public_key());
assert_eq!(
validator.verify_signature(validator_signer.author(), random_hash, &signature),
Ok(())
);
let unknown_validator_signer = ValidatorSigner::<Ed25519PrivateKey>::random([1; 32]);
let unknown_signature = unknown_validator_signer.sign_message(random_hash).unwrap();
assert_eq!(
validator.verify_signature(
unknown_validator_signer.author(),
random_hash,
&unknown_signature
),
Err(VerifyError::UnknownAuthor)
);
assert_eq!(
validator.verify_signature(validator_signer.author(), random_hash, &unknown_signature),
Err(VerifyError::InvalidSignature)
);
}
#[test]
fn test_equal_vote_quorum_validators() {
const NUM_SIGNERS: u8 = 7;
let validator_signers: Vec<ValidatorSigner<Ed25519PrivateKey>> = (0..NUM_SIGNERS)
.map(|i| ValidatorSigner::random([i; 32]))
.collect();
let random_hash = HashValue::random();
let mut author_to_public_key_map: HashMap<AccountAddress, ValidatorInfo<Ed25519PublicKey>> =
HashMap::new();
for validator in validator_signers.iter() {
author_to_public_key_map.insert(
validator.author(),
ValidatorInfo::new(validator.public_key(), 1),
);
}
let mut author_to_signature_map: HashMap<AccountAddress, Ed25519Signature> = HashMap::new();
for validator in validator_signers.iter() {
author_to_signature_map.insert(
validator.author(),
validator.sign_message(random_hash).unwrap(),
);
}
let validator_verifier =
ValidatorVerifier::<Ed25519PublicKey>::new_with_quorum_voting_power(
author_to_public_key_map,
5,
)
.expect("Incorrect quorum size.");
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Ok(())
);
let unknown_validator_signer =
ValidatorSigner::<Ed25519PrivateKey>::random([NUM_SIGNERS + 1; 32]);
let unknown_signature = unknown_validator_signer.sign_message(random_hash).unwrap();
author_to_signature_map
.insert(unknown_validator_signer.author(), unknown_signature.clone());
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Err(VerifyError::TooManySignatures {
num_of_signatures: 8,
num_of_authors: 7
})
);
author_to_signature_map.clear();
for validator in validator_signers.iter().take(5) {
author_to_signature_map.insert(
validator.author(),
validator.sign_message(random_hash).unwrap(),
);
}
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Ok(())
);
author_to_signature_map
.insert(unknown_validator_signer.author(), unknown_signature.clone());
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Err(VerifyError::UnknownAuthor)
);
author_to_signature_map.clear();
for validator in validator_signers.iter().take(4) {
author_to_signature_map.insert(
validator.author(),
validator.sign_message(random_hash).unwrap(),
);
}
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Err(VerifyError::TooLittleVotingPower {
voting_power: 4,
quorum_voting_power: 5
})
);
author_to_signature_map.insert(unknown_validator_signer.author(), unknown_signature);
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Err(VerifyError::UnknownAuthor)
);
}
#[test]
fn test_unequal_vote_quorum_validators() {
const NUM_SIGNERS: u8 = 4;
let validator_signers: Vec<ValidatorSigner<Ed25519PrivateKey>> = (0..NUM_SIGNERS)
.map(|i| ValidatorSigner::random([i; 32]))
.collect();
let random_hash = HashValue::random();
let mut author_to_public_key_map: HashMap<AccountAddress, ValidatorInfo<Ed25519PublicKey>> =
HashMap::new();
let mut author_to_signature_map: HashMap<AccountAddress, Ed25519Signature> = HashMap::new();
for (i, validator_signer) in validator_signers.iter().enumerate() {
author_to_public_key_map.insert(
validator_signer.author(),
ValidatorInfo::new(validator_signer.public_key(), i as u64),
);
author_to_signature_map.insert(
validator_signer.author(),
validator_signer.sign_message(random_hash).unwrap(),
);
}
let validator_verifier =
ValidatorVerifier::<Ed25519PublicKey>::new_with_quorum_voting_power(
author_to_public_key_map,
5,
)
.expect("Incorrect quorum size.");
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Ok(())
);
let unknown_validator_signer =
ValidatorSigner::<Ed25519PrivateKey>::random([NUM_SIGNERS + 1; 32]);
let unknown_signature = unknown_validator_signer.sign_message(random_hash).unwrap();
author_to_signature_map
.insert(unknown_validator_signer.author(), unknown_signature.clone());
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Err(VerifyError::TooManySignatures {
num_of_signatures: 5,
num_of_authors: 4
})
);
author_to_signature_map.clear();
for validator in validator_signers.iter().skip(2) {
author_to_signature_map.insert(
validator.author(),
validator.sign_message(random_hash).unwrap(),
);
}
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Ok(())
);
author_to_signature_map
.insert(unknown_validator_signer.author(), unknown_signature.clone());
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Err(VerifyError::UnknownAuthor)
);
author_to_signature_map.clear();
for validator in validator_signers.iter().take(3) {
author_to_signature_map.insert(
validator.author(),
validator.sign_message(random_hash).unwrap(),
);
}
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Err(VerifyError::TooLittleVotingPower {
voting_power: 3,
quorum_voting_power: 5
})
);
author_to_signature_map.insert(unknown_validator_signer.author(), unknown_signature);
assert_eq!(
validator_verifier
.batch_verify_aggregated_signature(random_hash, &author_to_signature_map),
Err(VerifyError::UnknownAuthor)
);
}
}