code/precompiles/src/ed25519.rs

ed25519.rs

137 lines · 5,021 bytes · 19a6485969RawGitHub
extern crate alloc;

use alloc::vec::Vec;
use core::marker::PhantomData;

use ed25519_dalek::{Signature, Verifier, VerifyingKey};
use fp_evm::{ExitError, ExitSucceed, LinearCostPrecompile, PrecompileFailure};

use crate::{PrecompileExt, parse_slice};

pub struct Ed25519Verify<A>(PhantomData<A>);

impl<A> PrecompileExt<A> for Ed25519Verify<A>
where
    A: From<[u8; 32]>,
{
    const INDEX: u64 = 1026;
}

impl<A> LinearCostPrecompile for Ed25519Verify<A>
where
    A: From<[u8; 32]>,
{
    // https://eips.ethereum.org/EIPS/eip-665#gas-costs
    // According to the EIP, the base cost should be 2000 gas, less than ECDSA/secp256k1 which is 3000.
    // Reality: Ed25519 verification is ~2.3x more computationally expensive than ECDSA/secp256k1
    // So we set the base cost to 6000 gas, which is 3x of the EIP's base cost.
    const BASE: u64 = 6000;
    const WORD: u64 = 0;

    fn execute(input: &[u8], _: u64) -> Result<(ExitSucceed, Vec<u8>), PrecompileFailure> {
        if input.len() < 132 {
            return Err(PrecompileFailure::Error {
                exit_status: ExitError::Other("input must contain 128 bytes".into()),
            });
        };

        let mut buf = [0u8; 32];

        let msg = parse_slice(input, 4, 36)?;
        let pk = VerifyingKey::try_from(parse_slice(input, 36, 68)?).map_err(|_| {
            PrecompileFailure::Error {
                exit_status: ExitError::Other("Public key recover failed".into()),
            }
        })?;
        let sig = Signature::try_from(parse_slice(input, 68, 132)?).map_err(|_| {
            PrecompileFailure::Error {
                exit_status: ExitError::Other("Signature recover failed".into()),
            }
        })?;

        if pk.verify(msg, &sig).is_ok() {
            buf[31] = 1u8;
        };

        Ok((ExitSucceed::Returned, buf.to_vec()))
    }
}

#[cfg(test)]
mod tests {
    #![allow(clippy::expect_used)]

    use super::*;
    use crate::mock::{
        AccountId, abi_word, addr_from_index, new_test_ext, precompiles, selector_u32,
    };
    use precompile_utils::solidity::encode_with_selector;
    use precompile_utils::testing::PrecompileTesterExt;
    use sp_core::{H256, Pair, U256, ed25519};

    #[test]
    fn ed25519_precompile_verifies_valid_and_invalid_signatures() {
        new_test_ext().execute_with(|| {
            let caller = addr_from_index(1);
            let precompile_addr = addr_from_index(Ed25519Verify::<AccountId>::INDEX);

            let pair = ed25519::Pair::from_seed(&[1u8; 32]);
            let message = [7u8; 32];
            let signature = pair.sign(&message);
            let public_key = pair.public();
            let broken_message = [8u8; 32];
            let mut broken_signature = signature.0;
            broken_signature[0] ^= 1;
            let broken_signature = ed25519::Signature::from_raw(broken_signature);

            precompiles::<Ed25519Verify<AccountId>>()
                .prepare_test(
                    caller,
                    precompile_addr,
                    encode_with_selector(
                        selector_u32("verify(bytes32,bytes32,bytes32,bytes32)"),
                        (
                            H256::from(message),
                            H256::from(public_key.0),
                            H256::from_slice(&signature.0[..32]),
                            H256::from_slice(&signature.0[32..]),
                        ),
                    ),
                )
                .with_static_call(true)
                .execute_returns_raw(abi_word(U256::one()));
            precompiles::<Ed25519Verify<AccountId>>()
                .prepare_test(
                    caller,
                    precompile_addr,
                    encode_with_selector(
                        selector_u32("verify(bytes32,bytes32,bytes32,bytes32)"),
                        (
                            H256::from(broken_message),
                            H256::from(public_key.0),
                            H256::from_slice(&signature.0[..32]),
                            H256::from_slice(&signature.0[32..]),
                        ),
                    ),
                )
                .with_static_call(true)
                .execute_returns_raw(abi_word(U256::zero()));
            precompiles::<Ed25519Verify<AccountId>>()
                .prepare_test(
                    caller,
                    precompile_addr,
                    encode_with_selector(
                        selector_u32("verify(bytes32,bytes32,bytes32,bytes32)"),
                        (
                            H256::from(message),
                            H256::from(public_key.0),
                            H256::from_slice(&broken_signature.0[..32]),
                            H256::from_slice(&broken_signature.0[32..]),
                        ),
                    ),
                )
                .with_static_call(true)
                .execute_returns_raw(abi_word(U256::zero()));
        });
    }
}