1use defuse_crypto::{Curve, Secp256k1};
2use impl_tools::autoimpl;
3
4#[cfg_attr(
6 feature = "serde",
7 derive(::serde::Serialize, ::serde::Deserialize),
8 cfg_attr(feature = "abi", derive(::schemars::JsonSchema))
9)]
10#[derive(Debug, Clone)]
11pub struct Erc191Payload(pub String);
12
13impl Erc191Payload {
14 #[inline]
15 pub fn prehash(&self) -> Vec<u8> {
16 let data = self.0.as_bytes();
17 [
18 format!("\x19Ethereum Signed Message:\n{}", data.len()).as_bytes(),
19 data,
20 ]
21 .concat()
22 }
23}
24
25#[cfg(any(test, feature = "sha3", feature = "near-contract"))]
26impl defuse_crypto::Payload for Erc191Payload {
27 #[inline]
28 fn hash(&self) -> defuse_crypto::CryptoHash {
29 use defuse_digest::{Digest, Keccak256};
30 Keccak256::digest(self.prehash().as_slice()).into()
31 }
32}
33
34#[cfg_attr(
35 feature = "serde",
36 ::cfg_eval::cfg_eval,
37 ::serde_with::serde_as,
38 derive(::serde::Serialize, ::serde::Deserialize),
39 cfg_attr(feature = "abi", derive(::schemars::JsonSchema))
40)]
41#[autoimpl(Deref using self.payload)]
42#[derive(Debug, Clone)]
43pub struct SignedErc191Payload {
44 pub payload: Erc191Payload,
45
46 #[cfg_attr(
49 feature = "serde",
50 serde_as(as = "defuse_crypto::serde::AsCurve<Secp256k1>")
51 )]
52 pub signature: <Secp256k1 as Curve>::Signature,
53}
54
55#[cfg(any(test, feature = "sha3", feature = "near-contract"))]
56impl defuse_crypto::Payload for SignedErc191Payload {
57 #[inline]
58 fn hash(&self) -> defuse_crypto::CryptoHash {
59 self.payload.hash()
60 }
61}
62
63#[cfg(any(test, feature = "near-contract"))]
64const _: () = {
65 use defuse_crypto::{Payload, SignedPayload, VerifiableCurve};
66 impl SignedPayload for SignedErc191Payload {
67 type PublicKey = <Secp256k1 as Curve>::PublicKey;
68
69 #[inline]
70 fn verify(&self) -> Option<Self::PublicKey> {
71 Secp256k1::verify(&self.signature, &self.payload.hash(), &())
72 }
73 }
74};
75
76#[cfg(test)]
77mod tests {
78 use super::*;
79 use defuse_crypto::SignedPayload;
80 use hex_literal::hex;
81
82 const fn fix_v_in_signature(mut sig: [u8; 65]) -> [u8; 65] {
83 if *sig.last().unwrap() >= 27 {
84 *sig.last_mut().unwrap() -= 27;
87 }
88 sig
89 }
90
91 const REFERENCE_SIGNATURE: [u8; 65] = hex!(
93 "7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e471c"
94 );
95 const INVALID_REFERENCE_SIGNATURE: [u8; 65] = hex!(
96 "7900a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e471c"
97 );
98 const REFERENCE_MESSAGE: &str = "Hello world!";
99 const INVALID_REFERENCE_MESSAGE: &str = "Hello, NEAR!";
100
101 const REFERENCE_PUBKEY: [u8; 64] = hex!(
111 "85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"
112 );
113
114 #[test]
115 fn test_reference_signature_verification_works() {
116 assert_eq!(
117 SignedErc191Payload {
118 payload: Erc191Payload(REFERENCE_MESSAGE.to_string()),
119 signature: fix_v_in_signature(REFERENCE_SIGNATURE),
120 }
121 .verify(),
122 Some(REFERENCE_PUBKEY)
123 );
124 }
125
126 #[test]
127 fn test_invalid_reference_message_verification_fails() {
128 assert_ne!(
129 SignedErc191Payload {
130 payload: Erc191Payload(INVALID_REFERENCE_MESSAGE.to_string()),
131 signature: fix_v_in_signature(REFERENCE_SIGNATURE),
132 }
133 .verify(),
134 Some(REFERENCE_PUBKEY)
135 );
136 }
137
138 #[test]
139 fn test_invalid_reference_signature_verification_fails() {
140 assert_ne!(
141 SignedErc191Payload {
142 payload: Erc191Payload(REFERENCE_MESSAGE.to_string()),
143 signature: fix_v_in_signature(INVALID_REFERENCE_SIGNATURE),
144 }
145 .verify(),
146 Some(REFERENCE_PUBKEY)
147 );
148 }
149}