diff --git a/bls/src/backends/bls12_381/cached_public_key.rs b/bls/src/backends/bls12_381/cached_public_key.rs index 74af45db..36dcfca8 100644 --- a/bls/src/backends/bls12_381/cached_public_key.rs +++ b/bls/src/backends/bls12_381/cached_public_key.rs @@ -1,110 +1,12 @@ -use derivative::Derivative; -use once_cell::race::OnceBox; -use serde::{Deserialize, Serialize}; -use ssz::{ReadError, Size, SszHash, SszRead, SszReadDefault as _, SszSize, SszWrite, H256}; - use super::{public_key::PublicKey, public_key_bytes::PublicKeyBytes}; -use crate::{error::Error, traits::CachedPublicKey as CachedPublicKeyTrait}; - -#[derive(Default, Debug, Derivative, Deserialize, Serialize)] -#[derivative(PartialEq, Eq)] -#[serde(transparent)] -pub struct CachedPublicKey { - bytes: PublicKeyBytes, - #[derivative(PartialEq = "ignore")] - #[serde(skip)] - decompressed: OnceBox, -} - -impl Clone for CachedPublicKey { - fn clone(&self) -> Self { - let Self { - bytes, - ref decompressed, - } = *self; - match decompressed.get().copied() { - Some(public_key) => Self::new(bytes, public_key), - None => bytes.into(), - } - } -} - -impl From for CachedPublicKey { - #[inline] - fn from(bytes: PublicKeyBytes) -> Self { - Self { - bytes, - decompressed: OnceBox::new(), - } - } -} - -impl From for CachedPublicKey { - #[inline] - fn from(public_key: PublicKey) -> Self { - Self::new(public_key.into(), public_key) - } -} - -impl SszSize for CachedPublicKey { - const SIZE: Size = PublicKeyBytes::SIZE; -} - -impl SszRead for CachedPublicKey { - #[inline] - fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { - Ok(Self { - bytes: PublicKeyBytes::from_ssz_default(bytes)?, - decompressed: OnceBox::new(), - }) - } -} - -impl SszWrite for CachedPublicKey { - #[inline] - fn write_fixed(&self, bytes: &mut [u8]) { - self.bytes.write_fixed(bytes); - } -} - -impl SszHash for CachedPublicKey { - type PackingFactor = ::PackingFactor; - - #[inline] - fn hash_tree_root(&self) -> H256 { - self.bytes.hash_tree_root() - } -} - -impl CachedPublicKeyTrait for CachedPublicKey { - type PublicKeyBytes = PublicKeyBytes; - type PublicKey = PublicKey; - - fn new(bytes: PublicKeyBytes, public_key: PublicKey) -> Self { - let decompressed = OnceBox::new(); - decompressed - .set(Box::new(public_key)) - .expect("decompressed is empty because OnceBox::new returns an empty cell"); - - Self { - bytes, - decompressed, - } - } - - #[inline] - fn as_bytes(&self) -> &PublicKeyBytes { - &self.bytes - } - #[inline] - fn to_bytes(&self) -> PublicKeyBytes { - self.bytes - } +use crate::{ + error::Error, impl_cached_public_key, traits::CachedPublicKey as CachedPublicKeyTrait, +}; - #[inline] - fn decompress(&self) -> Result<&PublicKey, Error> { - self.decompressed - .get_or_try_init(|| self.bytes.try_into().map(Box::new)) - } -} +impl_cached_public_key!( + CachedPublicKeyTrait, + CachedPublicKey, + PublicKeyBytes, + PublicKey +); diff --git a/bls/src/backends/bls12_381/public_key.rs b/bls/src/backends/bls12_381/public_key.rs index 191b4801..db43bb3a 100644 --- a/bls/src/backends/bls12_381/public_key.rs +++ b/bls/src/backends/bls12_381/public_key.rs @@ -34,20 +34,6 @@ impl TryFrom for PublicKey { impl PublicKeyTrait for PublicKey { type PublicKeyBytes = PublicKeyBytes; - fn aggregate_nonempty(public_keys: impl IntoIterator) -> Result { - public_keys - .into_iter() - .reduce(Self::aggregate) - .ok_or(Error::NoPublicKeysToAggregate) - } - - #[inline] - #[must_use] - fn aggregate(mut self, other: Self) -> Self { - self.aggregate_in_place(other); - self - } - #[inline] fn aggregate_in_place(&mut self, other: Self) { self.as_raw().add(other.as_raw()); diff --git a/bls/src/backends/bls12_381/public_key_bytes.rs b/bls/src/backends/bls12_381/public_key_bytes.rs index 0ec409ba..b4496a61 100644 --- a/bls/src/backends/bls12_381/public_key_bytes.rs +++ b/bls/src/backends/bls12_381/public_key_bytes.rs @@ -1,23 +1,20 @@ use bls12_381::G1Affine; -use derive_more::AsRef; +use derive_more::derive::AsRef; use fixed_hash::construct_fixed_hash; -use hex::FromHex; use impl_serde::impl_fixed_hash_serde; -use ssz::{BytesToDepth, MerkleTree, ReadError, Size, SszHash, SszRead, SszSize, SszWrite, H256}; -use typenum::{Unsigned as _, U1, U48}; -use crate::traits::PublicKeyBytes as PublicKeyBytesTrait; +use crate::{impl_public_key_bytes, traits::COMPRESSED_SIZE}; use super::public_key::PublicKey; -type CompressedSize = U48; - construct_fixed_hash! { #[derive(AsRef)] - pub struct PublicKeyBytes(CompressedSize::USIZE); + pub struct PublicKeyBytes(COMPRESSED_SIZE); } -impl_fixed_hash_serde!(PublicKeyBytes, CompressedSize::USIZE); +impl_fixed_hash_serde!(PublicKeyBytes, COMPRESSED_SIZE); + +impl_public_key_bytes!(PublicKeyBytes); impl From for PublicKeyBytes { #[inline] @@ -25,44 +22,3 @@ impl From for PublicKeyBytes { Self(G1Affine::from(public_key.as_raw()).to_compressed()) } } - -impl FromHex for PublicKeyBytes { - type Error = <[u8; CompressedSize::USIZE] as FromHex>::Error; - - fn from_hex>(digits: T) -> Result { - FromHex::from_hex(digits).map(Self) - } -} - -impl SszSize for PublicKeyBytes { - const SIZE: Size = Size::Fixed { - size: CompressedSize::USIZE, - }; -} - -impl SszRead for PublicKeyBytes { - #[inline] - fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { - Ok(Self::from_slice(bytes)) - } -} - -impl SszWrite for PublicKeyBytes { - #[inline] - fn write_fixed(&self, bytes: &mut [u8]) { - bytes.copy_from_slice(self.as_bytes()); - } -} - -impl SszHash for PublicKeyBytes { - type PackingFactor = U1; - - #[inline] - fn hash_tree_root(&self) -> H256 { - MerkleTree::>::merkleize_bytes(self) - } -} - -impl PublicKeyBytesTrait for PublicKeyBytes { - type PublicKey = PublicKey; -} diff --git a/bls/src/backends/bls12_381/secret_key.rs b/bls/src/backends/bls12_381/secret_key.rs index de702c16..e3a5472c 100644 --- a/bls/src/backends/bls12_381/secret_key.rs +++ b/bls/src/backends/bls12_381/secret_key.rs @@ -1,61 +1,27 @@ -use core::ops::Deref; -use core::{ - fmt::{Binary, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex}, - hash::{Hash, Hasher}, +use crate::{ + consts::DOMAIN_SEPARATION_TAG, error::Error, impl_secret_key, + traits::SecretKey as SecretKeyTrait, }; -use std::borrow::ToOwned; - use bls12_381::{ hash_to_curve::{ExpandMsgXmd, HashToCurve}, G1Projective, G2Projective, Scalar, }; -use derive_more::Debug; -use serde::Serialize; use sha2::Sha256; -use ssz::{SszHash, SszWrite}; -use static_assertions::assert_not_impl_any; use super::{ public_key::PublicKey, secret_key_bytes::{SecretKeyBytes, SIZE}, signature::Signature, }; -use crate::{consts::DOMAIN_SEPARATION_TAG, error::Error, traits::SecretKey as SecretKeyTrait}; - -#[derive(Debug)] -#[debug("[REDACTED]")] -pub struct SecretKey(Scalar); - -assert_not_impl_any! { - SecretKey: - - Clone, - Copy, - Deref, - ToOwned, - - Binary, - Display, - LowerExp, - LowerHex, - Octal, - Pointer, - UpperExp, - UpperHex, - - Serialize, - SszHash, - SszWrite, -} -impl PartialEq for SecretKey { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.as_raw().to_bytes() == other.as_raw().to_bytes() - } -} - -impl Eq for SecretKey {} +impl_secret_key!( + SecretKeyTrait, + SecretKey, + Scalar, + SecretKeyBytes, + PublicKey, + Signature +); impl TryFrom for SecretKey { type Error = Error; @@ -74,17 +40,19 @@ impl TryFrom for SecretKey { } } -impl Hash for SecretKey { - fn hash(&self, hasher: &mut H) { - self.as_raw().to_bytes().hash(hasher) - } -} - impl SecretKeyTrait for SecretKey { type SecretKeyBytes = SecretKeyBytes; type PublicKey = PublicKey; type Signature = Signature; + #[inline] + #[must_use] + fn to_bytes(&self) -> SecretKeyBytes { + SecretKeyBytes { + bytes: self.as_raw().to_bytes(), + } + } + #[inline] #[must_use] fn to_public_key(&self) -> PublicKey { @@ -99,21 +67,8 @@ impl SecretKeyTrait for SecretKey { &[message.as_ref()], DOMAIN_SEPARATION_TAG, ); - let signature = h * self.0; + let signature = h * self.as_raw(); Signature::from(signature) } - - #[inline] - #[must_use] - fn to_bytes(&self) -> SecretKeyBytes { - let bytes = self.as_raw().to_bytes(); - SecretKeyBytes { bytes } - } -} - -impl SecretKey { - const fn as_raw(&self) -> &Scalar { - &self.0 - } } diff --git a/bls/src/backends/bls12_381/secret_key_bytes.rs b/bls/src/backends/bls12_381/secret_key_bytes.rs index 2f70d87e..805e6d5d 100644 --- a/bls/src/backends/bls12_381/secret_key_bytes.rs +++ b/bls/src/backends/bls12_381/secret_key_bytes.rs @@ -1,79 +1,7 @@ -use core::{ - fmt::{Binary, Debug, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex}, - ops::Deref, -}; -use derive_more::{AsMut, AsRef, From}; -use hex::FromHex; -use serde::{Deserialize, Serialize}; -use ssz::{ReadError, Size, SszHash, SszRead, SszSize, SszWrite}; -use static_assertions::assert_not_impl_any; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -use crate::traits::SecretKeyBytes as SecretKeyBytesTrait; +use crate::impl_secret_key_bytes; use super::secret_key::SecretKey; pub const SIZE: usize = size_of::(); -#[derive(Default, AsRef, AsMut, From, Zeroize, ZeroizeOnDrop, Deserialize)] -#[as_ref(forward)] -#[as_mut(forward)] -#[serde(transparent)] -pub struct SecretKeyBytes { - #[serde(with = "serde_utils::prefixed_hex_or_bytes_array")] - pub(crate) bytes: [u8; SIZE], -} - -// Prevent leaking secret keys through common traits -assert_not_impl_any! { - SecretKeyBytes: - - Clone, - Copy, - Deref, - ToOwned, - - Debug, - Binary, - Display, - LowerExp, - LowerHex, - Octal, - Pointer, - UpperExp, - UpperHex, - - Serialize, - SszHash, -} - -impl FromHex for SecretKeyBytes { - type Error = <[u8; SIZE] as FromHex>::Error; - - fn from_hex>(digits: T) -> Result { - let bytes = FromHex::from_hex(digits)?; - Ok(Self { bytes }) - } -} - -impl SszSize for SecretKeyBytes { - const SIZE: Size = Size::Fixed { size: SIZE }; -} - -impl SszRead for SecretKeyBytes { - #[inline] - fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { - let mut secret_key = Self::default(); - secret_key.bytes.copy_from_slice(bytes); - Ok(secret_key) - } -} - -impl SszWrite for SecretKeyBytes { - #[inline] - fn write_fixed(&self, bytes: &mut [u8]) { - bytes.copy_from_slice(&self.bytes); - } -} - -impl SecretKeyBytesTrait for SecretKeyBytes {} +impl_secret_key_bytes!(SecretKeyBytes, SIZE); diff --git a/bls/src/backends/bls12_381/signature.rs b/bls/src/backends/bls12_381/signature.rs index 85e11614..982d49e8 100644 --- a/bls/src/backends/bls12_381/signature.rs +++ b/bls/src/backends/bls12_381/signature.rs @@ -54,13 +54,6 @@ impl SignatureTrait for Signature { gt1 == gt2 } - #[inline] - #[must_use] - fn aggregate(mut self, other: Self) -> Self { - self.aggregate_in_place(other); - self - } - #[inline] fn aggregate_in_place(&mut self, other: Self) { self.as_raw().add(other.as_raw()); diff --git a/bls/src/backends/bls12_381/signature_bytes.rs b/bls/src/backends/bls12_381/signature_bytes.rs index 75c6f846..94326b41 100644 --- a/bls/src/backends/bls12_381/signature_bytes.rs +++ b/bls/src/backends/bls12_381/signature_bytes.rs @@ -2,20 +2,16 @@ use bls12_381::G2Affine; use derive_more::AsRef; use fixed_hash::construct_fixed_hash; use impl_serde::impl_fixed_hash_serde; -use ssz::{BytesToDepth, MerkleTree, ReadError, Size, SszHash, SszRead, SszSize, SszWrite, H256}; -use typenum::{Unsigned as _, U1, U96}; +use typenum::Unsigned as _; -use super::signature::Signature; -use crate::traits::SignatureBytes as SignatureBytesTrait; - -type CompressedSize = U96; +use crate::{ + impl_signature_bytes, + traits::{CompressedSize, SignatureBytes as SignatureBytesTrait}, +}; -construct_fixed_hash! { - #[derive(AsRef)] - pub struct SignatureBytes(CompressedSize::USIZE); -} +use super::signature::Signature; -impl_fixed_hash_serde!(SignatureBytes, CompressedSize::USIZE); +impl_signature_bytes!(SignatureBytesTrait, SignatureBytes, CompressedSize::USIZE); impl From for SignatureBytes { #[inline] @@ -23,49 +19,3 @@ impl From for SignatureBytes { Self(G2Affine::from(signature.as_raw()).to_compressed()) } } - -impl SszSize for SignatureBytes { - const SIZE: Size = Size::Fixed { - size: CompressedSize::USIZE, - }; -} - -impl SszRead for SignatureBytes { - #[inline] - fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { - Ok(Self::from_slice(bytes)) - } -} - -impl SszWrite for SignatureBytes { - #[inline] - fn write_fixed(&self, bytes: &mut [u8]) { - bytes.copy_from_slice(self.as_bytes()); - } -} - -impl SszHash for SignatureBytes { - type PackingFactor = U1; - - #[inline] - fn hash_tree_root(&self) -> H256 { - MerkleTree::>::merkleize_bytes(self) - } -} - -impl SignatureBytesTrait for SignatureBytes { - #[inline] - fn empty() -> Self { - let mut bytes = Self::zero(); - - // The first byte of an empty signature must be 0xc0. - bytes.as_mut()[0] = 0xc0; - - bytes - } - - #[inline] - fn is_empty(self) -> bool { - self == Self::empty() - } -} diff --git a/bls/src/backends/blst/cached_public_key.rs b/bls/src/backends/blst/cached_public_key.rs index 0861e7a6..36dcfca8 100644 --- a/bls/src/backends/blst/cached_public_key.rs +++ b/bls/src/backends/blst/cached_public_key.rs @@ -1,113 +1,12 @@ -use derivative::Derivative; -use once_cell::race::OnceBox; -use serde::{Deserialize, Serialize}; -use ssz::{ReadError, Size, SszHash, SszRead, SszReadDefault as _, SszSize, SszWrite, H256}; - -use crate::{error::Error, traits::CachedPublicKey as CachedPublicKeyTrait}; - use super::{public_key::PublicKey, public_key_bytes::PublicKeyBytes}; -#[derive(Default, Debug, Derivative, Deserialize, Serialize)] -#[derivative(PartialEq, Eq)] -#[serde(transparent)] -pub struct CachedPublicKey { - bytes: PublicKeyBytes, - #[derivative(PartialEq = "ignore")] - #[serde(skip)] - decompressed: OnceBox, -} - -// `OnceBox` does not implement `Clone`. -impl Clone for CachedPublicKey { - fn clone(&self) -> Self { - let Self { - bytes, - ref decompressed, - } = *self; - match decompressed.get().copied() { - Some(public_key) => Self::new(bytes, public_key), - None => bytes.into(), - } - } -} - -impl From for CachedPublicKey { - #[inline] - fn from(bytes: PublicKeyBytes) -> Self { - Self { - bytes, - decompressed: OnceBox::new(), - } - } -} - -impl From for CachedPublicKey { - #[inline] - fn from(public_key: PublicKey) -> Self { - Self::new(public_key.into(), public_key) - } -} - -impl SszSize for CachedPublicKey { - const SIZE: Size = PublicKeyBytes::SIZE; -} - -impl SszRead for CachedPublicKey { - #[inline] - fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { - Ok(Self { - bytes: PublicKeyBytes::from_ssz_default(bytes)?, - decompressed: OnceBox::new(), - }) - } -} - -impl SszWrite for CachedPublicKey { - #[inline] - fn write_fixed(&self, bytes: &mut [u8]) { - self.bytes.write_fixed(bytes); - } -} - -impl SszHash for CachedPublicKey { - type PackingFactor = ::PackingFactor; - - #[inline] - fn hash_tree_root(&self) -> H256 { - self.bytes.hash_tree_root() - } -} - -impl CachedPublicKeyTrait for CachedPublicKey { - type PublicKeyBytes = PublicKeyBytes; - type PublicKey = PublicKey; - - fn new(bytes: PublicKeyBytes, public_key: PublicKey) -> Self { - let decompressed = OnceBox::new(); - - decompressed - .set(Box::new(public_key)) - .expect("decompressed is empty because OnceBox::new returns an empty cell"); - - Self { - bytes, - decompressed, - } - } - - #[inline] - fn as_bytes(&self) -> &PublicKeyBytes { - &self.bytes - } - - #[inline] - fn to_bytes(&self) -> PublicKeyBytes { - self.bytes - } +use crate::{ + error::Error, impl_cached_public_key, traits::CachedPublicKey as CachedPublicKeyTrait, +}; - #[inline] - fn decompress(&self) -> Result<&PublicKey, Error> { - self.decompressed - .get_or_try_init(|| self.bytes.try_into().map(Box::new)) - } -} +impl_cached_public_key!( + CachedPublicKeyTrait, + CachedPublicKey, + PublicKeyBytes, + PublicKey +); diff --git a/bls/src/backends/blst/public_key.rs b/bls/src/backends/blst/public_key.rs index c1a35319..37297d6c 100644 --- a/bls/src/backends/blst/public_key.rs +++ b/bls/src/backends/blst/public_key.rs @@ -28,21 +28,6 @@ impl TryFrom for PublicKey { impl PublicKeyTrait for PublicKey { type PublicKeyBytes = PublicKeyBytes; - /// [`eth_aggregate_pubkeys`](https://github.com/ethereum/consensus-specs/blob/86fb82b221474cc89387fa6436806507b3849d88/specs/altair/bls.md#eth_aggregate_pubkeys) - fn aggregate_nonempty(public_keys: impl IntoIterator) -> Result { - public_keys - .into_iter() - .reduce(Self::aggregate) - .ok_or(Error::NoPublicKeysToAggregate) - } - - #[inline] - #[must_use] - fn aggregate(mut self, other: Self) -> Self { - self.aggregate_in_place(other); - self - } - #[inline] fn aggregate_in_place(&mut self, other: Self) { let mut self_aggregate = RawAggregatePublicKey::from_public_key(self.as_raw()); diff --git a/bls/src/backends/blst/public_key_bytes.rs b/bls/src/backends/blst/public_key_bytes.rs index 161b7dd3..20ea7bfa 100644 --- a/bls/src/backends/blst/public_key_bytes.rs +++ b/bls/src/backends/blst/public_key_bytes.rs @@ -1,22 +1,19 @@ -use derive_more::AsRef; +use derive_more::derive::AsRef; use fixed_hash::construct_fixed_hash; -use hex::FromHex; use impl_serde::impl_fixed_hash_serde; -use ssz::{BytesToDepth, MerkleTree, ReadError, Size, SszHash, SszRead, SszSize, SszWrite, H256}; -use typenum::{Unsigned as _, U1, U48}; -use crate::traits::PublicKeyBytes as PublicKeyBytesTrait; +use crate::{impl_public_key_bytes, traits::COMPRESSED_SIZE}; use super::public_key::PublicKey; -type CompressedSize = U48; - construct_fixed_hash! { #[derive(AsRef)] - pub struct PublicKeyBytes(CompressedSize::USIZE); + pub struct PublicKeyBytes(COMPRESSED_SIZE); } -impl_fixed_hash_serde!(PublicKeyBytes, CompressedSize::USIZE); +impl_fixed_hash_serde!(PublicKeyBytes, COMPRESSED_SIZE); + +impl_public_key_bytes!(PublicKeyBytes); impl From for PublicKeyBytes { #[inline] @@ -24,44 +21,3 @@ impl From for PublicKeyBytes { Self(public_key.as_raw().compress()) } } - -impl FromHex for PublicKeyBytes { - type Error = <[u8; CompressedSize::USIZE] as FromHex>::Error; - - fn from_hex>(digits: T) -> Result { - FromHex::from_hex(digits).map(Self) - } -} - -impl SszSize for PublicKeyBytes { - const SIZE: Size = Size::Fixed { - size: CompressedSize::USIZE, - }; -} - -impl SszRead for PublicKeyBytes { - #[inline] - fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { - Ok(Self::from_slice(bytes)) - } -} - -impl SszWrite for PublicKeyBytes { - #[inline] - fn write_fixed(&self, bytes: &mut [u8]) { - bytes.copy_from_slice(self.as_bytes()); - } -} - -impl SszHash for PublicKeyBytes { - type PackingFactor = U1; - - #[inline] - fn hash_tree_root(&self) -> H256 { - MerkleTree::>::merkleize_bytes(self) - } -} - -impl PublicKeyBytesTrait for PublicKeyBytes { - type PublicKey = PublicKey; -} diff --git a/bls/src/backends/blst/secret_key.rs b/bls/src/backends/blst/secret_key.rs index 9d74ccf6..340b0c54 100644 --- a/bls/src/backends/blst/secret_key.rs +++ b/bls/src/backends/blst/secret_key.rs @@ -1,17 +1,8 @@ -use core::{ - fmt::{Binary, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex}, - hash::{Hash, Hasher}, - ops::Deref, +use crate::{ + consts::DOMAIN_SEPARATION_TAG, error::Error, impl_secret_key, + traits::SecretKey as SecretKeyTrait, }; -use std::borrow::ToOwned; - use blst::min_pk::SecretKey as RawSecretKey; -use derive_more::Debug; -use serde::Serialize; -use ssz::{SszHash, SszWrite}; -use static_assertions::assert_not_impl_any; - -use crate::{consts::DOMAIN_SEPARATION_TAG, error::Error, traits::SecretKey as SecretKeyTrait}; use super::{ public_key::PublicKey, @@ -19,45 +10,14 @@ use super::{ signature::Signature, }; -// `RawSecretKey` already implements `Zeroize` (with `zeroize(drop)`): -// -#[derive(Debug)] -// Inspired by `DebugSecret` from the `secrecy` crate. -#[debug("[REDACTED]")] -pub struct SecretKey(RawSecretKey); - -// Prevent `SecretKey` from implementing some traits to avoid leaking secret keys. -// This could also be done by wrapping it in `secrecy::Secret`. -assert_not_impl_any! { - SecretKey: - - Clone, - Copy, - Deref, - ToOwned, - - Binary, - Display, - LowerExp, - LowerHex, - Octal, - Pointer, - UpperExp, - UpperHex, - - Serialize, - SszHash, - SszWrite, -} - -impl PartialEq for SecretKey { - #[inline] - fn eq(&self, other: &Self) -> bool { - self.as_raw().to_bytes() == other.as_raw().to_bytes() - } -} - -impl Eq for SecretKey {} +impl_secret_key!( + SecretKeyTrait, + SecretKey, + RawSecretKey, + SecretKeyBytes, + PublicKey, + Signature +); impl TryFrom for SecretKey { type Error = Error; @@ -70,12 +30,6 @@ impl TryFrom for SecretKey { } } -impl Hash for SecretKey { - fn hash(&self, hasher: &mut H) { - self.as_raw().to_bytes().hash(hasher) - } -} - impl SecretKeyTrait for SecretKey { type SecretKeyBytes = SecretKeyBytes; type PublicKey = PublicKey; @@ -83,28 +37,21 @@ impl SecretKeyTrait for SecretKey { #[inline] #[must_use] + fn to_bytes(&self) -> SecretKeyBytes { + SecretKeyBytes { + bytes: self.as_raw().to_bytes(), + } + } + + #[inline] fn to_public_key(&self) -> PublicKey { self.as_raw().sk_to_pk().into() } #[inline] - #[must_use] fn sign(&self, message: impl AsRef<[u8]>) -> Signature { self.as_raw() .sign(message.as_ref(), DOMAIN_SEPARATION_TAG, &[]) .into() } - - #[inline] - #[must_use] - fn to_bytes(&self) -> SecretKeyBytes { - let bytes = self.as_raw().to_bytes(); - SecretKeyBytes { bytes } - } -} - -impl SecretKey { - const fn as_raw(&self) -> &RawSecretKey { - &self.0 - } } diff --git a/bls/src/backends/blst/secret_key_bytes.rs b/bls/src/backends/blst/secret_key_bytes.rs index ee72b3eb..805e6d5d 100644 --- a/bls/src/backends/blst/secret_key_bytes.rs +++ b/bls/src/backends/blst/secret_key_bytes.rs @@ -1,82 +1,7 @@ -use core::{ - fmt::{Binary, Debug, Display, LowerExp, LowerHex, Octal, Pointer, UpperExp, UpperHex}, - ops::Deref, -}; - -use derive_more::{AsMut, AsRef, From}; -use hex::FromHex; -use serde::{Deserialize, Serialize}; -use ssz::{ReadError, Size, SszHash, SszRead, SszSize, SszWrite}; -use static_assertions::assert_not_impl_any; -use zeroize::{Zeroize, ZeroizeOnDrop}; - -use crate::traits::SecretKeyBytes as SecretKeyBytesTrait; +use crate::impl_secret_key_bytes; use super::secret_key::SecretKey; -// Unlike public keys and signatures, secret keys are not compressed. pub const SIZE: usize = size_of::(); -#[derive(Default, AsRef, AsMut, From, Zeroize, ZeroizeOnDrop, Deserialize)] -#[as_ref(forward)] -#[as_mut(forward)] -#[serde(transparent)] -pub struct SecretKeyBytes { - #[serde(with = "serde_utils::prefixed_hex_or_bytes_array")] - pub(crate) bytes: [u8; SIZE], -} - -// Prevent `SecretKeyBytes` from implementing some traits to avoid leaking secret keys. -// This could also be done by wrapping it in `secrecy::Secret`. -assert_not_impl_any! { - SecretKeyBytes: - - Clone, - Copy, - Deref, - ToOwned, - - Debug, - Binary, - Display, - LowerExp, - LowerHex, - Octal, - Pointer, - UpperExp, - UpperHex, - - Serialize, - SszHash, -} - -impl FromHex for SecretKeyBytes { - type Error = <[u8; SIZE] as FromHex>::Error; - - fn from_hex>(digits: T) -> Result { - let bytes = FromHex::from_hex(digits)?; - Ok(Self { bytes }) - } -} - -impl SszSize for SecretKeyBytes { - const SIZE: Size = Size::Fixed { size: SIZE }; -} - -impl SszRead for SecretKeyBytes { - #[inline] - fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { - let mut secret_key = Self::default(); - secret_key.bytes.copy_from_slice(bytes); - Ok(secret_key) - } -} - -impl SszWrite for SecretKeyBytes { - #[inline] - fn write_fixed(&self, bytes: &mut [u8]) { - bytes.copy_from_slice(&self.bytes); - } -} - -impl SecretKeyBytesTrait for SecretKeyBytes {} +impl_secret_key_bytes!(SecretKeyBytes, SIZE); diff --git a/bls/src/backends/blst/signature.rs b/bls/src/backends/blst/signature.rs index 1a0656f7..7ab78f01 100644 --- a/bls/src/backends/blst/signature.rs +++ b/bls/src/backends/blst/signature.rs @@ -61,13 +61,6 @@ impl SignatureTrait for Signature { result == BLST_ERROR::BLST_SUCCESS } - #[inline] - #[must_use] - fn aggregate(mut self, other: Self) -> Self { - self.aggregate_in_place(other); - self - } - #[inline] fn aggregate_in_place(&mut self, other: Self) { let mut self_aggregate = RawAggregateSignature::from_signature(self.as_raw()); diff --git a/bls/src/backends/blst/signature_bytes.rs b/bls/src/backends/blst/signature_bytes.rs index 2bdebd92..0340146a 100644 --- a/bls/src/backends/blst/signature_bytes.rs +++ b/bls/src/backends/blst/signature_bytes.rs @@ -1,21 +1,16 @@ use derive_more::AsRef; use fixed_hash::construct_fixed_hash; use impl_serde::impl_fixed_hash_serde; -use ssz::{BytesToDepth, MerkleTree, ReadError, Size, SszHash, SszRead, SszSize, SszWrite, H256}; -use typenum::{Unsigned as _, U1, U96}; +use typenum::Unsigned as _; -use crate::traits::SignatureBytes as SignatureBytesTrait; +use crate::{ + impl_signature_bytes, + traits::{CompressedSize, SignatureBytes as SignatureBytesTrait}, +}; use super::signature::Signature; -type CompressedSize = U96; - -construct_fixed_hash! { - #[derive(AsRef)] - pub struct SignatureBytes(CompressedSize::USIZE); -} - -impl_fixed_hash_serde!(SignatureBytes, CompressedSize::USIZE); +impl_signature_bytes!(SignatureBytesTrait, SignatureBytes, CompressedSize::USIZE); impl From for SignatureBytes { #[inline] @@ -23,51 +18,3 @@ impl From for SignatureBytes { Self(signature.as_raw().compress()) } } - -impl SszSize for SignatureBytes { - const SIZE: Size = Size::Fixed { - size: CompressedSize::USIZE, - }; -} - -impl SszRead for SignatureBytes { - #[inline] - fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { - Ok(Self::from_slice(bytes)) - } -} - -impl SszWrite for SignatureBytes { - #[inline] - fn write_fixed(&self, bytes: &mut [u8]) { - bytes.copy_from_slice(self.as_bytes()); - } -} - -impl SszHash for SignatureBytes { - type PackingFactor = U1; - - #[inline] - fn hash_tree_root(&self) -> H256 { - MerkleTree::>::merkleize_bytes(self) - } -} - -impl SignatureBytesTrait for SignatureBytes { - #[inline] - #[must_use] - fn empty() -> Self { - let mut bytes = Self::zero(); - - // The first byte of an empty signature must be 0xc0. - bytes.as_mut()[0] = 0xc0; - - bytes - } - - #[inline] - #[must_use] - fn is_empty(self) -> bool { - self == Self::empty() - } -} diff --git a/bls/src/traits/cached_public_key.rs b/bls/src/traits/cached_public_key.rs index ab250151..09f4f62e 100644 --- a/bls/src/traits/cached_public_key.rs +++ b/bls/src/traits/cached_public_key.rs @@ -29,3 +29,111 @@ pub trait CachedPublicKey: fn to_bytes(&self) -> Self::PublicKeyBytes; fn decompress(&self) -> Result<&Self::PublicKey, Error>; } + +#[macro_export] +macro_rules! impl_cached_public_key { + ($trait:ty, $name:ident, $pkb:ty, $pk:ty) => { + #[derive(Default, Debug, derivative::Derivative, serde::Deserialize, serde::Serialize)] + #[derivative(PartialEq, Eq)] + #[serde(transparent)] + pub struct $name { + bytes: $pkb, + #[derivative(PartialEq = "ignore")] + #[serde(skip)] + decompressed: once_cell::race::OnceBox<$pk>, + } + + impl Clone for $name { + fn clone(&self) -> Self { + let Self { + bytes, + ref decompressed, + } = *self; + match decompressed.get().copied() { + Some(public_key) => Self::new(bytes, public_key), + None => bytes.into(), + } + } + } + + impl From<$pkb> for $name { + #[inline] + fn from(bytes: $pkb) -> Self { + Self { + bytes, + decompressed: once_cell::race::OnceBox::new(), + } + } + } + + impl From<$pk> for $name { + #[inline] + fn from(public_key: $pk) -> Self { + Self::new(public_key.into(), public_key) + } + } + + impl ssz::SszSize for $name { + const SIZE: ssz::Size = <$pkb>::SIZE; + } + + impl ssz::SszRead for $name { + #[inline] + fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { + Ok(Self { + bytes: <$pkb as ssz::SszReadDefault>::from_ssz_default(bytes)?, + decompressed: once_cell::race::OnceBox::new(), + }) + } + } + + impl ssz::SszWrite for $name { + #[inline] + fn write_fixed(&self, bytes: &mut [u8]) { + self.bytes.write_fixed(bytes); + } + } + + impl ssz::SszHash for $name { + type PackingFactor = <$pkb as ssz::SszHash>::PackingFactor; + + #[inline] + fn hash_tree_root(&self) -> ssz::H256 { + self.bytes.hash_tree_root() + } + } + + impl $trait for $name { + type PublicKeyBytes = $pkb; + type PublicKey = $pk; + + fn new(bytes: Self::PublicKeyBytes, public_key: Self::PublicKey) -> Self { + let decompressed = once_cell::race::OnceBox::new(); + decompressed + .set(Box::new(public_key)) + .expect("decompressed is empty because OnceBox::new returns an empty cell"); + + Self { + bytes, + decompressed, + } + } + + #[inline] + fn as_bytes(&self) -> &Self::PublicKeyBytes { + &self.bytes + } + + #[inline] + fn to_bytes(&self) -> Self::PublicKeyBytes { + self.bytes + } + + #[inline] + fn decompress(&self) -> Result<&Self::PublicKey, Error> { + self.decompressed + .get_or_try_init(|| self.bytes.try_into().map(Box::new)) + } + } + }; +} diff --git a/bls/src/traits/public_key.rs b/bls/src/traits/public_key.rs index bf13eb3b..863af476 100644 --- a/bls/src/traits/public_key.rs +++ b/bls/src/traits/public_key.rs @@ -9,7 +9,16 @@ pub trait PublicKey: { type PublicKeyBytes: PublicKeyBytesTrait; - fn aggregate(self, other: Self) -> Self; + fn aggregate_nonempty(keys: impl IntoIterator) -> Result { + keys.into_iter() + .reduce(Self::aggregate) + .ok_or(Error::NoPublicKeysToAggregate) + } + + fn aggregate(mut self, other: Self) -> Self { + self.aggregate_in_place(other); + self + } + fn aggregate_in_place(&mut self, other: Self); - fn aggregate_nonempty(keys: impl IntoIterator) -> Result; } diff --git a/bls/src/traits/public_key_bytes.rs b/bls/src/traits/public_key_bytes.rs index ebdfd5a0..3110eb85 100644 --- a/bls/src/traits/public_key_bytes.rs +++ b/bls/src/traits/public_key_bytes.rs @@ -4,6 +4,8 @@ use ssz::{SszHash, SszRead, SszSize, SszWrite}; use super::PublicKey as PublicKeyTrait; +pub const COMPRESSED_SIZE: usize = 48; + pub trait PublicKeyBytes: AsRef<[u8]> + AsMut<[u8]> @@ -25,3 +27,49 @@ pub trait PublicKeyBytes: { type PublicKey: PublicKeyTrait; } + +#[macro_export] +macro_rules! impl_public_key_bytes { + ($name:ident) => { + impl hex::FromHex for $name { + type Error = <[u8; $crate::traits::COMPRESSED_SIZE] as hex::FromHex>::Error; + + fn from_hex>(digits: T) -> Result { + hex::FromHex::from_hex(digits).map(Self) + } + } + + impl ssz::SszSize for $name { + const SIZE: ssz::Size = ssz::Size::Fixed { + size: $crate::traits::COMPRESSED_SIZE, + }; + } + + impl ssz::SszRead for $name { + #[inline] + fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { + Ok(Self::from_slice(bytes)) + } + } + + impl ssz::SszWrite for $name { + #[inline] + fn write_fixed(&self, bytes: &mut [u8]) { + bytes.copy_from_slice(self.as_bytes()); + } + } + + impl ssz::SszHash for $name { + type PackingFactor = typenum::U1; + + #[inline] + fn hash_tree_root(&self) -> ssz::H256 { + ssz::MerkleTree::>::merkleize_bytes(self) + } + } + + impl $crate::traits::PublicKeyBytes for $name { + type PublicKey = PublicKey; + } + }; +} diff --git a/bls/src/traits/secret_key.rs b/bls/src/traits/secret_key.rs index 242498d4..e6762ddb 100644 --- a/bls/src/traits/secret_key.rs +++ b/bls/src/traits/secret_key.rs @@ -4,28 +4,6 @@ use super::{ PublicKey as PublicKeyTrait, SecretKeyBytes as SecretKeyBytesTrait, Signature as SignatureTrait, }; -/// Secret key trait. -/// -/// # Safety -/// Implementors MUST: -/// 1. NOT implement: -/// - Clone, Copy, Deref, ToOwned -/// - Display and other formatting traits -/// - Serialize, SszHash, SszWrite -/// Use `assert_not_impl_any!` macro to enforce this -/// -/// 2. Implement Debug to only show "[REDACTED]": -/// ```rust -/// use core::fmt::Debug; -/// -/// struct SecretKeyImpl; -/// -/// impl Debug for SecretKeyImpl { -/// fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { -/// write!(f, "[REDACTED]") -/// } -/// } -/// ``` pub trait SecretKey: Debug + PartialEq + Eq + Hash { type SecretKeyBytes: SecretKeyBytesTrait; type PublicKey: PublicKeyTrait; @@ -35,3 +13,41 @@ pub trait SecretKey: Debug + PartialEq + Eq + Hash { fn sign(&self, message: impl AsRef<[u8]>) -> Self::Signature; fn to_bytes(&self) -> Self::SecretKeyBytes; } + +#[macro_export] +macro_rules! impl_secret_key { + ($trait:ty, $name:ident, $raw:ty, $skb:ty, $pk:ty, $sig:ty) => { + #[derive(derive_more::Debug)] + #[debug("[REDACTED]")] + pub struct $name($raw); + + static_assertions::assert_not_impl_any! { + $name: + Clone, Copy, core::ops::Deref, ToOwned, + core::fmt::Binary, core::fmt::Display, core::fmt::LowerExp, core::fmt::LowerHex, core::fmt::Octal, + core::fmt::Pointer, core::fmt::UpperExp, core::fmt::UpperHex, + serde::Serialize, ssz::SszHash, ssz::SszWrite, + } + + impl PartialEq for $name { + #[inline] + fn eq(&self, other: &Self) -> bool { + self.as_raw().to_bytes() == other.as_raw().to_bytes() + } + } + + impl Eq for $name {} + + impl core::hash::Hash for $name { + fn hash(&self, hasher: &mut H) { + self.as_raw().to_bytes().hash(hasher) + } + } + + impl $name { + const fn as_raw(&self) -> &$raw { + &self.0 + } + } + }; +} diff --git a/bls/src/traits/secret_key_bytes.rs b/bls/src/traits/secret_key_bytes.rs index 70586746..ff302e85 100644 --- a/bls/src/traits/secret_key_bytes.rs +++ b/bls/src/traits/secret_key_bytes.rs @@ -3,21 +3,6 @@ use serde::Deserialize; use ssz::{SszRead, SszSize, SszWrite}; use zeroize::{Zeroize, ZeroizeOnDrop}; -/// Secret key bytes trait. -/// -/// # Safety -/// Implementors MUST: -/// 1. NOT implement: -/// - Clone, Copy, Deref -/// - Debug, Display, Binary, Hex formatting traits -/// - Serialize, SszHash -/// Use `assert_not_impl_any!` macro to enforce this -/// -/// 2. Use these derive macros: -/// - `#[as_ref(forward)]` -/// - `#[as_mut(forward)]` -/// - `#[serde(transparent)]` -/// pub trait SecretKeyBytes: Default + AsRef<[u8]> @@ -32,3 +17,65 @@ pub trait SecretKeyBytes: + SszWrite { } + +#[macro_export] +macro_rules! impl_secret_key_bytes { + ($name:ident, $size:expr) => { + #[derive( + Default, + derive_more::AsRef, + derive_more::AsMut, + derive_more::From, + zeroize::Zeroize, + zeroize::ZeroizeOnDrop, + serde::Deserialize, + )] + #[as_ref(forward)] + #[as_mut(forward)] + #[serde(transparent)] + pub struct $name { + #[serde(with = "serde_utils::prefixed_hex_or_bytes_array")] + pub(crate) bytes: [u8; $size], + } + + static_assertions::assert_not_impl_any! { + $name: + Clone, Copy, std::ops::Deref, std::borrow::ToOwned, + std::fmt::Debug, std::fmt::Binary, std::fmt::Display, + std::fmt::LowerExp, std::fmt::LowerHex, std::fmt::Octal, + std::fmt::Pointer, std::fmt::UpperExp, std::fmt::UpperHex, + serde::Serialize, ssz::SszHash, + } + + impl hex::FromHex for $name { + type Error = <[u8; $size] as hex::FromHex>::Error; + + fn from_hex>(digits: T) -> Result { + let bytes = hex::FromHex::from_hex(digits)?; + Ok(Self { bytes }) + } + } + + impl ssz::SszSize for $name { + const SIZE: ssz::Size = ssz::Size::Fixed { size: $size }; + } + + impl ssz::SszRead for $name { + #[inline] + fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { + let mut secret_key = Self::default(); + secret_key.bytes.copy_from_slice(bytes); + Ok(secret_key) + } + } + + impl ssz::SszWrite for $name { + #[inline] + fn write_fixed(&self, bytes: &mut [u8]) { + bytes.copy_from_slice(&self.bytes); + } + } + + impl $crate::traits::SecretKeyBytes<$size> for $name {} + }; +} diff --git a/bls/src/traits/signature.rs b/bls/src/traits/signature.rs index 67f99ae0..35500063 100644 --- a/bls/src/traits/signature.rs +++ b/bls/src/traits/signature.rs @@ -10,7 +10,12 @@ where type PublicKey: PublicKeyTrait; fn verify(&self, message: impl AsRef<[u8]>, public_key: &Self::PublicKey) -> bool; - fn aggregate(self, other: Self) -> Self; + + fn aggregate(mut self, other: Self) -> Self { + self.aggregate_in_place(other); + self + } + fn aggregate_in_place(&mut self, other: Self); fn fast_aggregate_verify<'keys>( &self, diff --git a/bls/src/traits/signature_bytes.rs b/bls/src/traits/signature_bytes.rs index e739c892..57aa9842 100644 --- a/bls/src/traits/signature_bytes.rs +++ b/bls/src/traits/signature_bytes.rs @@ -1,5 +1,8 @@ use core::{fmt::Debug, str::FromStr}; use ssz::{SszHash, SszRead, SszSize, SszWrite}; +use typenum::U96; + +pub type CompressedSize = U96; pub trait SignatureBytes: AsRef<[u8]> @@ -21,3 +24,56 @@ pub trait SignatureBytes: fn empty() -> Self; fn is_empty(self) -> bool; } + +#[macro_export] +macro_rules! impl_signature_bytes { + ($trait:ident, $name:ident, $size:expr) => { + construct_fixed_hash! { + #[derive(derive_more::AsRef)] + pub struct $name($size); + } + + impl_fixed_hash_serde!($name, $size); + + impl ssz::SszSize for $name { + const SIZE: ssz::Size = ssz::Size::Fixed { size: $size }; + } + + impl ssz::SszRead for $name { + #[inline] + fn from_ssz_unchecked(_context: &C, bytes: &[u8]) -> Result { + Ok(Self::from_slice(bytes)) + } + } + + impl ssz::SszWrite for $name { + #[inline] + fn write_fixed(&self, bytes: &mut [u8]) { + bytes.copy_from_slice(self.as_bytes()); + } + } + + impl ssz::SszHash for $name { + type PackingFactor = typenum::U1; + + #[inline] + fn hash_tree_root(&self) -> ssz::H256 { + ssz::MerkleTree::>::merkleize_bytes(self) + } + } + + impl $trait for $name { + #[inline] + fn empty() -> Self { + let mut bytes = Self::zero(); + bytes.as_mut()[0] = 0xc0; + bytes + } + + #[inline] + fn is_empty(self) -> bool { + self == Self::empty() + } + } + }; +}