diff --git a/primitives/core/src/ecdsa.rs b/primitives/core/src/ecdsa.rs index 1fb80f24eaf3b..c567b3c44f6ce 100644 --- a/primitives/core/src/ecdsa.rs +++ b/primitives/core/src/ecdsa.rs @@ -354,6 +354,18 @@ impl Signature { .ok() .map(|recovered| Public(recovered.serialize_compressed())) } + + /// Recover the public key from this signature and a pre-hashed message. + #[cfg(feature = "full_crypto")] + pub fn recover_prehashed(&self, message: &[u8; 32]) -> Option { + let message = secp256k1::Message::parse(message); + + let sig: (_, _) = self.try_into().ok()?; + + secp256k1::recover(&message, &sig.0, &sig.1) + .ok() + .map(|key| Public(key.serialize_compressed())) + } } #[cfg(feature = "full_crypto")] @@ -537,6 +549,22 @@ impl Pair { let message = secp256k1::Message::parse(message); secp256k1::sign(&message, &self.secret).into() } + + /// Verify a signature on a pre-hashed message. Return `true` if the signature is valid + /// and thus matches the given `public` key. + pub fn verify_prehashed(sig: &Signature, message: &[u8; 32], public: &Public) -> bool { + let message = secp256k1::Message::parse(message); + + let sig: (_, _) = match sig.try_into() { + Ok(x) => x, + _ => return false, + }; + + match secp256k1::recover(&message, &sig.0, &sig.1) { + Ok(actual) => public.0[..] == actual.serialize_compressed()[..], + _ => false, + } + } } impl CryptoType for Public { @@ -791,4 +819,37 @@ mod test { assert_eq!(sig1, sig2); } + + #[test] + fn verify_prehashed_works() { + let (pair, _, _) = Pair::generate_with_phrase(Some("password")); + + // `msg` and `sig` match + let msg = keccak_256(b"this should be hashed"); + let sig = pair.sign_prehashed(&msg); + assert!(Pair::verify_prehashed(&sig, &msg, &pair.public())); + + // `msg` and `sig` don't match + let msg = keccak_256(b"this is a different message"); + assert!(!Pair::verify_prehashed(&sig, &msg, &pair.public())); + } + + #[test] + fn recover_prehashed_works() { + let (pair, _, _) = Pair::generate_with_phrase(Some("password")); + + // recovered key matches signing key + let msg = keccak_256(b"this should be hashed"); + let sig = pair.sign_prehashed(&msg); + let key = sig.recover_prehashed(&msg).unwrap(); + assert_eq!(pair.public(), key); + + // recovered key is useable + assert!(Pair::verify_prehashed(&sig, &msg, &key)); + + // recovered key and signing key don't match + let msg = keccak_256(b"this is a different message"); + let key = sig.recover_prehashed(&msg).unwrap(); + assert_ne!(pair.public(), key); + } }