1use defuse_crypto::{Curve, RecoverableCurve, secp256k1::Secp256k1};
4use defuse_digest::{Digest, sha3::Keccak256};
5
6pub struct Erc191;
8
9impl Erc191 {
10 #[must_use = "check recovered public key"]
14 #[inline]
15 pub fn recover(
16 msg: impl AsRef<[u8]>,
17 signature: &<Secp256k1 as Curve>::Signature,
18 recovery_id: <Secp256k1 as RecoverableCurve>::RecoveryId,
19 ) -> Option<<Secp256k1 as Curve>::PublicKey> {
20 Secp256k1::recover(&Self::prehash(msg), signature, recovery_id)
21 }
22
23 #[inline]
29 pub fn prehash(msg: impl AsRef<[u8]>) -> [u8; 32] {
30 let msg = msg.as_ref();
31
32 Keccak256::new_with_prefix(b"\x19Ethereum Signed Message:\n")
33 .chain_update(msg.len().to_string())
35 .chain_update(msg)
37 .finalize()
38 .into()
39 }
40}
41
42#[cfg(test)]
43mod tests {
44 use defuse_crypto::secp256k1::{Secp256k1RecoverableSignature, Secp256k1UncompressedPublicKey};
45 use hex_literal::hex;
46 use rstest::rstest;
47
48 use super::*;
49
50 #[rstest]
51 #[case(
52 hex!("85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"),
53 "Hello world!",
54 hex!("7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e4701"),
55 )]
56 fn recover_ok(
57 #[case] public_key: impl Into<Secp256k1UncompressedPublicKey>,
58 #[case] msg: impl AsRef<[u8]>,
59 #[case] signature: impl Into<Secp256k1RecoverableSignature>,
60 ) {
61 let (signature, recovery_id) = signature.into().try_into().unwrap();
62
63 assert_eq!(
64 Erc191::recover(msg, &signature, recovery_id),
65 Some(public_key.into().try_into().unwrap()),
66 "invalid recovered public key",
67 );
68 }
69}