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 defuse_crypto::Payload for Erc191Payload {
14 #[inline]
15 fn hash(&self) -> defuse_crypto::CryptoHash {
16 use defuse_digest::{Digest, sha3::Keccak256};
17
18 Keccak256::new_with_prefix(b"\x19Ethereum Signed Message:\n")
19 .chain_update(self.0.len().to_string())
20 .chain_update(self.0.as_bytes())
21 .finalize()
22 .into()
23 }
24}
25
26#[cfg_attr(
27 feature = "serde",
28 ::cfg_eval::cfg_eval,
29 ::serde_with::serde_as,
30 derive(::serde::Serialize, ::serde::Deserialize),
31 cfg_attr(feature = "abi", derive(::schemars::JsonSchema))
32)]
33#[autoimpl(Deref using self.payload)]
34#[derive(Debug, Clone)]
35pub struct SignedErc191Payload {
36 pub payload: Erc191Payload,
37
38 #[cfg_attr(
41 feature = "serde",
42 serde_as(as = "defuse_crypto::serde::AsCurve<Secp256k1>")
43 )]
44 pub signature: <Secp256k1 as Curve>::Signature,
45}
46
47impl defuse_crypto::Payload for SignedErc191Payload {
48 #[inline]
49 fn hash(&self) -> defuse_crypto::CryptoHash {
50 self.payload.hash()
51 }
52}
53
54#[cfg(any(test, feature = "near-contract"))]
55const _: () = {
56 use defuse_crypto::{Payload, SignedPayload, VerifiableCurve};
57 impl SignedPayload for SignedErc191Payload {
58 type PublicKey = <Secp256k1 as Curve>::PublicKey;
59
60 #[inline]
61 fn verify(&self) -> Option<Self::PublicKey> {
62 Secp256k1::verify(&self.signature, &self.payload.hash(), &())
63 }
64 }
65};
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70 use defuse_crypto::SignedPayload;
71 use hex_literal::hex;
72
73 const fn fix_v_in_signature(mut sig: [u8; 65]) -> [u8; 65] {
74 if *sig.last().unwrap() >= 27 {
75 *sig.last_mut().unwrap() -= 27;
78 }
79 sig
80 }
81
82 const REFERENCE_SIGNATURE: [u8; 65] = hex!(
84 "7800a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e471c"
85 );
86 const INVALID_REFERENCE_SIGNATURE: [u8; 65] = hex!(
87 "7900a70d05cde2c49ed546a6ce887ce6027c2c268c0285f6efef0cdfc4366b23643790f67a86468ee8301ed12cfffcb07c6530f90a9327ec057800fabd332e471c"
88 );
89 const REFERENCE_MESSAGE: &str = "Hello world!";
90 const INVALID_REFERENCE_MESSAGE: &str = "Hello, NEAR!";
91
92 const REFERENCE_PUBKEY: [u8; 64] = hex!(
102 "85a66984273f338ce4ef7b85e5430b008307e8591bb7c1b980852cf6423770b801f41e9438155eb53a5e20f748640093bb42ae3aeca035f7b7fd7a1a21f22f68"
103 );
104
105 #[test]
106 fn test_reference_signature_verification_works() {
107 assert_eq!(
108 SignedErc191Payload {
109 payload: Erc191Payload(REFERENCE_MESSAGE.to_string()),
110 signature: fix_v_in_signature(REFERENCE_SIGNATURE),
111 }
112 .verify(),
113 Some(REFERENCE_PUBKEY)
114 );
115 }
116
117 #[test]
118 fn test_invalid_reference_message_verification_fails() {
119 assert_ne!(
120 SignedErc191Payload {
121 payload: Erc191Payload(INVALID_REFERENCE_MESSAGE.to_string()),
122 signature: fix_v_in_signature(REFERENCE_SIGNATURE),
123 }
124 .verify(),
125 Some(REFERENCE_PUBKEY)
126 );
127 }
128
129 #[test]
130 fn test_invalid_reference_signature_verification_fails() {
131 assert_ne!(
132 SignedErc191Payload {
133 payload: Erc191Payload(REFERENCE_MESSAGE.to_string()),
134 signature: fix_v_in_signature(INVALID_REFERENCE_SIGNATURE),
135 }
136 .verify(),
137 Some(REFERENCE_PUBKEY)
138 );
139 }
140}