eth-valkyoth-verify 0.18.0

no_std Ethereum verification boundaries.
Documentation
use eth_valkyoth_codec::DecodeLimits;
use eth_valkyoth_hash::Keccak256Digest;
use eth_valkyoth_primitives::{Address, B256, ChainId};
use eth_valkyoth_protocol::{
    decode_access_list_transaction, decode_blob_transaction, decode_dynamic_fee_transaction,
    decode_set_code_transaction,
};
use sha3::Digest;
extern crate std;
use std::vec::Vec;

use super::*;
use crate::{ValidatedSetCodeAuthorization, validate_set_code_authorization_signature};

const KAT_LIMITS: DecodeLimits = DecodeLimits {
    max_input_bytes: 4096,
    max_list_items: 256,
    max_nesting_depth: 16,
    max_total_allocation: 4096,
    max_proof_nodes: 4,
    max_total_items: 512,
};

// Source: ethereum.publicnode.com, eth_getRawTransactionByHash for
// 0xeb8535da9d096a3be85faefd347feddaa82668ca4ef199203450065ef1c28d39.
// The expected sender is the RPC `from` field for the same transaction.
const ACCESS_LIST_RAW: &str = "0x01f9028f018220038512a05f200083124f809490d57c386e6403bf3473a1001d248d43eb85f84d80b90224a2abe54e00000000000000000000000000000000000000000000000050cbcc988bc3b3f700000000000000000000000000000000000000000000000000581b77f66e000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000570febdf89c07f256c75686caca215289bb11cfc000000000000000000000000bbc2ae13b23d715c30720f079fcd9b4a74093505000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000026f2000000000000000000000000000000000000000000000000000000000000271000000000000000000000000007ed78c6c91ce18811ad281d0533819cf848075b000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002710c080a028cf96d0c6cbbe7ec05e1f50e83805c3dd93d47afcb4c566b4d93f9baa628228a0081abbd4a24c5ef0f21b2cbddf2d934506eecf9b1a4cc8808dff6b8ebb1ecec5";
const ACCESS_LIST_SENDER: &str = "0xd006ad343886254359dc87aa5e1753446072fd50";

// Source: ethereum.publicnode.com, eth_getRawTransactionByHash for
// 0x6ee06d2bf01790bd98d068767321bd997df45352f126267143c13256e1623c5c.
// The expected sender is the RPC `from` field for the same transaction.
const DYNAMIC_FEE_RAW: &str = "0x02f90158018301097a8085093bcfd39a8307a120947a250d5630b4cf539739df2c5dacb4c659f2488d8809cfc4c110338000b8e47ff36ab500000000000000000000000000000000000000000000000000002c73c7b52a4000000000000000000000000000000000000000000000000000000000000000800000000000000000000000005daaf5eded5cc8daba8c5a28f7bb87efc2a7916e00000000000000000000000000000000000000000000000000000000673ba5570000000000000000000000000000000000000000000000000000000000000002000000000000000000000000c02aaa39b223fe8d0a0e5c4f27ead9083c756cc2000000000000000000000000446c9033e7516d820cc9a2ce2d0b7328b579406fc001a0d2a93b30d8319e709f5a59e2f270074e4afc773cd574ca0d42f8394dc55b1858a06e1ae8fe2c946620b809bd2950eac1b9a56b5864b11b8a6666b76377b69463ad";
const DYNAMIC_FEE_SENDER: &str = "0x9696a5c3eb572de180aa7f76e39c0f4418a34af1";

// Source: ethereum.publicnode.com, eth_getRawTransactionByHash for
// 0x0ff07f37baa7fa26bb7de3d3fc63002bf0acf3295bdab7f67c108c0d1a3bff15.
// The expected sender is the RPC `from` field for the same transaction.
const BLOB_RAW: &str = "0x03f905e0018250d685012a05f200850826299e00832dc6c09406a9ab27c7e2255df1815e6cc0168d7755feb19a843b9aca00b90544ef16e8450000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000052000000000000000000000000000000000000000000000000000000000000004c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000068d30f47f19c07bccef4ac7fae2dc12fca3e0dc9000000000000000000000000000000633b68f5d8d3a86593ebb815b4663bcbe0302e31382e302d64657600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0000000000000000000000000000000000000000000000000000000000000042000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000537a2f0d3a5879b41bcb5a2afe2ea5c4961796f6000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000002c0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000665ba98500000000000000000000000000000000000000000000000000000000013130ff000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001e0000000000000000000000000000000000000000000000000000000000000000300000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000003b9aca000000000000000000000000000000000000000000000000000000000000000384000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004104204bcc82b3c4237f7c87389a741a1a89d6607897e67b9e2838c52ff30ca6a11c589d058342e9f538e27d2fae89cad91e00f80e531390f3f4ffa49419fd15221b000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041f6f0daa2bf2af011c65f6b01aa6902ca099ff5fbdad194397ad4bd32b7b8d6482a9996b1682020433a79662551da6ae69510daef27b83f00d7f59796667cd6261c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000c0843b9aca00e1a0017ba4bd9c166498865a3d08618e333ee84812941b5c3a356971b4a6ffffa57401a079d49cd5724eb7194af4202b59a25e9782d3bd6cb8f20e7049dd0204c8ff58e8a0662fb12590d7121243aaddf9d39ab8231758abbfe84df53805750fd40db6c1ce";
const BLOB_SENDER: &str = "0x000000633b68f5d8d3a86593ebb815b4663bcbe0";

// Source: ethereum-rpc.publicnode.com, eth_getRawTransactionByHash for
// 0xb8d11b7f125cb01f913e2b7c562ca3a7613913d2bef0bdbd6f50d499bbfdc503.
// The expected sender is the RPC `from` field for the same transaction.
const SET_CODE_RAW: &str = "0x04f90d3201830158b584091813bc84091813bc830de5b2940000000071727de22e5e9d8baf0edac6f37da03280b90c64765e827f00000000000000000000000000000000000000000000000000000000000000400000000000000000000000004337002c5702ce424cb62a56ca038e31e1d4a93d0000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000006a0000000000000000000000000b46290109e5f4bf77caa708ec09e4571355f19a400000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000013dd00000000000000000000000000001226f000000000000000000000000000000000000000000000000000000000000df7f00000000000000000000000003a23b930000000000000000000000000c3112ce00000000000000000000000000000000000000000000000000000000000004c000000000000000000000000000000000000000000000000000000000000005e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000034426da7d8800000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000001400000000000000000000000000000000000000000000000000000000000000220000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000777777777777aec03fd955926dbf81597e66834c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000777777777777aec03fd955926dbf81597e66834c0000000000000000000000000000000000000000000000000000000000015ccc00000000000000000000000000000000000000000000000000000000000000000000000000000000dac17f958d2ee523a2206206994597c13d831ec7000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000005c20cc514e139d78468099169f7a42af3058190000000000000000000000000000000000000000000000000000000088402a0c000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eb777777777777aec03fd955926dbf81597e66834c0000000000000000000000000000ea600000000000000000000000000001e848020000006a4612eb000000000000dac17f958d2ee523a2206206994597c13d831ec700000000000000000000000000004c2c00000000000000000000000000000000000000000000000000000000654e3f940000000000000000000000000000bb804b742ad5ca91969e82aefb80072ae59121a3d72ae4fefe22918bf4e37b9bc6caf77c350db9acab449f0460d96aa1654ddb86062c01e1b6c00b65c20c5b456e62cbcd67eac9f98c68b93f1d0ae6d3466e6cc8c03f1b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000041e4d7e1196a9185f7be3639eb093041a12cd3dc09c29d1303c0d576eb3f0b72850e15f42c50560399c8c6375063de3924bc916e825e2d4899d9edfe0a17890f9d1c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000098ea019eeb1fd22e915b18400be99ffacaf6f2c600000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000120000000000000000000000000000000000000000000000000000000000000014000000000000000000000000000018b100000000000000000000000000001616000000000000000000000000000000000000000000000000000000000000143ac00000000000000000000000003a23b930000000000000000000000000c3112ce00000000000000000000000000000000000000000000000000000000000003c000000000000000000000000000000000000000000000000000000000000004e00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000024426da7d880000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000120000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044095ea7b3000000000000000000000000777777777777aec03fd955926dbf81597e66834c000000000000000000000000000000000000000000000000000000000001bbd600000000000000000000000000000000000000000000000000000000000000000000000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb48000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000044a9059cbb000000000000000000000000fdd317e08f89f29b4ae9f1173ef984ab3ae943630000000000000000000000000000000000000000000000000000000000895440000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000eb777777777777aec03fd955926dbf81597e66834c0000000000000000000000000000ea600000000000000000000000000001e848020000006a4612e7000000000000a0b86991c6218b36c1d19d4a2e9eb0ce3606eb4800000000000000000000000000004aa70000000000000000000000000000000000000000000000000000000065348dbd0000000000000000000000000000bb804b742ad5ca91969e82aefb80072ae59121a3d72adaa040c9559d4fd52a9436e119a612a5954c2ee581f947e1bb8fd490549f59302e85ffb4084fa2bc8df3c1341f109c012acf4299432893d78fcc920ba214ac431c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000413ac2ad521fef16ca005b3f30de148ef027a56dd2b9138ddd4435a87900afea117ce2a1b8d5c6359f40499966f4f2e16c19eb9a2d0d12a06eb988ece85f247b831c00000000000000000000000000000000000000000000000000000000000000c0f85cf85a0194d2e28229f6f2c235e57de2ebc727025a1d0530fb8001a023f3a0590ebdbc310a1d49f6193b2d37f11305e810bf0151ee4bc8f3415a2754a0412afbfd7baeddf4804c6d9b1dfdfe39133a233e60c1dfe3520d2f2ea85b2cb380a028ca0b54ee1e14537caeaf7b8c3eeeded9267541db15c046461c0cd24d2a4ca9a009f3f4532fe234dc633271f645bd8a51ec941828cd1e229084b5340e3adc5f99";
const SET_CODE_SENDER: &str = "0x4337002c5702ce424cb62a56ca038e31e1d4a93d";

struct RealKeccak {
    inner: sha3::Keccak256,
}

impl RealKeccak {
    fn new() -> Self {
        Self {
            inner: sha3::Keccak256::new(),
        }
    }
}

impl Keccak256 for RealKeccak {
    fn update(&mut self, input: &[u8]) {
        Digest::update(&mut self.inner, input);
    }

    fn finalize(self) -> Keccak256Digest {
        B256::from_bytes(self.inner.finalize().into())
    }
}

#[test]
fn validates_external_typed_transaction_kats() {
    validates_access_list_kat();
    validates_dynamic_fee_kat();
    validates_blob_kat();
    validates_set_code_kat();
}

fn validates_access_list_kat() {
    let raw = decode_hex(ACCESS_LIST_RAW);
    let expected = decode_address(ACCESS_LIST_SENDER);
    assert!(raw.is_ok());
    assert!(expected.is_ok());
    if let (Ok(raw), Ok(expected)) = (raw, expected) {
        let tx = decode_access_list_transaction(&raw, KAT_LIMITS);
        assert!(tx.is_ok(), "{tx:?}");
        if let Ok(tx) = tx {
            let mut scratch = [0_u8; 4096];
            assert_eq!(
                validate_access_list_transaction_signature(
                    ChainId::new(1),
                    &tx,
                    Some(expected),
                    &mut scratch,
                    RealKeccak::new(),
                    RealKeccak::new(),
                )
                .map(ValidatedTransactionSignature::sender),
                Ok(expected)
            );
        }
    }
}

fn validates_dynamic_fee_kat() {
    let raw = decode_hex(DYNAMIC_FEE_RAW);
    let expected = decode_address(DYNAMIC_FEE_SENDER);
    assert!(raw.is_ok());
    assert!(expected.is_ok());
    if let (Ok(raw), Ok(expected)) = (raw, expected) {
        let tx = decode_dynamic_fee_transaction(&raw, KAT_LIMITS);
        assert!(tx.is_ok(), "{tx:?}");
        if let Ok(tx) = tx {
            let mut scratch = [0_u8; 4096];
            assert_eq!(
                validate_dynamic_fee_transaction_signature(
                    ChainId::new(1),
                    &tx,
                    Some(expected),
                    &mut scratch,
                    RealKeccak::new(),
                    RealKeccak::new(),
                )
                .map(ValidatedTransactionSignature::sender),
                Ok(expected)
            );
        }
    }
}

fn validates_blob_kat() {
    let raw = decode_hex(BLOB_RAW);
    let expected = decode_address(BLOB_SENDER);
    assert!(raw.is_ok());
    assert!(expected.is_ok());
    if let (Ok(raw), Ok(expected)) = (raw, expected) {
        let tx = decode_blob_transaction(&raw, KAT_LIMITS);
        assert!(tx.is_ok(), "{tx:?}");
        if let Ok(tx) = tx {
            let mut scratch = [0_u8; 4096];
            assert_eq!(
                validate_blob_transaction_signature(
                    ChainId::new(1),
                    &tx,
                    Some(expected),
                    &mut scratch,
                    RealKeccak::new(),
                    RealKeccak::new(),
                )
                .map(ValidatedTransactionSignature::sender),
                Ok(expected)
            );
        }
    }
}

fn validates_set_code_kat() {
    let raw = decode_hex(SET_CODE_RAW);
    let expected = decode_address(SET_CODE_SENDER);
    assert!(raw.is_ok());
    assert!(expected.is_ok());
    if let (Ok(raw), Ok(expected)) = (raw, expected) {
        let tx = decode_set_code_transaction(&raw, KAT_LIMITS);
        assert!(tx.is_ok(), "{tx:?}");
        if let Ok(tx) = tx {
            let mut scratch = [0_u8; 4096];
            assert_eq!(
                validate_set_code_transaction_signature(
                    ChainId::new(1),
                    &tx,
                    Some(expected),
                    &mut scratch,
                    RealKeccak::new(),
                    RealKeccak::new(),
                )
                .map(ValidatedTransactionSignature::sender),
                Ok(expected)
            );

            let mut authorizations = tx.authorization_list.authorizations();
            let first = authorizations.next();
            assert!(matches!(first, Some(Ok(_))), "{first:?}");
            assert!(authorizations.next().is_none());
            if let Some(Ok(authorization)) = first {
                let mut scratch = [0_u8; 128];
                assert!(
                    validate_set_code_authorization_signature(
                        authorization,
                        None,
                        &mut scratch,
                        RealKeccak::new(),
                        RealKeccak::new(),
                    )
                    .map(ValidatedSetCodeAuthorization::authority)
                    .is_ok()
                );
            }
        }
    }
}

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

fn decode_hex(input: &str) -> Result<Vec<u8>, ()> {
    let hex = match input.strip_prefix("0x") {
        Some(value) => value,
        None => input,
    };
    let bytes = hex.as_bytes();
    let mut chunks = bytes.chunks_exact(2);
    if !chunks.remainder().is_empty() {
        return Err(());
    }
    let mut output = Vec::with_capacity(bytes.len() / 2);
    for chunk in &mut chunks {
        let high = chunk.first().copied().ok_or(())?;
        let low = chunk.get(1).copied().ok_or(())?;
        output.push((hex_nibble(high)? << 4) | hex_nibble(low)?);
    }
    Ok(output)
}

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