1use defuse_crypto::{CryptoHash, Curve, Payload, Secp256k1, SignedPayload, serde::AsCurve};
2use impl_tools::autoimpl;
3use near_sdk::{env, near, serde_with::serde_as};
4
5#[near(serializers = [json])]
7#[derive(Debug, Clone)]
8pub struct Tip191Payload(pub String);
9
10impl Tip191Payload {
11 #[inline]
12 pub fn prehash(&self) -> Vec<u8> {
13 let data = self.0.as_bytes();
14 [
15 format!("\x19TRON Signed Message:\n{}", data.len()).as_bytes(),
17 data,
18 ]
19 .concat()
20 }
21}
22
23impl Payload for Tip191Payload {
24 #[inline]
25 fn hash(&self) -> CryptoHash {
26 env::keccak256_array(self.prehash())
27 }
28}
29
30#[near(serializers = [json])]
31#[autoimpl(Deref using self.payload)]
32#[derive(Debug, Clone)]
33pub struct SignedTip191Payload {
34 pub payload: Tip191Payload,
35
36 #[serde_as(as = "AsCurve<Secp256k1>")]
39 pub signature: <Secp256k1 as Curve>::Signature,
40}
41
42impl Payload for SignedTip191Payload {
43 #[inline]
44 fn hash(&self) -> CryptoHash {
45 self.payload.hash()
46 }
47}
48
49impl SignedPayload for SignedTip191Payload {
50 type PublicKey = <Secp256k1 as Curve>::PublicKey;
51
52 #[inline]
53 fn verify(&self) -> Option<Self::PublicKey> {
54 Secp256k1::verify(&self.signature, &self.payload.hash(), &())
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
61 use hex_literal::hex;
62
63 const fn fix_v_in_signature(mut sig: [u8; 65]) -> [u8; 65] {
64 if *sig.last().unwrap() >= 27 {
65 *sig.last_mut().unwrap() -= 27;
68 }
69 sig
70 }
71
72 const REFERENCE_MESSAGE: &str = "Hello, TRON!";
83 const INVALID_REFERENCE_MESSAGE: &str = "this is not TRON reference input message";
84 const REFERENCE_SIGNATURE: [u8; 65] = hex!(
85 "eea1651a60600ec4d9c45e8ae81da1a78377f789f0ac2019de66ad943459913015ef9256809ee0e6bb76e303a0b4802e475c1d26ade5d585292b80c9fe9cb10c1c"
86 );
87 const INVALID_REFERENCE_SIGNATURE: [u8; 65] = hex!(
88 "0000000011111111000000001110111110000000011111111e66ad943459913015ef9256809ee0e6bb76e303a0b4802e475c1d26ade5d585292b80c9fe9cb10c1c"
89 );
90 const REFERENCE_PUBKEY: [u8; 64] = hex!(
91 "85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"
92 );
93
94 #[test]
95 fn test_reference_signature_verification_works() {
96 assert_eq!(
97 SignedTip191Payload {
98 payload: Tip191Payload(REFERENCE_MESSAGE.to_string()),
99 signature: fix_v_in_signature(REFERENCE_SIGNATURE),
100 }
101 .verify(),
102 Some(REFERENCE_PUBKEY)
103 );
104 }
105
106 #[test]
107 fn test_invalid_reference_message_verification_fails() {
108 assert_ne!(
109 SignedTip191Payload {
110 payload: Tip191Payload(INVALID_REFERENCE_MESSAGE.to_string()),
111 signature: fix_v_in_signature(REFERENCE_SIGNATURE),
112 }
113 .verify(),
114 Some(REFERENCE_PUBKEY)
115 );
116 }
117
118 #[test]
119 fn test_invalid_reference_signature_verification_fails() {
120 assert_ne!(
121 SignedTip191Payload {
122 payload: Tip191Payload(REFERENCE_MESSAGE.to_string()),
123 signature: fix_v_in_signature(INVALID_REFERENCE_SIGNATURE),
124 }
125 .verify(),
126 Some(REFERENCE_PUBKEY)
127 );
128 }
129}