use alloy_primitives::keccak256;
use crate::{Address, B256, RecoverableSignature, error::SignatureError};
pub const TRON_MESSAGE_PREFIX: &[u8] = b"\x19TRON Signed Message:\n";
pub fn hash_message(message: impl AsRef<[u8]>) -> B256 {
let message = message.as_ref();
let len = message.len().to_string();
let mut prefixed = Vec::with_capacity(TRON_MESSAGE_PREFIX.len() + len.len() + message.len());
prefixed.extend_from_slice(TRON_MESSAGE_PREFIX);
prefixed.extend_from_slice(len.as_bytes());
prefixed.extend_from_slice(message);
keccak256(prefixed)
}
pub fn recover_message_address(
message: impl AsRef<[u8]>,
signature: &RecoverableSignature,
) -> Result<Address, SignatureError> {
signature.recover_address_from_prehash(hash_message(message))
}
pub fn verify_message(
message: impl AsRef<[u8]>,
signature: &RecoverableSignature,
address: Address,
) -> bool {
recover_message_address(message, signature)
.map(|recovered| recovered == address)
.unwrap_or(false)
}
#[cfg(test)]
mod tests {
use super::*;
const TRONWEB_ADDR: &str = "TMVQGm1qAQYVdetCeGRRkTWYYrLXuHK2HC";
const VECTORS: &[(&str, &str)] = &[
(
"hello world",
"0x0dc0b53d525e0103a6013061cf18e60cf158809149f2b8994a545af65a7004cb1eeaff560e801ab51b28df5d42549aa024c2aa7e9d34de1e01294b9afb5e6c7e1c",
),
(
"",
"0x5fc8883facaeb9dbebe71ec179c2744f83dc0e4868c6c9a6d63d47b949f33ae4317dc553f28a9c64b06838dc1adf7f24da330394f05e4ea6318d18608293ffe91b",
),
(
"你好",
"0xa1a3ca757f1a6e52018bff5de55903be6073072f3e1fc82bea75f9f9e491bab4516d9a02f448f13127175fd2e91df6a9ea7da6422fc3c8f89353b64beb24c6321b",
),
(
"0x1234",
"0x76f9b3883c5fc5415a5f132fe6916d089441c27c2438bee9c8461409a2002c950d1882ed3b3468cfe8d77b303241446179e60971287fdd882ea566ef7e517a101c",
),
];
fn sig_from_hex(s: &str) -> RecoverableSignature {
let bytes = hex::decode(s.trim_start_matches("0x")).unwrap();
RecoverableSignature::from_bytes(&bytes).unwrap()
}
#[test]
fn hash_message_matches_manual_assembly() {
for (msg, _) in VECTORS {
let mut expected = Vec::new();
expected.extend_from_slice(b"\x19TRON Signed Message:\n");
expected.extend_from_slice(msg.len().to_string().as_bytes());
expected.extend_from_slice(msg.as_bytes());
assert_eq!(hash_message(msg), keccak256(&expected), "msg={msg:?}");
}
}
#[test]
fn byte_length_prefix_is_utf8_not_char_count() {
assert_eq!("你好".len(), 6);
assert_eq!("0x1234".len(), 6);
}
#[test]
fn recovers_tronweb_signatures() {
let expected = Address::from_base58(TRONWEB_ADDR).unwrap();
for (msg, sig_hex) in VECTORS {
let sig = sig_from_hex(sig_hex);
assert_eq!(recover_message_address(msg, &sig).unwrap(), expected, "msg={msg:?}");
assert!(verify_message(msg, &sig, expected), "msg={msg:?}");
}
}
#[test]
fn verify_rejects_tampered_message_and_wrong_address() {
let expected = Address::from_base58(TRONWEB_ADDR).unwrap();
let sig = sig_from_hex(VECTORS[0].1); assert!(!verify_message("hello worlx", &sig, expected));
assert!(!verify_message("hello world", &sig, Address::ZERO));
}
}