Skip to main content

defuse_tip191/
lib.rs

1//! [TIP-191](https://github.com/tronprotocol/tips/blob/master/tip-191.md)
2//! Signed Data Standard
3
4use defuse_crypto::{Curve, RecoverableCurve, secp256k1::Secp256k1};
5use defuse_digest::{Digest, sha3::Keccak256};
6
7/// [TIP-191](https://github.com/tronprotocol/tips/blob/master/tip-191.md)
8/// Signed Data Standard
9pub struct Tip191;
10
11impl Tip191 {
12    /// Try to recover public key which signed given message according to
13    /// [TIP-191](https://github.com/tronprotocol/tips/blob/master/tip-191.md)
14    /// and produced given signature and recovery id.
15    #[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    /// Derive prehash for signing
26    #[inline]
27    pub fn prehash(msg: impl AsRef<[u8]>) -> [u8; 32] {
28        let msg = msg.as_ref();
29
30        // Prefix itself is not specified in the standard. But from:
31        // https://tronweb.network/docu/docs/Sign%20and%20Verify%20Message/
32        Keccak256::new_with_prefix(b"\x19TRON Signed Message:\n")
33            // `len(message)` is the non-zero-padded ascii-decimal encoding of the number of bytes in message.
34            .chain_update(msg.len().to_string())
35            // <data to sign>
36            .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}