Update to spec v1.0.0-rc.0 and BLSv4 (#1765)

## Issue Addressed

Closes #1504 
Closes #1505
Replaces #1703
Closes #1707

## Proposed Changes

* Update BLST and Milagro to versions compatible with BLSv4 spec
* Update Lighthouse to spec v1.0.0-rc.0, and update EF test vectors
* Use the v1.0.0 constants for `MainnetEthSpec`.
* Rename `InteropEthSpec` -> `V012LegacyEthSpec`
    * Change all constants to suit the mainnet `v0.12.3` specification (i.e., Medalla).
* Deprecate the `--spec` flag for the `lighthouse` binary
    * This value is now obtained from the `config_name` field of the `YamlConfig`.
        * Built in testnet YAML files have been updated.
    * Ignore the `--spec` value, if supplied, log a warning that it will be deprecated
    * `lcli` still has the spec flag, that's fine because it's dev tooling.
* Remove the `E: EthSpec` from `YamlConfig`
    * This means we need to deser the genesis `BeaconState` on-demand, but this is fine.
* Swap the old "minimal", "mainnet" strings over to the new `EthSpecId` enum.
* Always require a `CONFIG_NAME` field in `YamlConfig` (it used to have a default).

## Additional Info

Lots of breaking changes, do not merge! ~~We will likely need a Lighthouse v0.4.0 branch, and possibly a long-term v0.3.0 branch to keep Medalla alive~~.

Co-authored-by: Kirk Baird <baird.k@outlook.com>
Co-authored-by: Paul Hauner <paul@paulhauner.com>
This commit is contained in:
Michael Sproul
2020-10-28 22:19:38 +00:00
parent ad846ad280
commit 36bd4d87f0
55 changed files with 596 additions and 661 deletions

View File

@@ -7,7 +7,7 @@ edition = "2018"
[dependencies]
eth2_ssz = "0.1.2"
tree_hash = "0.1.1"
milagro_bls = { git = "https://github.com/sigp/milagro_bls", branch = "paulh" }
milagro_bls = { git = "https://github.com/sigp/milagro_bls", tag = "v1.4.0" }
rand = "0.7.3"
serde = "1.0.116"
serde_derive = "1.0.116"
@@ -17,7 +17,7 @@ eth2_hashing = "0.1.0"
ethereum-types = "0.9.2"
arbitrary = { version = "0.4.6", features = ["derive"], optional = true }
zeroize = { version = "1.1.1", features = ["zeroize_derive"] }
blst = { git = "https://github.com/sigp/blst.git", rev = "284f7059642851c760a09fb1708bcb59c7ca323c" }
blst = { git = "https://github.com/sigp/blst.git", rev = "7cf47864627ca479cad06c2a164f30d0cbaf16ce" }
[features]
default = ["supranational"]
@@ -25,3 +25,4 @@ fake_crypto = []
milagro = []
supranational = []
supranational-portable = ["supranational", "blst/portable"]
supranational-force-adx = ["supranational", "blst/force-adx"]

View File

@@ -1,17 +1,5 @@
use crate::{Error, PUBLIC_KEY_BYTES_LEN};
/// Implemented on some struct from a BLS library so it may be used internally in this crate.
pub trait TAggregatePublicKey: Sized + Clone {
/// Initialize `Self` to the infinity value which can then have other public keys aggregated
/// upon it.
fn infinity() -> Self;
/// Serialize `self` as compressed bytes.
fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN];
/// Deserialize `self` from compressed bytes.
fn deserialize(bytes: &[u8]) -> Result<Self, Error>;
}
pub trait TAggregatePublicKey: Sized + Clone {}
/*
* Note: there is no immediate need for a `GenericAggregatePublicKey` struct.

View File

@@ -183,13 +183,6 @@ where
return false;
}
if self.is_infinity
&& pubkeys.len() == 1
&& pubkeys.first().map_or(false, |pk| pk.is_infinity)
{
return true;
}
match self.point.as_ref() {
Some(point) => point.fast_aggregate_verify(msg, pubkeys),
None => false,
@@ -207,13 +200,6 @@ where
return false;
}
if self.is_infinity
&& pubkeys.len() == 1
&& pubkeys.first().map_or(false, |pk| pk.is_infinity)
{
return true;
}
match self.point.as_ref() {
Some(point) => point.aggregate_verify(msgs, pubkeys),
None => false,

View File

@@ -33,8 +33,6 @@ pub trait TPublicKey: Sized + Clone {
pub struct GenericPublicKey<Pub> {
/// The underlying point which performs *actual* cryptographic operations.
point: Pub,
/// True if this point is equal to the `INFINITY_PUBLIC_KEY`.
pub(crate) is_infinity: bool,
}
impl<Pub> GenericPublicKey<Pub>
@@ -42,8 +40,8 @@ where
Pub: TPublicKey,
{
/// Instantiates `Self` from a `point`.
pub(crate) fn from_point(point: Pub, is_infinity: bool) -> Self {
Self { point, is_infinity }
pub(crate) fn from_point(point: Pub) -> Self {
Self { point }
}
/// Returns a reference to the underlying BLS point.
@@ -63,10 +61,13 @@ where
/// Deserialize `self` from compressed bytes.
pub fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
Ok(Self {
point: Pub::deserialize(bytes)?,
is_infinity: bytes == &INFINITY_PUBLIC_KEY[..],
})
if bytes == &INFINITY_PUBLIC_KEY[..] {
Err(Error::InvalidInfinityPublicKey)
} else {
Ok(Self {
point: Pub::deserialize(bytes)?,
})
}
}
}

View File

@@ -1,6 +1,6 @@
use crate::{
generic_public_key::{GenericPublicKey, TPublicKey},
Error, INFINITY_PUBLIC_KEY, PUBLIC_KEY_BYTES_LEN,
Error, PUBLIC_KEY_BYTES_LEN,
};
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
@@ -32,8 +32,7 @@ where
///
/// May fail if the bytes are invalid.
pub fn decompress(&self) -> Result<GenericPublicKey<Pub>, Error> {
let is_infinity = self.bytes[..] == INFINITY_PUBLIC_KEY[..];
Pub::deserialize(&self.bytes).map(|point| GenericPublicKey::from_point(point, is_infinity))
GenericPublicKey::deserialize(&self.bytes)
}
}

View File

@@ -58,8 +58,7 @@ where
/// Returns the public key that corresponds to self.
pub fn public_key(&self) -> GenericPublicKey<Pub> {
let is_infinity = false;
GenericPublicKey::from_point(self.point.public_key(), is_infinity)
GenericPublicKey::from_point(self.point.public_key())
}
/// Serialize `self` as compressed bytes.
@@ -79,6 +78,8 @@ where
got: bytes.len(),
expected: SECRET_KEY_BYTES_LEN,
})
} else if bytes.iter().all(|b| *b == 0) {
Err(Error::InvalidZeroSecretKey)
} else {
Ok(Self {
point: Sec::deserialize(bytes)?,

View File

@@ -125,10 +125,6 @@ where
{
/// Returns `true` if `self` is a signature across `msg` by `pubkey`.
pub fn verify(&self, pubkey: &GenericPublicKey<Pub>, msg: Hash256) -> bool {
if self.is_infinity && pubkey.is_infinity {
return true;
}
if let Some(point) = &self.point {
point.verify(pubkey.point(), msg)
} else {

View File

@@ -4,7 +4,7 @@ use crate::{
generic_public_key::{GenericPublicKey, TPublicKey, PUBLIC_KEY_BYTES_LEN},
generic_secret_key::TSecretKey,
generic_signature::{TSignature, SIGNATURE_BYTES_LEN},
Error, Hash256, ZeroizeHash, INFINITY_PUBLIC_KEY, INFINITY_SIGNATURE,
Error, Hash256, ZeroizeHash, INFINITY_SIGNATURE,
};
pub use blst::min_pk as blst_core;
use blst::{blst_scalar, BLST_ERROR};
@@ -50,18 +50,12 @@ pub fn verify_signature_sets<'a>(
let mut pks = Vec::with_capacity(sets.len());
for set in &sets {
// If this set is simply an infinity signature and infinity pubkey then skip verification.
// This has the effect of always declaring that this sig/pubkey combination is valid.
if set.signature.is_infinity
&& set.signing_keys.len() == 1
&& set.signing_keys.first().map_or(false, |pk| pk.is_infinity)
{
continue;
}
// Generate random scalars.
let mut vals = [0u64; 4];
vals[0] = rng.gen();
while vals[0] == 0 {
// Do not use zero
vals[0] = rng.gen();
}
let mut rand_i = std::mem::MaybeUninit::<blst_scalar>::uninit();
// TODO: remove this `unsafe` code-block once we get a safe option from `blst`.
@@ -75,8 +69,12 @@ pub fn verify_signature_sets<'a>(
// Grab a slice of the message, to satisfy the blst API.
msgs_refs.push(set.message.as_bytes());
// Convert the aggregate signature into a signature.
if let Some(point) = set.signature.point() {
// Subgroup check the signature
if !point.0.subgroup_check() {
return false;
}
// Convert the aggregate signature into a signature.
sigs.push(point.0.to_signature())
} else {
// Any "empty" signature should cause a signature failure.
@@ -103,12 +101,6 @@ pub fn verify_signature_sets<'a>(
pks.push(blst_core::AggregatePublicKey::aggregate(&signing_keys).to_public_key());
}
// Due to an earlier check, the only case this can be empty is if all the sets consisted of
// infinity pubkeys/sigs. In such a case we wish to return `true`.
if msgs_refs.is_empty() {
return true;
}
let (sig_refs, pks_refs): (Vec<_>, Vec<_>) = sigs.iter().zip(pks.iter()).unzip();
let err = blst_core::Signature::verify_multiple_aggregate_signatures(
@@ -124,7 +116,15 @@ impl TPublicKey for blst_core::PublicKey {
}
fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
Self::uncompress(&bytes).map_err(Into::into)
// key_validate accepts uncompressed bytes too so enforce byte length here.
// It also does subgroup checks, noting infinity check is done in `generic_public_key.rs`.
if bytes.len() != PUBLIC_KEY_BYTES_LEN {
return Err(Error::InvalidByteLength {
got: bytes.len(),
expected: PUBLIC_KEY_BYTES_LEN,
});
}
Self::key_validate(&bytes).map_err(Into::into)
}
}
@@ -145,25 +145,7 @@ impl PartialEq for BlstAggregatePublicKey {
}
}
impl TAggregatePublicKey for BlstAggregatePublicKey {
fn infinity() -> Self {
blst_core::PublicKey::from_bytes(&INFINITY_PUBLIC_KEY)
.map(|pk| blst_core::AggregatePublicKey::from_public_key(&pk))
.map(Self)
.expect("should decode infinity public key")
}
fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] {
self.0.to_public_key().compress()
}
fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
blst_core::PublicKey::from_bytes(&bytes)
.map_err(Into::into)
.map(|pk| blst_core::AggregatePublicKey::from_public_key(&pk))
.map(Self)
}
}
impl TAggregatePublicKey for BlstAggregatePublicKey {}
impl TSignature<blst_core::PublicKey> for blst_core::Signature {
fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] {
@@ -175,6 +157,9 @@ impl TSignature<blst_core::PublicKey> for blst_core::Signature {
}
fn verify(&self, pubkey: &blst_core::PublicKey, msg: Hash256) -> bool {
if !self.subgroup_check() {
return false;
}
self.verify(msg.as_bytes(), DST, &[], pubkey) == BLST_ERROR::BLST_SUCCESS
}
}
@@ -232,6 +217,9 @@ impl TAggregateSignature<blst_core::PublicKey, BlstAggregatePublicKey, blst_core
) -> bool {
let pubkeys = pubkeys.iter().map(|pk| pk.point()).collect::<Vec<_>>();
let signature = self.0.clone().to_signature();
if !signature.subgroup_check() {
return false;
}
signature.fast_aggregate_verify(msg.as_bytes(), DST, &pubkeys) == BLST_ERROR::BLST_SUCCESS
}
@@ -243,6 +231,9 @@ impl TAggregateSignature<blst_core::PublicKey, BlstAggregatePublicKey, blst_core
let pubkeys = pubkeys.iter().map(|pk| pk.point()).collect::<Vec<_>>();
let msgs = msgs.iter().map(|hash| hash.as_bytes()).collect::<Vec<_>>();
let signature = self.0.clone().to_signature();
if !signature.subgroup_check() {
return false;
}
signature.aggregate_verify(&msgs, DST, &pubkeys) == BLST_ERROR::BLST_SUCCESS
}
}

View File

@@ -63,25 +63,7 @@ impl PartialEq for PublicKey {
#[derive(Clone)]
pub struct AggregatePublicKey([u8; PUBLIC_KEY_BYTES_LEN]);
impl TAggregatePublicKey for AggregatePublicKey {
fn infinity() -> Self {
Self([0; PUBLIC_KEY_BYTES_LEN])
}
fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] {
let mut bytes = [0; PUBLIC_KEY_BYTES_LEN];
bytes[..].copy_from_slice(&self.0);
bytes
}
fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
let mut key = [0; PUBLIC_KEY_BYTES_LEN];
key[..].copy_from_slice(&bytes);
Ok(Self(key))
}
}
impl TAggregatePublicKey for AggregatePublicKey {}
impl Eq for AggregatePublicKey {}

View File

@@ -4,7 +4,7 @@ use crate::{
generic_public_key::{GenericPublicKey, TPublicKey, PUBLIC_KEY_BYTES_LEN},
generic_secret_key::{TSecretKey, SECRET_KEY_BYTES_LEN},
generic_signature::{TSignature, SIGNATURE_BYTES_LEN},
Error, Hash256, ZeroizeHash, INFINITY_PUBLIC_KEY,
Error, Hash256, ZeroizeHash,
};
pub use milagro_bls as milagro;
use rand::thread_rng;
@@ -86,21 +86,7 @@ impl TPublicKey for milagro::PublicKey {
}
}
impl TAggregatePublicKey for milagro::AggregatePublicKey {
fn infinity() -> Self {
Self::from_bytes(&INFINITY_PUBLIC_KEY).expect("should decode infinity public key")
}
fn serialize(&self) -> [u8; PUBLIC_KEY_BYTES_LEN] {
let mut bytes = [0; PUBLIC_KEY_BYTES_LEN];
bytes[..].copy_from_slice(&self.as_bytes());
bytes
}
fn deserialize(bytes: &[u8]) -> Result<Self, Error> {
Self::from_bytes(&bytes).map_err(Into::into)
}
}
impl TAggregatePublicKey for milagro::AggregatePublicKey {}
impl TSignature<milagro::PublicKey> for milagro::Signature {
fn serialize(&self) -> [u8; SIGNATURE_BYTES_LEN] {

View File

@@ -56,6 +56,10 @@ pub enum Error {
InvalidByteLength { got: usize, expected: usize },
/// The provided secret key bytes were an incorrect length.
InvalidSecretKeyLength { got: usize, expected: usize },
/// The public key represents the point at infinity, which is invalid.
InvalidInfinityPublicKey,
/// The secret key is all zero bytes, which is invalid.
InvalidZeroSecretKey,
}
impl From<AmclError> for Error {

View File

@@ -1,4 +1,4 @@
use bls::{Hash256, INFINITY_PUBLIC_KEY, INFINITY_SIGNATURE};
use bls::{Hash256, INFINITY_SIGNATURE, SECRET_KEY_BYTES_LEN};
use ssz::{Decode, Encode};
use std::borrow::Cow;
use std::fmt::Debug;
@@ -19,6 +19,11 @@ macro_rules! test_suite {
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!(
@@ -131,11 +136,6 @@ macro_rules! test_suite {
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);
@@ -153,14 +153,6 @@ macro_rules! test_suite {
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()
@@ -168,13 +160,6 @@ macro_rules! test_suite {
.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,
@@ -234,17 +219,6 @@ macro_rules! test_suite {
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();
@@ -304,15 +278,6 @@ macro_rules! test_suite {
.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() {
@@ -332,34 +297,6 @@ macro_rules! test_suite {
.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() {
@@ -463,18 +400,6 @@ macro_rules! test_suite {
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(),
@@ -485,16 +410,6 @@ macro_rules! test_suite {
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");
@@ -568,22 +483,6 @@ macro_rules! test_suite {
.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()
@@ -592,15 +491,6 @@ macro_rules! test_suite {
.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()
}
};
}