openauth_plugins/siwe/
address.rs1use std::error::Error;
2use std::fmt;
3
4use sha3::{Digest, Keccak256};
5
6#[derive(Debug, Clone, PartialEq, Eq)]
7pub enum SiweAddressError {
8 InvalidFormat,
9}
10
11impl fmt::Display for SiweAddressError {
12 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
13 match self {
14 Self::InvalidFormat => formatter.write_str("invalid Ethereum address format"),
15 }
16 }
17}
18
19impl Error for SiweAddressError {}
20
21pub fn checksum_address(address: &str) -> Result<String, SiweAddressError> {
22 if !is_valid_address(address) {
23 return Err(SiweAddressError::InvalidFormat);
24 }
25 let lower = address[2..].to_ascii_lowercase();
26 let hash = Keccak256::digest(lower.as_bytes());
27 let mut result = String::with_capacity(42);
28 result.push_str("0x");
29
30 for (index, byte) in lower.bytes().enumerate() {
31 let hash_byte = hash[index / 2];
32 let nibble = if index % 2 == 0 {
33 hash_byte >> 4
34 } else {
35 hash_byte & 0x0f
36 };
37 if byte.is_ascii_alphabetic() && nibble >= 8 {
38 result.push((byte as char).to_ascii_uppercase());
39 } else {
40 result.push(byte as char);
41 }
42 }
43 Ok(result)
44}
45
46fn is_valid_address(address: &str) -> bool {
47 address.len() == 42
48 && address
49 .get(..2)
50 .is_some_and(|prefix| prefix.eq_ignore_ascii_case("0x"))
51 && address[2..].bytes().all(|byte| byte.is_ascii_hexdigit())
52}