lighthouse/eth2/utils/bls/src/aggregate_signature.rs
Paul Hauner bcffe42712
Bulk signature verification (#507)
* Add basic block processing benches

* Start reviving state processing benches

* Fix old block builders

* Add optimization for faster pubkey add

* Tidy benches, add another

* Add extra block processing bench

* Start working on faster BLS scheme

* Add partially complete sig verify optimization

* Add .gitignore to state processing

* Add progress on faster signature verification

* Fix SignatureSet for fake_crypto

* Tidy attester slashings sig set

* Tidy bulk signature verifier

* Refactor signature sets to be cleaner

* Start threading SignatureStrategy through code

* Add (empty) test dir

* Move BenchingBlockBuilder

* Add initial block signature verification tests

* Add tests for bulk signature verification

* Start threading SignatureStrategy in block proc.

* Refactor per_block_processing errors

* Use sig set tuples instead of lists of two

* Remove dead code

* Thread VerifySignatures through per_block_processing

* Add bulk signature verification

* Introduce parallel bulk signature verification

* Expand state processing benches

* Fix additional compile errors

* Fix issue where par iter chunks is 0

* Update milagro_bls dep

* Remove debugs, code fragment in beacon chain

* Tidy, add comments to block sig verifier

* Fix various PR comments

* Add block_root option to per_block_processing

* Fix comment in block signature verifier

* Fix comments from PR review

* Remove old comment

* Fix comment
2019-08-29 11:34:25 +10:00

203 lines
5.9 KiB
Rust

use super::*;
use milagro_bls::{
AggregatePublicKey as RawAggregatePublicKey, AggregateSignature as RawAggregateSignature,
G2Point,
};
use serde::de::{Deserialize, Deserializer};
use serde::ser::{Serialize, Serializer};
use serde_hex::{encode as hex_encode, HexVisitor};
use ssz::{Decode, DecodeError, Encode};
/// A BLS aggregate signature.
///
/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ
/// serialization).
#[derive(Debug, PartialEq, Clone, Default, Eq)]
pub struct AggregateSignature {
aggregate_signature: RawAggregateSignature,
is_empty: bool,
}
impl AggregateSignature {
/// Instantiate a new AggregateSignature.
///
/// is_empty is false
/// AggregateSignature is point at infinity
pub fn new() -> Self {
Self {
aggregate_signature: RawAggregateSignature::new(),
is_empty: false,
}
}
/// Add (aggregate) a signature to the `AggregateSignature`.
pub fn add(&mut self, signature: &Signature) {
if !self.is_empty {
self.aggregate_signature.add(signature.as_raw())
}
}
/// Add (aggregate) another `AggregateSignature`.
pub fn add_aggregate(&mut self, agg_signature: &AggregateSignature) {
self.aggregate_signature
.add_aggregate(&agg_signature.aggregate_signature)
}
/// Verify the `AggregateSignature` against an `AggregatePublicKey`.
///
/// Only returns `true` if the set of keys in the `AggregatePublicKey` match the set of keys
/// that signed the `AggregateSignature`.
pub fn verify(
&self,
msg: &[u8],
domain: u64,
aggregate_public_key: &AggregatePublicKey,
) -> bool {
if self.is_empty {
return false;
}
self.aggregate_signature
.verify(msg, domain, aggregate_public_key.as_raw())
}
/// Verify this AggregateSignature against multiple AggregatePublickeys with multiple Messages.
///
/// All PublicKeys related to a Message should be aggregated into one AggregatePublicKey.
/// Each AggregatePublicKey has a 1:1 ratio with a 32 byte Message.
pub fn verify_multiple(
&self,
messages: &[&[u8]],
domain: u64,
aggregate_public_keys: &[&AggregatePublicKey],
) -> bool {
if self.is_empty {
return false;
}
let aggregate_public_keys: Vec<&RawAggregatePublicKey> =
aggregate_public_keys.iter().map(|pk| pk.as_raw()).collect();
// Messages are concatenated into one long message.
let mut msgs: Vec<Vec<u8>> = vec![];
for message in messages {
msgs.push(message.to_vec());
}
self.aggregate_signature
.verify_multiple(&msgs, domain, &aggregate_public_keys[..])
}
/// Return AggregateSignature as bytes
pub fn as_bytes(&self) -> Vec<u8> {
if self.is_empty {
return vec![0; BLS_AGG_SIG_BYTE_SIZE];
}
self.aggregate_signature.as_bytes()
}
/// Convert bytes to AggregateSignature
pub fn from_bytes(bytes: &[u8]) -> Result<Self, DecodeError> {
for byte in bytes {
if *byte != 0 {
let sig = RawAggregateSignature::from_bytes(&bytes).map_err(|_| {
DecodeError::BytesInvalid(
format!("Invalid AggregateSignature bytes: {:?}", bytes).to_string(),
)
})?;
return Ok(Self {
aggregate_signature: sig,
is_empty: false,
});
}
}
Ok(Self::empty_signature())
}
/// Returns the underlying signature.
pub fn as_raw(&self) -> &RawAggregateSignature {
&self.aggregate_signature
}
/// Returns the underlying signature.
pub fn from_point(point: G2Point) -> Self {
Self {
aggregate_signature: RawAggregateSignature { point },
is_empty: false,
}
}
/// Returns if the AggregateSignature `is_empty`
pub fn is_empty(&self) -> bool {
self.is_empty
}
/// Creates a new AggregateSignature
///
/// aggregate_signature set to the point infinity
/// is_empty set to true
pub fn empty_signature() -> Self {
Self {
aggregate_signature: RawAggregateSignature::new(),
is_empty: true,
}
}
/// Return a hex string representation of the bytes of this signature.
#[cfg(test)]
pub fn as_hex_string(&self) -> String {
hex_encode(self.as_bytes())
}
}
impl_ssz!(
AggregateSignature,
BLS_AGG_SIG_BYTE_SIZE,
"AggregateSignature"
);
impl_tree_hash!(AggregateSignature, U96);
impl Serialize for AggregateSignature {
/// Serde serialization is compliant the Ethereum YAML test format.
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: Serializer,
{
serializer.serialize_str(&hex_encode(self.as_bytes()))
}
}
impl<'de> Deserialize<'de> for AggregateSignature {
/// Serde serialization is compliant the Ethereum YAML test format.
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
let bytes = deserializer.deserialize_str(HexVisitor)?;
let agg_sig = AggregateSignature::from_ssz_bytes(&bytes)
.map_err(|e| serde::de::Error::custom(format!("invalid ssz ({:?})", e)))?;
Ok(agg_sig)
}
}
#[cfg(test)]
mod tests {
use super::super::{Keypair, Signature};
use super::*;
use ssz::Encode;
#[test]
pub fn test_ssz_round_trip() {
let keypair = Keypair::random();
let mut original = AggregateSignature::new();
original.add(&Signature::new(&[42, 42], 0, &keypair.sk));
let bytes = original.as_ssz_bytes();
let decoded = AggregateSignature::from_ssz_bytes(&bytes).unwrap();
assert_eq!(original, decoded);
}
}