defuse_tip191/
lib.rs

1use defuse_crypto::{Curve, Secp256k1};
2use impl_tools::autoimpl;
3
4/// See [TIP-191](https://github.com/tronprotocol/tips/blob/master/tip-191.md)
5#[cfg_attr(
6    feature = "serde",
7    derive(::serde::Serialize, ::serde::Deserialize),
8    cfg_attr(feature = "abi", derive(::schemars::JsonSchema))
9)]
10#[derive(Debug, Clone)]
11pub struct Tip191Payload(pub String);
12
13impl Tip191Payload {
14    #[inline]
15    pub fn prehash(&self) -> Vec<u8> {
16        let data = self.0.as_bytes();
17        [
18            // Prefix not specified in the standard. But from: https://tronweb.network/docu/docs/Sign%20and%20Verify%20Message/
19            format!("\x19TRON Signed Message:\n{}", data.len()).as_bytes(),
20            data,
21        ]
22        .concat()
23    }
24}
25
26#[cfg(any(test, feature = "sha3", feature = "near-contract"))]
27impl defuse_crypto::Payload for Tip191Payload {
28    #[inline]
29    fn hash(&self) -> defuse_crypto::CryptoHash {
30        use defuse_digest::{Digest, Keccak256};
31        Keccak256::digest(self.prehash()).into()
32    }
33}
34
35#[cfg_attr(
36    feature = "serde",
37    ::cfg_eval::cfg_eval,
38    ::serde_with::serde_as,
39    derive(::serde::Serialize, ::serde::Deserialize),
40    cfg_attr(feature = "abi", derive(::schemars::JsonSchema))
41)]
42#[autoimpl(Deref using self.payload)]
43#[derive(Debug, Clone)]
44pub struct SignedTip191Payload {
45    pub payload: Tip191Payload,
46
47    /// There is no public key member because the public key can be recovered
48    /// via `ecrecover()` knowing the data and the signature
49    #[cfg_attr(
50        feature = "serde",
51        serde_as(as = "defuse_crypto::serde::AsCurve<Secp256k1>")
52    )]
53    pub signature: <Secp256k1 as Curve>::Signature,
54}
55
56#[cfg(any(test, feature = "sha3", feature = "near-contract"))]
57impl defuse_crypto::Payload for SignedTip191Payload {
58    #[inline]
59    fn hash(&self) -> defuse_crypto::CryptoHash {
60        self.payload.hash()
61    }
62}
63
64#[cfg(any(test, feature = "near-contract"))]
65impl defuse_crypto::SignedPayload for SignedTip191Payload {
66    type PublicKey = <Secp256k1 as Curve>::PublicKey;
67
68    #[inline]
69    fn verify(&self) -> Option<Self::PublicKey> {
70        use defuse_crypto::{Payload, VerifiableCurve};
71        Secp256k1::verify(&self.signature, &self.payload.hash(), &())
72    }
73}
74
75#[cfg(test)]
76mod tests {
77    use super::*;
78    use defuse_crypto::SignedPayload;
79    use hex_literal::hex;
80
81    const fn fix_v_in_signature(mut sig: [u8; 65]) -> [u8; 65] {
82        if *sig.last().unwrap() >= 27 {
83            // Ethereum only uses uncompressed keys, with corresponding value v=27/28
84            // https://bitcoin.stackexchange.com/a/38909/58790
85            *sig.last_mut().unwrap() -= 27;
86        }
87        sig
88    }
89
90    // NOTE: Public key can be derived using `ethers_signers` crate:
91    // let wallet = LocalWallet::from_str(
92    //     "a4b319a82adfc43584e4537fec97a80516e16673db382cd91eba97abbab8ca56",
93    // )?;
94    // let signing_key = wallet.signer();
95    // let verifying_key = signing_key.verifying_key();
96    // let public_key = verifying_key.to_encoded_point(false);
97    // // Notice that we skip the first byte, 0x04
98    // println!("Public key: 0x{}", hex::encode(public_key.as_bytes()[1..]));
99
100    const REFERENCE_MESSAGE: &str = "Hello, TRON!";
101    const INVALID_REFERENCE_MESSAGE: &str = "this is not TRON reference input message";
102    const REFERENCE_SIGNATURE: [u8; 65] = hex!(
103        "eea1651a60600ec4d9c45e8ae81da1a78377f789f0ac2019de66ad943459913015ef9256809ee0e6bb76e303a0b4802e475c1d26ade5d585292b80c9fe9cb10c1c"
104    );
105    const INVALID_REFERENCE_SIGNATURE: [u8; 65] = hex!(
106        "0000000011111111000000001110111110000000011111111e66ad943459913015ef9256809ee0e6bb76e303a0b4802e475c1d26ade5d585292b80c9fe9cb10c1c"
107    );
108    const REFERENCE_PUBKEY: [u8; 64] = hex!(
109        "85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"
110    );
111
112    #[test]
113    fn test_reference_signature_verification_works() {
114        assert_eq!(
115            SignedTip191Payload {
116                payload: Tip191Payload(REFERENCE_MESSAGE.to_string()),
117                signature: fix_v_in_signature(REFERENCE_SIGNATURE),
118            }
119            .verify(),
120            Some(REFERENCE_PUBKEY)
121        );
122    }
123
124    #[test]
125    fn test_invalid_reference_message_verification_fails() {
126        assert_ne!(
127            SignedTip191Payload {
128                payload: Tip191Payload(INVALID_REFERENCE_MESSAGE.to_string()),
129                signature: fix_v_in_signature(REFERENCE_SIGNATURE),
130            }
131            .verify(),
132            Some(REFERENCE_PUBKEY)
133        );
134    }
135
136    #[test]
137    fn test_invalid_reference_signature_verification_fails() {
138        assert_ne!(
139            SignedTip191Payload {
140                payload: Tip191Payload(REFERENCE_MESSAGE.to_string()),
141                signature: fix_v_in_signature(INVALID_REFERENCE_SIGNATURE),
142            }
143            .verify(),
144            Some(REFERENCE_PUBKEY)
145        );
146    }
147}