diff --git a/beacon_chain/utils/bls/Cargo.toml b/beacon_chain/utils/bls/Cargo.toml index 1199efc15..81d467a46 100644 --- a/beacon_chain/utils/bls/Cargo.toml +++ b/beacon_chain/utils/bls/Cargo.toml @@ -6,3 +6,4 @@ authors = ["Paul Hauner "] [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 000000000..0970ce56f --- /dev/null +++ b/beacon_chain/utils/bls/src/aggregate_signature.rs @@ -0,0 +1,72 @@ +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(&mut self, msg: &[u8], avk: &AggregatePublicKey) -> bool { + self.0.verify(msg, avk) + } +} + +impl Default for AggregateSignature { + /// A "default" signature is a signature across an empty message by a secret key of 48 zeros. + fn default() -> Self { + AggregateSignature::new() + } +} + +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 dcd2a9d29..15abca098 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 000000000..bfeaca45e --- /dev/null +++ b/beacon_chain/utils/bls/src/signature.rs @@ -0,0 +1,81 @@ +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 Default for Signature { + /// A "default" signature is a signature across an empty message by a secret key of 48 zeros. + fn default() -> Self { + let sk = match SecretKey::from_bytes(&[0; 48]) { + Ok(key) => key, + _ => unreachable!(), // Key is static, should not fail. + }; + Signature(RawSignature::new(&[], &sk)) + } +} + +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); + } +}