Skip to main content

openauth_plugins/siwe/
address.rs

1use 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}