1use defuse_crypto::{CryptoHash, Curve, Payload, Secp256k1, SignedPayload, serde::AsCurve};
2use impl_tools::autoimpl;
3use near_sdk::{env, near};
4use serde_with::serde_as;
5
6#[near(serializers = [json])]
8#[derive(Debug, Clone)]
9pub struct Erc191Payload(pub String);
10
11impl Erc191Payload {
12 #[inline]
13 pub fn prehash(&self) -> Vec<u8> {
14 let data = self.0.as_bytes();
15 [
16 format!("\x19Ethereum Signed Message:\n{}", data.len()).as_bytes(),
17 data,
18 ]
19 .concat()
20 }
21}
22
23impl Payload for Erc191Payload {
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 SignedErc191Payload {
34 pub payload: Erc191Payload,
35
36 #[serde_as(as = "AsCurve<Secp256k1>")]
39 pub signature: <Secp256k1 as Curve>::Signature,
40}
41
42impl Payload for SignedErc191Payload {
43 #[inline]
44 fn hash(&self) -> CryptoHash {
45 self.payload.hash()
46 }
47}
48
49impl SignedPayload for SignedErc191Payload {
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_SIGNATURE: [u8; 65] = hex!(
74 "7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e471c"
75 );
76 const INVALID_REFERENCE_SIGNATURE: [u8; 65] = hex!(
77 "7900a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e471c"
78 );
79 const REFERENCE_MESSAGE: &str = "Hello world!";
80 const INVALID_REFERENCE_MESSAGE: &str = "Hello, NEAR!";
81
82 const REFERENCE_PUBKEY: [u8; 64] = hex!(
92 "85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"
93 );
94
95 #[test]
96 fn test_reference_signature_verification_works() {
97 assert_eq!(
98 SignedErc191Payload {
99 payload: Erc191Payload(REFERENCE_MESSAGE.to_string()),
100 signature: fix_v_in_signature(REFERENCE_SIGNATURE),
101 }
102 .verify(),
103 Some(REFERENCE_PUBKEY)
104 );
105 }
106
107 #[test]
108 fn test_invalid_reference_message_verification_fails() {
109 assert_ne!(
110 SignedErc191Payload {
111 payload: Erc191Payload(INVALID_REFERENCE_MESSAGE.to_string()),
112 signature: fix_v_in_signature(REFERENCE_SIGNATURE),
113 }
114 .verify(),
115 Some(REFERENCE_PUBKEY)
116 );
117 }
118
119 #[test]
120 fn test_invalid_reference_signature_verification_fails() {
121 assert_ne!(
122 SignedErc191Payload {
123 payload: Erc191Payload(REFERENCE_MESSAGE.to_string()),
124 signature: fix_v_in_signature(INVALID_REFERENCE_SIGNATURE),
125 }
126 .verify(),
127 Some(REFERENCE_PUBKEY)
128 );
129 }
130}