lighthouse/crypto/bls/tests/tests.rs
Michael Sproul 923486f34c Use bulk verification for sync_aggregate signature (#2415)
## Proposed Changes

Add the `sync_aggregate` from `BeaconBlock` to the bulk signature verifier for blocks. This necessitates a new signature set constructor for the sync aggregate, which is different from the others due to the use of [`eth2_fast_aggregate_verify`](https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.7/specs/altair/bls.md#eth2_fast_aggregate_verify) for sync aggregates, per [`process_sync_aggregate`](https://github.com/ethereum/eth2.0-specs/blob/v1.1.0-alpha.7/specs/altair/beacon-chain.md#sync-aggregate-processing). I made the choice to return an optional signature set, with `None` representing the case where the signature is valid on account of being the point at infinity (requires no further checking).

To "dogfood" the changes and prevent duplication, the consensus logic now uses the signature set approach as well whenever it is required to verify signatures (which should only be in testing AFAIK). The EF tests pass with the code as it exists currently, but failed before I adapted the `eth2_fast_aggregate_verify` changes (which is good).

As a result of this change Altair block processing should be a little faster, and importantly, we will no longer accidentally verify signatures when replaying blocks, e.g. when replaying blocks from the database.
2021-07-28 05:40:21 +00:00

517 lines
17 KiB
Rust

use bls::{Hash256, INFINITY_SIGNATURE, SECRET_KEY_BYTES_LEN};
use ssz::{Decode, Encode};
use std::borrow::Cow;
use std::fmt::Debug;
fn ssz_round_trip<T: Encode + Decode + PartialEq + Debug>(item: T) {
assert_eq!(item, T::from_ssz_bytes(&item.as_ssz_bytes()).unwrap());
}
macro_rules! test_suite {
($impls: ident) => {
use super::*;
use bls::$impls::*;
fn secret_from_u64(i: u64) -> SecretKey {
let mut secret_bytes = [0; 32];
// Use i + 1 to avoid the all-zeros secret key.
secret_bytes[32 - 8..].copy_from_slice(&(i + 1).to_be_bytes());
SecretKey::deserialize(&secret_bytes).unwrap()
}
#[test]
fn invalid_zero_secret_key() {
assert!(SecretKey::deserialize(&[0; SECRET_KEY_BYTES_LEN]).is_err());
}
#[test]
fn infinity_agg_sig() {
assert_eq!(
&AggregateSignature::infinity().serialize()[..],
&INFINITY_SIGNATURE[..]
);
assert_eq!(
AggregateSignature::deserialize(&INFINITY_SIGNATURE).unwrap(),
AggregateSignature::infinity(),
);
assert!(AggregateSignature::infinity().is_infinity());
}
#[test]
fn ssz_round_trip_multiple_types() {
let mut agg_sig = AggregateSignature::infinity();
ssz_round_trip(agg_sig.clone());
let msg = Hash256::from_low_u64_be(42);
let secret = secret_from_u64(42);
let sig = secret.sign(msg);
ssz_round_trip(sig.clone());
agg_sig.add_assign(&sig);
ssz_round_trip(agg_sig);
}
#[test]
fn ssz_round_trip_sig_empty() {
ssz_round_trip(Signature::empty())
}
#[test]
fn ssz_round_trip_agg_sig_empty() {
ssz_round_trip(AggregateSignature::empty())
}
#[test]
fn ssz_round_trip_agg_sig_infinity() {
ssz_round_trip(AggregateSignature::infinity())
}
#[test]
fn partial_eq_empty_sig() {
assert_eq!(Signature::empty(), Signature::empty())
}
#[test]
fn partial_eq_empty_sig_and_non_empty_sig() {
assert!(Signature::empty() != SignatureTester::default().sig)
}
#[test]
fn partial_eq_empty_agg_sig() {
assert_eq!(AggregateSignature::empty(), AggregateSignature::empty())
}
#[test]
fn partial_eq_empty_agg_sig_and_real_agg_sig() {
assert!(
AggregateSignature::empty() != AggregateSignatureTester::new_with_single_msg(1).sig
)
}
#[test]
fn partial_eq_infinity_agg_sig() {
assert_eq!(
AggregateSignature::infinity(),
AggregateSignature::infinity()
)
}
#[test]
fn partial_eq_infinity_agg_sig_and_real_agg_sig() {
assert!(
AggregateSignature::infinity()
!= AggregateSignatureTester::new_with_single_msg(1).sig
)
}
#[test]
fn partial_eq_infinity_agg_sig_and_empty_agg_sig() {
assert!(AggregateSignature::infinity() != AggregateSignature::empty())
}
/// A helper struct for composing tests via the builder pattern.
struct SignatureTester {
sig: Signature,
pubkey: PublicKey,
msg: Hash256,
}
impl Default for SignatureTester {
fn default() -> Self {
let secret = SecretKey::deserialize(&[42; 32]).unwrap();
let pubkey = secret.public_key();
let msg = Hash256::from_low_u64_be(42);
Self {
sig: secret.sign(msg),
pubkey,
msg,
}
}
}
impl SignatureTester {
pub fn infinity_sig(mut self) -> Self {
self.sig = Signature::deserialize(&INFINITY_SIGNATURE[..]).unwrap();
self
}
pub fn assert_verify(self, is_valid: bool) {
assert_eq!(self.sig.verify(&self.pubkey, self.msg), is_valid);
// Check a single-signature signature set.
assert_eq!(
SignatureSet::single_pubkey(&self.sig, Cow::Borrowed(&self.pubkey), self.msg,)
.verify(),
is_valid
)
}
}
#[test]
fn standard_signature_is_valid_with_standard_pubkey() {
SignatureTester::default().assert_verify(true)
}
#[test]
fn infinity_signature_is_invalid_with_standard_pubkey() {
SignatureTester::default()
.infinity_sig()
.assert_verify(false)
}
/// A helper struct for composing tests via the builder pattern.
struct AggregateSignatureTester {
sig: AggregateSignature,
pubkeys: Vec<PublicKey>,
msgs: Vec<Hash256>,
}
impl AggregateSignatureTester {
fn new_with_single_msg(num_pubkeys: u64) -> Self {
let mut pubkeys = Vec::with_capacity(num_pubkeys as usize);
let mut sig = AggregateSignature::infinity();
let msg = Hash256::from_low_u64_be(42);
for i in 0..num_pubkeys {
let secret = secret_from_u64(i);
pubkeys.push(secret.public_key());
sig.add_assign(&secret.sign(msg));
}
Self {
sig,
pubkeys,
msgs: vec![msg],
}
}
pub fn empty_sig(mut self) -> Self {
self.sig = AggregateSignature::empty();
self
}
pub fn wrong_sig(mut self) -> Self {
let sk = SecretKey::deserialize(&[1; 32]).unwrap();
self.sig = AggregateSignature::infinity();
self.sig.add_assign(&sk.sign(Hash256::from_low_u64_be(1)));
self
}
pub fn infinity_sig(mut self) -> Self {
self.sig = AggregateSignature::deserialize(&INFINITY_SIGNATURE[..]).unwrap();
self
}
pub fn aggregate_empty_sig(mut self) -> Self {
self.sig.add_assign(&Signature::empty());
self
}
pub fn aggregate_empty_agg_sig(mut self) -> Self {
self.sig.add_assign_aggregate(&AggregateSignature::empty());
self
}
pub fn aggregate_infinity_sig(mut self) -> Self {
self.sig
.add_assign(&Signature::deserialize(&INFINITY_SIGNATURE[..]).unwrap());
self
}
pub fn assert_single_message_verify(self, is_valid: bool) {
assert!(self.msgs.len() == 1);
let msg = self.msgs.first().unwrap();
let pubkeys = self.pubkeys.iter().collect::<Vec<_>>();
assert_eq!(
self.sig.fast_aggregate_verify(*msg, &pubkeys),
is_valid,
"fast_aggregate_verify expected {} but got {}",
is_valid,
!is_valid
);
let msgs = pubkeys.iter().map(|_| msg.clone()).collect::<Vec<_>>();
assert_eq!(
self.sig.aggregate_verify(&msgs, &pubkeys),
is_valid,
"aggregate_verify expected {} but got {}",
is_valid,
!is_valid
);
}
}
/// An aggregate without any signatures should not verify.
#[test]
fn fast_aggregate_verify_0_pubkeys() {
AggregateSignatureTester::new_with_single_msg(0).assert_single_message_verify(false)
}
/// An aggregate of size 1 should verify.
#[test]
fn fast_aggregate_verify_1_pubkey() {
AggregateSignatureTester::new_with_single_msg(1).assert_single_message_verify(true)
}
/// An aggregate of size 128 should verify.
#[test]
fn fast_aggregate_verify_128_pubkeys() {
AggregateSignatureTester::new_with_single_msg(128).assert_single_message_verify(true)
}
/// The infinity signature should not verify against 1 non-infinity pubkey.
#[test]
fn fast_aggregate_verify_infinity_signature_with_1_regular_public_key() {
AggregateSignatureTester::new_with_single_msg(1)
.infinity_sig()
.assert_single_message_verify(false)
}
/// The infinity signature should not verify against 128 non-infinity pubkeys.
#[test]
fn fast_aggregate_verify_infinity_signature_with_128_regular_public_keys() {
AggregateSignatureTester::new_with_single_msg(128)
.infinity_sig()
.assert_single_message_verify(false)
}
/// Adding a infinity signature (without an infinity pubkey) should verify.
#[test]
fn fast_aggregate_verify_with_one_aggregated_infinity_sig() {
AggregateSignatureTester::new_with_single_msg(1)
.aggregate_infinity_sig()
.assert_single_message_verify(true)
}
/// Adding four infinity signatures (without any infinity pubkeys) should verify.
#[test]
fn fast_aggregate_verify_with_four_aggregated_infinity_sig() {
AggregateSignatureTester::new_with_single_msg(1)
.aggregate_infinity_sig()
.aggregate_infinity_sig()
.aggregate_infinity_sig()
.aggregate_infinity_sig()
.assert_single_message_verify(true)
}
/// Adding two infinity signatures should yield the infinity signature.
#[test]
fn add_two_infinity_signatures() {
let tester = AggregateSignatureTester::new_with_single_msg(1)
.infinity_sig()
.aggregate_infinity_sig();
assert!(tester.sig.is_infinity());
assert_eq!(tester.sig, AggregateSignature::infinity());
tester.assert_single_message_verify(false)
}
/// The wrong signature should not verify.
#[test]
fn fast_aggregate_verify_wrong_signature() {
AggregateSignatureTester::new_with_single_msg(1)
.wrong_sig()
.assert_single_message_verify(false)
}
/// An "empty" signature should not verify.
#[test]
fn fast_aggregate_verify_empty_signature() {
AggregateSignatureTester::new_with_single_msg(1)
.empty_sig()
.assert_single_message_verify(false)
}
/// Aggregating an "empty" signature should have no effect.
#[test]
fn fast_aggregate_verify_with_aggregated_empty_sig() {
AggregateSignatureTester::new_with_single_msg(1)
.aggregate_empty_sig()
.assert_single_message_verify(true)
}
/// Aggregating an "empty" aggregate signature should have no effect.
#[test]
fn fast_aggregate_verify_with_aggregated_empty_agg_sig() {
AggregateSignatureTester::new_with_single_msg(1)
.aggregate_empty_agg_sig()
.assert_single_message_verify(true)
}
/// A helper struct to make it easer to deal with `SignatureSet` lifetimes.
struct OwnedSignatureSet {
signature: AggregateSignature,
signing_keys: Vec<PublicKey>,
message: Hash256,
should_be_valid: bool,
}
impl OwnedSignatureSet {
pub fn multiple_pubkeys(&self) -> SignatureSet {
let signing_keys = self.signing_keys.iter().map(Cow::Borrowed).collect();
SignatureSet::multiple_pubkeys(&self.signature, signing_keys, self.message)
}
pub fn run_checks(&self) {
assert_eq!(
self.multiple_pubkeys().verify(),
self.should_be_valid,
"multiple pubkey expected {} but got {}",
self.should_be_valid,
!self.should_be_valid
)
}
}
/// A helper struct for composing tests via the builder pattern.
#[derive(Default)]
struct SignatureSetTester {
owned_sets: Vec<OwnedSignatureSet>,
}
impl SignatureSetTester {
pub fn push_valid_set(mut self, num_signers: usize) -> Self {
let mut signature = AggregateSignature::infinity();
let message = Hash256::from_low_u64_be(42);
let signing_keys = (0..num_signers)
.map(|i| {
let secret = secret_from_u64(i as u64);
signature.add_assign(&secret.sign(message));
secret.public_key()
})
.collect();
self.owned_sets.push(OwnedSignatureSet {
signature,
signing_keys,
message,
should_be_valid: true,
});
self
}
pub fn push_invalid_set(mut self) -> Self {
let mut signature = AggregateSignature::infinity();
let message = Hash256::from_low_u64_be(42);
signature.add_assign(&secret_from_u64(0).sign(message));
self.owned_sets.push(OwnedSignatureSet {
signature,
signing_keys: vec![secret_from_u64(42).public_key()],
message,
should_be_valid: false,
});
self
}
pub fn push_invalid_pubkey_infinity_set(mut self) -> Self {
self.owned_sets.push(OwnedSignatureSet {
signature: AggregateSignature::deserialize(&INFINITY_SIGNATURE).unwrap(),
signing_keys: vec![secret_from_u64(42).public_key()],
message: Hash256::zero(),
should_be_valid: false,
});
self
}
pub fn run_checks(self) {
assert!(!self.owned_sets.is_empty(), "empty test is meaningless");
for owned_set in &self.owned_sets {
owned_set.run_checks()
}
let should_be_valid = self
.owned_sets
.iter()
.all(|owned_set| owned_set.should_be_valid);
let signature_sets = self
.owned_sets
.iter()
.map(|owned_set| owned_set.multiple_pubkeys())
.collect::<Vec<_>>();
assert_eq!(
verify_signature_sets(signature_sets.iter()),
should_be_valid
);
}
}
#[test]
fn signature_set_1_valid_set_with_1_signer() {
SignatureSetTester::default().push_valid_set(1).run_checks()
}
#[test]
fn signature_set_1_invalid_set() {
SignatureSetTester::default()
.push_invalid_set()
.run_checks()
}
#[test]
fn signature_set_1_valid_set_with_2_signers() {
SignatureSetTester::default().push_valid_set(2).run_checks()
}
#[test]
fn signature_set_1_valid_set_with_128_signers() {
SignatureSetTester::default()
.push_valid_set(128)
.run_checks()
}
#[test]
fn signature_set_2_valid_set_with_one_signer_each() {
SignatureSetTester::default()
.push_valid_set(1)
.push_valid_set(1)
.run_checks()
}
#[test]
fn signature_set_2_valid_set_with_2_signers_each() {
SignatureSetTester::default()
.push_valid_set(2)
.push_valid_set(2)
.run_checks()
}
#[test]
fn signature_set_2_valid_set_with_1_invalid_set() {
SignatureSetTester::default()
.push_valid_set(2)
.push_invalid_set()
.run_checks()
}
#[test]
fn signature_set_3_sets_with_one_invalid_pubkey_infinity_set() {
SignatureSetTester::default()
.push_valid_set(2)
.push_invalid_pubkey_infinity_set()
.push_valid_set(2)
.run_checks()
}
};
}
mod blst {
test_suite!(blst_implementations);
}
#[cfg(all(feature = "milagro", not(debug_assertions)))]
mod milagro {
test_suite!(milagro_implementations);
}