defuse_erc191/
lib.rs

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