tronz_primitives/
message.rs1use alloy_primitives::keccak256;
7
8use crate::{Address, B256, RecoverableSignature, error::SignatureError};
9
10pub const TRON_MESSAGE_PREFIX: &[u8] = b"\x19TRON Signed Message:\n";
12
13pub fn hash_message(message: impl AsRef<[u8]>) -> B256 {
17 let message = message.as_ref();
18 let len = message.len().to_string();
19
20 let mut prefixed = Vec::with_capacity(TRON_MESSAGE_PREFIX.len() + len.len() + message.len());
21 prefixed.extend_from_slice(TRON_MESSAGE_PREFIX);
22 prefixed.extend_from_slice(len.as_bytes());
23 prefixed.extend_from_slice(message);
24
25 keccak256(prefixed)
26}
27
28pub fn recover_message_address(
32 message: impl AsRef<[u8]>,
33 signature: &RecoverableSignature,
34) -> Result<Address, SignatureError> {
35 signature.recover_address_from_prehash(hash_message(message))
36}
37
38pub fn verify_message(
43 message: impl AsRef<[u8]>,
44 signature: &RecoverableSignature,
45 address: Address,
46) -> bool {
47 recover_message_address(message, signature)
48 .map(|recovered| recovered == address)
49 .unwrap_or(false)
50}
51
52#[cfg(test)]
53mod tests {
54 use super::*;
55
56 const TRONWEB_ADDR: &str = "TMVQGm1qAQYVdetCeGRRkTWYYrLXuHK2HC";
58 const VECTORS: &[(&str, &str)] = &[
59 (
60 "hello world",
61 "0x0dc0b53d525e0103a6013061cf18e60cf158809149f2b8994a545af65a7004cb1eeaff560e801ab51b28df5d42549aa024c2aa7e9d34de1e01294b9afb5e6c7e1c",
62 ),
63 (
64 "",
65 "0x5fc8883facaeb9dbebe71ec179c2744f83dc0e4868c6c9a6d63d47b949f33ae4317dc553f28a9c64b06838dc1adf7f24da330394f05e4ea6318d18608293ffe91b",
66 ),
67 (
68 "你好",
69 "0xa1a3ca757f1a6e52018bff5de55903be6073072f3e1fc82bea75f9f9e491bab4516d9a02f448f13127175fd2e91df6a9ea7da6422fc3c8f89353b64beb24c6321b",
70 ),
71 (
72 "0x1234",
73 "0x76f9b3883c5fc5415a5f132fe6916d089441c27c2438bee9c8461409a2002c950d1882ed3b3468cfe8d77b303241446179e60971287fdd882ea566ef7e517a101c",
74 ),
75 ];
76
77 fn sig_from_hex(s: &str) -> RecoverableSignature {
78 let bytes = hex::decode(s.trim_start_matches("0x")).unwrap();
79 RecoverableSignature::from_bytes(&bytes).unwrap()
80 }
81
82 #[test]
83 fn hash_message_matches_manual_assembly() {
84 for (msg, _) in VECTORS {
85 let mut expected = Vec::new();
86 expected.extend_from_slice(b"\x19TRON Signed Message:\n");
87 expected.extend_from_slice(msg.len().to_string().as_bytes());
88 expected.extend_from_slice(msg.as_bytes());
89 assert_eq!(hash_message(msg), keccak256(&expected), "msg={msg:?}");
90 }
91 }
92
93 #[test]
94 fn byte_length_prefix_is_utf8_not_char_count() {
95 assert_eq!("你好".len(), 6);
96 assert_eq!("0x1234".len(), 6);
97 }
98
99 #[test]
100 fn recovers_tronweb_signatures() {
101 let expected = Address::from_base58(TRONWEB_ADDR).unwrap();
102 for (msg, sig_hex) in VECTORS {
103 let sig = sig_from_hex(sig_hex);
104 assert_eq!(recover_message_address(msg, &sig).unwrap(), expected, "msg={msg:?}");
105 assert!(verify_message(msg, &sig, expected), "msg={msg:?}");
106 }
107 }
108
109 #[test]
110 fn verify_rejects_tampered_message_and_wrong_address() {
111 let expected = Address::from_base58(TRONWEB_ADDR).unwrap();
112 let sig = sig_from_hex(VECTORS[0].1); assert!(!verify_message("hello worlx", &sig, expected));
114 assert!(!verify_message("hello world", &sig, Address::ZERO));
115 }
116}