From 8b244cac471c8ce2dd437d2d49335c2d579d655f Mon Sep 17 00:00:00 2001 From: samkim-crypto Date: Mon, 1 Dec 2025 12:07:13 +0900 Subject: [PATCH] refactor pubkey, signature, and proof of possession modules --- bls-signatures/src/proof_of_possession.rs | 284 ------ .../src/proof_of_possession/bytes.rs | 115 +++ .../src/proof_of_possession/conversion.rs | 21 + bls-signatures/src/proof_of_possession/mod.rs | 143 ++++ .../src/proof_of_possession/points.rs | 34 + bls-signatures/src/pubkey.rs | 544 ------------ bls-signatures/src/pubkey/bytes.rs | 236 +++++ bls-signatures/src/pubkey/conversion.rs | 43 + bls-signatures/src/pubkey/mod.rs | 206 +++++ bls-signatures/src/pubkey/points.rs | 98 +++ bls-signatures/src/signature.rs | 808 ------------------ bls-signatures/src/signature/bytes.rs | 109 +++ bls-signatures/src/signature/conversion.rs | 21 + bls-signatures/src/signature/mod.rs | 410 +++++++++ bls-signatures/src/signature/points.rs | 300 +++++++ 15 files changed, 1736 insertions(+), 1636 deletions(-) delete mode 100644 bls-signatures/src/proof_of_possession.rs create mode 100644 bls-signatures/src/proof_of_possession/bytes.rs create mode 100644 bls-signatures/src/proof_of_possession/conversion.rs create mode 100644 bls-signatures/src/proof_of_possession/mod.rs create mode 100644 bls-signatures/src/proof_of_possession/points.rs delete mode 100644 bls-signatures/src/pubkey.rs create mode 100644 bls-signatures/src/pubkey/bytes.rs create mode 100644 bls-signatures/src/pubkey/conversion.rs create mode 100644 bls-signatures/src/pubkey/mod.rs create mode 100644 bls-signatures/src/pubkey/points.rs delete mode 100644 bls-signatures/src/signature.rs create mode 100644 bls-signatures/src/signature/bytes.rs create mode 100644 bls-signatures/src/signature/conversion.rs create mode 100644 bls-signatures/src/signature/mod.rs create mode 100644 bls-signatures/src/signature/points.rs diff --git a/bls-signatures/src/proof_of_possession.rs b/bls-signatures/src/proof_of_possession.rs deleted file mode 100644 index d5b77a29a..000000000 --- a/bls-signatures/src/proof_of_possession.rs +++ /dev/null @@ -1,284 +0,0 @@ -#[cfg(feature = "bytemuck")] -use bytemuck::{Pod, PodInOption, Zeroable, ZeroableInOption}; -#[cfg(not(target_os = "solana"))] -use { - crate::{error::BlsError, pubkey::VerifiablePubkey}, - blstrs::{G2Affine, G2Projective}, -}; -use { - base64::{prelude::BASE64_STANDARD, Engine}, - core::fmt, -}; -#[cfg(feature = "serde")] -use { - serde::{Deserialize, Serialize}, - serde_with::serde_as, -}; - -/// Domain separation tag used when hashing public keys to G2 in the proof of -/// possession signing and verification functions. See the -/// [standard](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#section-4.2.3). -pub const POP_DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; - -/// Size of a BLS proof of possession in a compressed point representation -pub const BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE: usize = 96; - -/// Size of a BLS proof of possession in a compressed point representation in base64 -pub const BLS_PROOF_OF_POSSESSION_COMPRESSED_BASE64_SIZE: usize = 128; - -/// Size of a BLS proof of possession in an affine point representation -pub const BLS_PROOF_OF_POSSESSION_AFFINE_SIZE: usize = 192; - -/// Size of a BLS proof of possession in an affine point representation in base64 -pub const BLS_PROOF_OF_POSSESSION_AFFINE_BASE64_SIZE: usize = 256; - -/// A trait for types that can be converted into a `ProofOfPossessionProjective`. -#[cfg(not(target_os = "solana"))] -pub trait AsProofOfPossessionProjective { - /// Attempt to convert the type into a `ProofOfPossessionProjective`. - fn try_as_projective(&self) -> Result; -} - -/// A trait for types that can be converted into a `ProofOfPossession` (affine). -#[cfg(not(target_os = "solana"))] -pub trait AsProofOfPossession { - /// Attempt to convert the type into a `ProofOfPossession`. - fn try_as_affine(&self) -> Result; -} - -/// A trait that provides verification methods to any convertible proof of possession type. -#[cfg(not(target_os = "solana"))] -pub trait VerifiableProofOfPossession: AsProofOfPossessionProjective { - /// Verifies the proof of possession against any convertible public key type. - fn verify( - &self, - pubkey: &P, - payload: Option<&[u8]>, - ) -> Result { - let proof_projective = self.try_as_projective()?; - pubkey.verify_proof_of_possession(&proof_projective, payload) - } -} - -/// A BLS proof of possession in a projective point representation -#[cfg(not(target_os = "solana"))] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct ProofOfPossessionProjective(pub(crate) G2Projective); - -#[cfg(not(target_os = "solana"))] -impl VerifiableProofOfPossession for T {} - -#[cfg(not(target_os = "solana"))] -impl_bls_conversions!( - ProofOfPossessionProjective, - ProofOfPossession, - ProofOfPossessionCompressed, - G2Affine, - AsProofOfPossessionProjective, - AsProofOfPossession -); - -/// A serialized BLS signature in a compressed point representation -#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] -#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] -#[repr(transparent)] -pub struct ProofOfPossessionCompressed( - #[cfg_attr( - feature = "serde", - serde_as(as = "[_; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]") - )] - pub [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], -); - -impl Default for ProofOfPossessionCompressed { - fn default() -> Self { - Self([0; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]) - } -} - -impl fmt::Display for ProofOfPossessionCompressed { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.0)) - } -} - -impl_from_str!( - TYPE = ProofOfPossessionCompressed, - BYTES_LEN = BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, - BASE64_LEN = BLS_PROOF_OF_POSSESSION_COMPRESSED_BASE64_SIZE -); - -/// A serialized BLS signature in an affine point representation -#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] -#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] -#[repr(transparent)] -pub struct ProofOfPossession( - #[cfg_attr( - feature = "serde", - serde_as(as = "[_; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE]") - )] - pub [u8; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE], -); - -impl Default for ProofOfPossession { - fn default() -> Self { - Self([0; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE]) - } -} - -impl fmt::Display for ProofOfPossession { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.0)) - } -} - -impl_from_str!( - TYPE = ProofOfPossession, - BYTES_LEN = BLS_PROOF_OF_POSSESSION_AFFINE_SIZE, - BASE64_LEN = BLS_PROOF_OF_POSSESSION_AFFINE_BASE64_SIZE -); - -// Byte arrays are both `Pod` and `Zeraoble`, but the traits `bytemuck::Pod` and -// `bytemuck::Zeroable` can only be derived for power-of-two length byte arrays. -// Directly implement these traits for types that are simple wrappers around -// byte arrays. -#[cfg(feature = "bytemuck")] -mod bytemuck_impls { - use super::*; - - unsafe impl Zeroable for ProofOfPossessionCompressed {} - unsafe impl Pod for ProofOfPossessionCompressed {} - unsafe impl ZeroableInOption for ProofOfPossessionCompressed {} - unsafe impl PodInOption for ProofOfPossessionCompressed {} - - unsafe impl Zeroable for ProofOfPossession {} - unsafe impl Pod for ProofOfPossession {} - unsafe impl ZeroableInOption for ProofOfPossession {} - unsafe impl PodInOption for ProofOfPossession {} -} - -#[cfg(test)] -mod tests { - use { - super::*, - crate::{ - keypair::Keypair, - pubkey::{Pubkey, PubkeyCompressed, PubkeyProjective}, - }, - core::str::FromStr, - std::string::ToString, - }; - - #[test] - fn test_proof_of_possession() { - let keypair = Keypair::new(); - let proof_projective = keypair.proof_of_possession(None); - - let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); - let pubkey_affine: Pubkey = keypair.public; - let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); - - let proof_affine: ProofOfPossession = proof_projective.into(); - let proof_compressed: ProofOfPossessionCompressed = proof_affine.try_into().unwrap(); - - assert!(proof_projective.verify(&pubkey_projective, None).unwrap()); - assert!(proof_affine.verify(&pubkey_projective, None).unwrap()); - assert!(proof_compressed.verify(&pubkey_projective, None).unwrap()); - - assert!(proof_projective.verify(&pubkey_affine, None).unwrap()); - assert!(proof_affine.verify(&pubkey_affine, None).unwrap()); - assert!(proof_compressed.verify(&pubkey_affine, None).unwrap()); - - assert!(proof_projective.verify(&pubkey_compressed, None).unwrap()); - assert!(proof_affine.verify(&pubkey_compressed, None).unwrap()); - assert!(proof_compressed.verify(&pubkey_compressed, None).unwrap()); - } - - #[test] - fn proof_of_possession_from_str() { - let proof_of_possession = ProofOfPossession([1; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE]); - let proof_of_possession_string = proof_of_possession.to_string(); - let proof_of_possession_from_string = - ProofOfPossession::from_str(&proof_of_possession_string).unwrap(); - assert_eq!(proof_of_possession, proof_of_possession_from_string); - - let proof_of_possession_compressed = - ProofOfPossessionCompressed([1; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]); - let proof_of_possession_compressed_string = proof_of_possession_compressed.to_string(); - let proof_of_possession_compressed_from_string = - ProofOfPossessionCompressed::from_str(&proof_of_possession_compressed_string).unwrap(); - assert_eq!( - proof_of_possession_compressed, - proof_of_possession_compressed_from_string - ); - } - - #[cfg(feature = "serde")] - #[test] - fn serialize_and_deserialize_proof_of_possession() { - let original = ProofOfPossession::default(); - let serialized = bincode::serialize(&original).unwrap(); - let deserialized: ProofOfPossession = bincode::deserialize(&serialized).unwrap(); - assert_eq!(original, deserialized); - - let original = ProofOfPossession([1; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE]); - let serialized = bincode::serialize(&original).unwrap(); - let deserialized: ProofOfPossession = bincode::deserialize(&serialized).unwrap(); - assert_eq!(original, deserialized); - } - - #[cfg(feature = "serde")] - #[test] - fn serialize_and_deserialize_proof_of_possession_compressed() { - let original = ProofOfPossessionCompressed::default(); - let serialized = bincode::serialize(&original).unwrap(); - let deserialized: ProofOfPossessionCompressed = bincode::deserialize(&serialized).unwrap(); - assert_eq!(original, deserialized); - - let original = ProofOfPossessionCompressed([1; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]); - let serialized = bincode::serialize(&original).unwrap(); - let deserialized: ProofOfPossessionCompressed = bincode::deserialize(&serialized).unwrap(); - assert_eq!(original, deserialized); - } - - #[test] - fn test_proof_of_possession_with_custom_payload() { - let keypair = Keypair::new(); - let custom_payload = b"SIMD-0387-context-data"; - - let proof_custom = keypair.proof_of_possession(Some(custom_payload)); - assert!(keypair - .public - .verify_proof_of_possession(&proof_custom, Some(custom_payload)) - .unwrap()); - - assert!(!keypair - .public - .verify_proof_of_possession(&proof_custom, None) // try verify with `None` - .unwrap()); - - let wrong_payload = b"wrong-context"; - assert!(!keypair - .public - // try verify with wrong payload - .verify_proof_of_possession(&proof_custom, Some(wrong_payload)) - .unwrap()); - - // verify standard PoP behavior - let proof_standard = keypair.proof_of_possession(None); - // standard passes with None - assert!(keypair - .public - .verify_proof_of_possession(&proof_standard, None) - .unwrap()); - // standard fails with custom payload - assert!(!keypair - .public - .verify_proof_of_possession(&proof_standard, Some(custom_payload)) - .unwrap()); - } -} diff --git a/bls-signatures/src/proof_of_possession/bytes.rs b/bls-signatures/src/proof_of_possession/bytes.rs new file mode 100644 index 000000000..0a0ef718a --- /dev/null +++ b/bls-signatures/src/proof_of_possession/bytes.rs @@ -0,0 +1,115 @@ +#[cfg(not(target_os = "solana"))] +use crate::error::BlsError; +#[cfg(feature = "bytemuck")] +use bytemuck::{Pod, PodInOption, Zeroable, ZeroableInOption}; +use { + base64::{prelude::BASE64_STANDARD, Engine}, + core::fmt, +}; +#[cfg(feature = "serde")] +use { + serde::{Deserialize, Serialize}, + serde_with::serde_as, +}; + +/// Size of a BLS proof of possession in a compressed point representation +pub const BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE: usize = 96; + +/// Size of a BLS proof of possession in a compressed point representation in base64 +pub const BLS_PROOF_OF_POSSESSION_COMPRESSED_BASE64_SIZE: usize = 128; + +/// Size of a BLS proof of possession in an affine point representation +pub const BLS_PROOF_OF_POSSESSION_AFFINE_SIZE: usize = 192; + +/// Size of a BLS proof of possession in an affine point representation in base64 +pub const BLS_PROOF_OF_POSSESSION_AFFINE_BASE64_SIZE: usize = 256; + +/// A trait for types that can be converted into a `ProofOfPossession` (affine). +#[cfg(not(target_os = "solana"))] +pub trait AsProofOfPossession { + /// Attempt to convert the type into a `ProofOfPossession`. + fn try_as_affine(&self) -> Result; +} + +/// A serialized BLS proof of possession in a compressed point representation. +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] +#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct ProofOfPossessionCompressed( + #[cfg_attr( + feature = "serde", + serde_as(as = "[_; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]") + )] + pub [u8; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE], +); + +impl Default for ProofOfPossessionCompressed { + fn default() -> Self { + Self([0; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]) + } +} + +impl fmt::Display for ProofOfPossessionCompressed { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.0)) + } +} + +impl_from_str!( + TYPE = ProofOfPossessionCompressed, + BYTES_LEN = BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, + BASE64_LEN = BLS_PROOF_OF_POSSESSION_COMPRESSED_BASE64_SIZE +); + +/// A serialized BLS proof of possession in an affine point representation. +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] +#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct ProofOfPossession( + #[cfg_attr( + feature = "serde", + serde_as(as = "[_; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE]") + )] + pub [u8; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE], +); + +impl Default for ProofOfPossession { + fn default() -> Self { + Self([0; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE]) + } +} + +impl fmt::Display for ProofOfPossession { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.0)) + } +} + +impl_from_str!( + TYPE = ProofOfPossession, + BYTES_LEN = BLS_PROOF_OF_POSSESSION_AFFINE_SIZE, + BASE64_LEN = BLS_PROOF_OF_POSSESSION_AFFINE_BASE64_SIZE +); + +// Byte arrays are both `Pod` and `Zeraoble`, but the traits `bytemuck::Pod` and +// `bytemuck::Zeroable` can only be derived for power-of-two length byte arrays. +// Directly implement these traits for types that are simple wrappers around +// byte arrays. +#[cfg(feature = "bytemuck")] +mod bytemuck_impls { + use super::*; + + unsafe impl Zeroable for ProofOfPossessionCompressed {} + unsafe impl Pod for ProofOfPossessionCompressed {} + unsafe impl ZeroableInOption for ProofOfPossessionCompressed {} + unsafe impl PodInOption for ProofOfPossessionCompressed {} + + unsafe impl Zeroable for ProofOfPossession {} + unsafe impl Pod for ProofOfPossession {} + unsafe impl ZeroableInOption for ProofOfPossession {} + unsafe impl PodInOption for ProofOfPossession {} +} diff --git a/bls-signatures/src/proof_of_possession/conversion.rs b/bls-signatures/src/proof_of_possession/conversion.rs new file mode 100644 index 000000000..9e2292f29 --- /dev/null +++ b/bls-signatures/src/proof_of_possession/conversion.rs @@ -0,0 +1,21 @@ +#[cfg(not(target_os = "solana"))] +use { + crate::{ + error::BlsError, + proof_of_possession::{ + bytes::{AsProofOfPossession, ProofOfPossession, ProofOfPossessionCompressed}, + points::{AsProofOfPossessionProjective, ProofOfPossessionProjective}, + }, + }, + blstrs::G2Affine, +}; + +#[cfg(not(target_os = "solana"))] +impl_bls_conversions!( + ProofOfPossessionProjective, + ProofOfPossession, + ProofOfPossessionCompressed, + G2Affine, + AsProofOfPossessionProjective, + AsProofOfPossession +); diff --git a/bls-signatures/src/proof_of_possession/mod.rs b/bls-signatures/src/proof_of_possession/mod.rs new file mode 100644 index 000000000..b157ee607 --- /dev/null +++ b/bls-signatures/src/proof_of_possession/mod.rs @@ -0,0 +1,143 @@ +pub mod bytes; +pub mod conversion; +pub mod points; + +pub use bytes::{ + ProofOfPossession, ProofOfPossessionCompressed, BLS_PROOF_OF_POSSESSION_AFFINE_BASE64_SIZE, + BLS_PROOF_OF_POSSESSION_AFFINE_SIZE, BLS_PROOF_OF_POSSESSION_COMPRESSED_BASE64_SIZE, + BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE, +}; +#[cfg(not(target_os = "solana"))] +pub use { + bytes::AsProofOfPossession, + points::{ + AsProofOfPossessionProjective, ProofOfPossessionProjective, VerifiableProofOfPossession, + }, +}; + +/// Domain separation tag used when hashing public keys to G2 in the proof of +/// possession signing and verification functions. See the +/// [standard](https://datatracker.ietf.org/doc/html/draft-irtf-cfrg-bls-signature-05#section-4.2.3). +pub const POP_DST: &[u8] = b"BLS_SIG_BLS12381G2_XMD:SHA-256_SSWU_RO_POP_"; + +#[cfg(test)] +mod tests { + use { + super::*, + crate::{ + keypair::Keypair, + pubkey::{Pubkey, PubkeyCompressed, PubkeyProjective, VerifiablePubkey}, + }, + core::str::FromStr, + std::string::ToString, + }; + + #[test] + fn test_proof_of_possession() { + let keypair = Keypair::new(); + let proof_projective = keypair.proof_of_possession(None); + + let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); + let pubkey_affine: Pubkey = keypair.public; + let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); + + let proof_affine: ProofOfPossession = proof_projective.into(); + let proof_compressed: ProofOfPossessionCompressed = proof_affine.try_into().unwrap(); + + assert!(proof_projective.verify(&pubkey_projective, None).unwrap()); + assert!(proof_affine.verify(&pubkey_projective, None).unwrap()); + assert!(proof_compressed.verify(&pubkey_projective, None).unwrap()); + + assert!(proof_projective.verify(&pubkey_affine, None).unwrap()); + assert!(proof_affine.verify(&pubkey_affine, None).unwrap()); + assert!(proof_compressed.verify(&pubkey_affine, None).unwrap()); + + assert!(proof_projective.verify(&pubkey_compressed, None).unwrap()); + assert!(proof_affine.verify(&pubkey_compressed, None).unwrap()); + assert!(proof_compressed.verify(&pubkey_compressed, None).unwrap()); + } + + #[test] + fn proof_of_possession_from_str() { + let proof_of_possession = ProofOfPossession([1; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE]); + let proof_of_possession_string = proof_of_possession.to_string(); + let proof_of_possession_from_string = + ProofOfPossession::from_str(&proof_of_possession_string).unwrap(); + assert_eq!(proof_of_possession, proof_of_possession_from_string); + + let proof_of_possession_compressed = + ProofOfPossessionCompressed([1; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]); + let proof_of_possession_compressed_string = proof_of_possession_compressed.to_string(); + let proof_of_possession_compressed_from_string = + ProofOfPossessionCompressed::from_str(&proof_of_possession_compressed_string).unwrap(); + assert_eq!( + proof_of_possession_compressed, + proof_of_possession_compressed_from_string + ); + } + + #[cfg(feature = "serde")] + #[test] + fn serialize_and_deserialize_proof_of_possession() { + let original = ProofOfPossession::default(); + let serialized = bincode::serialize(&original).unwrap(); + let deserialized: ProofOfPossession = bincode::deserialize(&serialized).unwrap(); + assert_eq!(original, deserialized); + + let original = ProofOfPossession([1; BLS_PROOF_OF_POSSESSION_AFFINE_SIZE]); + let serialized = bincode::serialize(&original).unwrap(); + let deserialized: ProofOfPossession = bincode::deserialize(&serialized).unwrap(); + assert_eq!(original, deserialized); + } + + #[cfg(feature = "serde")] + #[test] + fn serialize_and_deserialize_proof_of_possession_compressed() { + let original = ProofOfPossessionCompressed::default(); + let serialized = bincode::serialize(&original).unwrap(); + let deserialized: ProofOfPossessionCompressed = bincode::deserialize(&serialized).unwrap(); + assert_eq!(original, deserialized); + + let original = ProofOfPossessionCompressed([1; BLS_PROOF_OF_POSSESSION_COMPRESSED_SIZE]); + let serialized = bincode::serialize(&original).unwrap(); + let deserialized: ProofOfPossessionCompressed = bincode::deserialize(&serialized).unwrap(); + assert_eq!(original, deserialized); + } + + #[test] + fn test_proof_of_possession_with_custom_payload() { + let keypair = Keypair::new(); + let custom_payload = b"SIMD-0387-context-data"; + + let proof_custom = keypair.proof_of_possession(Some(custom_payload)); + assert!(keypair + .public + .verify_proof_of_possession(&proof_custom, Some(custom_payload)) + .unwrap()); + + assert!(!keypair + .public + .verify_proof_of_possession(&proof_custom, None) // try verify with `None` + .unwrap()); + + let wrong_payload = b"wrong-context"; + assert!(!keypair + .public + // try verify with wrong payload + .verify_proof_of_possession(&proof_custom, Some(wrong_payload)) + .unwrap()); + + // verify standard PoP behavior + let proof_standard = keypair.proof_of_possession(None); + // standard passes with None + assert!(keypair + .public + .verify_proof_of_possession(&proof_standard, None) + .unwrap()); + // standard fails with custom payload + assert!(!keypair + .public + .verify_proof_of_possession(&proof_standard, Some(custom_payload)) + .unwrap()); + } +} diff --git a/bls-signatures/src/proof_of_possession/points.rs b/bls-signatures/src/proof_of_possession/points.rs new file mode 100644 index 000000000..2c86f1940 --- /dev/null +++ b/bls-signatures/src/proof_of_possession/points.rs @@ -0,0 +1,34 @@ +#[cfg(not(target_os = "solana"))] +use { + crate::{error::BlsError, pubkey::VerifiablePubkey}, + blstrs::G2Projective, +}; + +/// A trait for types that can be converted into a `ProofOfPossessionProjective`. +#[cfg(not(target_os = "solana"))] +pub trait AsProofOfPossessionProjective { + /// Attempt to convert the type into a `ProofOfPossessionProjective`. + fn try_as_projective(&self) -> Result; +} + +/// A trait that provides verification methods to any convertible proof of possession type. +#[cfg(not(target_os = "solana"))] +pub trait VerifiableProofOfPossession: AsProofOfPossessionProjective { + /// Verifies the proof of possession against any convertible public key type. + fn verify( + &self, + pubkey: &P, + payload: Option<&[u8]>, + ) -> Result { + let proof_projective = self.try_as_projective()?; + pubkey.verify_proof_of_possession(&proof_projective, payload) + } +} + +/// A BLS proof of possession in a projective point representation. +#[cfg(not(target_os = "solana"))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct ProofOfPossessionProjective(pub(crate) G2Projective); + +#[cfg(not(target_os = "solana"))] +impl VerifiableProofOfPossession for T {} diff --git a/bls-signatures/src/pubkey.rs b/bls-signatures/src/pubkey.rs deleted file mode 100644 index 94f226d77..000000000 --- a/bls-signatures/src/pubkey.rs +++ /dev/null @@ -1,544 +0,0 @@ -#[cfg(feature = "bytemuck")] -use bytemuck::{Pod, PodInOption, Zeroable, ZeroableInOption}; -#[cfg(all(feature = "parallel", not(target_os = "solana")))] -use rayon::prelude::*; -#[cfg(all(not(target_os = "solana"), feature = "std"))] -use std::sync::LazyLock; -#[cfg(not(target_os = "solana"))] -use { - crate::{ - error::BlsError, - hash::{hash_message_to_point, hash_pubkey_to_g2}, - proof_of_possession::{AsProofOfPossession, ProofOfPossession}, - secret_key::SecretKey, - signature::{AsSignature, Signature}, - }, - blstrs::{Bls12, G1Affine, G1Projective, G2Affine, G2Prepared, Gt}, - group::Group, - pairing::{MillerLoopResult, MultiMillerLoop}, -}; -use { - base64::{prelude::BASE64_STANDARD, Engine}, - core::fmt, -}; -#[cfg(feature = "serde")] -use { - serde::{Deserialize, Serialize}, - serde_with::serde_as, -}; - -/// Size of a BLS public key in a compressed point representation -pub const BLS_PUBLIC_KEY_COMPRESSED_SIZE: usize = 48; - -/// Size of a BLS public key in a compressed point representation in base64 -pub const BLS_PUBLIC_KEY_COMPRESSED_BASE64_SIZE: usize = 128; - -/// Size of a BLS public key in an affine point representation -pub const BLS_PUBLIC_KEY_AFFINE_SIZE: usize = 96; - -/// Size of a BLS public key in an affine point representation in base64 -pub const BLS_PUBLIC_KEY_AFFINE_BASE64_SIZE: usize = 256; - -#[cfg(all(not(target_os = "solana"), feature = "std"))] -pub(crate) static NEG_G1_GENERATOR_AFFINE: LazyLock = - LazyLock::new(|| (-G1Projective::generator()).into()); - -/// A trait for types that can be converted into a `PubkeyProjective`. -#[cfg(not(target_os = "solana"))] -pub trait AsPubkeyProjective { - /// Attempt to convert the type into a `PubkeyProjective`. - fn try_as_projective(&self) -> Result; -} - -/// A trait for types that can be converted into a `Pubkey` (affine). -#[cfg(not(target_os = "solana"))] -pub trait AsPubkey { - /// Attempt to convert the type into a `Pubkey`. - fn try_as_affine(&self) -> Result; -} - -/// A trait that provides verification methods to any convertible public key type. -#[cfg(not(target_os = "solana"))] -pub trait VerifiablePubkey: AsPubkey { - /// Uses this public key to verify any convertible signature type. - fn verify_signature( - &self, - signature: &S, - message: &[u8], - ) -> Result { - let pubkey_affine = self.try_as_affine()?; - let signature_affine = signature.try_as_affine()?; - Ok(pubkey_affine._verify_signature(&signature_affine, message)) - } - - /// Uses this public key to verify any convertible proof of possession type. - fn verify_proof_of_possession( - &self, - proof: &P, - payload: Option<&[u8]>, - ) -> Result { - let pubkey_affine = self.try_as_affine()?; - let proof_affine = proof.try_as_affine()?; - Ok(pubkey_affine._verify_proof_of_possession(&proof_affine, payload)) - } -} - -/// A BLS public key in a projective point representation -#[cfg(not(target_os = "solana"))] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct PubkeyProjective(pub(crate) G1Projective); - -#[cfg(not(target_os = "solana"))] -impl PubkeyProjective { - /// Creates the identity element, which is the starting point for aggregation - /// - /// The identity element is not a valid public key and it should only be used - /// for the purpose of aggregation - pub fn identity() -> Self { - Self(G1Projective::identity()) - } - - /// Construct a corresponding `BlsPubkey` for a `BlsSecretKey` - #[allow(clippy::arithmetic_side_effects)] - pub fn from_secret(secret: &SecretKey) -> Self { - Self(G1Projective::generator() * secret.0) - } - - /// Aggregate a list of public keys into an existing aggregate - #[allow(clippy::arithmetic_side_effects)] - pub fn aggregate_with<'a, P: AsPubkeyProjective + ?Sized + 'a>( - &mut self, - pubkeys: impl Iterator, - ) -> Result<(), BlsError> { - for pubkey in pubkeys { - self.0 += pubkey.try_as_projective()?.0; - } - Ok(()) - } - - /// Aggregate a list of public keys - #[allow(clippy::arithmetic_side_effects)] - pub fn aggregate<'a, P: AsPubkeyProjective + ?Sized + 'a>( - mut pubkeys: impl Iterator, - ) -> Result { - match pubkeys.next() { - Some(first) => { - let mut aggregate = first.try_as_projective()?; - aggregate.aggregate_with(pubkeys)?; - Ok(aggregate) - } - None => Err(BlsError::EmptyAggregation), - } - } - - /// Aggregate a list of public keys into an existing aggregate - #[allow(clippy::arithmetic_side_effects)] - #[cfg(feature = "parallel")] - pub fn par_aggregate_with<'a, P: AsPubkeyProjective + Sync + 'a>( - &mut self, - pubkeys: impl ParallelIterator, - ) -> Result<(), BlsError> { - let aggregate = PubkeyProjective::par_aggregate(pubkeys)?; - self.0 += &aggregate.0; - Ok(()) - } - - /// Aggregate a list of public keys - #[allow(clippy::arithmetic_side_effects)] - #[cfg(feature = "parallel")] - pub fn par_aggregate<'a, P: AsPubkeyProjective + Sync + 'a>( - pubkeys: impl ParallelIterator, - ) -> Result { - pubkeys - .into_par_iter() - .map(|key| key.try_as_projective()) - .try_reduce_with(|mut a, b| { - a.0 += b.0; - Ok(a) - }) - .ok_or(BlsError::EmptyAggregation)? - } -} - -#[cfg(not(target_os = "solana"))] -impl VerifiablePubkey for T {} - -#[cfg(not(target_os = "solana"))] -impl_bls_conversions!( - PubkeyProjective, - Pubkey, - PubkeyCompressed, - G1Affine, - AsPubkeyProjective, - AsPubkey -); - -#[cfg(not(target_os = "solana"))] -impl TryFrom<&[u8]> for PubkeyProjective { - type Error = BlsError; - fn try_from(bytes: &[u8]) -> Result { - if bytes.len() != BLS_PUBLIC_KEY_AFFINE_SIZE { - return Err(BlsError::ParseFromBytes); - } - // unwrap safe due to the length check above - let public_affine = Pubkey(bytes.try_into().unwrap()); - - public_affine.try_into() - } -} - -#[cfg(not(target_os = "solana"))] -impl From<&PubkeyProjective> for [u8; BLS_PUBLIC_KEY_AFFINE_SIZE] { - fn from(pubkey: &PubkeyProjective) -> Self { - let pubkey_affine: Pubkey = (*pubkey).into(); - pubkey_affine.0 - } -} - -/// A serialized BLS public key in a compressed point representation -#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] -#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[repr(transparent)] -pub struct PubkeyCompressed( - #[cfg_attr( - feature = "serde", - serde_as(as = "[_; BLS_PUBLIC_KEY_COMPRESSED_SIZE]") - )] - pub [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], -); - -impl Default for PubkeyCompressed { - fn default() -> Self { - Self([0; BLS_PUBLIC_KEY_COMPRESSED_SIZE]) - } -} - -impl fmt::Display for PubkeyCompressed { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.0)) - } -} - -impl_from_str!( - TYPE = PubkeyCompressed, - BYTES_LEN = BLS_PUBLIC_KEY_COMPRESSED_SIZE, - BASE64_LEN = BLS_PUBLIC_KEY_COMPRESSED_BASE64_SIZE -); - -/// A serialized BLS public key in an affine point representation -#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] -#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] -#[repr(transparent)] -pub struct Pubkey( - #[cfg_attr(feature = "serde", serde_as(as = "[_; BLS_PUBLIC_KEY_AFFINE_SIZE]"))] - pub [u8; BLS_PUBLIC_KEY_AFFINE_SIZE], -); - -#[cfg(not(target_os = "solana"))] -impl Pubkey { - /// Verify a signature and a message against a public key - pub(crate) fn _verify_signature(&self, signature: &Signature, message: &[u8]) -> bool { - let Some(pubkey_affine): Option = G1Affine::from_uncompressed(&self.0).into() - else { - return false; - }; - let Some(signature_affine): Option = - G2Affine::from_uncompressed(&signature.0).into() - else { - return false; - }; - - // The verification equation is e(pubkey, H(m)) = e(g1, signature). - // This can be rewritten as e(pubkey, H(m)) * e(-g1, signature) = 1, which - // allows for a more efficient verification using a multi-miller loop. - let hashed_message: G2Affine = hash_message_to_point(message).into(); - let hashed_message_prepared = G2Prepared::from(hashed_message); - let signature_prepared = G2Prepared::from(signature_affine); - - // use the static valud if `std` is available, otherwise compute it - #[cfg(feature = "std")] - let neg_g1_generator = &NEG_G1_GENERATOR_AFFINE; - #[cfg(not(feature = "std"))] - let neg_g1_generator_val: G1Affine = (-G1Projective::generator()).into(); - #[cfg(not(feature = "std"))] - let neg_g1_generator = &neg_g1_generator_val; - - let miller_loop_result = Bls12::multi_miller_loop(&[ - (&pubkey_affine, &hashed_message_prepared), - (neg_g1_generator, &signature_prepared), - ]); - miller_loop_result.final_exponentiation() == Gt::identity() - } - - /// Verify a proof of possession against a public key - pub(crate) fn _verify_proof_of_possession( - &self, - proof: &ProofOfPossession, - payload: Option<&[u8]>, - ) -> bool { - let Some(pubkey_affine): Option = G1Affine::from_uncompressed(&self.0).into() - else { - return false; - }; - let Some(proof_affine): Option = G2Affine::from_uncompressed(&proof.0).into() - else { - return false; - }; - let Ok(pubkey_projective) = PubkeyProjective::try_from(self) else { - return false; - }; - - // The verification equation is e(pubkey, H(pubkey)) == e(g1, proof). - // This is rewritten to e(pubkey, H(pubkey)) * e(-g1, proof) = 1 for batching. - let hashed_pubkey_affine: G2Affine = hash_pubkey_to_g2(&pubkey_projective, payload).into(); - let hashed_pubkey_prepared = G2Prepared::from(hashed_pubkey_affine); - let proof_prepared = G2Prepared::from(proof_affine); - - // Use the static value if std is available, otherwise compute it - #[cfg(feature = "std")] - let neg_g1_generator = &NEG_G1_GENERATOR_AFFINE; - #[cfg(not(feature = "std"))] - let neg_g1_generator_val: G1Affine = (-G1Projective::generator()).into(); - #[cfg(not(feature = "std"))] - let neg_g1_generator = &neg_g1_generator_val; - - let miller_loop_result = Bls12::multi_miller_loop(&[ - (&pubkey_affine, &hashed_pubkey_prepared), - // Reuse the same pre-computed static value here for efficiency - (neg_g1_generator, &proof_prepared), - ]); - - miller_loop_result.final_exponentiation() == Gt::identity() - } -} - -impl Default for Pubkey { - fn default() -> Self { - Self([0; BLS_PUBLIC_KEY_AFFINE_SIZE]) - } -} - -impl fmt::Display for Pubkey { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.0)) - } -} - -impl_from_str!( - TYPE = Pubkey, - BYTES_LEN = BLS_PUBLIC_KEY_AFFINE_SIZE, - BASE64_LEN = BLS_PUBLIC_KEY_AFFINE_BASE64_SIZE -); - -// Byte arrays are both `Pod` and `Zeraoble`, but the traits `bytemuck::Pod` and -// `bytemuck::Zeroable` can only be derived for power-of-two length byte arrays. -// Directly implement these traits for types that are simple wrappers around -// byte arrays. -#[cfg(feature = "bytemuck")] -mod bytemuck_impls { - use super::*; - unsafe impl Zeroable for PubkeyCompressed {} - unsafe impl Pod for PubkeyCompressed {} - unsafe impl ZeroableInOption for PubkeyCompressed {} - unsafe impl PodInOption for PubkeyCompressed {} - - unsafe impl Zeroable for Pubkey {} - unsafe impl Pod for Pubkey {} - unsafe impl ZeroableInOption for Pubkey {} - unsafe impl PodInOption for Pubkey {} -} - -#[cfg(test)] -mod tests { - use { - super::*, - crate::{ - keypair::Keypair, - proof_of_possession::{ProofOfPossession, ProofOfPossessionCompressed}, - signature::{Signature, SignatureCompressed}, - }, - core::str::FromStr, - std::string::ToString, - }; - - #[test] - fn test_pubkey_verify_signature() { - let keypair = Keypair::new(); - let test_message = b"test message"; - let signature_projective = keypair.sign(test_message); - - let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); - let pubkey_affine: Pubkey = keypair.public; - let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); - - let signature_affine: Signature = signature_projective.into(); - let signature_compressed: SignatureCompressed = signature_affine.try_into().unwrap(); - - assert!(pubkey_projective - .verify_signature(&signature_projective, test_message) - .unwrap()); - assert!(pubkey_affine - .verify_signature(&signature_projective, test_message) - .unwrap()); - assert!(pubkey_compressed - .verify_signature(&signature_projective, test_message) - .unwrap()); - - assert!(pubkey_projective - .verify_signature(&signature_affine, test_message) - .unwrap()); - assert!(pubkey_affine - .verify_signature(&signature_affine, test_message) - .unwrap()); - assert!(pubkey_compressed - .verify_signature(&signature_affine, test_message) - .unwrap()); - - assert!(pubkey_projective - .verify_signature(&signature_compressed, test_message) - .unwrap()); - assert!(pubkey_affine - .verify_signature(&signature_compressed, test_message) - .unwrap()); - assert!(pubkey_compressed - .verify_signature(&signature_compressed, test_message) - .unwrap()); - } - - #[test] - fn test_pubkey_verify_proof_of_possession() { - let keypair = Keypair::new(); - let proof_projective = keypair.proof_of_possession(None); - - let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); - let pubkey_affine: Pubkey = pubkey_projective.into(); - let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); - - let proof_affine: ProofOfPossession = proof_projective.into(); - let proof_compressed: ProofOfPossessionCompressed = proof_affine.try_into().unwrap(); - - assert!(pubkey_projective - .verify_proof_of_possession(&proof_projective, None) - .unwrap()); - assert!(pubkey_affine - .verify_proof_of_possession(&proof_projective, None) - .unwrap()); - assert!(pubkey_compressed - .verify_proof_of_possession(&proof_projective, None) - .unwrap()); - - assert!(pubkey_projective - .verify_proof_of_possession(&proof_affine, None) - .unwrap()); - assert!(pubkey_affine - .verify_proof_of_possession(&proof_affine, None) - .unwrap()); - assert!(pubkey_compressed - .verify_proof_of_possession(&proof_affine, None) - .unwrap()); - - assert!(pubkey_projective - .verify_proof_of_possession(&proof_compressed, None) - .unwrap()); - assert!(pubkey_affine - .verify_proof_of_possession(&proof_compressed, None) - .unwrap()); - assert!(pubkey_compressed - .verify_proof_of_possession(&proof_compressed, None) - .unwrap()); - } - - #[test] - fn test_pubkey_aggregate_dyn() { - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - - let pubkey_projective: PubkeyProjective = (&keypair0.public).try_into().unwrap(); - let pubkey_affine: Pubkey = keypair1.public; - let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); - - let dyn_pubkeys: std::vec::Vec<&dyn AsPubkeyProjective> = - std::vec![&pubkey_projective, &pubkey_affine, &pubkey_compressed]; - - let aggregate_from_dyn = PubkeyProjective::aggregate(dyn_pubkeys.into_iter()).unwrap(); - let pubkeys_for_baseline = [&keypair0.public, &keypair1.public, &keypair1.public]; - let baseline_aggregate = - PubkeyProjective::aggregate(pubkeys_for_baseline.into_iter()).unwrap(); - - assert_eq!(aggregate_from_dyn, baseline_aggregate); - } - - #[test] - fn pubkey_from_str() { - let pubkey_affine = Keypair::new().public; - let pubkey_affine_string = pubkey_affine.to_string(); - let pubkey_affine_from_string = Pubkey::from_str(&pubkey_affine_string).unwrap(); - assert_eq!(pubkey_affine, pubkey_affine_from_string); - - let pubkey_compressed = PubkeyCompressed([1; BLS_PUBLIC_KEY_COMPRESSED_SIZE]); - let pubkey_compressed_string = pubkey_compressed.to_string(); - let pubkey_compressed_from_string = - PubkeyCompressed::from_str(&pubkey_compressed_string).unwrap(); - assert_eq!(pubkey_compressed, pubkey_compressed_from_string); - } - - #[cfg(feature = "serde")] - #[test] - fn serialize_and_deserialize_pubkey() { - let original = Pubkey::default(); - let serialized = bincode::serialize(&original).unwrap(); - let deserialized: Pubkey = bincode::deserialize(&serialized).unwrap(); - assert_eq!(original, deserialized); - - let original = Pubkey([1; BLS_PUBLIC_KEY_AFFINE_SIZE]); - let serialized = bincode::serialize(&original).unwrap(); - let deserialized: Pubkey = bincode::deserialize(&serialized).unwrap(); - assert_eq!(original, deserialized); - } - - #[cfg(feature = "serde")] - #[test] - fn serialize_and_deserialize_pubkey_compressed() { - let original = PubkeyCompressed::default(); - let serialized = bincode::serialize(&original).unwrap(); - let deserialized: PubkeyCompressed = bincode::deserialize(&serialized).unwrap(); - assert_eq!(original, deserialized); - - let original = PubkeyCompressed([1; BLS_PUBLIC_KEY_COMPRESSED_SIZE]); - let serialized = bincode::serialize(&original).unwrap(); - let deserialized: PubkeyCompressed = bincode::deserialize(&serialized).unwrap(); - assert_eq!(original, deserialized); - } - - #[test] - #[cfg(feature = "parallel")] - fn test_parallel_pubkey_aggregation() { - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - let pubkey0 = PubkeyProjective::try_from(&keypair0.public).unwrap(); - let pubkey1 = PubkeyProjective::try_from(&keypair1.public).unwrap(); - - // Test `aggregate` - let sequential_agg = PubkeyProjective::aggregate([pubkey0, pubkey1].iter()).unwrap(); - let parallel_agg = PubkeyProjective::par_aggregate([pubkey0, pubkey1].par_iter()).unwrap(); - assert_eq!(sequential_agg, parallel_agg); - - // Test `aggregate_with` - let mut parallel_agg_with = pubkey0; - parallel_agg_with - .par_aggregate_with([pubkey1].par_iter()) - .unwrap(); - assert_eq!(sequential_agg, parallel_agg_with); - - // Test empty case - let empty: std::vec::Vec = std::vec![]; - assert_eq!( - PubkeyProjective::par_aggregate(empty.par_iter()).unwrap_err(), - BlsError::EmptyAggregation - ); - } -} diff --git a/bls-signatures/src/pubkey/bytes.rs b/bls-signatures/src/pubkey/bytes.rs new file mode 100644 index 000000000..ea14b5c2f --- /dev/null +++ b/bls-signatures/src/pubkey/bytes.rs @@ -0,0 +1,236 @@ +#[cfg(all(not(target_os = "solana"), feature = "std"))] +use crate::pubkey::points::NEG_G1_GENERATOR_AFFINE; +#[cfg(not(feature = "std"))] +use blstrs::G1Projective; +#[cfg(feature = "bytemuck")] +use bytemuck::{Pod, PodInOption, Zeroable, ZeroableInOption}; +#[cfg(not(target_os = "solana"))] +use { + crate::{ + error::BlsError, + hash::{hash_message_to_point, hash_pubkey_to_g2}, + proof_of_possession::{AsProofOfPossession, ProofOfPossession}, + pubkey::points::PubkeyProjective, + signature::{AsSignature, Signature}, + }, + blstrs::{Bls12, G1Affine, G2Affine, G2Prepared, Gt}, + group::Group, + pairing::{MillerLoopResult, MultiMillerLoop}, +}; +use { + base64::{prelude::BASE64_STANDARD, Engine}, + core::fmt, +}; +#[cfg(feature = "serde")] +use { + serde::{Deserialize, Serialize}, + serde_with::serde_as, +}; + +/// Size of a BLS public key in a compressed point representation +pub const BLS_PUBLIC_KEY_COMPRESSED_SIZE: usize = 48; + +/// Size of a BLS public key in a compressed point representation in base64 +pub const BLS_PUBLIC_KEY_COMPRESSED_BASE64_SIZE: usize = 128; + +/// Size of a BLS public key in an affine point representation +pub const BLS_PUBLIC_KEY_AFFINE_SIZE: usize = 96; + +/// Size of a BLS public key in an affine point representation in base64 +pub const BLS_PUBLIC_KEY_AFFINE_BASE64_SIZE: usize = 256; + +/// A trait for types that can be converted into a `Pubkey` (affine/uncompressed bytes). +#[cfg(not(target_os = "solana"))] +pub trait AsPubkey { + /// Attempt to convert the type into a `Pubkey`. + fn try_as_affine(&self) -> Result; +} + +/// A trait that provides verification methods to any convertible public key type. +#[cfg(not(target_os = "solana"))] +pub trait VerifiablePubkey: AsPubkey { + /// Uses this public key to verify any convertible signature type. + fn verify_signature( + &self, + signature: &S, + message: &[u8], + ) -> Result { + let pubkey_affine = self.try_as_affine()?; + let signature_affine = signature.try_as_affine()?; + Ok(pubkey_affine._verify_signature(&signature_affine, message)) + } + + /// Uses this public key to verify any convertible proof of possession type. + fn verify_proof_of_possession( + &self, + proof: &P, + payload: Option<&[u8]>, + ) -> Result { + let pubkey_affine = self.try_as_affine()?; + let proof_affine = proof.try_as_affine()?; + Ok(pubkey_affine._verify_proof_of_possession(&proof_affine, payload)) + } +} + +/// A serialized BLS public key in a compressed point representation. +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] +#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct PubkeyCompressed( + #[cfg_attr( + feature = "serde", + serde_as(as = "[_; BLS_PUBLIC_KEY_COMPRESSED_SIZE]") + )] + pub [u8; BLS_PUBLIC_KEY_COMPRESSED_SIZE], +); + +impl Default for PubkeyCompressed { + fn default() -> Self { + Self([0; BLS_PUBLIC_KEY_COMPRESSED_SIZE]) + } +} + +impl fmt::Display for PubkeyCompressed { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.0)) + } +} + +impl_from_str!( + TYPE = PubkeyCompressed, + BYTES_LEN = BLS_PUBLIC_KEY_COMPRESSED_SIZE, + BASE64_LEN = BLS_PUBLIC_KEY_COMPRESSED_BASE64_SIZE +); + +/// A serialized BLS public key in an affine point representation. +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] +#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct Pubkey( + #[cfg_attr(feature = "serde", serde_as(as = "[_; BLS_PUBLIC_KEY_AFFINE_SIZE]"))] + pub [u8; BLS_PUBLIC_KEY_AFFINE_SIZE], +); + +#[cfg(not(target_os = "solana"))] +impl Pubkey { + /// Verify a signature and a message against a public key + pub(crate) fn _verify_signature(&self, signature: &Signature, message: &[u8]) -> bool { + let Some(pubkey_affine): Option = G1Affine::from_uncompressed(&self.0).into() + else { + return false; + }; + let Some(signature_affine): Option = + G2Affine::from_uncompressed(&signature.0).into() + else { + return false; + }; + + // The verification equation is e(pubkey, H(m)) = e(g1, signature). + // This can be rewritten as e(pubkey, H(m)) * e(-g1, signature) = 1, which + // allows for a more efficient verification using a multi-miller loop. + let hashed_message: G2Affine = hash_message_to_point(message).into(); + let hashed_message_prepared = G2Prepared::from(hashed_message); + let signature_prepared = G2Prepared::from(signature_affine); + + // use the static valud if `std` is available, otherwise compute it + #[cfg(feature = "std")] + let neg_g1_generator = &NEG_G1_GENERATOR_AFFINE; + #[cfg(not(feature = "std"))] + let neg_g1_generator_val: G1Affine = (-G1Projective::generator()).into(); + #[cfg(not(feature = "std"))] + let neg_g1_generator = &neg_g1_generator_val; + + let miller_loop_result = Bls12::multi_miller_loop(&[ + (&pubkey_affine, &hashed_message_prepared), + (neg_g1_generator, &signature_prepared), + ]); + miller_loop_result.final_exponentiation() == Gt::identity() + } + + /// Verify a proof of possession against a public key + pub(crate) fn _verify_proof_of_possession( + &self, + proof: &ProofOfPossession, + payload: Option<&[u8]>, + ) -> bool { + let Some(pubkey_affine): Option = G1Affine::from_uncompressed(&self.0).into() + else { + return false; + }; + let Some(proof_affine): Option = G2Affine::from_uncompressed(&proof.0).into() + else { + return false; + }; + // Dependency on conversion: PubkeyProjective::try_from(self) + // Since we are in the same crate, this circular logic works via the trait system, + // but explicit usage requires the trait or impl to be visible. + let Ok(pubkey_projective) = PubkeyProjective::try_from(self) else { + return false; + }; + + // The verification equation is e(pubkey, H(pubkey)) == e(g1, proof). + // This is rewritten to e(pubkey, H(pubkey)) * e(-g1, proof) = 1 for batching. + let hashed_pubkey_affine: G2Affine = hash_pubkey_to_g2(&pubkey_projective, payload).into(); + let hashed_pubkey_prepared = G2Prepared::from(hashed_pubkey_affine); + let proof_prepared = G2Prepared::from(proof_affine); + + // Use the static value if std is available, otherwise compute it + #[cfg(feature = "std")] + let neg_g1_generator = &NEG_G1_GENERATOR_AFFINE; + #[cfg(not(feature = "std"))] + let neg_g1_generator_val: G1Affine = (-G1Projective::generator()).into(); + #[cfg(not(feature = "std"))] + let neg_g1_generator = &neg_g1_generator_val; + + let miller_loop_result = Bls12::multi_miller_loop(&[ + (&pubkey_affine, &hashed_pubkey_prepared), + // Reuse the same pre-computed static value here for efficiency + (neg_g1_generator, &proof_prepared), + ]); + + miller_loop_result.final_exponentiation() == Gt::identity() + } +} + +impl Default for Pubkey { + fn default() -> Self { + Self([0; BLS_PUBLIC_KEY_AFFINE_SIZE]) + } +} + +impl fmt::Display for Pubkey { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.0)) + } +} + +impl_from_str!( + TYPE = Pubkey, + BYTES_LEN = BLS_PUBLIC_KEY_AFFINE_SIZE, + BASE64_LEN = BLS_PUBLIC_KEY_AFFINE_BASE64_SIZE +); + +// Byte arrays are both `Pod` and `Zeraoble`, but the traits `bytemuck::Pod` and +// `bytemuck::Zeroable` can only be derived for power-of-two length byte arrays. +// Directly implement these traits for types that are simple wrappers around +// byte arrays. +#[cfg(feature = "bytemuck")] +mod bytemuck_impls { + use super::*; + unsafe impl Zeroable for PubkeyCompressed {} + unsafe impl Pod for PubkeyCompressed {} + unsafe impl ZeroableInOption for PubkeyCompressed {} + unsafe impl PodInOption for PubkeyCompressed {} + + unsafe impl Zeroable for Pubkey {} + unsafe impl Pod for Pubkey {} + unsafe impl ZeroableInOption for Pubkey {} + unsafe impl PodInOption for Pubkey {} +} + +#[cfg(not(target_os = "solana"))] +impl VerifiablePubkey for T {} diff --git a/bls-signatures/src/pubkey/conversion.rs b/bls-signatures/src/pubkey/conversion.rs new file mode 100644 index 000000000..920e2f64e --- /dev/null +++ b/bls-signatures/src/pubkey/conversion.rs @@ -0,0 +1,43 @@ +#[cfg(not(target_os = "solana"))] +use { + crate::{ + error::BlsError, + pubkey::{ + bytes::{AsPubkey, Pubkey, PubkeyCompressed, BLS_PUBLIC_KEY_AFFINE_SIZE}, + points::{AsPubkeyProjective, PubkeyProjective}, + }, + }, + blstrs::G1Affine, +}; + +#[cfg(not(target_os = "solana"))] +impl_bls_conversions!( + PubkeyProjective, + Pubkey, + PubkeyCompressed, + G1Affine, + AsPubkeyProjective, + AsPubkey +); + +#[cfg(not(target_os = "solana"))] +impl TryFrom<&[u8]> for PubkeyProjective { + type Error = BlsError; + fn try_from(bytes: &[u8]) -> Result { + if bytes.len() != BLS_PUBLIC_KEY_AFFINE_SIZE { + return Err(BlsError::ParseFromBytes); + } + // unwrap safe due to the length check above + let public_affine = Pubkey(bytes.try_into().unwrap()); + + public_affine.try_into() + } +} + +#[cfg(not(target_os = "solana"))] +impl From<&PubkeyProjective> for [u8; BLS_PUBLIC_KEY_AFFINE_SIZE] { + fn from(pubkey: &PubkeyProjective) -> Self { + let pubkey_affine: Pubkey = (*pubkey).into(); + pubkey_affine.0 + } +} diff --git a/bls-signatures/src/pubkey/mod.rs b/bls-signatures/src/pubkey/mod.rs new file mode 100644 index 000000000..fe07a3319 --- /dev/null +++ b/bls-signatures/src/pubkey/mod.rs @@ -0,0 +1,206 @@ +pub mod bytes; +pub mod conversion; +pub mod points; + +pub use bytes::{ + Pubkey, PubkeyCompressed, BLS_PUBLIC_KEY_AFFINE_BASE64_SIZE, BLS_PUBLIC_KEY_AFFINE_SIZE, + BLS_PUBLIC_KEY_COMPRESSED_BASE64_SIZE, BLS_PUBLIC_KEY_COMPRESSED_SIZE, +}; +#[cfg(not(target_os = "solana"))] +pub use { + bytes::{AsPubkey, VerifiablePubkey}, + points::{AsPubkeyProjective, PubkeyProjective}, +}; + +#[cfg(test)] +mod tests { + use { + super::*, + crate::{ + keypair::Keypair, + proof_of_possession::{ProofOfPossession, ProofOfPossessionCompressed}, + signature::{Signature, SignatureCompressed}, + }, + core::str::FromStr, + std::string::ToString, + }; + #[cfg(feature = "parallel")] + use {crate::error::BlsError, rayon::prelude::*}; + + #[test] + fn test_pubkey_verify_signature() { + let keypair = Keypair::new(); + let test_message = b"test message"; + let signature_projective = keypair.sign(test_message); + + let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); + let pubkey_affine: Pubkey = keypair.public; + let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); + + let signature_affine: Signature = signature_projective.into(); + let signature_compressed: SignatureCompressed = signature_affine.try_into().unwrap(); + + assert!(pubkey_projective + .verify_signature(&signature_projective, test_message) + .unwrap()); + assert!(pubkey_affine + .verify_signature(&signature_projective, test_message) + .unwrap()); + assert!(pubkey_compressed + .verify_signature(&signature_projective, test_message) + .unwrap()); + + assert!(pubkey_projective + .verify_signature(&signature_affine, test_message) + .unwrap()); + assert!(pubkey_affine + .verify_signature(&signature_affine, test_message) + .unwrap()); + assert!(pubkey_compressed + .verify_signature(&signature_affine, test_message) + .unwrap()); + + assert!(pubkey_projective + .verify_signature(&signature_compressed, test_message) + .unwrap()); + assert!(pubkey_affine + .verify_signature(&signature_compressed, test_message) + .unwrap()); + assert!(pubkey_compressed + .verify_signature(&signature_compressed, test_message) + .unwrap()); + } + + #[test] + fn test_pubkey_verify_proof_of_possession() { + let keypair = Keypair::new(); + let proof_projective = keypair.proof_of_possession(None); + + let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); + let pubkey_affine: Pubkey = pubkey_projective.into(); + let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); + + let proof_affine: ProofOfPossession = proof_projective.into(); + let proof_compressed: ProofOfPossessionCompressed = proof_affine.try_into().unwrap(); + + assert!(pubkey_projective + .verify_proof_of_possession(&proof_projective, None) + .unwrap()); + assert!(pubkey_affine + .verify_proof_of_possession(&proof_projective, None) + .unwrap()); + assert!(pubkey_compressed + .verify_proof_of_possession(&proof_projective, None) + .unwrap()); + + assert!(pubkey_projective + .verify_proof_of_possession(&proof_affine, None) + .unwrap()); + assert!(pubkey_affine + .verify_proof_of_possession(&proof_affine, None) + .unwrap()); + assert!(pubkey_compressed + .verify_proof_of_possession(&proof_affine, None) + .unwrap()); + + assert!(pubkey_projective + .verify_proof_of_possession(&proof_compressed, None) + .unwrap()); + assert!(pubkey_affine + .verify_proof_of_possession(&proof_compressed, None) + .unwrap()); + assert!(pubkey_compressed + .verify_proof_of_possession(&proof_compressed, None) + .unwrap()); + } + + #[test] + fn test_pubkey_aggregate_dyn() { + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + + let pubkey_projective: PubkeyProjective = (&keypair0.public).try_into().unwrap(); + let pubkey_affine: Pubkey = keypair1.public; + let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); + + let dyn_pubkeys: std::vec::Vec<&dyn AsPubkeyProjective> = + std::vec![&pubkey_projective, &pubkey_affine, &pubkey_compressed]; + + let aggregate_from_dyn = PubkeyProjective::aggregate(dyn_pubkeys.into_iter()).unwrap(); + let pubkeys_for_baseline = [&keypair0.public, &keypair1.public, &keypair1.public]; + let baseline_aggregate = + PubkeyProjective::aggregate(pubkeys_for_baseline.into_iter()).unwrap(); + + assert_eq!(aggregate_from_dyn, baseline_aggregate); + } + + #[test] + fn pubkey_from_str() { + let pubkey_affine = Keypair::new().public; + let pubkey_affine_string = pubkey_affine.to_string(); + let pubkey_affine_from_string = Pubkey::from_str(&pubkey_affine_string).unwrap(); + assert_eq!(pubkey_affine, pubkey_affine_from_string); + + let pubkey_compressed = PubkeyCompressed([1; BLS_PUBLIC_KEY_COMPRESSED_SIZE]); + let pubkey_compressed_string = pubkey_compressed.to_string(); + let pubkey_compressed_from_string = + PubkeyCompressed::from_str(&pubkey_compressed_string).unwrap(); + assert_eq!(pubkey_compressed, pubkey_compressed_from_string); + } + + #[cfg(feature = "serde")] + #[test] + fn serialize_and_deserialize_pubkey() { + let original = Pubkey::default(); + let serialized = bincode::serialize(&original).unwrap(); + let deserialized: Pubkey = bincode::deserialize(&serialized).unwrap(); + assert_eq!(original, deserialized); + + let original = Pubkey([1; BLS_PUBLIC_KEY_AFFINE_SIZE]); + let serialized = bincode::serialize(&original).unwrap(); + let deserialized: Pubkey = bincode::deserialize(&serialized).unwrap(); + assert_eq!(original, deserialized); + } + + #[cfg(feature = "serde")] + #[test] + fn serialize_and_deserialize_pubkey_compressed() { + let original = PubkeyCompressed::default(); + let serialized = bincode::serialize(&original).unwrap(); + let deserialized: PubkeyCompressed = bincode::deserialize(&serialized).unwrap(); + assert_eq!(original, deserialized); + + let original = PubkeyCompressed([1; BLS_PUBLIC_KEY_COMPRESSED_SIZE]); + let serialized = bincode::serialize(&original).unwrap(); + let deserialized: PubkeyCompressed = bincode::deserialize(&serialized).unwrap(); + assert_eq!(original, deserialized); + } + + #[test] + #[cfg(feature = "parallel")] + fn test_parallel_pubkey_aggregation() { + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let pubkey0 = PubkeyProjective::try_from(&keypair0.public).unwrap(); + let pubkey1 = PubkeyProjective::try_from(&keypair1.public).unwrap(); + + // Test `aggregate` + let sequential_agg = PubkeyProjective::aggregate([pubkey0, pubkey1].iter()).unwrap(); + let parallel_agg = PubkeyProjective::par_aggregate([pubkey0, pubkey1].par_iter()).unwrap(); + assert_eq!(sequential_agg, parallel_agg); + + // Test `aggregate_with` + let mut parallel_agg_with = pubkey0; + parallel_agg_with + .par_aggregate_with([pubkey1].par_iter()) + .unwrap(); + assert_eq!(sequential_agg, parallel_agg_with); + + // Test empty case + let empty: std::vec::Vec = std::vec![]; + assert_eq!( + PubkeyProjective::par_aggregate(empty.par_iter()).unwrap_err(), + BlsError::EmptyAggregation + ); + } +} diff --git a/bls-signatures/src/pubkey/points.rs b/bls-signatures/src/pubkey/points.rs new file mode 100644 index 000000000..d8efffb4d --- /dev/null +++ b/bls-signatures/src/pubkey/points.rs @@ -0,0 +1,98 @@ +#[cfg(all(feature = "parallel", not(target_os = "solana")))] +use rayon::prelude::*; +#[cfg(not(target_os = "solana"))] +use { + crate::{error::BlsError, secret_key::SecretKey}, + blstrs::G1Projective, + group::Group, +}; +#[cfg(all(not(target_os = "solana"), feature = "std"))] +use {blstrs::G1Affine, std::sync::LazyLock}; + +#[cfg(all(not(target_os = "solana"), feature = "std"))] +pub(crate) static NEG_G1_GENERATOR_AFFINE: LazyLock = + LazyLock::new(|| (-G1Projective::generator()).into()); + +/// A trait for types that can be converted into a `PubkeyProjective`. +#[cfg(not(target_os = "solana"))] +pub trait AsPubkeyProjective { + /// Attempt to convert the type into a `PubkeyProjective`. + fn try_as_projective(&self) -> Result; +} + +/// A BLS public key in a projective point representation. +#[cfg(not(target_os = "solana"))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct PubkeyProjective(pub(crate) G1Projective); + +#[cfg(not(target_os = "solana"))] +impl PubkeyProjective { + /// Creates the identity element, which is the starting point for aggregation + /// + /// The identity element is not a valid public key and it should only be used + /// for the purpose of aggregation + pub fn identity() -> Self { + Self(G1Projective::identity()) + } + + /// Construct a corresponding `BlsPubkey` for a `BlsSecretKey` + #[allow(clippy::arithmetic_side_effects)] + pub fn from_secret(secret: &SecretKey) -> Self { + Self(G1Projective::generator() * secret.0) + } + + /// Aggregate a list of public keys into an existing aggregate + #[allow(clippy::arithmetic_side_effects)] + pub fn aggregate_with<'a, P: AsPubkeyProjective + ?Sized + 'a>( + &mut self, + pubkeys: impl Iterator, + ) -> Result<(), BlsError> { + for pubkey in pubkeys { + self.0 += pubkey.try_as_projective()?.0; + } + Ok(()) + } + + /// Aggregate a list of public keys + #[allow(clippy::arithmetic_side_effects)] + pub fn aggregate<'a, P: AsPubkeyProjective + ?Sized + 'a>( + mut pubkeys: impl Iterator, + ) -> Result { + match pubkeys.next() { + Some(first) => { + let mut aggregate = first.try_as_projective()?; + aggregate.aggregate_with(pubkeys)?; + Ok(aggregate) + } + None => Err(BlsError::EmptyAggregation), + } + } + + /// Aggregate a list of public keys into an existing aggregate + #[allow(clippy::arithmetic_side_effects)] + #[cfg(feature = "parallel")] + pub fn par_aggregate_with<'a, P: AsPubkeyProjective + Sync + 'a>( + &mut self, + pubkeys: impl ParallelIterator, + ) -> Result<(), BlsError> { + let aggregate = PubkeyProjective::par_aggregate(pubkeys)?; + self.0 += &aggregate.0; + Ok(()) + } + + /// Aggregate a list of public keys + #[allow(clippy::arithmetic_side_effects)] + #[cfg(feature = "parallel")] + pub fn par_aggregate<'a, P: AsPubkeyProjective + Sync + 'a>( + pubkeys: impl ParallelIterator, + ) -> Result { + pubkeys + .into_par_iter() + .map(|key| key.try_as_projective()) + .try_reduce_with(|mut a, b| { + a.0 += b.0; + Ok(a) + }) + .ok_or(BlsError::EmptyAggregation)? + } +} diff --git a/bls-signatures/src/signature.rs b/bls-signatures/src/signature.rs deleted file mode 100644 index 7634a1cba..000000000 --- a/bls-signatures/src/signature.rs +++ /dev/null @@ -1,808 +0,0 @@ -#[cfg(all(not(target_os = "solana"), feature = "std"))] -use crate::pubkey::NEG_G1_GENERATOR_AFFINE; -#[cfg(not(feature = "std"))] -use blstrs::G1Projective; -#[cfg(feature = "bytemuck")] -use bytemuck::{Pod, PodInOption, Zeroable, ZeroableInOption}; -#[cfg(not(target_os = "solana"))] -use { - crate::{ - error::BlsError, - hash::hash_message_to_point, - pubkey::{AsPubkeyProjective, Pubkey, PubkeyProjective, VerifiablePubkey}, - }, - blstrs::{Bls12, G1Affine, G2Affine, G2Prepared, G2Projective, Gt}, - group::Group, - pairing::{MillerLoopResult, MultiMillerLoop}, -}; -#[cfg(all(feature = "parallel", not(target_os = "solana")))] -use {alloc::vec::Vec, rayon::prelude::*}; -use { - base64::{prelude::BASE64_STANDARD, Engine}, - core::fmt, -}; -#[cfg(feature = "serde")] -use { - serde::{Deserialize, Serialize}, - serde_with::serde_as, -}; - -/// Size of a BLS signature in a compressed point representation -pub const BLS_SIGNATURE_COMPRESSED_SIZE: usize = 96; - -/// Size of a BLS signature in a compressed point representation in base64 -pub const BLS_SIGNATURE_COMPRESSED_BASE64_SIZE: usize = 128; - -/// Size of a BLS signature in an affine point representation -pub const BLS_SIGNATURE_AFFINE_SIZE: usize = 192; - -/// Size of a BLS signature in an affine point representation in base64 -pub const BLS_SIGNATURE_AFFINE_BASE64_SIZE: usize = 256; - -/// A trait for types that can be converted into a `SignatureProjective`. -#[cfg(not(target_os = "solana"))] -pub trait AsSignatureProjective { - /// Attempt to convert the type into a `SignatureProjective`. - fn try_as_projective(&self) -> Result; -} - -/// A trait for types that can be converted into a `Signature` (affine). -#[cfg(not(target_os = "solana"))] -pub trait AsSignature { - /// Attempt to convert the type into a `Signature`. - fn try_as_affine(&self) -> Result; -} - -/// A trait that provides verification methods to any convertible signature type. -#[cfg(not(target_os = "solana"))] -pub trait VerifiableSignature: AsSignatureProjective { - /// Verify the signature against any convertible public key type and a message. - fn verify(&self, pubkey: &P, message: &[u8]) -> Result { - // The logic is defined once here. - let signature_projective = self.try_as_projective()?; - pubkey.verify_signature(&signature_projective, message) - } -} - -/// A BLS signature in a projective point representation -#[cfg(not(target_os = "solana"))] -#[derive(Clone, Copy, Debug, Eq, PartialEq)] -pub struct SignatureProjective(pub(crate) G2Projective); - -#[cfg(not(target_os = "solana"))] -impl SignatureProjective { - /// Creates the identity element, which is the starting point for aggregation - /// - /// The identity element is not a valid signature and it should only be used - /// for the purpose of aggregation - pub fn identity() -> Self { - Self(G2Projective::identity()) - } - - /// Aggregate a list of signatures into an existing aggregate - #[allow(clippy::arithmetic_side_effects)] - pub fn aggregate_with<'a, S: AsSignatureProjective + ?Sized + 'a>( - &mut self, - signatures: impl Iterator, - ) -> Result<(), BlsError> { - for signature in signatures { - self.0 += signature.try_as_projective()?.0; - } - Ok(()) - } - - /// Aggregate a list of signatures - pub fn aggregate<'a, S: AsSignatureProjective + ?Sized + 'a>( - mut signatures: impl Iterator, - ) -> Result { - match signatures.next() { - Some(first) => { - let mut aggregate = first.try_as_projective()?; - aggregate.aggregate_with(signatures)?; - Ok(aggregate) - } - None => Err(BlsError::EmptyAggregation), - } - } - - /// Verify a list of signatures against a message and a list of public keys - pub fn verify_aggregate< - 'a, - P: AsPubkeyProjective + ?Sized + 'a, - S: AsSignatureProjective + ?Sized + 'a, - >( - public_keys: impl Iterator, - signatures: impl Iterator, - message: &[u8], - ) -> Result { - let aggregate_pubkey = PubkeyProjective::aggregate(public_keys)?; - let aggregate_signature = SignatureProjective::aggregate(signatures)?; - - aggregate_pubkey.verify_signature(&aggregate_signature, message) - } - - /// Verifies an aggregated signature over a set of distinct messages and - /// public keys. - pub fn verify_distinct<'a>( - public_keys: impl ExactSizeIterator, - signatures: impl ExactSizeIterator, - messages: impl ExactSizeIterator, - ) -> Result { - if public_keys.len() != messages.len() || public_keys.len() != signatures.len() { - return Err(BlsError::InputLengthMismatch); - } - if public_keys.len() == 0 { - return Err(BlsError::EmptyAggregation); - } - let aggregate_signature = SignatureProjective::aggregate(signatures)?; - Self::verify_distinct_aggregated(public_keys, &aggregate_signature.into(), messages) - } - - /// Verifies a pre-aggregated signature over a set of distinct messages and - /// public keys. - pub fn verify_distinct_aggregated<'a>( - public_keys: impl ExactSizeIterator, - aggregate_signature: &Signature, - messages: impl ExactSizeIterator, - ) -> Result { - if public_keys.len() != messages.len() { - return Err(BlsError::InputLengthMismatch); - } - if public_keys.len() == 0 { - return Err(BlsError::EmptyAggregation); - } - - // TODO: remove `Vec` allocation if possible for efficiency - let mut pubkeys_affine = alloc::vec::Vec::with_capacity(public_keys.len()); - let public_keys_len = public_keys.len(); - for pubkey in public_keys { - let maybe_g1_affine: Option<_> = G1Affine::from_uncompressed(&pubkey.0).into(); - let g1_affine: G1Affine = maybe_g1_affine.ok_or(BlsError::PointConversion)?; - pubkeys_affine.push(g1_affine); - } - - let mut prepared_hashes = alloc::vec::Vec::with_capacity(messages.len()); - for message in messages { - let hashed_message: G2Affine = hash_message_to_point(message).into(); - prepared_hashes.push(G2Prepared::from(hashed_message)); - } - - let maybe_aggregate_signature_affine: Option = - G2Affine::from_uncompressed(&aggregate_signature.0).into(); - let aggregate_signature_affine = - maybe_aggregate_signature_affine.ok_or(BlsError::PointConversion)?; - let signature_prepared = G2Prepared::from(aggregate_signature_affine); - - #[cfg(feature = "std")] - let neg_g1_generator = &*NEG_G1_GENERATOR_AFFINE; - #[cfg(not(feature = "std"))] - let neg_g1_generator_val: G1Affine = (-G1Projective::generator()).into(); - #[cfg(not(feature = "std"))] - let neg_g1_generator = &neg_g1_generator_val; - - let mut terms = alloc::vec::Vec::with_capacity(public_keys_len.saturating_add(1)); - for i in 0..public_keys_len { - terms.push((&pubkeys_affine[i], &prepared_hashes[i])); - } - terms.push((neg_g1_generator, &signature_prepared)); - - let miller_loop_result = Bls12::multi_miller_loop(&terms); - Ok(miller_loop_result.final_exponentiation() == Gt::identity()) - } - - /// Aggregate a list of signatures into an existing aggregate - #[allow(clippy::arithmetic_side_effects)] - #[cfg(feature = "parallel")] - pub fn par_aggregate_with<'a, S: AsSignatureProjective + Sync + 'a>( - &mut self, - signatures: impl ParallelIterator, - ) -> Result<(), BlsError> { - let aggregate = SignatureProjective::par_aggregate(signatures)?; - self.0 += &aggregate.0; - Ok(()) - } - - /// Aggregate a list of signatures - #[allow(clippy::arithmetic_side_effects)] - #[cfg(feature = "parallel")] - pub fn par_aggregate<'a, S: AsSignatureProjective + Sync + 'a>( - signatures: impl ParallelIterator, - ) -> Result { - signatures - .into_par_iter() - .map(|sig| sig.try_as_projective()) - .try_reduce_with(|mut a, b| { - a.0 += b.0; - Ok(a) - }) - .ok_or(BlsError::EmptyAggregation)? - } - - /// Verify a list of signatures against a message and a list of public keys - #[cfg(feature = "parallel")] - pub fn par_verify_aggregate( - public_keys: &[P], - signatures: &[S], - message: &[u8], - ) -> Result { - if public_keys.len() != signatures.len() { - return Err(BlsError::InputLengthMismatch); - } - - let (aggregate_pubkey_res, aggregate_signature_res) = rayon::join( - || PubkeyProjective::par_aggregate(public_keys.into_par_iter()), - || SignatureProjective::par_aggregate(signatures.into_par_iter()), - ); - let aggregate_pubkey = aggregate_pubkey_res?; - let aggregate_signature = aggregate_signature_res?; - aggregate_pubkey.verify_signature(&aggregate_signature, message) - } - - /// Verifies a set of signatures over a set of distinct messages and - /// public keys in parallel. - #[cfg(feature = "parallel")] - pub fn par_verify_distinct( - public_keys: &[Pubkey], - signatures: &[Signature], - messages: &[&[u8]], - ) -> Result { - if public_keys.len() != messages.len() || public_keys.len() != signatures.len() { - return Err(BlsError::InputLengthMismatch); - } - if public_keys.is_empty() { - return Err(BlsError::EmptyAggregation); - } - let aggregate_signature = SignatureProjective::par_aggregate(signatures.into_par_iter())?; - Self::par_verify_distinct_aggregated(public_keys, &aggregate_signature.into(), messages) - } - - /// In parallel, verifies a pre-aggregated signature over a set of distinct - /// messages and public keys. - #[cfg(feature = "parallel")] - pub fn par_verify_distinct_aggregated( - public_keys: &[Pubkey], - aggregate_signature: &Signature, - messages: &[&[u8]], - ) -> Result { - if public_keys.len() != messages.len() { - return Err(BlsError::InputLengthMismatch); - } - if public_keys.is_empty() { - return Err(BlsError::EmptyAggregation); - } - - // Use `rayon` to perform the three expensive, independent tasks in parallel: - // 1. Deserialize public keys into curve points. - // 2. Hash messages into curve points and prepare them for pairing. - let (pubkeys_affine_res, prepared_hashes_res): (Result, _>, Result, _>) = - rayon::join( - || { - public_keys - .par_iter() - .map(|pk| { - let maybe_pubkey_affine: Option<_> = - G1Affine::from_uncompressed(&pk.0).into(); - maybe_pubkey_affine.ok_or(BlsError::PointConversion) - }) - .collect() - }, - || { - messages - .par_iter() - .map(|msg| { - let hashed_message: G2Affine = hash_message_to_point(msg).into(); - Ok::<_, BlsError>(G2Prepared::from(hashed_message)) - }) - .collect() - }, - ); - - // Check for errors from the parallel operations and unwrap the results. - let pubkeys_affine = pubkeys_affine_res?; - let prepared_hashes = prepared_hashes_res?; - - let maybe_aggregate_signature_affine: Option = - G2Affine::from_uncompressed(&aggregate_signature.0).into(); - let aggregate_signature_affine = - maybe_aggregate_signature_affine.ok_or(BlsError::PointConversion)?; - let signature_prepared = G2Prepared::from(aggregate_signature_affine); - - #[cfg(feature = "std")] - let neg_g1_generator = &*NEG_G1_GENERATOR_AFFINE; - #[cfg(not(feature = "std"))] - let neg_g1_generator_val: G1Affine = (-G1Projective::generator()).into(); - #[cfg(not(feature = "std"))] - let neg_g1_generator = &neg_g1_generator_val; - - let mut terms = alloc::vec::Vec::with_capacity(public_keys.len() + 1); - for i in 0..public_keys.len() { - terms.push((&pubkeys_affine[i], &prepared_hashes[i])); - } - terms.push((neg_g1_generator, &signature_prepared)); - - let miller_loop_result = Bls12::multi_miller_loop(&terms); - Ok(miller_loop_result.final_exponentiation() == Gt::identity()) - } -} - -#[cfg(not(target_os = "solana"))] -impl VerifiableSignature for T {} - -#[cfg(not(target_os = "solana"))] -impl_bls_conversions!( - SignatureProjective, - Signature, - SignatureCompressed, - G2Affine, - AsSignatureProjective, - AsSignature -); - -/// A serialized BLS signature in a compressed point representation -#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] -#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] -#[repr(transparent)] -pub struct SignatureCompressed( - #[cfg_attr(feature = "serde", serde_as(as = "[_; BLS_SIGNATURE_COMPRESSED_SIZE]"))] - pub [u8; BLS_SIGNATURE_COMPRESSED_SIZE], -); - -impl Default for SignatureCompressed { - fn default() -> Self { - Self([0; BLS_SIGNATURE_COMPRESSED_SIZE]) - } -} - -impl fmt::Display for SignatureCompressed { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.0)) - } -} - -impl_from_str!( - TYPE = SignatureCompressed, - BYTES_LEN = BLS_SIGNATURE_COMPRESSED_SIZE, - BASE64_LEN = BLS_SIGNATURE_COMPRESSED_BASE64_SIZE -); - -/// A serialized BLS signature in an affine point representation -#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] -#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] -#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] -#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] -#[repr(transparent)] -pub struct Signature( - #[cfg_attr(feature = "serde", serde_as(as = "[_; BLS_SIGNATURE_AFFINE_SIZE]"))] - pub [u8; BLS_SIGNATURE_AFFINE_SIZE], -); - -impl Default for Signature { - fn default() -> Self { - Self([0; BLS_SIGNATURE_AFFINE_SIZE]) - } -} - -impl fmt::Display for Signature { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", BASE64_STANDARD.encode(self.0)) - } -} - -impl_from_str!( - TYPE = Signature, - BYTES_LEN = BLS_SIGNATURE_AFFINE_SIZE, - BASE64_LEN = BLS_SIGNATURE_AFFINE_BASE64_SIZE -); - -// Byte arrays are both `Pod` and `Zeraoble`, but the traits `bytemuck::Pod` and -// `bytemuck::Zeroable` can only be derived for power-of-two length byte arrays. -// Directly implement these traits for types that are simple wrappers around -// byte arrays. -#[cfg(feature = "bytemuck")] -mod bytemuck_impls { - use super::*; - - unsafe impl Zeroable for Signature {} - unsafe impl Pod for Signature {} - unsafe impl ZeroableInOption for Signature {} - unsafe impl PodInOption for Signature {} - - unsafe impl Zeroable for SignatureCompressed {} - unsafe impl Pod for SignatureCompressed {} - unsafe impl ZeroableInOption for SignatureCompressed {} - unsafe impl PodInOption for SignatureCompressed {} -} - -#[cfg(test)] -mod tests { - use { - super::*, - crate::{ - keypair::Keypair, - pubkey::{Pubkey, PubkeyCompressed}, - }, - core::{iter::empty, str::FromStr}, - std::{string::ToString, vec::Vec}, - }; - - #[test] - fn test_signature_verification() { - let keypair = Keypair::new(); - let test_message = b"test message"; - let signature_projective = keypair.sign(test_message); - - let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); - let pubkey_affine: Pubkey = pubkey_projective.into(); - let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); - - let signature_affine: Signature = signature_projective.into(); - let signature_compressed: SignatureCompressed = signature_affine.try_into().unwrap(); - - assert!(signature_projective - .verify(&pubkey_projective, test_message) - .unwrap()); - assert!(signature_affine - .verify(&pubkey_projective, test_message) - .unwrap()); - assert!(signature_compressed - .verify(&pubkey_projective, test_message) - .unwrap()); - - assert!(signature_projective - .verify(&pubkey_affine, test_message) - .unwrap()); - assert!(signature_affine - .verify(&pubkey_affine, test_message) - .unwrap()); - assert!(signature_compressed - .verify(&pubkey_affine, test_message) - .unwrap()); - - assert!(signature_projective - .verify(&pubkey_compressed, test_message) - .unwrap()); - assert!(signature_affine - .verify(&pubkey_compressed, test_message) - .unwrap()); - assert!(signature_compressed - .verify(&pubkey_compressed, test_message) - .unwrap()); - } - - #[test] - fn test_signature_aggregate() { - let test_message = b"test message"; - let keypair0 = Keypair::new(); - let signature0 = keypair0.sign(test_message); - - let test_message = b"test message"; - let keypair1 = Keypair::new(); - let signature1 = keypair1.sign(test_message); - let signature1_affine: Signature = signature1.into(); - - let aggregate_signature = - SignatureProjective::aggregate([&signature0, &signature1].into_iter()).unwrap(); - - let mut aggregate_signature_with = signature0; - aggregate_signature_with - .aggregate_with([&signature1_affine].into_iter()) - .unwrap(); - - assert_eq!(aggregate_signature, aggregate_signature_with); - } - - #[test] - fn test_verify_aggregate() { - let test_message = b"test message"; - - let keypair0 = Keypair::new(); - let signature0 = keypair0.sign(test_message); - assert!(keypair0 - .public - .verify_signature(&signature0, test_message) - .unwrap()); - - let keypair1 = Keypair::new(); - let signature1 = keypair1.secret.sign(test_message); - assert!(keypair1 - .public - .verify_signature(&signature1, test_message) - .unwrap()); - - // basic case - assert!(SignatureProjective::verify_aggregate( - [&keypair0.public, &keypair1.public].into_iter(), - [&signature0, &signature1].into_iter(), - test_message, - ) - .unwrap()); - - // verify with affine and compressed types - let pubkey0_affine: Pubkey = keypair0.public; - let pubkey1_affine: Pubkey = keypair1.public; - let signature0_affine: Signature = signature0.into(); - let signature1_affine: Signature = signature1.into(); - assert!(SignatureProjective::verify_aggregate( - [&pubkey0_affine, &pubkey1_affine].into_iter(), - [&signature0_affine, &signature1_affine].into_iter(), - test_message, - ) - .unwrap()); - - // pre-aggregate the signatures - let aggregate_signature = - SignatureProjective::aggregate([&signature0, &signature1].into_iter()).unwrap(); - assert!(SignatureProjective::verify_aggregate( - [&keypair0.public, &keypair1.public].into_iter(), - [&aggregate_signature].into_iter(), - test_message, - ) - .unwrap()); - - // pre-aggregate the public keys - let aggregate_pubkey = - PubkeyProjective::aggregate([&keypair0.public, &keypair1.public].into_iter()).unwrap(); - assert!(SignatureProjective::verify_aggregate( - [&aggregate_pubkey].into_iter(), - [&signature0, &signature1].into_iter(), - test_message, - ) - .unwrap()); - let pubkeys = Vec::new() as Vec; - - // empty set of public keys or signatures - let err = SignatureProjective::verify_aggregate( - pubkeys.iter(), - [&signature0, &signature1].into_iter(), - test_message, - ) - .unwrap_err(); - assert_eq!(err, BlsError::EmptyAggregation); - - let signatures = Vec::new() as Vec<&SignatureProjective>; - let err = SignatureProjective::verify_aggregate( - [&keypair0.public, &keypair1.public].into_iter(), - signatures.into_iter(), - test_message, - ) - .unwrap_err(); - assert_eq!(err, BlsError::EmptyAggregation); - } - - #[test] - fn test_verify_distinct() { - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - let keypair2 = Keypair::new(); - - let message0 = b"message zero"; - let message1 = b"message one"; - let message2 = b"message two"; - - let signature0_proj = keypair0.sign(message0); - let signature1_proj = keypair1.sign(message1); - let signature2_proj = keypair2.sign(message2); - - let signature0: Signature = signature0_proj.into(); - let signature1: Signature = signature1_proj.into(); - let signature2: Signature = signature2_proj.into(); - - // Success cases - let pubkeys = [keypair0.public, keypair1.public, keypair2.public]; - let messages: Vec<&[u8]> = std::vec![message0, message1, message2]; - let signatures = std::vec![signature0, signature1, signature2]; - - assert!(SignatureProjective::verify_distinct( - pubkeys.iter(), - signatures.iter(), - messages.iter().cloned() - ) - .unwrap()); - - // Failure cases - let wrong_order_messages: Vec<&[u8]> = std::vec![message1, message0, message2]; - assert!(!SignatureProjective::verify_distinct( - pubkeys.iter(), - signatures.iter(), - wrong_order_messages.into_iter() - ) - .unwrap()); - - let one_wrong_message_refs: Vec<&[u8]> = std::vec![message0, b"this is wrong", message2]; - assert!(!SignatureProjective::verify_distinct( - pubkeys.iter(), - signatures.iter(), - one_wrong_message_refs.into_iter() - ) - .unwrap()); - - let wrong_keypair = Keypair::new(); - let wrong_pubkeys = [keypair0.public, wrong_keypair.public, keypair2.public]; - assert!(!SignatureProjective::verify_distinct( - wrong_pubkeys.iter(), - signatures.iter(), - messages.iter().cloned() - ) - .unwrap()); - - let wrong_signature_proj = wrong_keypair.sign(message1); - let wrong_signature: Signature = wrong_signature_proj.into(); - let wrong_signatures = [signature0, wrong_signature, signature2]; - assert!(!SignatureProjective::verify_distinct( - pubkeys.iter(), - wrong_signatures.iter(), - messages.iter().cloned() - ) - .unwrap()); - - let err = SignatureProjective::verify_distinct( - pubkeys.iter(), - signatures.iter(), - messages[..2].iter().cloned(), - ) - .unwrap_err(); - assert_eq!(err, BlsError::InputLengthMismatch); - - let err = SignatureProjective::verify_distinct( - pubkeys.iter(), - signatures[..2].iter(), - messages.into_iter(), - ) - .unwrap_err(); - assert_eq!(err, BlsError::InputLengthMismatch); - - let err = SignatureProjective::verify_distinct(empty(), empty(), empty()).unwrap_err(); - assert_eq!(err, BlsError::EmptyAggregation); - } - - #[test] - fn test_verify_aggregate_dyn() { - let test_message = b"test message for dyn verify"; - - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - let keypair2 = Keypair::new(); - - let signature0_projective = keypair0.sign(test_message); - let signature1_projective = keypair1.sign(test_message); - let signature2_projective = keypair2.sign(test_message); - - let pubkey0 = PubkeyProjective::try_from(keypair0.public).unwrap(); // Projective - let pubkey1_affine: Pubkey = keypair1.public; // Affine - let pubkey2_compressed: PubkeyCompressed = keypair2.public.try_into().unwrap(); // Compressed - - let signature0 = signature0_projective; // Projective - let signature1_affine: Signature = signature1_projective.into(); // Affine - let signature2_compressed: SignatureCompressed = - Signature::from(signature2_projective).try_into().unwrap(); // Compressed - - let dyn_pubkeys: Vec<&dyn AsPubkeyProjective> = - std::vec![&pubkey0, &pubkey1_affine, &pubkey2_compressed]; - let dyn_signatures: Vec<&dyn AsSignatureProjective> = - std::vec![&signature0, &signature1_affine, &signature2_compressed]; - - assert!(SignatureProjective::verify_aggregate( - dyn_pubkeys.into_iter(), - dyn_signatures.into_iter(), - test_message - ) - .unwrap()); - - let wrong_message = b"this is not the correct message"; - let dyn_pubkeys_fail: Vec<&dyn AsPubkeyProjective> = - std::vec![&pubkey0, &pubkey1_affine, &pubkey2_compressed]; - let dyn_signatures_fail: Vec<&dyn AsSignatureProjective> = - std::vec![&signature0, &signature1_affine, &signature2_compressed]; - assert!(!SignatureProjective::verify_aggregate( - dyn_pubkeys_fail.into_iter(), - dyn_signatures_fail.into_iter(), - wrong_message - ) - .unwrap()); - } - - #[test] - fn signature_from_str() { - let signature_affine = Signature([1; BLS_SIGNATURE_AFFINE_SIZE]); - let signature_affine_string = signature_affine.to_string(); - let signature_affine_from_string = Signature::from_str(&signature_affine_string).unwrap(); - assert_eq!(signature_affine, signature_affine_from_string); - - let signature_compressed = SignatureCompressed([1; BLS_SIGNATURE_COMPRESSED_SIZE]); - let signature_compressed_string = signature_compressed.to_string(); - let signature_compressed_from_string = - SignatureCompressed::from_str(&signature_compressed_string).unwrap(); - assert_eq!(signature_compressed, signature_compressed_from_string); - } - - #[test] - #[cfg(feature = "parallel")] - fn test_parallel_signature_aggregation() { - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - let signature0 = keypair0.sign(b""); - let signature1 = keypair1.sign(b""); - - // Test `aggregate` - let sequential_agg = - SignatureProjective::aggregate([signature0, signature1].iter()).unwrap(); - let parallel_agg = - SignatureProjective::par_aggregate([signature0, signature1].par_iter()).unwrap(); - assert_eq!(sequential_agg, parallel_agg); - - // Test `aggregate_with` - let mut parallel_agg_with = signature0; - parallel_agg_with - .par_aggregate_with([signature1].par_iter()) - .unwrap(); - assert_eq!(sequential_agg, parallel_agg_with); - - // Test empty case - let empty: std::vec::Vec = Vec::new(); - assert_eq!( - SignatureProjective::par_aggregate(empty.par_iter()).unwrap_err(), - BlsError::EmptyAggregation - ); - } - - #[test] - #[cfg(feature = "parallel")] - fn test_parallel_verify_aggregate() { - let message = b"test message"; - let keypairs: Vec<_> = (0..5).map(|_| Keypair::new()).collect(); - let pubkeys: Vec<_> = keypairs - .iter() - .map(|kp| PubkeyProjective::try_from(&kp.public).unwrap()) - .collect(); - let signatures: Vec<_> = keypairs.iter().map(|kp| kp.sign(message)).collect(); - - // Success case - assert!(SignatureProjective::par_verify_aggregate(&pubkeys, &signatures, message).unwrap()); - - // Failure case (wrong message) - assert!(!SignatureProjective::par_verify_aggregate( - &pubkeys, - &signatures, - b"wrong message" - ) - .unwrap()); - - // Failure case (bad signature) - let mut bad_signatures = signatures.clone(); - bad_signatures[0] = keypairs[0].sign(b"a different message"); - assert!( - !SignatureProjective::par_verify_aggregate(&pubkeys, &bad_signatures, message).unwrap() - ); - } - - #[test] - #[cfg(feature = "parallel")] - fn test_par_verify_distinct() { - let keypair0 = Keypair::new(); - let keypair1 = Keypair::new(); - let keypair2 = Keypair::new(); - - let message0 = b"message zero"; - let message1 = b"message one"; - let message2 = b"message two"; - - let signature0_proj = keypair0.sign(message0); - let signature1_proj = keypair1.sign(message1); - let signature2_proj = keypair2.sign(message2); - - let signature0: Signature = signature0_proj.into(); - let signature1: Signature = signature1_proj.into(); - let signature2: Signature = signature2_proj.into(); - - let pubkeys = [keypair0.public, keypair1.public, keypair2.public]; - let messages_refs: Vec<&[u8]> = std::vec![message0, message1, message2]; - let signatures = [signature0, signature1, signature2]; - - assert!( - SignatureProjective::par_verify_distinct(&pubkeys, &signatures, &messages_refs) - .unwrap() - ); - } -} diff --git a/bls-signatures/src/signature/bytes.rs b/bls-signatures/src/signature/bytes.rs new file mode 100644 index 000000000..c52a94f64 --- /dev/null +++ b/bls-signatures/src/signature/bytes.rs @@ -0,0 +1,109 @@ +#[cfg(not(target_os = "solana"))] +use crate::error::BlsError; +#[cfg(feature = "bytemuck")] +use bytemuck::{Pod, PodInOption, Zeroable, ZeroableInOption}; +use { + base64::{prelude::BASE64_STANDARD, Engine}, + core::fmt, +}; +#[cfg(feature = "serde")] +use { + serde::{Deserialize, Serialize}, + serde_with::serde_as, +}; + +/// Size of a BLS signature in a compressed point representation +pub const BLS_SIGNATURE_COMPRESSED_SIZE: usize = 96; + +/// Size of a BLS signature in a compressed point representation in base64 +pub const BLS_SIGNATURE_COMPRESSED_BASE64_SIZE: usize = 128; + +/// Size of a BLS signature in an affine point representation +pub const BLS_SIGNATURE_AFFINE_SIZE: usize = 192; + +/// Size of a BLS signature in an affine point representation in base64 +pub const BLS_SIGNATURE_AFFINE_BASE64_SIZE: usize = 256; + +/// A trait for types that can be converted into a `Signature` (affine/uncompressed bytes). +#[cfg(not(target_os = "solana"))] +pub trait AsSignature { + /// Attempt to convert the type into a `Signature`. + fn try_as_affine(&self) -> Result; +} + +/// A serialized BLS signature in a compressed point representation +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] +#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct SignatureCompressed( + #[cfg_attr(feature = "serde", serde_as(as = "[_; BLS_SIGNATURE_COMPRESSED_SIZE]"))] + pub [u8; BLS_SIGNATURE_COMPRESSED_SIZE], +); + +impl Default for SignatureCompressed { + fn default() -> Self { + Self([0; BLS_SIGNATURE_COMPRESSED_SIZE]) + } +} + +impl fmt::Display for SignatureCompressed { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.0)) + } +} + +impl_from_str!( + TYPE = SignatureCompressed, + BYTES_LEN = BLS_SIGNATURE_COMPRESSED_SIZE, + BASE64_LEN = BLS_SIGNATURE_COMPRESSED_BASE64_SIZE +); + +/// A serialized BLS signature in an affine point representation +#[cfg_attr(feature = "frozen-abi", derive(solana_frozen_abi_macro::AbiExample))] +#[cfg_attr(feature = "serde", cfg_eval::cfg_eval, serde_as)] +#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))] +#[derive(Clone, Copy, Debug, Hash, Eq, Ord, PartialEq, PartialOrd)] +#[repr(transparent)] +pub struct Signature( + #[cfg_attr(feature = "serde", serde_as(as = "[_; BLS_SIGNATURE_AFFINE_SIZE]"))] + pub [u8; BLS_SIGNATURE_AFFINE_SIZE], +); + +impl Default for Signature { + fn default() -> Self { + Self([0; BLS_SIGNATURE_AFFINE_SIZE]) + } +} + +impl fmt::Display for Signature { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!(f, "{}", BASE64_STANDARD.encode(self.0)) + } +} + +impl_from_str!( + TYPE = Signature, + BYTES_LEN = BLS_SIGNATURE_AFFINE_SIZE, + BASE64_LEN = BLS_SIGNATURE_AFFINE_BASE64_SIZE +); + +// Byte arrays are both `Pod` and `Zeraoble`, but the traits `bytemuck::Pod` and +// `bytemuck::Zeroable` can only be derived for power-of-two length byte arrays. +// Directly implement these traits for types that are simple wrappers around +// byte arrays. +#[cfg(feature = "bytemuck")] +mod bytemuck_impls { + use super::*; + + unsafe impl Zeroable for Signature {} + unsafe impl Pod for Signature {} + unsafe impl ZeroableInOption for Signature {} + unsafe impl PodInOption for Signature {} + + unsafe impl Zeroable for SignatureCompressed {} + unsafe impl Pod for SignatureCompressed {} + unsafe impl ZeroableInOption for SignatureCompressed {} + unsafe impl PodInOption for SignatureCompressed {} +} diff --git a/bls-signatures/src/signature/conversion.rs b/bls-signatures/src/signature/conversion.rs new file mode 100644 index 000000000..44207d69a --- /dev/null +++ b/bls-signatures/src/signature/conversion.rs @@ -0,0 +1,21 @@ +#[cfg(not(target_os = "solana"))] +use { + crate::{ + error::BlsError, + signature::{ + bytes::{AsSignature, Signature, SignatureCompressed}, + points::{AsSignatureProjective, SignatureProjective}, + }, + }, + blstrs::G2Affine, +}; + +#[cfg(not(target_os = "solana"))] +impl_bls_conversions!( + SignatureProjective, + Signature, + SignatureCompressed, + G2Affine, + AsSignatureProjective, + AsSignature +); diff --git a/bls-signatures/src/signature/mod.rs b/bls-signatures/src/signature/mod.rs new file mode 100644 index 000000000..9d26dc99e --- /dev/null +++ b/bls-signatures/src/signature/mod.rs @@ -0,0 +1,410 @@ +pub mod bytes; +pub mod conversion; +pub mod points; + +pub use bytes::{ + Signature, SignatureCompressed, BLS_SIGNATURE_AFFINE_BASE64_SIZE, BLS_SIGNATURE_AFFINE_SIZE, + BLS_SIGNATURE_COMPRESSED_BASE64_SIZE, BLS_SIGNATURE_COMPRESSED_SIZE, +}; +#[cfg(not(target_os = "solana"))] +pub use { + bytes::AsSignature, + points::{AsSignatureProjective, SignatureProjective, VerifiableSignature}, +}; + +#[cfg(test)] +mod tests { + #[cfg(feature = "parallel")] + use rayon::prelude::*; + use { + super::*, + crate::{ + error::BlsError, + keypair::Keypair, + pubkey::{ + AsPubkeyProjective, Pubkey, PubkeyCompressed, PubkeyProjective, VerifiablePubkey, + }, + }, + core::{iter::empty, str::FromStr}, + std::{string::ToString, vec::Vec}, + }; + + #[test] + fn test_signature_verification() { + let keypair = Keypair::new(); + let test_message = b"test message"; + let signature_projective = keypair.sign(test_message); + + let pubkey_projective: PubkeyProjective = (&keypair.public).try_into().unwrap(); + let pubkey_affine: Pubkey = pubkey_projective.into(); + let pubkey_compressed: PubkeyCompressed = pubkey_affine.try_into().unwrap(); + + let signature_affine: Signature = signature_projective.into(); + let signature_compressed: SignatureCompressed = signature_affine.try_into().unwrap(); + + assert!(signature_projective + .verify(&pubkey_projective, test_message) + .unwrap()); + assert!(signature_affine + .verify(&pubkey_projective, test_message) + .unwrap()); + assert!(signature_compressed + .verify(&pubkey_projective, test_message) + .unwrap()); + + assert!(signature_projective + .verify(&pubkey_affine, test_message) + .unwrap()); + assert!(signature_affine + .verify(&pubkey_affine, test_message) + .unwrap()); + assert!(signature_compressed + .verify(&pubkey_affine, test_message) + .unwrap()); + + assert!(signature_projective + .verify(&pubkey_compressed, test_message) + .unwrap()); + assert!(signature_affine + .verify(&pubkey_compressed, test_message) + .unwrap()); + assert!(signature_compressed + .verify(&pubkey_compressed, test_message) + .unwrap()); + } + + #[test] + fn test_signature_aggregate() { + let test_message = b"test message"; + let keypair0 = Keypair::new(); + let signature0 = keypair0.sign(test_message); + + let test_message = b"test message"; + let keypair1 = Keypair::new(); + let signature1 = keypair1.sign(test_message); + let signature1_affine: Signature = signature1.into(); + + let aggregate_signature = + SignatureProjective::aggregate([&signature0, &signature1].into_iter()).unwrap(); + + let mut aggregate_signature_with = signature0; + aggregate_signature_with + .aggregate_with([&signature1_affine].into_iter()) + .unwrap(); + + assert_eq!(aggregate_signature, aggregate_signature_with); + } + + #[test] + fn test_verify_aggregate() { + let test_message = b"test message"; + + let keypair0 = Keypair::new(); + let signature0 = keypair0.sign(test_message); + assert!(keypair0 + .public + .verify_signature(&signature0, test_message) + .unwrap()); + + let keypair1 = Keypair::new(); + let signature1 = keypair1.secret.sign(test_message); + assert!(keypair1 + .public + .verify_signature(&signature1, test_message) + .unwrap()); + + // basic case + assert!(SignatureProjective::verify_aggregate( + [&keypair0.public, &keypair1.public].into_iter(), + [&signature0, &signature1].into_iter(), + test_message, + ) + .unwrap()); + + // verify with affine and compressed types + let pubkey0_affine: Pubkey = keypair0.public; + let pubkey1_affine: Pubkey = keypair1.public; + let signature0_affine: Signature = signature0.into(); + let signature1_affine: Signature = signature1.into(); + assert!(SignatureProjective::verify_aggregate( + [&pubkey0_affine, &pubkey1_affine].into_iter(), + [&signature0_affine, &signature1_affine].into_iter(), + test_message, + ) + .unwrap()); + + // pre-aggregate the signatures + let aggregate_signature = + SignatureProjective::aggregate([&signature0, &signature1].into_iter()).unwrap(); + assert!(SignatureProjective::verify_aggregate( + [&keypair0.public, &keypair1.public].into_iter(), + [&aggregate_signature].into_iter(), + test_message, + ) + .unwrap()); + + // pre-aggregate the public keys + let aggregate_pubkey = + PubkeyProjective::aggregate([&keypair0.public, &keypair1.public].into_iter()).unwrap(); + assert!(SignatureProjective::verify_aggregate( + [&aggregate_pubkey].into_iter(), + [&signature0, &signature1].into_iter(), + test_message, + ) + .unwrap()); + let pubkeys = Vec::new() as Vec; + + // empty set of public keys or signatures + let err = SignatureProjective::verify_aggregate( + pubkeys.iter(), + [&signature0, &signature1].into_iter(), + test_message, + ) + .unwrap_err(); + assert_eq!(err, BlsError::EmptyAggregation); + + let signatures = Vec::new() as Vec<&SignatureProjective>; + let err = SignatureProjective::verify_aggregate( + [&keypair0.public, &keypair1.public].into_iter(), + signatures.into_iter(), + test_message, + ) + .unwrap_err(); + assert_eq!(err, BlsError::EmptyAggregation); + } + + #[test] + fn test_verify_distinct() { + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let keypair2 = Keypair::new(); + + let message0 = b"message zero"; + let message1 = b"message one"; + let message2 = b"message two"; + + let signature0_proj = keypair0.sign(message0); + let signature1_proj = keypair1.sign(message1); + let signature2_proj = keypair2.sign(message2); + + let signature0: Signature = signature0_proj.into(); + let signature1: Signature = signature1_proj.into(); + let signature2: Signature = signature2_proj.into(); + + // Success cases + let pubkeys = [keypair0.public, keypair1.public, keypair2.public]; + let messages: Vec<&[u8]> = std::vec![message0, message1, message2]; + let signatures = std::vec![signature0, signature1, signature2]; + + assert!(SignatureProjective::verify_distinct( + pubkeys.iter(), + signatures.iter(), + messages.iter().cloned() + ) + .unwrap()); + + // Failure cases + let wrong_order_messages: Vec<&[u8]> = std::vec![message1, message0, message2]; + assert!(!SignatureProjective::verify_distinct( + pubkeys.iter(), + signatures.iter(), + wrong_order_messages.into_iter() + ) + .unwrap()); + + let one_wrong_message_refs: Vec<&[u8]> = std::vec![message0, b"this is wrong", message2]; + assert!(!SignatureProjective::verify_distinct( + pubkeys.iter(), + signatures.iter(), + one_wrong_message_refs.into_iter() + ) + .unwrap()); + + let wrong_keypair = Keypair::new(); + let wrong_pubkeys = [keypair0.public, wrong_keypair.public, keypair2.public]; + assert!(!SignatureProjective::verify_distinct( + wrong_pubkeys.iter(), + signatures.iter(), + messages.iter().cloned() + ) + .unwrap()); + + let wrong_signature_proj = wrong_keypair.sign(message1); + let wrong_signature: Signature = wrong_signature_proj.into(); + let wrong_signatures = [signature0, wrong_signature, signature2]; + assert!(!SignatureProjective::verify_distinct( + pubkeys.iter(), + wrong_signatures.iter(), + messages.iter().cloned() + ) + .unwrap()); + + let err = SignatureProjective::verify_distinct( + pubkeys.iter(), + signatures.iter(), + messages[..2].iter().cloned(), + ) + .unwrap_err(); + assert_eq!(err, BlsError::InputLengthMismatch); + + let err = SignatureProjective::verify_distinct( + pubkeys.iter(), + signatures[..2].iter(), + messages.into_iter(), + ) + .unwrap_err(); + assert_eq!(err, BlsError::InputLengthMismatch); + + let err = SignatureProjective::verify_distinct(empty(), empty(), empty()).unwrap_err(); + assert_eq!(err, BlsError::EmptyAggregation); + } + + #[test] + fn test_verify_aggregate_dyn() { + let test_message = b"test message for dyn verify"; + + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let keypair2 = Keypair::new(); + + let signature0_projective = keypair0.sign(test_message); + let signature1_projective = keypair1.sign(test_message); + let signature2_projective = keypair2.sign(test_message); + + let pubkey0 = PubkeyProjective::try_from(keypair0.public).unwrap(); // Projective + let pubkey1_affine: Pubkey = keypair1.public; // Affine + let pubkey2_compressed: PubkeyCompressed = keypair2.public.try_into().unwrap(); // Compressed + + let signature0 = signature0_projective; // Projective + let signature1_affine: Signature = signature1_projective.into(); // Affine + let signature2_compressed: SignatureCompressed = + Signature::from(signature2_projective).try_into().unwrap(); // Compressed + + let dyn_pubkeys: Vec<&dyn AsPubkeyProjective> = + std::vec![&pubkey0, &pubkey1_affine, &pubkey2_compressed]; + let dyn_signatures: Vec<&dyn AsSignatureProjective> = + std::vec![&signature0, &signature1_affine, &signature2_compressed]; + + assert!(SignatureProjective::verify_aggregate( + dyn_pubkeys.into_iter(), + dyn_signatures.into_iter(), + test_message + ) + .unwrap()); + + let wrong_message = b"this is not the correct message"; + let dyn_pubkeys_fail: Vec<&dyn AsPubkeyProjective> = + std::vec![&pubkey0, &pubkey1_affine, &pubkey2_compressed]; + let dyn_signatures_fail: Vec<&dyn AsSignatureProjective> = + std::vec![&signature0, &signature1_affine, &signature2_compressed]; + assert!(!SignatureProjective::verify_aggregate( + dyn_pubkeys_fail.into_iter(), + dyn_signatures_fail.into_iter(), + wrong_message + ) + .unwrap()); + } + + #[test] + fn signature_from_str() { + let signature_affine = Signature([1; BLS_SIGNATURE_AFFINE_SIZE]); + let signature_affine_string = signature_affine.to_string(); + let signature_affine_from_string = Signature::from_str(&signature_affine_string).unwrap(); + assert_eq!(signature_affine, signature_affine_from_string); + + let signature_compressed = SignatureCompressed([1; BLS_SIGNATURE_COMPRESSED_SIZE]); + let signature_compressed_string = signature_compressed.to_string(); + let signature_compressed_from_string = + SignatureCompressed::from_str(&signature_compressed_string).unwrap(); + assert_eq!(signature_compressed, signature_compressed_from_string); + } + + #[test] + #[cfg(feature = "parallel")] + fn test_parallel_signature_aggregation() { + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let signature0 = keypair0.sign(b""); + let signature1 = keypair1.sign(b""); + + // Test `aggregate` + let sequential_agg = + SignatureProjective::aggregate([signature0, signature1].iter()).unwrap(); + let parallel_agg = + SignatureProjective::par_aggregate([signature0, signature1].par_iter()).unwrap(); + assert_eq!(sequential_agg, parallel_agg); + + // Test `aggregate_with` + let mut parallel_agg_with = signature0; + parallel_agg_with + .par_aggregate_with([signature1].par_iter()) + .unwrap(); + assert_eq!(sequential_agg, parallel_agg_with); + + // Test empty case + let empty: std::vec::Vec = Vec::new(); + assert_eq!( + SignatureProjective::par_aggregate(empty.par_iter()).unwrap_err(), + BlsError::EmptyAggregation + ); + } + + #[test] + #[cfg(feature = "parallel")] + fn test_parallel_verify_aggregate() { + let message = b"test message"; + let keypairs: Vec<_> = (0..5).map(|_| Keypair::new()).collect(); + let pubkeys: Vec<_> = keypairs + .iter() + .map(|kp| PubkeyProjective::try_from(&kp.public).unwrap()) + .collect(); + let signatures: Vec<_> = keypairs.iter().map(|kp| kp.sign(message)).collect(); + + // Success case + assert!(SignatureProjective::par_verify_aggregate(&pubkeys, &signatures, message).unwrap()); + + // Failure case (wrong message) + assert!(!SignatureProjective::par_verify_aggregate( + &pubkeys, + &signatures, + b"wrong message" + ) + .unwrap()); + + // Failure case (bad signature) + let mut bad_signatures = signatures.clone(); + bad_signatures[0] = keypairs[0].sign(b"a different message"); + assert!( + !SignatureProjective::par_verify_aggregate(&pubkeys, &bad_signatures, message).unwrap() + ); + } + + #[test] + #[cfg(feature = "parallel")] + fn test_par_verify_distinct() { + let keypair0 = Keypair::new(); + let keypair1 = Keypair::new(); + let keypair2 = Keypair::new(); + + let message0 = b"message zero"; + let message1 = b"message one"; + let message2 = b"message two"; + + let signature0_proj = keypair0.sign(message0); + let signature1_proj = keypair1.sign(message1); + let signature2_proj = keypair2.sign(message2); + + let signature0: Signature = signature0_proj.into(); + let signature1: Signature = signature1_proj.into(); + let signature2: Signature = signature2_proj.into(); + + let pubkeys = [keypair0.public, keypair1.public, keypair2.public]; + let messages_refs: Vec<&[u8]> = std::vec![message0, message1, message2]; + let signatures = [signature0, signature1, signature2]; + + assert!( + SignatureProjective::par_verify_distinct(&pubkeys, &signatures, &messages_refs) + .unwrap() + ); + } +} diff --git a/bls-signatures/src/signature/points.rs b/bls-signatures/src/signature/points.rs new file mode 100644 index 000000000..257b3f7fb --- /dev/null +++ b/bls-signatures/src/signature/points.rs @@ -0,0 +1,300 @@ +#[cfg(all(not(target_os = "solana"), feature = "std"))] +use crate::pubkey::points::NEG_G1_GENERATOR_AFFINE; +#[cfg(not(feature = "std"))] +use blstrs::G1Projective; +#[cfg(not(target_os = "solana"))] +use { + crate::{ + error::BlsError, + hash::hash_message_to_point, + pubkey::{AsPubkeyProjective, Pubkey, PubkeyProjective, VerifiablePubkey}, + signature::bytes::Signature, + }, + blstrs::{Bls12, G1Affine, G2Affine, G2Prepared, G2Projective, Gt}, + group::Group, + pairing::{MillerLoopResult, MultiMillerLoop}, +}; +#[cfg(all(feature = "parallel", not(target_os = "solana")))] +use {alloc::vec::Vec, rayon::prelude::*}; + +/// A trait for types that can be converted into a `SignatureProjective`. +#[cfg(not(target_os = "solana"))] +pub trait AsSignatureProjective { + /// Attempt to convert the type into a `SignatureProjective`. + fn try_as_projective(&self) -> Result; +} + +/// A trait that provides verification methods to any convertible signature type. +#[cfg(not(target_os = "solana"))] +pub trait VerifiableSignature: AsSignatureProjective { + /// Verify the signature against any convertible public key type and a message. + fn verify(&self, pubkey: &P, message: &[u8]) -> Result { + // The logic is defined once here. + let signature_projective = self.try_as_projective()?; + pubkey.verify_signature(&signature_projective, message) + } +} + +/// A BLS signature in a projective point representation. +#[cfg(not(target_os = "solana"))] +#[derive(Clone, Copy, Debug, Eq, PartialEq)] +pub struct SignatureProjective(pub(crate) G2Projective); + +#[cfg(not(target_os = "solana"))] +impl SignatureProjective { + /// Creates the identity element, which is the starting point for aggregation + /// + /// The identity element is not a valid signature and it should only be used + /// for the purpose of aggregation + pub fn identity() -> Self { + Self(G2Projective::identity()) + } + + /// Aggregate a list of signatures into an existing aggregate + #[allow(clippy::arithmetic_side_effects)] + pub fn aggregate_with<'a, S: AsSignatureProjective + ?Sized + 'a>( + &mut self, + signatures: impl Iterator, + ) -> Result<(), BlsError> { + for signature in signatures { + self.0 += signature.try_as_projective()?.0; + } + Ok(()) + } + + /// Aggregate a list of signatures + pub fn aggregate<'a, S: AsSignatureProjective + ?Sized + 'a>( + mut signatures: impl Iterator, + ) -> Result { + match signatures.next() { + Some(first) => { + let mut aggregate = first.try_as_projective()?; + aggregate.aggregate_with(signatures)?; + Ok(aggregate) + } + None => Err(BlsError::EmptyAggregation), + } + } + + /// Verify a list of signatures against a message and a list of public keys + pub fn verify_aggregate< + 'a, + P: AsPubkeyProjective + ?Sized + 'a, + S: AsSignatureProjective + ?Sized + 'a, + >( + public_keys: impl Iterator, + signatures: impl Iterator, + message: &[u8], + ) -> Result { + let aggregate_pubkey = PubkeyProjective::aggregate(public_keys)?; + let aggregate_signature = SignatureProjective::aggregate(signatures)?; + + aggregate_pubkey.verify_signature(&aggregate_signature, message) + } + + /// Verifies an aggregated signature over a set of distinct messages and + /// public keys. + pub fn verify_distinct<'a>( + public_keys: impl ExactSizeIterator, + signatures: impl ExactSizeIterator, + messages: impl ExactSizeIterator, + ) -> Result { + if public_keys.len() != messages.len() || public_keys.len() != signatures.len() { + return Err(BlsError::InputLengthMismatch); + } + if public_keys.len() == 0 { + return Err(BlsError::EmptyAggregation); + } + let aggregate_signature = SignatureProjective::aggregate(signatures)?; + Self::verify_distinct_aggregated(public_keys, &aggregate_signature.into(), messages) + } + + /// Verifies a pre-aggregated signature over a set of distinct messages and + /// public keys. + pub fn verify_distinct_aggregated<'a>( + public_keys: impl ExactSizeIterator, + aggregate_signature: &Signature, + messages: impl ExactSizeIterator, + ) -> Result { + if public_keys.len() != messages.len() { + return Err(BlsError::InputLengthMismatch); + } + if public_keys.len() == 0 { + return Err(BlsError::EmptyAggregation); + } + + // TODO: remove `Vec` allocation if possible for efficiency + let mut pubkeys_affine = alloc::vec::Vec::with_capacity(public_keys.len()); + let public_keys_len = public_keys.len(); + for pubkey in public_keys { + let maybe_g1_affine: Option<_> = G1Affine::from_uncompressed(&pubkey.0).into(); + let g1_affine: G1Affine = maybe_g1_affine.ok_or(BlsError::PointConversion)?; + pubkeys_affine.push(g1_affine); + } + + let mut prepared_hashes = alloc::vec::Vec::with_capacity(messages.len()); + for message in messages { + let hashed_message: G2Affine = hash_message_to_point(message).into(); + prepared_hashes.push(G2Prepared::from(hashed_message)); + } + + let maybe_aggregate_signature_affine: Option = + G2Affine::from_uncompressed(&aggregate_signature.0).into(); + let aggregate_signature_affine = + maybe_aggregate_signature_affine.ok_or(BlsError::PointConversion)?; + let signature_prepared = G2Prepared::from(aggregate_signature_affine); + + #[cfg(feature = "std")] + let neg_g1_generator = &*NEG_G1_GENERATOR_AFFINE; + #[cfg(not(feature = "std"))] + let neg_g1_generator_val: G1Affine = (-G1Projective::generator()).into(); + #[cfg(not(feature = "std"))] + let neg_g1_generator = &neg_g1_generator_val; + + let mut terms = alloc::vec::Vec::with_capacity(public_keys_len.saturating_add(1)); + for i in 0..public_keys_len { + terms.push((&pubkeys_affine[i], &prepared_hashes[i])); + } + terms.push((neg_g1_generator, &signature_prepared)); + + let miller_loop_result = Bls12::multi_miller_loop(&terms); + Ok(miller_loop_result.final_exponentiation() == Gt::identity()) + } + + /// Aggregate a list of signatures into an existing aggregate + #[allow(clippy::arithmetic_side_effects)] + #[cfg(feature = "parallel")] + pub fn par_aggregate_with<'a, S: AsSignatureProjective + Sync + 'a>( + &mut self, + signatures: impl ParallelIterator, + ) -> Result<(), BlsError> { + let aggregate = SignatureProjective::par_aggregate(signatures)?; + self.0 += &aggregate.0; + Ok(()) + } + + /// Aggregate a list of signatures + #[allow(clippy::arithmetic_side_effects)] + #[cfg(feature = "parallel")] + pub fn par_aggregate<'a, S: AsSignatureProjective + Sync + 'a>( + signatures: impl ParallelIterator, + ) -> Result { + signatures + .into_par_iter() + .map(|sig| sig.try_as_projective()) + .try_reduce_with(|mut a, b| { + a.0 += b.0; + Ok(a) + }) + .ok_or(BlsError::EmptyAggregation)? + } + + /// Verify a list of signatures against a message and a list of public keys + #[cfg(feature = "parallel")] + pub fn par_verify_aggregate( + public_keys: &[P], + signatures: &[S], + message: &[u8], + ) -> Result { + if public_keys.len() != signatures.len() { + return Err(BlsError::InputLengthMismatch); + } + + let (aggregate_pubkey_res, aggregate_signature_res) = rayon::join( + || PubkeyProjective::par_aggregate(public_keys.into_par_iter()), + || SignatureProjective::par_aggregate(signatures.into_par_iter()), + ); + let aggregate_pubkey = aggregate_pubkey_res?; + let aggregate_signature = aggregate_signature_res?; + aggregate_pubkey.verify_signature(&aggregate_signature, message) + } + + /// Verifies a set of signatures over a set of distinct messages and + /// public keys in parallel. + #[cfg(feature = "parallel")] + pub fn par_verify_distinct( + public_keys: &[Pubkey], + signatures: &[Signature], + messages: &[&[u8]], + ) -> Result { + if public_keys.len() != messages.len() || public_keys.len() != signatures.len() { + return Err(BlsError::InputLengthMismatch); + } + if public_keys.is_empty() { + return Err(BlsError::EmptyAggregation); + } + let aggregate_signature = SignatureProjective::par_aggregate(signatures.into_par_iter())?; + Self::par_verify_distinct_aggregated(public_keys, &aggregate_signature.into(), messages) + } + + /// In parallel, verifies a pre-aggregated signature over a set of distinct + /// messages and public keys. + #[cfg(feature = "parallel")] + pub fn par_verify_distinct_aggregated( + public_keys: &[Pubkey], + aggregate_signature: &Signature, + messages: &[&[u8]], + ) -> Result { + if public_keys.len() != messages.len() { + return Err(BlsError::InputLengthMismatch); + } + if public_keys.is_empty() { + return Err(BlsError::EmptyAggregation); + } + + // Use `rayon` to perform the three expensive, independent tasks in parallel: + // 1. Deserialize public keys into curve points. + // 2. Hash messages into curve points and prepare them for pairing. + let (pubkeys_affine_res, prepared_hashes_res): (Result, _>, Result, _>) = + rayon::join( + || { + public_keys + .par_iter() + .map(|pk| { + let maybe_pubkey_affine: Option<_> = + G1Affine::from_uncompressed(&pk.0).into(); + maybe_pubkey_affine.ok_or(BlsError::PointConversion) + }) + .collect() + }, + || { + messages + .par_iter() + .map(|msg| { + let hashed_message: G2Affine = hash_message_to_point(msg).into(); + Ok::<_, BlsError>(G2Prepared::from(hashed_message)) + }) + .collect() + }, + ); + + // Check for errors from the parallel operations and unwrap the results. + let pubkeys_affine = pubkeys_affine_res?; + let prepared_hashes = prepared_hashes_res?; + + let maybe_aggregate_signature_affine: Option = + G2Affine::from_uncompressed(&aggregate_signature.0).into(); + let aggregate_signature_affine = + maybe_aggregate_signature_affine.ok_or(BlsError::PointConversion)?; + let signature_prepared = G2Prepared::from(aggregate_signature_affine); + + #[cfg(feature = "std")] + let neg_g1_generator = &*NEG_G1_GENERATOR_AFFINE; + #[cfg(not(feature = "std"))] + let neg_g1_generator_val: G1Affine = (-G1Projective::generator()).into(); + #[cfg(not(feature = "std"))] + let neg_g1_generator = &neg_g1_generator_val; + + let mut terms = alloc::vec::Vec::with_capacity(public_keys.len() + 1); + for i in 0..public_keys.len() { + terms.push((&pubkeys_affine[i], &prepared_hashes[i])); + } + terms.push((neg_g1_generator, &signature_prepared)); + + let miller_loop_result = Bls12::multi_miller_loop(&terms); + Ok(miller_loop_result.final_exponentiation() == Gt::identity()) + } +} + +#[cfg(not(target_os = "solana"))] +impl VerifiableSignature for T {}