signinwithethereum 0.8.1

Rust implementation of EIP-4361: Sign In With Ethereum
//! EIP-6492 signature detection and verification.
//!
//! EIP-6492 enables signature verification for counterfactual (not yet deployed)
//! smart contract wallets by wrapping signatures with deployment data.

/// EIP-6492 magic suffix (32 bytes) appended to wrapped signatures.
pub const MAGIC_SUFFIX: [u8; 32] = [
    0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64,
    0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92, 0x64, 0x92,
    0x64, 0x92,
];

/// Returns true if the signature ends with the EIP-6492 magic suffix.
pub fn is_eip6492_signature(sig: &[u8]) -> bool {
    sig.len() > 32 && sig[sig.len() - 32..] == MAGIC_SUFFIX
}

/// EIP-6492 universal off-chain signature validator bytecode (hex-encoded, no 0x prefix).
///
/// This is the `ValidateSigOffchain` contract from the EIP-6492 reference implementation.
/// Used via `eth_call` without requiring any pre-deployed contract.
///
/// Constructor args: `(address signer, bytes32 hash, bytes signature)`
/// Returns: `0x01` if valid, `0x00` if invalid.
#[cfg(feature = "alloy")]
const VALIDATOR_BYTECODE_HEX: &str = "608060405234801561001057600080fd5b5060405161069438038061069483398101604081905261002f9161051e565b600061003c848484610048565b9050806000526001601ff35b60007f649264926492649264926492649264926492649264926492649264926492649261007483610\
40c565b036101e7576000606080848060200190518101906100929190610577565b60405192955090935091506000906001600160a01b038516906100b69085906105dd565b6000604051808303816000865af19150503d80600081146100f3576040519150601f19603f3d011682016040523d82523d6000602084013e6100f8565b606091505b50509050876001600160a01b03163b600003610\
16057806101605760405162461bcd60e51b815260206004820152601e60248201527f5369676e617475726556616c696461746f723a206465706c6f796d656e74000060448201526064015b60405180910390fd5b604051630b135d3f60e11b808252906001600160a01b038a1690631626ba7e90610190908b9087906004016105f9565b602060405180830381865afa1580156101ad573d6000803e3d\
6000fd5b505050506040513d601f19601f820116820180604052508101906101d19190610633565b6001600160e01b03191614945050505050610405565b6001600160a01b0384163b1561027a57604051630b135d3f60e11b808252906001600160a01b03861690631626ba7e9061022790879087906004016105f9565b602060405180830381865afa158015610244573d6000803e3d6000fd5b5050\
50506040513d601f19601f820116820180604052508101906102689190610633565b6001600160e01b031916149050610405565b81516041146102df5760405162461bcd60e51b815260206004820152603a602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e6174757265206c656e6774680000000000006064820152608401610157565b6102e76104\
25565b5060208201516040808401518451859392600091859190811061030c5761030c61065d565b016020015160f81c9050601b811480159061032b57508060ff16601c14155b1561038c5760405162461bcd60e51b815260206004820152603b602482015260008051602061067483398151915260448201527f3a20696e76616c6964207369676e617475726520762076616c756500000000006064\
820152608401610157565b60408051600081526020810180835289905260ff83169181019190915260608101849052608081018390526001600160a01b0389169060019060a0016020604051602081039080840390855afa1580156103ea573d6000803e3d6000fd5b505050602060405103516001600160a01b0316149450505050505b9392505050565b600060208251101561041d57600080fd5b\
508051015190565b60405180606001604052806003906020820280368337509192915050565b6001600160a01b038116811461045857600080fd5b50565b634e487b7160e01b600052604160045260246000fd5b60005b8381101561048c578181015183820152602001610474565b50506000910152565b600082601f8301126104a657600080fd5b81516001600160401b038111156104bf576104\
bf61045b565b604051601f8201601f19908116603f011681016001600160401b03811182821017156104ed576104ed61045b565b60405281815283820160200185101561050557600080fd5b610516826020830160208701610471565b949350505050565b60008060006060848603121561053357600080fd5b835161053e81610443565b6020850151604086015191945092506001600160401b038111\
1561056157600080fd5b61056d86828701610495565b9150509250925092565b60008060006060848603121561058c57600080fd5b835161059781610443565b60208501519093506001600160401b038111156105b357600080fd5b6105bf86828701610495565b604086015190935090506001600160401b0381111561056157600080fd5b600082516105ef818460208701610471565b9190910192\
915050565b828152604060208201526000825180604084015261061e816060850160208701610471565b601f01601f1916919091016060019392505050565b60006020828403121561064557600080fd5b81516001600160e01b03198116811461040557600080fd5b634e487b7160e01b600052603260045260246000fdfe5369676e617475726556616c696461746f72237265636f7665725369676e6572";

/// Verify a signature using the EIP-6492 universal off-chain validator.
///
/// This handles all three signature types: EIP-6492 (counterfactual wallets),
/// EIP-1271 (deployed contract wallets), and EOA (ecrecover).
///
/// Caller must validate the RPC chain ID matches the message before calling.
#[cfg(feature = "alloy")]
pub async fn verify_eip6492(
    address: [u8; 20],
    message_hash: [u8; 32],
    signature: &[u8],
    rpc_url: &str,
) -> Result<bool, crate::VerificationError> {
    use alloy::{
        primitives::{Address, Bytes, FixedBytes},
        providers::{Provider, ProviderBuilder},
        rpc::types::TransactionRequest,
        sol_types::SolValue,
    };

    let provider = ProviderBuilder::new().connect_http(
        rpc_url
            .parse()
            .map_err(|e| crate::VerificationError::ContractCall(format!("Invalid RPC URL: {e}")))?,
    );

    use std::sync::LazyLock;
    static VALIDATOR_BYTECODE: LazyLock<Vec<u8>> = LazyLock::new(|| {
        hex::decode(VALIDATOR_BYTECODE_HEX).expect("validator bytecode is valid hex")
    });

    let args = (
        Address::from(address),
        FixedBytes::<32>::from(message_hash),
        Bytes::copy_from_slice(signature),
    )
        .abi_encode_params();

    let mut data = VALIDATOR_BYTECODE.clone();
    data.extend_from_slice(&args);

    let tx = TransactionRequest::default().input(Bytes::from(data).into());

    let result: Bytes = provider
        .call(tx)
        .await
        .map_err(|e| crate::VerificationError::ContractCall(e.to_string()))?;

    // Validator returns 0x01 (valid) or 0x00 (invalid), possibly zero-padded
    Ok(!result.is_empty() && result[result.len() - 1] == 0x01)
}