wagyu_ethereum/
public_key.rs1use 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#[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 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 fn to_address(&self, _format: &Self::Format) -> Result<Self::Address, AddressError> {
28 EthereumAddress::from_public_key(self, _format)
29 }
30}
31
32impl EthereumPublicKey {
33 pub fn from_secp256k1_public_key(public_key: secp256k1::PublicKey) -> Self {
35 Self(public_key)
36 }
37
38 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 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}