openauth-plugins 0.0.4

Official OpenAuth plugin modules.
Documentation
use std::error::Error;
use std::fmt;

use sha3::{Digest, Keccak256};

#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SiweAddressError {
    InvalidFormat,
}

impl fmt::Display for SiweAddressError {
    fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
        match self {
            Self::InvalidFormat => formatter.write_str("invalid Ethereum address format"),
        }
    }
}

impl Error for SiweAddressError {}

pub fn checksum_address(address: &str) -> Result<String, SiweAddressError> {
    if !is_valid_address(address) {
        return Err(SiweAddressError::InvalidFormat);
    }
    let lower = address[2..].to_ascii_lowercase();
    let hash = Keccak256::digest(lower.as_bytes());
    let mut result = String::with_capacity(42);
    result.push_str("0x");

    for (index, byte) in lower.bytes().enumerate() {
        let hash_byte = hash[index / 2];
        let nibble = if index % 2 == 0 {
            hash_byte >> 4
        } else {
            hash_byte & 0x0f
        };
        if byte.is_ascii_alphabetic() && nibble >= 8 {
            result.push((byte as char).to_ascii_uppercase());
        } else {
            result.push(byte as char);
        }
    }
    Ok(result)
}

fn is_valid_address(address: &str) -> bool {
    address.len() == 42
        && address
            .get(..2)
            .is_some_and(|prefix| prefix.eq_ignore_ascii_case("0x"))
        && address[2..].bytes().all(|byte| byte.is_ascii_hexdigit())
}