1use defuse_crypto::{Curve, RecoverableCurve, secp256k1::Secp256k1};
5use defuse_digest::{Digest, sha3::Keccak256};
6
7pub struct Tip191;
10
11impl Tip191 {
12 #[must_use = "check recovered public key"]
16 #[inline]
17 pub fn recover(
18 msg: impl AsRef<[u8]>,
19 signature: &<Secp256k1 as Curve>::Signature,
20 recovery_id: <Secp256k1 as RecoverableCurve>::RecoveryId,
21 ) -> Option<<Secp256k1 as Curve>::PublicKey> {
22 Secp256k1::recover(&Self::prehash(msg.as_ref()), signature, recovery_id)
23 }
24
25 #[inline]
27 pub fn prehash(msg: impl AsRef<[u8]>) -> [u8; 32] {
28 let msg = msg.as_ref();
29
30 Keccak256::new_with_prefix(b"\x19TRON 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, TRON!",
54 hex!("eea1651a60600ec4d9c45e8ae81da1a78377f789f0ac2019de66ad943459913015ef9256809ee0e6bb76e303a0b4802e475c1d26ade5d585292b80c9fe9cb10c01"),
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 Tip191::recover(msg, &signature, recovery_id),
65 Some(public_key.into().try_into().unwrap()),
66 "invalid recovered public key",
67 );
68 }
69}