From ed39290f913fce47b388091d25ddcb3064cf323e Mon Sep 17 00:00:00 2001 From: Andreas Doerr Date: Tue, 18 May 2021 16:07:45 +0200 Subject: [PATCH] Add CryptoStore::ecdsa_sign_prehashed() (#8838) * Pair::sign_prehashed() * add CryptoStore::ecdsa_sign_prehashed() * add test for testing keystore * address review comments --- substrate/client/keystore/src/local.rs | 21 ++++++++++ substrate/primitives/core/src/ecdsa.rs | 32 ++++++++++++++- substrate/primitives/keystore/src/lib.rs | 28 +++++++++++++ substrate/primitives/keystore/src/testing.rs | 43 +++++++++++++++++++- 4 files changed, 122 insertions(+), 2 deletions(-) diff --git a/substrate/client/keystore/src/local.rs b/substrate/client/keystore/src/local.rs index 482ef40760..2377ea1277 100644 --- a/substrate/client/keystore/src/local.rs +++ b/substrate/client/keystore/src/local.rs @@ -142,6 +142,15 @@ impl CryptoStore for LocalKeystore { ) -> std::result::Result, TraitError> { SyncCryptoStore::sr25519_vrf_sign(self, key_type, public, transcript_data) } + + async fn ecdsa_sign_prehashed( + &self, + id: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8; 32], + ) -> std::result::Result, TraitError> { + SyncCryptoStore::ecdsa_sign_prehashed(self, id, public, msg) + } } impl SyncCryptoStore for LocalKeystore { @@ -301,6 +310,18 @@ impl SyncCryptoStore for LocalKeystore { Ok(None) } } + + fn ecdsa_sign_prehashed( + &self, + id: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8; 32], + ) -> std::result::Result, TraitError> { + let pair = self.0.read() + .key_pair_by_type::(public, id)?; + + pair.map(|k| k.sign_prehashed(msg)).map(Ok).transpose() + } } impl Into for LocalKeystore { diff --git a/substrate/primitives/core/src/ecdsa.rs b/substrate/primitives/core/src/ecdsa.rs index 2ec10681e7..60fa7c3e81 100644 --- a/substrate/primitives/core/src/ecdsa.rs +++ b/substrate/primitives/core/src/ecdsa.rs @@ -531,6 +531,12 @@ impl Pair { Self::from_seed(&padded_seed) }) } + + /// Sign a pre-hashed message + pub fn sign_prehashed(&self, message: &[u8; 32]) -> Signature { + let message = secp256k1::Message::parse(message); + secp256k1::sign(&message, &self.secret).into() + } } impl CryptoType for Public { @@ -552,7 +558,7 @@ impl CryptoType for Pair { mod test { use super::*; use hex_literal::hex; - use crate::crypto::{DEV_PHRASE, set_default_ss58_version}; + use crate::{crypto::{DEV_PHRASE, set_default_ss58_version}, keccak_256}; use serde_json; use crate::crypto::PublicError; @@ -761,4 +767,28 @@ mod test { // Poorly-sized assert!(deserialize_signature("\"abc123\"").is_err()); } + + #[test] + fn sign_prehashed_works() { + let (pair, _, _) = Pair::generate_with_phrase(Some("password")); + + // `msg` shouldn't be mangled + let msg = [0u8; 32]; + let sig1 = pair.sign_prehashed(&msg); + let sig2: Signature = secp256k1::sign(&secp256k1::Message::parse(&msg), &pair.secret).into(); + + assert_eq!(sig1, sig2); + + // signature is actually different + let sig2 = pair.sign(&msg); + + assert_ne!(sig1, sig2); + + // using pre-hashed `msg` works + let msg = keccak_256(b"this should be hashed"); + let sig1 = pair.sign_prehashed(&msg); + let sig2: Signature = secp256k1::sign(&secp256k1::Message::parse(&msg), &pair.secret).into(); + + assert_eq!(sig1, sig2); + } } diff --git a/substrate/primitives/keystore/src/lib.rs b/substrate/primitives/keystore/src/lib.rs index 2fda3a48c5..352154d824 100644 --- a/substrate/primitives/keystore/src/lib.rs +++ b/substrate/primitives/keystore/src/lib.rs @@ -194,6 +194,20 @@ pub trait CryptoStore: Send + Sync { public: &sr25519::Public, transcript_data: VRFTranscriptData, ) -> Result, Error>; + + /// Sign pre-hashed + /// + /// Signs a pre-hashed message with the private key that matches + /// the ECDSA public key passed. + /// + /// Returns the SCALE encoded signature if key is found and supported, + /// `None` if the key doesn't exist or an error when something failed. + async fn ecdsa_sign_prehashed( + &self, + id: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8; 32], + ) -> Result, Error>; } /// Sync version of the CryptoStore @@ -353,6 +367,20 @@ pub trait SyncCryptoStore: CryptoStore + Send + Sync { public: &sr25519::Public, transcript_data: VRFTranscriptData, ) -> Result, Error>; + + /// Sign pre-hashed + /// + /// Signs a pre-hashed message with the private key that matches + /// the ECDSA public key passed. + /// + /// Returns the SCALE encoded signature if key is found and supported, + /// `None` if the key doesn't exist or an error when something failed. + fn ecdsa_sign_prehashed( + &self, + id: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8; 32], + ) -> Result, Error>; } /// A pointer to a keystore. diff --git a/substrate/primitives/keystore/src/testing.rs b/substrate/primitives/keystore/src/testing.rs index caee7178e0..9cc8b8fc64 100644 --- a/substrate/primitives/keystore/src/testing.rs +++ b/substrate/primitives/keystore/src/testing.rs @@ -22,6 +22,7 @@ use sp_core::{ crypto::{Pair, Public, CryptoTypePublicPair}, ed25519, sr25519, ecdsa, }; + use crate::{ {CryptoStore, SyncCryptoStorePtr, Error, SyncCryptoStore}, vrf::{VRFTranscriptData, VRFSignature, make_transcript}, @@ -144,6 +145,15 @@ impl CryptoStore for KeyStore { ) -> Result, Error> { SyncCryptoStore::sr25519_vrf_sign(self, key_type, public, transcript_data) } + + async fn ecdsa_sign_prehashed( + &self, + id: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8; 32], + ) -> Result, Error> { + SyncCryptoStore::ecdsa_sign_prehashed(self, id, public, msg) + } } impl SyncCryptoStore for KeyStore { @@ -325,6 +335,16 @@ impl SyncCryptoStore for KeyStore { proof, })) } + + fn ecdsa_sign_prehashed( + &self, + id: KeyTypeId, + public: &ecdsa::Public, + msg: &[u8; 32], + ) -> Result, Error> { + let pair = self.ecdsa_key_pair(id, public); + pair.map(|k| k.sign_prehashed(msg)).map(Ok).transpose() + } } impl Into for KeyStore { @@ -342,7 +362,7 @@ impl Into> for KeyStore { #[cfg(test)] mod tests { use super::*; - use sp_core::{sr25519, testing::{ED25519, SR25519}}; + use sp_core::{sr25519, testing::{ED25519, SR25519, ECDSA}}; use crate::{SyncCryptoStore, vrf::VRFTranscriptValue}; #[test] @@ -416,4 +436,25 @@ mod tests { assert!(result.unwrap().is_some()); } + + #[test] + fn ecdsa_sign_prehashed_works() { + let store = KeyStore::new(); + + let suri = "//Alice"; + let pair = ecdsa::Pair::from_string(suri, None).unwrap(); + + let msg = sp_core::keccak_256(b"this should be a hashed message"); + + // no key in key store + let res = SyncCryptoStore::ecdsa_sign_prehashed(&store, ECDSA, &pair.public(), &msg).unwrap(); + assert!(res.is_none()); + + // insert key, sign again + let res = SyncCryptoStore::insert_unknown(&store, ECDSA, suri, pair.public().as_ref()).unwrap(); + assert_eq!((), res); + + let res = SyncCryptoStore::ecdsa_sign_prehashed(&store, ECDSA, &pair.public(), &msg).unwrap(); + assert!(res.is_some()); + } }