diff --git a/beacon_chain/types/src/attestation.rs b/beacon_chain/types/src/attestation.rs index 2c2015cd3c..40ee2173c1 100644 --- a/beacon_chain/types/src/attestation.rs +++ b/beacon_chain/types/src/attestation.rs @@ -1,6 +1,6 @@ use super::attestation_data::SSZ_ATTESTION_DATA_LENGTH; use super::bls::{AggregateSignature, BLS_AGG_SIG_BYTE_SIZE}; -use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream, LENGTH_BYTES}; +use super::ssz::{Decodable, DecodeError, Encodable, SszStream, LENGTH_BYTES}; use super::{AttestationData, Bitfield}; pub const MIN_SSZ_ATTESTION_RECORD_LENGTH: usize = { @@ -23,7 +23,7 @@ impl Encodable for Attestation { s.append(&self.data); s.append(&self.participation_bitfield); s.append(&self.custody_bitfield); - s.append_vec(&self.aggregate_sig.as_bytes()); + s.append(&self.aggregate_sig); } } @@ -32,9 +32,7 @@ impl Decodable for Attestation { let (data, i) = AttestationData::ssz_decode(bytes, i)?; let (participation_bitfield, i) = Bitfield::ssz_decode(bytes, i)?; let (custody_bitfield, i) = Bitfield::ssz_decode(bytes, i)?; - let (agg_sig_bytes, i) = decode_ssz_list(bytes, i)?; - let aggregate_sig = - AggregateSignature::from_bytes(&agg_sig_bytes).map_err(|_| DecodeError::TooShort)?; // also could be TooLong + let (aggregate_sig, i) = AggregateSignature::ssz_decode(bytes, i)?; let attestation_record = Self { data, diff --git a/beacon_chain/utils/bls/Cargo.toml b/beacon_chain/utils/bls/Cargo.toml index d38b5c6048..9e782b0597 100644 --- a/beacon_chain/utils/bls/Cargo.toml +++ b/beacon_chain/utils/bls/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" [dependencies] bls-aggregates = { git = "https://github.com/sigp/signature-schemes" } hashing = { path = "../hashing" } +ssz = { path = "../ssz" } diff --git a/beacon_chain/utils/bls/src/aggregate_signature.rs b/beacon_chain/utils/bls/src/aggregate_signature.rs new file mode 100644 index 0000000000..2e36302688 --- /dev/null +++ b/beacon_chain/utils/bls/src/aggregate_signature.rs @@ -0,0 +1,65 @@ +use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream}; +use super::{AggregatePublicKey, Signature}; +use bls_aggregates::AggregateSignature as RawAggregateSignature; + +/// 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)] +pub struct AggregateSignature(RawAggregateSignature); + +impl AggregateSignature { + /// Instantiate a new AggregateSignature. + pub fn new() -> Self { + AggregateSignature(RawAggregateSignature::new()) + } + + /// Add (aggregate) a signature to the `AggregateSignature`. + pub fn add(&mut self, signature: &Signature) { + self.0.add(signature.as_raw()) + } + + /// 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], aggregate_public_key: &AggregatePublicKey) -> bool { + self.0.verify(msg, aggregate_public_key) + } +} + +impl Encodable for AggregateSignature { + fn ssz_append(&self, s: &mut SszStream) { + s.append_vec(&self.0.as_bytes()); + } +} + +impl Decodable for AggregateSignature { + fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { + let (sig_bytes, i) = decode_ssz_list(bytes, i)?; + let raw_sig = + RawAggregateSignature::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?; + Ok((AggregateSignature(raw_sig), i)) + } +} + +#[cfg(test)] +mod tests { + use super::super::ssz::ssz_encode; + use super::super::{Keypair, Signature}; + use super::*; + + #[test] + pub fn test_ssz_round_trip() { + let keypair = Keypair::random(); + + let mut original = AggregateSignature::new(); + original.add(&Signature::new(&[42, 42], &keypair.sk)); + + let bytes = ssz_encode(&original); + let (decoded, _) = AggregateSignature::ssz_decode(&bytes, 0).unwrap(); + + assert_eq!(original, decoded); + } +} diff --git a/beacon_chain/utils/bls/src/lib.rs b/beacon_chain/utils/bls/src/lib.rs index fc665b206b..ebbc039c60 100644 --- a/beacon_chain/utils/bls/src/lib.rs +++ b/beacon_chain/utils/bls/src/lib.rs @@ -1,12 +1,17 @@ extern crate bls_aggregates; extern crate hashing; +extern crate ssz; + +mod aggregate_signature; +mod signature; + +pub use aggregate_signature::AggregateSignature; +pub use signature::Signature; pub use self::bls_aggregates::AggregatePublicKey; -pub use self::bls_aggregates::AggregateSignature; pub use self::bls_aggregates::Keypair; pub use self::bls_aggregates::PublicKey; pub use self::bls_aggregates::SecretKey; -pub use self::bls_aggregates::Signature; pub const BLS_AGG_SIG_BYTE_SIZE: usize = 97; diff --git a/beacon_chain/utils/bls/src/signature.rs b/beacon_chain/utils/bls/src/signature.rs new file mode 100644 index 0000000000..ebdb5b8176 --- /dev/null +++ b/beacon_chain/utils/bls/src/signature.rs @@ -0,0 +1,70 @@ +use super::ssz::{decode_ssz_list, Decodable, DecodeError, Encodable, SszStream}; +use bls_aggregates::{PublicKey, SecretKey, Signature as RawSignature}; + +/// A single BLS signature. +/// +/// This struct is a wrapper upon a base type and provides helper functions (e.g., SSZ +/// serialization). +#[derive(Debug, PartialEq, Clone)] +pub struct Signature(RawSignature); + +impl Signature { + /// Instantiate a new Signature from a message and a SecretKey. + pub fn new(msg: &[u8], sk: &SecretKey) -> Self { + Signature(RawSignature::new(msg, sk)) + } + + /// Instantiate a new Signature from a message and a SecretKey, where the message has already + /// been hashed. + pub fn new_hashed(msg_hashed: &[u8], sk: &SecretKey) -> Self { + Signature(RawSignature::new_hashed(msg_hashed, sk)) + } + + /// Verify the Signature against a PublicKey. + pub fn verify(&self, msg: &[u8], pk: &PublicKey) -> bool { + self.0.verify(msg, pk) + } + + /// Verify the Signature against a PublicKey, where the message has already been hashed. + pub fn verify_hashed(&self, msg_hash: &[u8], pk: &PublicKey) -> bool { + self.0.verify_hashed(msg_hash, pk) + } + + /// Returns the underlying signature. + pub fn as_raw(&self) -> &RawSignature { + &self.0 + } +} + +impl Encodable for Signature { + fn ssz_append(&self, s: &mut SszStream) { + s.append_vec(&self.0.as_bytes()); + } +} + +impl Decodable for Signature { + fn ssz_decode(bytes: &[u8], i: usize) -> Result<(Self, usize), DecodeError> { + let (sig_bytes, i) = decode_ssz_list(bytes, i)?; + let raw_sig = RawSignature::from_bytes(&sig_bytes).map_err(|_| DecodeError::TooShort)?; + Ok((Signature(raw_sig), i)) + } +} + +#[cfg(test)] +mod tests { + use super::super::ssz::ssz_encode; + use super::super::Keypair; + use super::*; + + #[test] + pub fn test_ssz_round_trip() { + let keypair = Keypair::random(); + + let original = Signature::new(&[42, 42], &keypair.sk); + + let bytes = ssz_encode(&original); + let (decoded, _) = Signature::ssz_decode(&bytes, 0).unwrap(); + + assert_eq!(original, decoded); + } +}