openzeppelin_relayer/utils/
address_derivation.rs1use super::der::extract_public_key_from_der;
8
9#[derive(Debug, thiserror::Error)]
10pub enum AddressDerivationError {
11 #[error("Parse Error: {0}")]
12 ParseError(String),
13}
14
15pub fn derive_ethereum_address_from_der(der: &[u8]) -> Result<[u8; 20], AddressDerivationError> {
17 let pub_key = extract_public_key_from_der(der)
18 .map_err(|e| AddressDerivationError::ParseError(e.to_string()))?;
19
20 let hash = alloy::primitives::keccak256(pub_key);
21
22 let address_bytes = &hash[hash.len() - 20..];
24
25 let mut array = [0u8; 20];
26 array.copy_from_slice(address_bytes);
27
28 Ok(array)
29}
30
31pub fn derive_ethereum_address_from_pem(pem_str: &str) -> Result<[u8; 20], AddressDerivationError> {
33 let pkey =
34 pem::parse(pem_str).map_err(|e| AddressDerivationError::ParseError(e.to_string()))?;
35 let der = pkey.contents();
36 derive_ethereum_address_from_der(der)
37}
38
39pub fn derive_solana_address_from_pem(pem_str: &str) -> Result<String, AddressDerivationError> {
41 let pkey =
42 pem::parse(pem_str).map_err(|e| AddressDerivationError::ParseError(e.to_string()))?;
43 let content = pkey.contents();
44
45 let mut array = [0u8; 32];
46
47 match content.len() {
48 32 => array.copy_from_slice(content),
49 44 => array.copy_from_slice(&content[12..]),
50 _ => {
51 return Err(AddressDerivationError::ParseError(format!(
52 "Unexpected ed25519 public key length: got {} bytes (expected 32 or 44).",
53 content.len()
54 )));
55 }
56 }
57
58 let solana_address = bs58::encode(array).into_string();
59 Ok(solana_address)
60}
61
62pub fn derive_stellar_address_from_pem(pem_str: &str) -> Result<String, AddressDerivationError> {
65 let pkey =
66 pem::parse(pem_str).map_err(|e| AddressDerivationError::ParseError(e.to_string()))?;
67 let content = pkey.contents();
68
69 let mut array = [0u8; 32];
70
71 match content.len() {
72 32 => array.copy_from_slice(content),
73 44 => array.copy_from_slice(&content[12..]),
74 _ => {
75 return Err(AddressDerivationError::ParseError(format!(
76 "Unexpected ed25519 public key length: got {} bytes (expected 32 or 44).",
77 content.len()
78 )));
79 }
80 }
81
82 use stellar_strkey::ed25519::PublicKey;
84 let public_key = PublicKey(array);
85 Ok(public_key.to_string())
86}
87
88#[cfg(test)]
89mod tests {
90 use super::*;
91
92 const VALID_SECP256K1_PEM: &str = "-----BEGIN PUBLIC KEY-----\nMFYwEAYHKoZIzj0CAQYFK4EEAAoDQgAEjJaJh5wfZwvj8b3bQ4GYikqDTLXWUjMh\nkFs9lGj2N9B17zo37p4PSy99rDio0QHLadpso0rtTJDSISRW9MdOqA==\n-----END PUBLIC KEY-----\n"; const VALID_ED25519_PEM: &str = "-----BEGIN PUBLIC KEY-----\nMCowBQYDK2VwAyEAnUV+ReQWxMZ3Z2pC/5aOPPjcc8jzOo0ZgSl7+j4AMLo=\n-----END PUBLIC KEY-----\n";
95
96 #[test]
97 fn test_derive_ethereum_address_from_pem_with_invalid_data() {
98 let invalid_pem = "not-a-valid-pem";
99 let result = derive_ethereum_address_from_pem(invalid_pem);
100 assert!(result.is_err());
101
102 assert!(matches!(result, Err(AddressDerivationError::ParseError(_))));
104 }
105
106 #[test]
107 fn test_derive_ethereum_address_from_pem_with_valid_secp256k1() {
108 let result = derive_ethereum_address_from_pem(VALID_SECP256K1_PEM);
109 assert!(result.is_ok());
110
111 let address = result.unwrap();
112 assert_eq!(address.len(), 20); assert_eq!(
115 format!("0x{}", hex::encode(address)),
116 "0xeeb8861f51b3f3f2204d64bbf7a7eb25e1b4d6cd"
117 );
118 }
119
120 #[test]
121 fn test_derive_ethereum_address_from_der_with_invalid_data() {
122 let invalid_der = &[1, 2, 3];
123 let result = derive_ethereum_address_from_der(invalid_der);
124 assert!(result.is_err());
125
126 assert!(matches!(result, Err(AddressDerivationError::ParseError(_))));
128 }
129
130 #[test]
131 fn test_derive_ethereum_address_from_der_with_valid_secp256k1() {
132 let pem = pem::parse(VALID_SECP256K1_PEM).unwrap();
133 let der = pem.contents();
134 let result = derive_ethereum_address_from_der(der);
135
136 assert!(result.is_ok());
137
138 let address = result.unwrap();
139 assert_eq!(address.len(), 20); assert_eq!(
142 format!("0x{}", hex::encode(address)),
143 "0xeeb8861f51b3f3f2204d64bbf7a7eb25e1b4d6cd"
144 );
145 }
146
147 #[test]
148 fn test_derive_solana_address_from_pem_with_invalid_data() {
149 let invalid_pem = "not-a-valid-pem";
150 let result = derive_solana_address_from_pem(invalid_pem);
151 assert!(result.is_err());
152
153 assert!(matches!(result, Err(AddressDerivationError::ParseError(_))));
155 }
156
157 #[test]
158 fn test_derive_solana_address_from_pem_with_valid_ed25519() {
159 let result = derive_solana_address_from_pem(VALID_ED25519_PEM);
160 assert!(result.is_ok());
161
162 let address = result.unwrap();
163 assert!(!address.is_empty());
165 assert!(address.len() >= 32 && address.len() <= 44);
166
167 assert_eq!(address, "BavUBpkD77FABnevMkBVqV8BDHv7gX8sSoYYJY9WU9L5");
168 }
169
170 #[test]
171 fn test_derive_solana_address_from_pem_with_invalid_key_length() {
172 let invalid_ed25519_der = vec![0u8; 10]; let invalid_pem = pem::Pem::new("PUBLIC KEY", invalid_ed25519_der);
175 let invalid_pem_str = pem::encode(&invalid_pem);
176
177 let result = derive_solana_address_from_pem(&invalid_pem_str);
178 assert!(result.is_err());
179
180 assert!(matches!(result, Err(AddressDerivationError::ParseError(_))));
181 }
182
183 #[test]
184 fn test_derive_stellar_address_from_pem_with_valid_ed25519() {
185 let result = derive_stellar_address_from_pem(VALID_ED25519_PEM);
186 assert!(result.is_ok());
187
188 let address = result.unwrap();
189 assert!(address.starts_with('G'));
191 assert_eq!(address.len(), 56);
193 }
194
195 #[test]
196 fn test_derive_stellar_address_from_pem_with_invalid_data() {
197 let invalid_pem = "not-a-valid-pem";
198 let result = derive_stellar_address_from_pem(invalid_pem);
199 assert!(result.is_err());
200
201 assert!(matches!(result, Err(AddressDerivationError::ParseError(_))));
203 }
204
205 #[test]
206 fn test_derive_stellar_address_from_pem_with_invalid_key_length() {
207 let invalid_ed25519_der = vec![0u8; 10]; let invalid_pem = pem::Pem::new("PUBLIC KEY", invalid_ed25519_der);
210 let invalid_pem_str = pem::encode(&invalid_pem);
211
212 let result = derive_stellar_address_from_pem(&invalid_pem_str);
213 assert!(result.is_err());
214
215 assert!(matches!(result, Err(AddressDerivationError::ParseError(_))));
216 }
217}