diff --git a/relay_rpc/build.rs b/relay_rpc/build.rs index 6b2b241..0e0c746 100644 --- a/relay_rpc/build.rs +++ b/relay_rpc/build.rs @@ -9,7 +9,7 @@ fn main() { compile_contracts(); #[cfg(feature = "cacao")] - extract_eip6492_bytecode(); + extract_bytecodes(); } fn compile_contracts() { @@ -36,12 +36,18 @@ fn compile_contracts() { assert!(output.status.success()); } -fn extract_eip6492_bytecode() { - const EIP6492_FILE: &str = "../target/.forge/out/Eip6492.sol/ValidateSigOffchain.json"; - const EIP6492_BYTECODE_FILE: &str = - "../target/.forge/out/Eip6492.sol/ValidateSigOffchain.bytecode"; +const EIP6492_FILE: &str = "../target/.forge/out/Eip6492.sol/ValidateSigOffchain.json"; +const EIP6492_BYTECODE_FILE: &str = "../target/.forge/out/Eip6492.sol/ValidateSigOffchain.bytecode"; +const EIP1271_MOCK_FILE: &str = "../target/.forge/out/Eip1271Mock.sol/Eip1271Mock.json"; +const EIP1271_MOCK_BYTECODE_FILE: &str = + "../target/.forge/out/Eip1271Mock.sol/Eip1271Mock.bytecode"; +fn extract_bytecodes() { + extract_bytecode(EIP6492_FILE, EIP6492_BYTECODE_FILE); + extract_bytecode(EIP1271_MOCK_FILE, EIP1271_MOCK_BYTECODE_FILE); +} - let contents = serde_json::from_slice::(&std::fs::read(EIP6492_FILE).unwrap()).unwrap(); +fn extract_bytecode(input_file: &str, output_file: &str) { + let contents = serde_json::from_slice::(&std::fs::read(input_file).unwrap()).unwrap(); let bytecode = contents .get("bytecode") .unwrap() @@ -52,5 +58,5 @@ fn extract_eip6492_bytecode() { .strip_prefix("0x") .unwrap(); let bytecode = hex::decode(bytecode).unwrap(); - std::fs::write(EIP6492_BYTECODE_FILE, bytecode).unwrap(); + std::fs::write(output_file, bytecode).unwrap(); } diff --git a/relay_rpc/contracts/Create2.sol b/relay_rpc/contracts/Create2.sol new file mode 100644 index 0000000..062722e --- /dev/null +++ b/relay_rpc/contracts/Create2.sol @@ -0,0 +1,49 @@ +// https://github.com/Genesis3800/CREATE2Factory/blob/b202029eadc0299e6e5923dd90db4200c2f7955a/src/Create2.sol + +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +contract Create2 { + + error Create2InsufficientBalance(uint256 received, uint256 minimumNeeded); + + error Create2EmptyBytecode(); + + error Create2FailedDeployment(); + + function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) external payable returns (address addr) { + + if (msg.value < amount) { + revert Create2InsufficientBalance(msg.value, amount); + } + + if (bytecode.length == 0) { + revert Create2EmptyBytecode(); + } + + assembly { + addr := create2(amount, add(bytecode, 0x20), mload(bytecode), salt) + } + + if (addr == address(0)) { + revert Create2FailedDeployment(); + } + } + + function computeAddress(bytes32 salt, bytes32 bytecodeHash) external view returns (address addr) { + + address contractAddress = address(this); + + assembly { + let ptr := mload(0x40) + + mstore(add(ptr, 0x40), bytecodeHash) + mstore(add(ptr, 0x20), salt) + mstore(ptr, contractAddress) + let start := add(ptr, 0x0b) + mstore8(start, 0xff) + addr := keccak256(start, 85) + } + } + +} diff --git a/relay_rpc/contracts/Eip1271Mock.sol b/relay_rpc/contracts/Eip1271Mock.sol index 6a50659..35fa8b9 100644 --- a/relay_rpc/contracts/Eip1271Mock.sol +++ b/relay_rpc/contracts/Eip1271Mock.sol @@ -3,10 +3,10 @@ pragma solidity ^0.8.25; // https://eips.ethereum.org/EIPS/eip-1271#reference-implementation contract Eip1271Mock { - address owner; + address owner_eoa; - constructor() { - owner = msg.sender; + constructor(address _owner_eoa) { + owner_eoa = _owner_eoa; } /** @@ -17,7 +17,7 @@ contract Eip1271Mock { bytes calldata _signature ) external view returns (bytes4) { // Validate signatures - if (recoverSigner(_hash, _signature) == owner) { + if (recoverSigner(_hash, _signature) == owner_eoa) { return 0x1626ba7e; } else { return 0xffffffff; diff --git a/relay_rpc/src/auth/cacao/signature/eip1271.rs b/relay_rpc/src/auth/cacao/signature/eip1271.rs index 9e78a5d..6deddf7 100644 --- a/relay_rpc/src/auth/cacao/signature/eip1271.rs +++ b/relay_rpc/src/auth/cacao/signature/eip1271.rs @@ -73,7 +73,13 @@ mod test { crate::auth::cacao::signature::{ eip191::eip191_bytes, strip_hex_prefix, - test_helpers::{deploy_contract, message_hash, sign_message, spawn_anvil}, + test_helpers::{ + deploy_contract, + message_hash, + sign_message, + spawn_anvil, + EIP1271_MOCK_CONTRACT, + }, }, alloy_primitives::address, k256::ecdsa::SigningKey, @@ -106,7 +112,13 @@ mod test { #[tokio::test] async fn test_eip1271_pass() { let (_anvil, rpc_url, private_key) = spawn_anvil().await; - let contract_address = deploy_contract(&rpc_url, &private_key).await; + let contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; let message = "xxx"; let signature = sign_message(message, &private_key); @@ -119,7 +131,13 @@ mod test { #[tokio::test] async fn test_eip1271_wrong_signature() { let (_anvil, rpc_url, private_key) = spawn_anvil().await; - let contract_address = deploy_contract(&rpc_url, &private_key).await; + let contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; let message = "xxx"; let mut signature = sign_message(message, &private_key); @@ -134,7 +152,13 @@ mod test { #[tokio::test] async fn test_eip1271_fail_wrong_signer() { let (anvil, rpc_url, private_key) = spawn_anvil().await; - let contract_address = deploy_contract(&rpc_url, &private_key).await; + let contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; let message = "xxx"; let signature = sign_message( @@ -151,7 +175,13 @@ mod test { #[tokio::test] async fn test_eip1271_fail_wrong_contract_address() { let (_anvil, rpc_url, private_key) = spawn_anvil().await; - let mut contract_address = deploy_contract(&rpc_url, &private_key).await; + let mut contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; *contract_address.0.first_mut().unwrap() = contract_address.0.first().unwrap().wrapping_add(1); @@ -168,7 +198,13 @@ mod test { #[tokio::test] async fn test_eip1271_wrong_message() { let (_anvil, rpc_url, private_key) = spawn_anvil().await; - let contract_address = deploy_contract(&rpc_url, &private_key).await; + let contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; let message = "xxx"; let signature = sign_message(message, &private_key); diff --git a/relay_rpc/src/auth/cacao/signature/eip6492.rs b/relay_rpc/src/auth/cacao/signature/eip6492.rs index 0782cc6..8e5edc1 100644 --- a/relay_rpc/src/auth/cacao/signature/eip6492.rs +++ b/relay_rpc/src/auth/cacao/signature/eip6492.rs @@ -66,7 +66,11 @@ mod test { message_hash, sign_message, spawn_anvil, + CREATE2_CONTRACT, + EIP1271_MOCK_CONTRACT, }, + alloy_primitives::{b256, Uint}, + alloy_sol_types::{SolCall, SolValue}, k256::ecdsa::SigningKey, }; @@ -134,7 +138,13 @@ mod test { #[tokio::test] async fn test_eip1271_pass() { let (_anvil, rpc_url, private_key) = spawn_anvil().await; - let contract_address = deploy_contract(&rpc_url, &private_key).await; + let contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; let message = "xxx"; let signature = sign_message(message, &private_key); @@ -147,7 +157,13 @@ mod test { #[tokio::test] async fn test_eip1271_wrong_signature() { let (_anvil, rpc_url, private_key) = spawn_anvil().await; - let contract_address = deploy_contract(&rpc_url, &private_key).await; + let contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; let message = "xxx"; let mut signature = sign_message(message, &private_key); @@ -162,7 +178,13 @@ mod test { #[tokio::test] async fn test_eip1271_fail_wrong_signer() { let (anvil, rpc_url, private_key) = spawn_anvil().await; - let contract_address = deploy_contract(&rpc_url, &private_key).await; + let contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; let message = "xxx"; let signature = sign_message( @@ -179,7 +201,13 @@ mod test { #[tokio::test] async fn test_eip1271_fail_wrong_contract_address() { let (_anvil, rpc_url, private_key) = spawn_anvil().await; - let mut contract_address = deploy_contract(&rpc_url, &private_key).await; + let mut contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; *contract_address.0.first_mut().unwrap() = contract_address.0.first().unwrap().wrapping_add(1); @@ -196,7 +224,13 @@ mod test { #[tokio::test] async fn test_eip1271_wrong_message() { let (_anvil, rpc_url, private_key) = spawn_anvil().await; - let contract_address = deploy_contract(&rpc_url, &private_key).await; + let contract_address = deploy_contract( + &rpc_url, + &private_key, + EIP1271_MOCK_CONTRACT, + Some(&Address::from_private_key(&private_key).to_string()), + ) + .await; let message = "xxx"; let signature = sign_message(message, &private_key); @@ -213,4 +247,199 @@ mod test { Err(CacaoError::Verification) )); } + + const EIP1271_MOCK_BYTECODE: &[u8] = + include_bytes!("../../../../../target/.forge/out/Eip1271Mock.sol/Eip1271Mock.bytecode"); + const EIP6492_MAGIC_BYTES: [u16; 16] = [ + 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, + 0x6492, 0x6492, 0x6492, 0x6492, 0x6492, + ]; + sol! { + contract Eip1271Mock { + address owner_eoa; + + constructor(address owner_eoa) { + owner_eoa = owner_eoa; + } + } + } + + sol! { + contract Create2 { + function deploy(uint256 amount, bytes32 salt, bytes memory bytecode) external payable returns (address addr); + } + } + + fn predeploy_signature( + owner_eoa: Address, + create2_factory_address: Address, + signature: Vec, + ) -> (Address, Vec) { + let salt = b256!("7c5ea36004851c764c44143b1dcb59679b11c9a68e5f41497f6cf3d480715331"); + let contract_bytecode = EIP1271_MOCK_BYTECODE; + let contract_constructor = Eip1271Mock::constructorCall { owner_eoa }; + + let bytecode = contract_bytecode + .iter() + .cloned() + .chain(contract_constructor.abi_encode()) + .collect::>(); + let predeploy_address = create2_factory_address.create2_from_code(salt, bytecode.clone()); + let signature = ( + create2_factory_address, + Create2::deployCall { + amount: Uint::ZERO, + salt, + bytecode: bytecode.into(), + } + .abi_encode(), + signature, + ) + .abi_encode_sequence() + .into_iter() + .chain( + EIP6492_MAGIC_BYTES + .iter() + .flat_map(|&x| x.to_be_bytes().into_iter()), + ) + .collect::>(); + (predeploy_address, signature) + } + + #[tokio::test] + async fn test_eip6492_pass() { + let (_anvil, rpc_url, private_key) = spawn_anvil().await; + let create2_factory_address = + deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; + + let message = "xxx"; + let signature = sign_message(message, &private_key); + let (predeploy_address, signature) = predeploy_signature( + Address::from_private_key(&private_key), + create2_factory_address, + signature, + ); + + verify_eip6492( + signature, + predeploy_address, + &message_hash(message), + rpc_url, + ) + .await + .unwrap(); + } + + #[tokio::test] + async fn test_eip6492_wrong_signature() { + let (_anvil, rpc_url, private_key) = spawn_anvil().await; + let create2_factory_address = + deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; + + let message = "xxx"; + let mut signature = sign_message(message, &private_key); + *signature.first_mut().unwrap() = signature.first().unwrap().wrapping_add(1); + let (predeploy_address, signature) = predeploy_signature( + Address::from_private_key(&private_key), + create2_factory_address, + signature, + ); + + assert!(matches!( + verify_eip6492( + signature, + predeploy_address, + &message_hash(message), + rpc_url + ) + .await, + Err(CacaoError::Verification) + )); + } + + #[tokio::test] + async fn test_eip6492_fail_wrong_signer() { + let (anvil, rpc_url, private_key) = spawn_anvil().await; + let create2_factory_address = + deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; + + let message = "xxx"; + let signature = sign_message( + message, + &SigningKey::from_bytes(&anvil.keys().get(1).unwrap().to_bytes()).unwrap(), + ); + let (predeploy_address, signature) = predeploy_signature( + Address::from_private_key(&private_key), + create2_factory_address, + signature, + ); + + assert!(matches!( + verify_eip6492( + signature, + predeploy_address, + &message_hash(message), + rpc_url + ) + .await, + Err(CacaoError::Verification) + )); + } + + #[tokio::test] + #[ignore] + async fn test_eip6492_fail_wrong_contract_address() { + let (_anvil, rpc_url, private_key) = spawn_anvil().await; + let create2_factory_address = + deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; + + let message = "xxx"; + let signature = sign_message(message, &private_key); + let (mut predeploy_address, signature) = predeploy_signature( + Address::from_private_key(&private_key), + create2_factory_address, + signature, + ); + + *predeploy_address.0.first_mut().unwrap() = + predeploy_address.0.first().unwrap().wrapping_add(1); + + assert!(matches!( + verify_eip6492( + signature, + predeploy_address, + &message_hash(message), + rpc_url + ) + .await, + Err(CacaoError::Verification) + )); + } + + #[tokio::test] + async fn test_eip6492_wrong_message() { + let (_anvil, rpc_url, private_key) = spawn_anvil().await; + let create2_factory_address = + deploy_contract(&rpc_url, &private_key, CREATE2_CONTRACT, None).await; + + let message = "xxx"; + let signature = sign_message(message, &private_key); + let (predeploy_address, signature) = predeploy_signature( + Address::from_private_key(&private_key), + create2_factory_address, + signature, + ); + + let message2 = "yyy"; + assert!(matches!( + verify_eip6492( + signature, + predeploy_address, + &message_hash(message2), + rpc_url + ) + .await, + Err(CacaoError::Verification) + )); + } } diff --git a/relay_rpc/src/auth/cacao/signature/test_helpers.rs b/relay_rpc/src/auth/cacao/signature/test_helpers.rs index fe12b9a..0828cef 100644 --- a/relay_rpc/src/auth/cacao/signature/test_helpers.rs +++ b/relay_rpc/src/auth/cacao/signature/test_helpers.rs @@ -22,22 +22,35 @@ pub async fn spawn_anvil() -> (AnvilInstance, Url, SigningKey) { ) } -pub async fn deploy_contract(rpc_url: &Url, private_key: &SigningKey) -> Address { +pub const EIP1271_MOCK_CONTRACT: &str = "Eip1271Mock"; +pub const CREATE2_CONTRACT: &str = "Create2"; + +pub async fn deploy_contract( + rpc_url: &Url, + private_key: &SigningKey, + contract_name: &str, + constructor_arg: Option<&str>, +) -> Address { let key_encoded = data_encoding::HEXLOWER_PERMISSIVE.encode(&private_key.to_bytes()); + let mut args = vec![ + "create", + "--contracts=relay_rpc/contracts", + contract_name, + "--rpc-url", + rpc_url.as_str(), + "--private-key", + &key_encoded, + "--cache-path", + "target/.forge/cache", + "--out", + "target/.forge/out", + ]; + if let Some(arg) = constructor_arg { + args.push("--constructor-args"); + args.push(arg); + } let output = Command::new("forge") - .args([ - "create", - "--contracts=relay_rpc/contracts", - "Eip1271Mock", - "--rpc-url", - rpc_url.as_str(), - "--private-key", - &key_encoded, - "--cache-path", - "target/.forge/cache", - "--out", - "target/.forge/out", - ]) + .args(args) .stdout(Stdio::piped()) .stderr(Stdio::piped()) .spawn()