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 Erc191Payload(pub String);
9
10impl Erc191Payload {
11 #[inline]
12 pub fn prehash(&self) -> Vec<u8> {
13 let data = self.0.as_bytes();
14 [
15 format!("\x19Ethereum Signed Message:\n{}", data.len()).as_bytes(),
16 data,
17 ]
18 .concat()
19 }
20}
21
22impl Payload for Erc191Payload {
23 #[inline]
24 fn hash(&self) -> CryptoHash {
25 env::keccak256_array(self.prehash())
26 }
27}
28
29#[near(serializers = [json])]
30#[autoimpl(Deref using self.payload)]
31#[derive(Debug, Clone)]
32pub struct SignedErc191Payload {
33 pub payload: Erc191Payload,
34
35 #[serde_as(as = "AsCurve<Secp256k1>")]
38 pub signature: <Secp256k1 as Curve>::Signature,
39}
40
41impl Payload for SignedErc191Payload {
42 #[inline]
43 fn hash(&self) -> CryptoHash {
44 self.payload.hash()
45 }
46}
47
48impl SignedPayload for SignedErc191Payload {
49 type PublicKey = <Secp256k1 as Curve>::PublicKey;
50
51 #[inline]
52 fn verify(&self) -> Option<Self::PublicKey> {
53 Secp256k1::verify(&self.signature, &self.payload.hash(), &())
54 }
55}
56
57#[cfg(test)]
58mod tests {
59 use super::*;
60 use hex_literal::hex;
61
62 const fn fix_v_in_signature(mut sig: [u8; 65]) -> [u8; 65] {
63 if *sig.last().unwrap() >= 27 {
64 *sig.last_mut().unwrap() -= 27;
67 }
68 sig
69 }
70
71 const REFERENCE_SIGNATURE: [u8; 65] = hex!(
73 "7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e471c"
74 );
75 const INVALID_REFERENCE_SIGNATURE: [u8; 65] = hex!(
76 "7900a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e471c"
77 );
78 const REFERENCE_MESSAGE: &str = "Hello world!";
79 const INVALID_REFERENCE_MESSAGE: &str = "Hello, NEAR!";
80
81 const REFERENCE_PUBKEY: [u8; 64] = hex!(
91 "85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"
92 );
93
94 #[test]
95 fn test_reference_signature_verification_works() {
96 assert_eq!(
97 SignedErc191Payload {
98 payload: Erc191Payload(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 SignedErc191Payload {
110 payload: Erc191Payload(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 SignedErc191Payload {
122 payload: Erc191Payload(REFERENCE_MESSAGE.to_string()),
123 signature: fix_v_in_signature(INVALID_REFERENCE_SIGNATURE),
124 }
125 .verify(),
126 Some(REFERENCE_PUBKEY)
127 );
128 }
129}