defuse_erc191/
lib.rs

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/// See [ERC-191](https://github.com/ethereum/ercs/blob/master/ERCS/erc-191.md)
7#[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    /// There is no public key member because the public key can be recovered
37    /// via `ecrecover()` knowing the data and the signature
38    #[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            // Ethereum only uses uncompressed keys, with corresponding value v=27/28
66            // https://bitcoin.stackexchange.com/a/38909/58790
67            *sig.last_mut().unwrap() -= 27;
68        }
69        sig
70    }
71
72    // Signature constructed in Metamask, using private key: a4b319a82adfc43584e4537fec97a80516e16673db382cd91eba97abbab8ca56
73    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    // Public key can be derived using `ethers_signers` crate:
83    // let wallet = LocalWallet::from_str(
84    //     "a4b319a82adfc43584e4537fec97a80516e16673db382cd91eba97abbab8ca56",
85    // )?;
86    // let signing_key = wallet.signer();
87    // let verifying_key = signing_key.verifying_key();
88    // let public_key = verifying_key.to_encoded_point(false);
89    // // Notice that we skip the first byte, 0x04
90    // println!("Public key: 0x{}", hex::encode(public_key.as_bytes()[1..]));
91    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}