diff --git a/substrate/primitives/application-crypto/src/bandersnatch.rs b/substrate/primitives/application-crypto/src/bandersnatch.rs index c5fc7043def4..e753482be330 100644 --- a/substrate/primitives/application-crypto/src/bandersnatch.rs +++ b/substrate/primitives/application-crypto/src/bandersnatch.rs @@ -21,7 +21,7 @@ use crate::{KeyTypeId, RuntimePublic}; use alloc::vec::Vec; pub use sp_core::bandersnatch::*; -use sp_core::crypto::{ProofOfPossessionGenerator, ProofOfPossessionVerifier}; +use sp_core::crypto::ProofOfPossessionVerifier; mod app { crate::app_crypto!(super, sp_core::testing::BANDERSNATCH); diff --git a/substrate/primitives/application-crypto/src/bls381.rs b/substrate/primitives/application-crypto/src/bls381.rs index 7a218e03cdf9..6fcf5c6b530f 100644 --- a/substrate/primitives/application-crypto/src/bls381.rs +++ b/substrate/primitives/application-crypto/src/bls381.rs @@ -21,7 +21,7 @@ use crate::{KeyTypeId, RuntimePublic}; use alloc::vec::Vec; pub use sp_core::bls::bls381::*; -use sp_core::crypto::{ProofOfPossessionGenerator, ProofOfPossessionVerifier}; +use sp_core::crypto::ProofOfPossessionVerifier; mod app { crate::app_crypto!(super, sp_core::testing::BLS381); @@ -54,9 +54,7 @@ impl RuntimePublic for Public { } fn generate_pop(&mut self, key_type: KeyTypeId) -> Option { - // TODO: Add host function - // sp_io::crypto::generate_pop() - None + sp_io::crypto::bls381_generate_pop(key_type, self) } fn verify_pop(&self, pop: &Self::Signature) -> bool { diff --git a/substrate/primitives/application-crypto/src/ecdsa.rs b/substrate/primitives/application-crypto/src/ecdsa.rs index 79885a3cebba..687b7de8316e 100644 --- a/substrate/primitives/application-crypto/src/ecdsa.rs +++ b/substrate/primitives/application-crypto/src/ecdsa.rs @@ -22,7 +22,7 @@ use crate::{KeyTypeId, RuntimePublic}; use alloc::vec::Vec; pub use sp_core::ecdsa::*; -use sp_core::crypto::{ProofOfPossessionGenerator, ProofOfPossessionVerifier}; +use sp_core::crypto::ProofOfPossessionVerifier; mod app { crate::app_crypto!(super, sp_core::testing::ECDSA); diff --git a/substrate/primitives/application-crypto/src/ecdsa_bls381.rs b/substrate/primitives/application-crypto/src/ecdsa_bls381.rs index 858d84d270f9..9fa12dd0dbb7 100644 --- a/substrate/primitives/application-crypto/src/ecdsa_bls381.rs +++ b/substrate/primitives/application-crypto/src/ecdsa_bls381.rs @@ -20,8 +20,9 @@ use crate::{KeyTypeId, RuntimePublic}; use alloc::vec::Vec; +use sp_core::{bls381, ecdsa, ecdsa_bls381}; pub use sp_core::paired_crypto::ecdsa_bls381::*; -use sp_core::crypto::{ProofOfPossessionGenerator, ProofOfPossessionVerifier}; +use sp_core::crypto::ProofOfPossessionVerifier; mod app { crate::app_crypto!(super, sp_core::testing::ECDSA_BLS381); @@ -54,8 +55,25 @@ impl RuntimePublic for Public { } fn generate_pop(&mut self, key_type: KeyTypeId) -> Option { - // TODO: Implement Special Case by calling both pop generations - None + if key_type != sp_core::testing::ECDSA_BLS381 { + return None + } + + let pub_key_as_bytes = self.to_raw_vec(); + + // Split public key bytes into ECDSA and BLS381 parts + let (ecdsa_pub_as_bytes, bls381_pub_as_bytes) = split_pub_key_bytes(&pub_key_as_bytes)?; + + // Generate ECDSA proof of possession + let ecdsa_pop = generate_ecdsa_pop(ecdsa_pub_as_bytes)?; + + // Generate BLS381 proof of possession + let bls381_pop = generate_bls381_pop(bls381_pub_as_bytes)?; + + // Combine the two pops into a single pop + let combined_pop_raw = combine_pop(&ecdsa_pop, &bls381_pop)?; + + Some(Self::Signature::from_raw(combined_pop_raw)) } fn verify_pop(&self, pop: &Self::Signature) -> bool { @@ -68,3 +86,41 @@ impl RuntimePublic for Public { sp_core::crypto::ByteArray::to_raw_vec(self) } } + +/// Helper: Split public key bytes into ECDSA and BLS381 parts +fn split_pub_key_bytes(pub_key_as_bytes: &[u8]) + -> Option<([u8; ecdsa::PUBLIC_KEY_SERIALIZED_SIZE], [u8; bls381::PUBLIC_KEY_SERIALIZED_SIZE])> { + let ecdsa_pub_as_bytes = pub_key_as_bytes[..ecdsa::PUBLIC_KEY_SERIALIZED_SIZE] + .try_into() + .ok()?; + let bls381_pub_as_bytes = pub_key_as_bytes[ecdsa::PUBLIC_KEY_SERIALIZED_SIZE..] + .try_into() + .ok()?; + Some((ecdsa_pub_as_bytes, bls381_pub_as_bytes)) +} + +/// Helper: Generate ECDSA proof of possession +fn generate_ecdsa_pop(ecdsa_pub_as_bytes: [u8; ecdsa::PUBLIC_KEY_SERIALIZED_SIZE]) -> Option { + let pop_context_tag: &[u8] = b"POP_"; + let ecdsa_statement = [pop_context_tag, ecdsa_pub_as_bytes.as_slice()].concat(); + let ecdsa_pub = ecdsa::Public::from_raw(ecdsa_pub_as_bytes); + sp_io::crypto::ecdsa_sign(sp_core::testing::ECDSA, &ecdsa_pub, ecdsa_statement.as_slice()) +} + +/// Helper: Generate BLS381 proof of possession +fn generate_bls381_pop(bls381_pub_as_bytes: [u8; bls381::PUBLIC_KEY_SERIALIZED_SIZE]) -> Option { + let bls381_pub = bls381::Public::from_raw(bls381_pub_as_bytes); + sp_io::crypto::bls381_generate_pop(sp_core::testing::BLS381, &bls381_pub) +} + +/// Helper: Combine ECDSA and BLS381 pops into a single raw pop +fn combine_pop( + ecdsa_pop: &ecdsa::Signature, + bls381_pop: &bls381::Signature, +) -> Option<[u8; ecdsa_bls381::SIGNATURE_LEN]> { + let mut combined_pop_raw = [0u8; ecdsa_bls381::SIGNATURE_LEN]; + combined_pop_raw[..ecdsa::SIGNATURE_SERIALIZED_SIZE].copy_from_slice(ecdsa_pop.as_ref()); + combined_pop_raw[ecdsa::SIGNATURE_SERIALIZED_SIZE..].copy_from_slice(bls381_pop.as_ref()); + Some(combined_pop_raw) +} + diff --git a/substrate/primitives/application-crypto/src/ed25519.rs b/substrate/primitives/application-crypto/src/ed25519.rs index 67d97cb8e070..891796807e50 100644 --- a/substrate/primitives/application-crypto/src/ed25519.rs +++ b/substrate/primitives/application-crypto/src/ed25519.rs @@ -22,7 +22,7 @@ use crate::{KeyTypeId, RuntimePublic}; use alloc::vec::Vec; pub use sp_core::ed25519::*; -use sp_core::crypto::{ProofOfPossessionGenerator, ProofOfPossessionVerifier}; +use sp_core::crypto::ProofOfPossessionVerifier; mod app { crate::app_crypto!(super, sp_core::testing::ED25519); diff --git a/substrate/primitives/core/src/paired_crypto.rs b/substrate/primitives/core/src/paired_crypto.rs index ac5f5526338e..3bb88f300a1d 100644 --- a/substrate/primitives/core/src/paired_crypto.rs +++ b/substrate/primitives/core/src/paired_crypto.rs @@ -140,9 +140,9 @@ pub mod ecdsa_bls381 { /// An identifier used to match public keys against BLS12-381 keys pub const CRYPTO_ID: CryptoTypeId = CryptoTypeId(*b"ecb8"); - const PUBLIC_KEY_LEN: usize = + pub const PUBLIC_KEY_LEN: usize = ecdsa::PUBLIC_KEY_SERIALIZED_SIZE + bls381::PUBLIC_KEY_SERIALIZED_SIZE; - const SIGNATURE_LEN: usize = + pub const SIGNATURE_LEN: usize = ecdsa::SIGNATURE_SERIALIZED_SIZE + bls381::SIGNATURE_SERIALIZED_SIZE; #[doc(hidden)] diff --git a/substrate/primitives/io/src/lib.rs b/substrate/primitives/io/src/lib.rs index 38cf72a0c790..67eac35136c1 100644 --- a/substrate/primitives/io/src/lib.rs +++ b/substrate/primitives/io/src/lib.rs @@ -86,7 +86,7 @@ use tracing; #[cfg(feature = "std")] use sp_core::{ - crypto::Pair, + crypto::{Pair, ProofOfPossessionGenerator}, hexdisplay::HexDisplay, offchain::{OffchainDbExt, OffchainWorkerExt, TransactionPoolExt}, storage::ChildInfo, @@ -1219,6 +1219,15 @@ pub trait Crypto { .expect("`bls381_generate` failed") } + #[cfg(feature = "bls-experimental")] + fn bls381_generate_pop(&mut self, id: KeyTypeId, pub_key: &bls381::Public) -> Option { + self.extension::() + .expect("No `keystore` associated for the current context!") + .bls381_generate_pop(id, pub_key) + .ok() + .flatten() + } + /// Generate an `(ecdsa,bls12-381)` key for the given key type using an optional `seed` and /// store it in the keystore. /// diff --git a/substrate/primitives/keystore/src/lib.rs b/substrate/primitives/keystore/src/lib.rs index 42ad2c600d02..3df1ea8f8960 100644 --- a/substrate/primitives/keystore/src/lib.rs +++ b/substrate/primitives/keystore/src/lib.rs @@ -322,6 +322,13 @@ pub trait Keystore: Send + Sync { msg: &[u8], ) -> Result, Error>; + #[cfg(feature = "bls-experimental")] + fn bls381_generate_pop( + &self, + key_type: KeyTypeId, + public: &bls381::Public + ) -> Result, Error>; + /// Generate a (ecdsa,bls381) signature pair for a given message. /// /// Receives [`KeyTypeId`] and a [`ecdsa_bls381::Public`] key to be able to map @@ -620,6 +627,15 @@ impl Keystore for Arc { (**self).bls381_sign(key_type, public, msg) } + #[cfg(feature = "bls-experimental")] + fn bls381_generate_pop( + &self, + key_type: KeyTypeId, + public: &bls381::Public + ) -> Result, Error> { + (**self).bls381_generate_pop(key_type, public) + } + #[cfg(feature = "bls-experimental")] fn ecdsa_bls381_sign( &self, diff --git a/substrate/primitives/keystore/src/testing.rs b/substrate/primitives/keystore/src/testing.rs index 745f42e3477a..eb5b02057c9a 100644 --- a/substrate/primitives/keystore/src/testing.rs +++ b/substrate/primitives/keystore/src/testing.rs @@ -24,7 +24,7 @@ use sp_core::bandersnatch; #[cfg(feature = "bls-experimental")] use sp_core::{bls381, ecdsa_bls381, KeccakHasher}; use sp_core::{ - crypto::{ByteArray, KeyTypeId, Pair, VrfSecret}, + crypto::{ByteArray, KeyTypeId, Pair, VrfSecret, ProofOfPossessionGenerator}, ecdsa, ed25519, sr25519, }; @@ -122,6 +122,15 @@ impl MemoryKeystore { let pre_output = self.pair::(key_type, public).map(|pair| pair.vrf_pre_output(input)); Ok(pre_output) } + + fn generate_pop( + &self, + key_type: KeyTypeId, + public: &T::Public + ) -> Result, Error> { + let pop = self.pair::(key_type, public).map(|mut pair| pair.generate_proof_of_possession()); + Ok(pop) + } } impl Keystore for MemoryKeystore { @@ -298,6 +307,15 @@ impl Keystore for MemoryKeystore { self.sign::(key_type, public, msg) } + #[cfg(feature = "bls-experimental")] + fn bls381_generate_pop( + &self, + key_type: KeyTypeId, + public: &bls381::Public + ) -> Result, Error> { + self.generate_pop::(key_type, public) + } + #[cfg(feature = "bls-experimental")] fn ecdsa_bls381_public_keys(&self, key_type: KeyTypeId) -> Vec { self.public_keys::(key_type)