Skip to main content

anvil_polkadot/substrate_node/
host.rs

1//! Host functions overrides for Ethereum address recovery.
2//!
3//! The host functions overriding is required because of impersonation feature.
4//! Impersonation is used for sending/executing transactions with a signer
5//! where its private key is unknown. Sent transactions require a signature
6//! that is obtained with sender's private key and the pallet-revive
7//! runtime logic can recover the signer's ethereum address from the signature
8//! and the rest of the transaction bytes. The runtime recovers the signer's
9//! address by running `secp256k1_ecdsa_recover` to recover the signer's public
10//! key, and `keccak_256` to hash the public key to a 20 byes Ethereum address.
11//! These functions are exposed by `sp-io` and used as host functions by the runtime.
12//! This module implements tweaked versions of the host functions from `sp-io`, which
13//! can recognize fake signatures used for impersonated transactions, and can recover
14//! the signer address from them, while expecting those fake signatures to be built in
15//! a certain way ([0; 12] + sender's Ethereum address + [IMPERSONATION_MARKER; 32] + recovery_id).
16//!
17//! The tweaked host functions are especially useful in the context of overriding the
18//! same `sp-io` host functions in the wasm executor type.
19
20use polkadot_sdk::sp_io::{self, EcdsaVerifyError};
21use sp_runtime_interface::{
22    pass_by::{
23        AllocateAndReturnByCodec, AllocateAndReturnPointer, PassFatPointerAndRead,
24        PassPointerAndRead,
25    },
26    runtime_interface,
27};
28
29/// Magic marker used to identify impersonated transactions.
30/// Using 0xDE for "DEfake" - a distinctive pattern that won't collide with legitimate EVM data.
31/// Previously we used [0; 33] but this collided with Solidity mapping key computations
32/// for slot 0 (which also have 32 trailing zero bytes).
33pub const IMPERSONATION_MARKER: u8 = 0xDE;
34
35// The host functions in this module expect transactions
36// with fake signatures conforming the format checked in this function.
37// Format: [0; 12] + [20-byte address] + [IMPERSONATION_MARKER; 32] + [recovery_id]
38// Note: We only check bytes 32..64 (the 's' component of ECDSA signature) for the marker.
39// Byte 64 (recovery_id/v) must be a valid value (0 or 1) for the transaction to be accepted.
40pub fn is_impersonated(sig: &[u8]) -> bool {
41    sig[..12] == [0; 12] && sig[32..64] == [IMPERSONATION_MARKER; 32]
42}
43
44/// Recover sender address from signed transaction, handling impersonated transactions.
45/// For impersonated transactions (fake signatures), extracts the address embedded in the signature.
46/// For normal transactions, performs standard ECDSA recovery.
47#[allow(clippy::result_unit_err)]
48pub fn recover_maybe_impersonated_address(
49    signed_tx: &polkadot_sdk::pallet_revive::evm::TransactionSigned,
50) -> Result<polkadot_sdk::sp_core::H160, ()> {
51    let sig = signed_tx.raw_signature()?;
52    if is_impersonated(&sig) {
53        let mut res = [0; 20];
54        res.copy_from_slice(&sig[12..32]);
55        Ok(polkadot_sdk::sp_core::H160::from(res))
56    } else {
57        signed_tx.recover_eth_address()
58    }
59}
60
61#[runtime_interface]
62pub trait Crypto {
63    #[version(1)]
64    fn secp256k1_ecdsa_recover(
65        sig: PassPointerAndRead<&[u8; 65], 65>,
66        msg: PassPointerAndRead<&[u8; 32], 32>,
67    ) -> AllocateAndReturnByCodec<Result<[u8; 64], EcdsaVerifyError>> {
68        if is_impersonated(sig) {
69            trace!(
70                target = "host_fn_overrides",
71                name = "secp256k1_ecdsa_recover - version 1",
72                "impersonation for: {:?}",
73                &sig[12..32]
74            );
75            let mut res = [IMPERSONATION_MARKER; 64];
76            res[..12].fill(0);
77            res[12..32].copy_from_slice(&sig[12..32]);
78            Ok(res)
79        } else {
80            sp_io::crypto::secp256k1_ecdsa_recover(sig, msg)
81        }
82    }
83
84    #[version(2)]
85    fn secp256k1_ecdsa_recover(
86        sig: PassPointerAndRead<&[u8; 65], 65>,
87        msg: PassPointerAndRead<&[u8; 32], 32>,
88    ) -> AllocateAndReturnByCodec<Result<[u8; 64], EcdsaVerifyError>> {
89        if is_impersonated(sig) {
90            trace!(
91                target = "host_fn_overrides",
92                name = "secp256k1_ecdsa_recover - version 2",
93                "impersonation for: {:?}",
94                &sig[12..32]
95            );
96
97            let mut res = [IMPERSONATION_MARKER; 64];
98            res[..12].fill(0);
99            res[12..32].copy_from_slice(&sig[12..32]);
100            Ok(res)
101        } else {
102            sp_io::crypto::secp256k1_ecdsa_recover(sig, msg)
103        }
104    }
105}
106
107#[runtime_interface]
108pub trait Hashing {
109    fn keccak_256(data: PassFatPointerAndRead<&[u8]>) -> AllocateAndReturnPointer<[u8; 32], 32> {
110        if data.len() == 64 && is_impersonated(data) {
111            trace!(
112                target = "host_fn_overrides",
113                name = "keccak_256",
114                "impersonation for: {:?}",
115                &data[12..32]
116            );
117            let mut res = [0; 32];
118            res.copy_from_slice(&data[0..32]);
119            res
120        } else {
121            sp_io::hashing::keccak_256(data)
122        }
123    }
124}
125
126/// Provides host function that overrides ETH address recovery from
127/// signature in the scope of impersonation.
128pub type SenderAddressRecoveryOverride = self::crypto::HostFunctions;
129/// Provides host function that override hashing functions in the
130/// scope of impersonation.
131pub type PublicKeyToHashOverride = self::hashing::HostFunctions;