eth-valkyoth-verify 0.21.0

no_std Ethereum verification boundaries.
Documentation
use eth_valkyoth_primitives::{Address, B256, ChainId};
use std::vec::Vec;

use super::super::typed_helpers::encode_numeric_or_fixed_bytes;
use super::dom::Json;
use super::{Eip712JsonError, Eip712JsonLimits};
use crate::{Eip712EncodeError, Eip712ValueKind};

const WORD_BYTES: usize = 32;

pub(super) fn encode_json_numeric_or_bytes(
    type_name: &str,
    value: &Json,
    out: &mut [u8; WORD_BYTES],
    _limits: Eip712JsonLimits,
) -> Result<(), Eip712JsonError> {
    if type_name.starts_with("uint") {
        encode_numeric_or_fixed_bytes(
            type_name,
            Eip712ValueKind::Uint256(parse_u256(value)?),
            out,
        )?;
    } else if type_name.starts_with("int") {
        encode_numeric_or_fixed_bytes(type_name, Eip712ValueKind::Int256(parse_i256(value)?), out)?;
    } else if type_name.starts_with("bytes") {
        let bytes = parse_hex(value.as_str()?, WORD_BYTES)?;
        encode_numeric_or_fixed_bytes(type_name, Eip712ValueKind::FixedBytes(&bytes), out)?;
    } else {
        return Err(Eip712JsonError::Encode(Eip712EncodeError::InvalidType));
    }
    Ok(())
}

pub(super) fn parse_chain_id(value: &Json) -> Result<ChainId, Eip712JsonError> {
    let chain_id = parse_u64(value)?;
    if chain_id == 0 {
        return Err(Eip712JsonError::Integer);
    }
    Ok(ChainId::new(chain_id))
}

pub(super) fn parse_address(input: &str) -> Result<Address, Eip712JsonError> {
    let bytes = parse_hex(input, 20)?;
    let bytes = <[u8; 20]>::try_from(bytes.as_slice()).map_err(|_| Eip712JsonError::Hex)?;
    Ok(Address::from_bytes(bytes))
}

pub(super) fn parse_b256(input: &str) -> Result<B256, Eip712JsonError> {
    let bytes = parse_hex(input, WORD_BYTES)?;
    let bytes = <[u8; WORD_BYTES]>::try_from(bytes.as_slice()).map_err(|_| Eip712JsonError::Hex)?;
    Ok(B256::from_bytes(bytes))
}

fn parse_u64(value: &Json) -> Result<u64, Eip712JsonError> {
    match value {
        Json::Number(number) => number.as_u64().ok_or(Eip712JsonError::Integer),
        Json::String(text) => parse_decimal_u64(text),
        _ => Err(Eip712JsonError::Shape),
    }
}

fn parse_u256(value: &Json) -> Result<[u8; WORD_BYTES], Eip712JsonError> {
    match value {
        Json::Number(number) => Ok(u64_to_word(
            number.as_u64().ok_or(Eip712JsonError::Integer)?,
        )),
        Json::String(text) if text.starts_with("0x") => parse_hex_word(text),
        Json::String(text) => parse_decimal_u256(text),
        _ => Err(Eip712JsonError::Shape),
    }
}

fn parse_i256(value: &Json) -> Result<[u8; WORD_BYTES], Eip712JsonError> {
    match value {
        Json::Number(number) => {
            if let Some(signed) = number.as_i64() {
                Ok(i64_to_word(signed))
            } else {
                number
                    .as_u64()
                    .map(u64_to_word)
                    .ok_or(Eip712JsonError::Integer)
            }
        }
        Json::String(text) if text.starts_with("0x") => parse_hex_word(text),
        Json::String(text) => parse_decimal_i256(text),
        _ => Err(Eip712JsonError::Shape),
    }
}

fn parse_decimal_u64(input: &str) -> Result<u64, Eip712JsonError> {
    reject_empty_decimal(input)?;
    let mut value = 0_u64;
    for byte in input.bytes() {
        let digit = decimal_digit(byte)?;
        value = value
            .checked_mul(10)
            .and_then(|current| current.checked_add(u64::from(digit)))
            .ok_or(Eip712JsonError::Integer)?;
    }
    Ok(value)
}

fn parse_decimal_i256(input: &str) -> Result<[u8; WORD_BYTES], Eip712JsonError> {
    let negative = input.starts_with('-');
    let digits = input.strip_prefix('-').unwrap_or(input);
    let magnitude = parse_decimal_u256(digits)?;
    if !negative {
        if magnitude.first().copied().ok_or(Eip712JsonError::Integer)? & 0x80 == 0 {
            return Ok(magnitude);
        }
        return Err(Eip712JsonError::Integer);
    }
    if magnitude > i256_min_magnitude() {
        return Err(Eip712JsonError::Integer);
    }
    negate_twos_complement(magnitude)
}

fn negate_twos_complement(word: [u8; WORD_BYTES]) -> Result<[u8; WORD_BYTES], Eip712JsonError> {
    let mut out = [0_u8; WORD_BYTES];
    let mut carry = 1_u16;
    for (target, byte) in out.iter_mut().rev().zip(word.iter().rev()) {
        let sum = u16::from(!byte)
            .checked_add(carry)
            .ok_or(Eip712JsonError::Integer)?;
        *target = u8::try_from(sum & 0xff).map_err(|_| Eip712JsonError::Integer)?;
        carry = sum >> 8;
    }
    Ok(out)
}

fn i256_min_magnitude() -> [u8; WORD_BYTES] {
    let mut word = [0_u8; WORD_BYTES];
    if let Some(first) = word.first_mut() {
        *first = 0x80;
    }
    word
}

fn reject_empty_decimal(input: &str) -> Result<(), Eip712JsonError> {
    if input.is_empty() {
        Err(Eip712JsonError::Integer)
    } else {
        Ok(())
    }
}

fn parse_decimal_u256(input: &str) -> Result<[u8; WORD_BYTES], Eip712JsonError> {
    reject_empty_decimal(input)?;
    let mut word = [0_u8; WORD_BYTES];
    for byte in input.bytes() {
        let digit = decimal_digit(byte)?;
        mul_add_word(&mut word, digit)?;
    }
    Ok(word)
}

fn mul_add_word(word: &mut [u8; WORD_BYTES], add: u8) -> Result<(), Eip712JsonError> {
    let mut carry = u16::from(add);
    for byte in word.iter_mut().rev() {
        let value = u16::from(*byte)
            .checked_mul(10)
            .and_then(|current| current.checked_add(carry))
            .ok_or(Eip712JsonError::Integer)?;
        *byte = u8::try_from(value & 0xff).map_err(|_| Eip712JsonError::Integer)?;
        carry = value >> 8;
    }
    if carry == 0 {
        Ok(())
    } else {
        Err(Eip712JsonError::Integer)
    }
}

fn decimal_digit(byte: u8) -> Result<u8, Eip712JsonError> {
    if !byte.is_ascii_digit() {
        return Err(Eip712JsonError::Integer);
    }
    byte.checked_sub(b'0').ok_or(Eip712JsonError::Integer)
}

fn parse_hex_word(input: &str) -> Result<[u8; WORD_BYTES], Eip712JsonError> {
    let bytes = parse_hex(input, WORD_BYTES)?;
    let mut word = [0_u8; WORD_BYTES];
    let start = WORD_BYTES
        .checked_sub(bytes.len())
        .ok_or(Eip712JsonError::Hex)?;
    word.get_mut(start..)
        .ok_or(Eip712JsonError::Hex)?
        .copy_from_slice(&bytes);
    Ok(word)
}

fn u64_to_word(value: u64) -> [u8; WORD_BYTES] {
    let mut word = [0_u8; WORD_BYTES];
    if let Some(target) = word.get_mut(24..) {
        target.copy_from_slice(&value.to_be_bytes());
    }
    word
}

fn i64_to_word(value: i64) -> [u8; WORD_BYTES] {
    let fill = if value < 0 { u8::MAX } else { 0 };
    let mut word = [fill; WORD_BYTES];
    if let Some(target) = word.get_mut(24..) {
        target.copy_from_slice(&value.to_be_bytes());
    }
    word
}

pub(super) fn parse_hex(input: &str, max: usize) -> Result<Vec<u8>, Eip712JsonError> {
    let hex = input.strip_prefix("0x").ok_or(Eip712JsonError::Hex)?;
    if !hex.len().is_multiple_of(2) {
        return Err(Eip712JsonError::Hex);
    }
    let byte_len = hex.len().checked_div(2).ok_or(Eip712JsonError::Hex)?;
    check_len(byte_len, max)?;
    let mut bytes = Vec::with_capacity(byte_len);
    for chunk in hex.as_bytes().chunks_exact(2) {
        let high = hex_nibble(chunk.first().copied().ok_or(Eip712JsonError::Hex)?)?;
        let low = hex_nibble(chunk.get(1).copied().ok_or(Eip712JsonError::Hex)?)?;
        bytes.push((high << 4) | low);
    }
    Ok(bytes)
}

fn hex_nibble(byte: u8) -> Result<u8, Eip712JsonError> {
    match byte {
        b'0'..=b'9' => byte.checked_sub(b'0').ok_or(Eip712JsonError::Hex),
        b'a'..=b'f' => byte
            .checked_sub(b'a')
            .and_then(|value| value.checked_add(10))
            .ok_or(Eip712JsonError::Hex),
        b'A'..=b'F' => byte
            .checked_sub(b'A')
            .and_then(|value| value.checked_add(10))
            .ok_or(Eip712JsonError::Hex),
        _ => Err(Eip712JsonError::Hex),
    }
}

pub(super) fn check_len(len: usize, max: usize) -> Result<(), Eip712JsonError> {
    if len <= max {
        Ok(())
    } else {
        Err(Eip712JsonError::Limit)
    }
}

pub(super) fn check_depth(depth: usize, limits: Eip712JsonLimits) -> Result<(), Eip712JsonError> {
    let max = limits.max_depth.min(super::super::MAX_TYPE_DEPTH);
    if depth >= max {
        Err(Eip712JsonError::Encode(Eip712EncodeError::RecursionLimit))
    } else {
        Ok(())
    }
}