b73c497be2
## Issue Addressed NA ## Proposed Changes - Refactor the `bls` crate to support multiple BLS "backends" (e.g., milagro, blst, etc). - Removes some duplicate, unused code in `common/rest_types/src/validator.rs`. - Removes the old "upgrade legacy keypairs" functionality (these were unencrypted keys that haven't been supported for a few testnets, no one should be using them anymore). ## Additional Info Most of the files changed are just inconsequential changes to function names. ## TODO - [x] Optimization levels - [x] Infinity point: https://github.com/supranational/blst/issues/11 - [x] Ensure milagro *and* blst are tested via CI - [x] What to do with unsafe code? - [x] Test infinity point in signature sets
615 lines
20 KiB
Rust
615 lines
20 KiB
Rust
use bls::{Hash256, INFINITY_PUBLIC_KEY, INFINITY_SIGNATURE};
|
|
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 infinity_agg_sig() {
|
|
assert_eq!(
|
|
&AggregateSignature::infinity().serialize()[..],
|
|
&INFINITY_SIGNATURE[..]
|
|
);
|
|
assert_eq!(
|
|
AggregateSignature::deserialize(&INFINITY_SIGNATURE).unwrap(),
|
|
AggregateSignature::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 infinity_pubkey(mut self) -> Self {
|
|
self.pubkey = PublicKey::deserialize(&INFINITY_PUBLIC_KEY[..]).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_valid_with_infinity_pubkey() {
|
|
SignatureTester::default()
|
|
.infinity_sig()
|
|
.infinity_pubkey()
|
|
.assert_verify(true)
|
|
}
|
|
|
|
#[test]
|
|
fn infinity_signature_is_invalid_with_standard_pubkey() {
|
|
SignatureTester::default()
|
|
.infinity_sig()
|
|
.assert_verify(false)
|
|
}
|
|
|
|
#[test]
|
|
fn standard_signature_is_invalid_with_infinity_pubkey() {
|
|
SignatureTester::default()
|
|
.infinity_pubkey()
|
|
.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 single_infinity_pubkey(mut self) -> Self {
|
|
self.pubkeys = vec![PublicKey::deserialize(&INFINITY_PUBLIC_KEY[..]).unwrap()];
|
|
self
|
|
}
|
|
|
|
pub fn push_infinity_pubkey(mut self) -> Self {
|
|
self.pubkeys
|
|
.push(PublicKey::deserialize(&INFINITY_PUBLIC_KEY[..]).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)
|
|
}
|
|
|
|
/// The infinity signature and one infinity pubkey should verify.
|
|
#[test]
|
|
fn fast_aggregate_verify_infinity_signature_with_one_infinity_pubkey() {
|
|
AggregateSignatureTester::new_with_single_msg(1)
|
|
.infinity_sig()
|
|
.single_infinity_pubkey()
|
|
.assert_single_message_verify(true)
|
|
}
|
|
|
|
/// 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 a infinity pubkey and an infinity signature should verify.
|
|
#[test]
|
|
fn fast_aggregate_verify_with_one_additional_infinity_pubkey_and_matching_sig() {
|
|
AggregateSignatureTester::new_with_single_msg(1)
|
|
.aggregate_infinity_sig()
|
|
.push_infinity_pubkey()
|
|
.assert_single_message_verify(true)
|
|
}
|
|
|
|
/// Adding a single infinity pubkey **without** updating the signature **should verify**.
|
|
#[test]
|
|
fn fast_aggregate_verify_with_one_additional_infinity_pubkey() {
|
|
AggregateSignatureTester::new_with_single_msg(1)
|
|
.push_infinity_pubkey()
|
|
.assert_single_message_verify(true)
|
|
}
|
|
|
|
/// Adding multiple infinity pubkeys **without** updating the signature **should verify**.
|
|
#[test]
|
|
fn fast_aggregate_verify_with_four_additional_infinity_pubkeys() {
|
|
AggregateSignatureTester::new_with_single_msg(1)
|
|
.push_infinity_pubkey()
|
|
.push_infinity_pubkey()
|
|
.push_infinity_pubkey()
|
|
.push_infinity_pubkey()
|
|
.assert_single_message_verify(true)
|
|
}
|
|
|
|
/// 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_sig_infinity_set(mut self) -> Self {
|
|
let mut signature = AggregateSignature::infinity();
|
|
signature.add_assign(&secret_from_u64(42).sign(Hash256::zero()));
|
|
self.owned_sets.push(OwnedSignatureSet {
|
|
signature,
|
|
signing_keys: vec![PublicKey::deserialize(&INFINITY_PUBLIC_KEY).unwrap()],
|
|
message: Hash256::zero(),
|
|
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 push_valid_infinity_set(mut self) -> Self {
|
|
self.owned_sets.push(OwnedSignatureSet {
|
|
signature: AggregateSignature::deserialize(&INFINITY_SIGNATURE).unwrap(),
|
|
signing_keys: vec![PublicKey::deserialize(&INFINITY_PUBLIC_KEY).unwrap()],
|
|
message: Hash256::zero(),
|
|
should_be_valid: true,
|
|
});
|
|
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_1_valid_set_with_1_infinity_set() {
|
|
SignatureSetTester::default()
|
|
.push_valid_infinity_set()
|
|
.run_checks()
|
|
}
|
|
|
|
#[test]
|
|
fn signature_set_3_sets_with_one_valid_infinity_set() {
|
|
SignatureSetTester::default()
|
|
.push_valid_set(2)
|
|
.push_valid_infinity_set()
|
|
.push_valid_set(2)
|
|
.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()
|
|
}
|
|
|
|
#[test]
|
|
fn signature_set_3_sets_with_one_invalid_sig_infinity_set() {
|
|
SignatureSetTester::default()
|
|
.push_valid_set(2)
|
|
.push_invalid_sig_infinity_set()
|
|
.push_valid_set(2)
|
|
.run_checks()
|
|
}
|
|
};
|
|
}
|
|
|
|
mod blst {
|
|
test_suite!(blst_implementations);
|
|
}
|
|
|
|
#[cfg(not(debug_assertions))]
|
|
mod milagro {
|
|
test_suite!(milagro_implementations);
|
|
}
|