wagyu_ethereum/
public_key.rs

1use crate::address::EthereumAddress;
2use crate::format::EthereumFormat;
3use crate::private_key::EthereumPrivateKey;
4use wagyu_model::{Address, AddressError, PublicKey, PublicKeyError};
5
6use secp256k1;
7use std::{fmt, fmt::Display, str::FromStr};
8
9/// Represents an Ethereum public key
10#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
11pub struct EthereumPublicKey(secp256k1::PublicKey);
12
13impl PublicKey for EthereumPublicKey {
14    type Address = EthereumAddress;
15    type Format = EthereumFormat;
16    type PrivateKey = EthereumPrivateKey;
17
18    /// Returns the address corresponding to the given public key.
19    fn from_private_key(private_key: &Self::PrivateKey) -> Self {
20        Self(secp256k1::PublicKey::from_secret_key(
21            &secp256k1::Secp256k1::new(),
22            &private_key.to_secp256k1_secret_key(),
23        ))
24    }
25
26    /// Returns the address of the corresponding private key.
27    fn to_address(&self, _format: &Self::Format) -> Result<Self::Address, AddressError> {
28        EthereumAddress::from_public_key(self, _format)
29    }
30}
31
32impl EthereumPublicKey {
33    /// Returns a public key given a secp256k1 public key.
34    pub fn from_secp256k1_public_key(public_key: secp256k1::PublicKey) -> Self {
35        Self(public_key)
36    }
37
38    /// Returns the secp256k1 public key of the public key
39    pub fn to_secp256k1_public_key(&self) -> secp256k1::PublicKey {
40        self.0.clone()
41    }
42}
43
44impl FromStr for EthereumPublicKey {
45    type Err = PublicKeyError;
46
47    fn from_str(public_key: &str) -> Result<Self, Self::Err> {
48        Ok(Self(secp256k1::PublicKey::from_str(
49            format!("04{}", public_key).as_str(),
50        )?))
51    }
52}
53
54impl Display for EthereumPublicKey {
55    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
56        for s in &self.0.serialize_uncompressed()[1..] {
57            write!(f, "{:02x}", s)?;
58        }
59        Ok(())
60    }
61}
62
63#[cfg(test)]
64mod tests {
65    use super::*;
66
67    fn test_from_private_key(expected_public_key: &EthereumPublicKey, private_key: &EthereumPrivateKey) {
68        let public_key = EthereumPublicKey::from_private_key(private_key);
69        assert_eq!(*expected_public_key, public_key);
70    }
71
72    fn test_to_address(expected_address: &EthereumAddress, public_key: &EthereumPublicKey) {
73        let address = public_key.to_address(&EthereumFormat::Standard).unwrap();
74        assert_eq!(*expected_address, address);
75    }
76
77    fn test_from_str(expected_public_key: &str, expected_address: &str) {
78        let public_key = EthereumPublicKey::from_str(expected_public_key).unwrap();
79        let address = public_key.to_address(&EthereumFormat::Standard).unwrap();
80        assert_eq!(expected_public_key, public_key.to_string());
81        assert_eq!(expected_address, address.to_string());
82    }
83
84    fn test_to_str(expected_public_key: &str, public_key: &EthereumPublicKey) {
85        assert_eq!(expected_public_key, public_key.to_string());
86    }
87
88    mod checksum_address {
89        use super::*;
90
91        const KEYPAIRS: [(&str, &str, &str); 5] = [
92            (
93                "2f46188bd601ece2a4446fa31de9419ee9baabf5305d65a5a7aea8badee27a5a",
94                "06d68e391c6961fceb5d8c5ad8ee5c6346db24df9dae61c9c0b0142409760451d982c0f35931f33e57adfc4f11bdf1946be2d75d6ecc925e8d22f319c71a721c",
95                "0x9Ed0C5817aE96Cb886BF74EB02B238De682e9B07"
96            ),
97            (
98                "d96c4c30bbabde58653e4fb4f4d97d064c70e300a37ab8780a8ecc15220423fb",
99                "bfe0746c85802c3ca1c2d5e4f4d23fb8321b8b1009af67855cc9a4aed8285567d7045bb700e27d5e33572ae5d84a8d1e11bb134f6f14f37ffcb2fa73f7c6b0ac",
100                "0xBc90633A78dA594ace8e25AAA3517F924C76099d"
101            ),
102            (
103                "c677a1215eebd35d20337d8896ee6579c78f41f93946b17c8d4ccb772c25cde4",
104                "ff3e50efb509efd0d18ff9074bc8b253419d2437e0c1e81661c1ba419f877162eed685d80bdd3b33adde4ff2a0946dd97460f126992064059a129e2a7172d566",
105                "0xA99E404A60ab8561F7c844529F735A88D7A61C5A"
106            ),
107            (
108                "b681e5bd4ddffefe1a691fe7c6375775c11992b9a25e4f9e3f235eb054d49343",
109                "d9ed72afa68a9732df005df2dbbfb2abcad050579bd8dfeb32389d0f1e492d130ca33f9e71345d558da5859026fee86c03be685f95a4c8ddc55e048c5ff8b398",
110                "0x28826C9f713c96ee63e59Ed9220c77b021FAfC3e"
111            ),
112            (
113                "da5d359af6827e76e0a1b71c75c375f0d33f63bae4fd551d81ee10faa34e33e9",
114                "0b752d5e89126b62a99edfe40a4cbd9122cfb04257a28d225858d38bc92a0e1517e797e9029e810b329afa32a1d46268e84eb10c700314b0059f506130d1e9e6",
115                "0x9eC59170674DbEfeF40efE2ED03175b39fCA921a"
116            )
117        ];
118
119        #[test]
120        fn from_private_key() {
121            KEYPAIRS.iter().for_each(|(private_key, public_key, _)| {
122                let public_key = EthereumPublicKey::from_str(public_key).unwrap();
123                let private_key = EthereumPrivateKey::from_str(&private_key).unwrap();
124                test_from_private_key(&public_key, &private_key);
125            });
126        }
127
128        #[test]
129        fn to_address() {
130            KEYPAIRS.iter().for_each(|(_, public_key, address)| {
131                let address = EthereumAddress::from_str(address).unwrap();
132                let public_key = EthereumPublicKey::from_str(&public_key).unwrap();
133                test_to_address(&address, &public_key);
134            });
135        }
136
137        #[test]
138        fn from_str() {
139            KEYPAIRS.iter().for_each(|(_, expected_public_key, expected_address)| {
140                test_from_str(expected_public_key, expected_address);
141            });
142        }
143
144        #[test]
145        fn to_str() {
146            KEYPAIRS.iter().for_each(|(_, expected_public_key, _)| {
147                let public_key = EthereumPublicKey::from_str(expected_public_key).unwrap();
148                test_to_str(expected_public_key, &public_key);
149            });
150        }
151    }
152
153    #[test]
154    fn test_checksum_address_invalid() {
155        // Invalid public key length
156
157        let public_key = "0";
158        assert!(EthereumPublicKey::from_str(public_key).is_err());
159
160        let public_key = "06d68e391c6961fceb5d8c5ad8ee5c6346db24df9dae61c9c0b014";
161        assert!(EthereumPublicKey::from_str(public_key).is_err());
162
163        let public_key = "06d68e391c6961fceb5d8c5ad8ee5c6346db24df9dae61c9c0b0142409760451d982c0f35931f33e57adfc4f11bdf1946be2d75d6ecc925e8d22f319c71a721";
164        assert!(EthereumPublicKey::from_str(public_key).is_err());
165
166        let public_key = "06d68e391c6961fceb5d8c5ad8ee5c6346db24df9dae61c9c0b0142409760451d982c0f35931f33e57adfc4f11bdf1946be2d75d6ecc925e8d22f319c71a721c06d68e391c6961fceb5d8c5ad8ee5c6346db24df9dae61c9c0b0142409760451d982c0f3593";
167        assert!(EthereumPublicKey::from_str(public_key).is_err());
168
169        let public_key = "06d68e391c6961fceb5d8c5ad8ee5c6346db24df9dae61c9c0b0142409760451d982c0f35931f33e57adfc4f11bdf1946be2d75d6ecc925e8d22f319c71a721c06d68e391c6961fceb5d8c5ad8ee5c6346db24df9dae61c9c0b0142409760451d982c0f35931f33e57adfc4f11bdf1946be2d75d6ecc925e8d22f319c71a721c";
170        assert!(EthereumPublicKey::from_str(public_key).is_err());
171    }
172}