Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 3 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@ members = [
"precompiles/peaq-rbac",
"precompiles/parachain-staking",
"precompiles/vesting",
"precompiles/p256verify"
"precompiles/p256verify",
"precompiles/ed25519"
]
resolver = "2"

Expand Down Expand Up @@ -54,6 +55,7 @@ impl-trait-for-tuples = "0.2.2"
jsonrpsee = { version = "0.24.9", default-features = false }
libsecp256k1 = { version = "0.7", default-features = false }
p256 = { version = "0.13.2", default-features = false, features = [ "ecdsa" ] }
ed25519-dalek = { version = "2.1.0", default-features = false, features = [ "alloc" ] }
log = { version = "0.4.17", default-features = false }
macrotest = { version = "1.0.9", default-features = false }
num_enum = { version = "0.5.3", default-features = false }
Expand Down
17 changes: 17 additions & 0 deletions precompiles/ed25519/Cargo.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
[package]
name = "pallet-evm-precompile-ed25519"
authors = [ "peaq" ]
description = "Ed25519 signature verification precompile for Frontier EVM (vendored from Frontier, Apache-2.0)."
edition = "2021"
version = "0.1.0"

[dependencies]
ed25519-dalek = { workspace = true }
fp-evm = { workspace = true, default-features = false }

[features]
default = [ "std" ]
std = [
"ed25519-dalek/std",
"fp-evm/std",
]
140 changes: 140 additions & 0 deletions precompiles/ed25519/src/lib.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
// SPDX-License-Identifier: Apache-2.0
// This file is part of Frontier; vendored and adapted for peaq.
//
// Copyright (c) 2020-2022 Parity Technologies (UK) Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#![cfg_attr(not(feature = "std"), no_std)]
#![warn(unused_crate_dependencies)]

extern crate alloc;

use alloc::vec::Vec;

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

/// Ed25519 signature verification precompile (vendored from Frontier, adapted for peaq).
///
/// Input (128 bytes): message(32) || public key(32) || signature(64).
/// Output: a 32-byte big-endian `1` if the signature is valid, empty output otherwise --
/// identical to P256VERIFY (RIP-7212). The call never reverts; malformed input (wrong
/// length or bad key/signature encoding) is treated as an invalid signature (empty output).
pub struct Ed25519Verify;

impl Ed25519Verify {
/// True iff `input` is a well-formed 128-byte `message || public_key || signature` blob
/// carrying a valid Ed25519 signature. Never panics: the length check guards the
/// fixed-width slicing, and every decode is fallible.
fn is_valid(input: &[u8]) -> bool {
// Exactly 128 bytes, mirroring P256VERIFY's exact-length rule (RIP-7212 rejects
// wrong-length input); trailing garbage is not silently ignored.
if input.len() != 128 {
return false;
}
let msg = &input[0..32];
let pk = match VerifyingKey::try_from(&input[32..64]) {
Ok(pk) => pk,
Err(_) => return false,
};
let sig = match Signature::try_from(&input[64..128]) {
Ok(sig) => sig,
Err(_) => return false,
};
// Standard RFC 8032 verification via ed25519-dalek (cofactorless; rejects non-canonical
// s, so classic (R, s+L) malleability is blocked). NOTE: `verify` (not `verify_strict`)
// accepts small-order public keys -- callers must treat the pubkey as a trusted binding.
pk.verify(msg, &sig).is_ok()
}
}

impl LinearCostPrecompile for Ed25519Verify {
// Frontier's upstream default (BASE=15, WORD=3) underprices an ed25519 verify ~100x vs
// its real CPU cost -> block-time DoS. Price at ecrecover parity: flat 3000 gas.
const BASE: u64 = 3000;
const WORD: u64 = 0;

fn execute(input: &[u8], _: u64) -> Result<(ExitSucceed, Vec<u8>), PrecompileFailure> {
// Output matches P256VERIFY / RIP-7212: 32-byte big-endian 1 if valid, empty otherwise.
let output = if Self::is_valid(input) {
let mut out = [0u8; 32];
out[31] = 1;
out.to_vec()
} else {
Vec::new()
};
Ok((ExitSucceed::Returned, output))
}
}

#[cfg(test)]
mod tests {
use super::*;
use ed25519_dalek::{Signer, SigningKey};

fn valid_output() -> Vec<u8> {
let mut out = [0u8; 32];
out[31] = 1;
out.to_vec()
}

#[test]
fn test_empty_input() {
// Malformed (too short) input -> empty output, NOT a revert.
let (exit, out) = Ed25519Verify::execute(&[], 1).expect("must not revert");
assert_eq!(exit, ExitSucceed::Returned);
assert!(out.is_empty());
}

#[test]
fn test_verify() {
#[allow(clippy::zero_prefixed_literal)]
let secret_key_bytes: [u8; ed25519_dalek::SECRET_KEY_LENGTH] = [
157, 097, 177, 157, 239, 253, 090, 096, 186, 132, 074, 244, 146, 236, 044, 196, 068,
073, 197, 105, 123, 050, 105, 025, 112, 059, 172, 003, 028, 174, 127, 096,
];
let keypair = SigningKey::from_bytes(&secret_key_bytes);
let public_key = keypair.verifying_key();

let msg: &[u8] = b"abcdefghijklmnopqrstuvwxyz123456";
assert_eq!(msg.len(), 32);
let signature = keypair.sign(msg);

// input: message(32) || pubkey(32) || signature(64)
let mut input: Vec<u8> = Vec::with_capacity(128);
input.extend_from_slice(msg);
input.extend_from_slice(&public_key.to_bytes());
input.extend_from_slice(&signature.to_bytes());
assert_eq!(input.len(), 128);

// valid -> 32-byte 0x..01
let (_, out) = Ed25519Verify::execute(&input, 1).expect("must not revert");
assert_eq!(out, valid_output());

// oversized input (trailing byte) -> rejected -> empty
let mut input_long = input.clone();
input_long.push(0u8);
let (_, out) = Ed25519Verify::execute(&input_long, 1).expect("must not revert");
assert!(out.is_empty());

// wrong message -> invalid -> empty
let bad_msg: &[u8] = b"BAD_MESSAGE_mnopqrstuvwxyz123456";
let mut input2: Vec<u8> = Vec::with_capacity(128);
input2.extend_from_slice(bad_msg);
input2.extend_from_slice(&public_key.to_bytes());
input2.extend_from_slice(&signature.to_bytes());
let (_, out) = Ed25519Verify::execute(&input2, 1).expect("must not revert");
assert!(out.is_empty());
}
}
2 changes: 2 additions & 0 deletions runtime/krest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ pallet-evm-precompile-xcm-utils = { path = "../../precompiles/xcm-utils", defaul
pallet-evm-precompile-parachain-staking = { path = "../../precompiles/parachain-staking", default-features = false }
pallet-evm-precompile-vesting = { path = "../../precompiles/vesting", default-features = false }
pallet-evm-precompile-p256verify = { path = "../../precompiles/p256verify", default-features = false }
pallet-evm-precompile-ed25519 = { path = "../../precompiles/ed25519", default-features = false }
pallet-evm-precompile-balances-erc20 = { path = "../../precompiles/balances-erc20", default-features = false}
runtime-common = { path = "../common", default-features = false }
pallet-scheduler = { workspace = true, default-features = false }
Expand Down Expand Up @@ -299,6 +300,7 @@ std = [
"pallet-evm-precompile-parachain-staking/std",
"pallet-evm-precompile-vesting/std",
"pallet-evm-precompile-p256verify/std",
"pallet-evm-precompile-ed25519/std",
"pallet-xcm/std",
"inflation-manager/std",
"pallet-message-queue/std",
Expand Down
8 changes: 8 additions & 0 deletions runtime/krest/src/precompiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use pallet_evm_precompile_balances_erc20::{Erc20BalancesPrecompile, Erc20Metadat
use pallet_evm_precompile_batch::BatchPrecompile;
use pallet_evm_precompile_blake2::Blake2F;
use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing};
use pallet_evm_precompile_ed25519::Ed25519Verify;
use pallet_evm_precompile_modexp::Modexp;
use pallet_evm_precompile_p256verify::P256Verify;
use pallet_evm_precompile_parachain_staking::ParachainStakingPrecompile;
Expand Down Expand Up @@ -109,6 +110,13 @@ pub type PeaqPrecompiles<R> = PrecompileSetBuilder<
ECRecoverPublicKey,
(CallableByContract, CallableByPrecompile),
>,
// Ed25519 signature verification (peaq-specific address; Ed25519 has no ecosystem
// standard). Output matches P256VERIFY: 32-byte 1 = valid, empty = invalid.
PrecompileAt<
AddressU64<1027>,
Ed25519Verify,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2048>,
PeaqDIDPrecompile<R>,
Expand Down
2 changes: 2 additions & 0 deletions runtime/peaq-dev/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,7 @@ pallet-evm-precompile-xcm-utils = { path = "../../precompiles/xcm-utils", defaul
pallet-evm-precompile-parachain-staking = { path = "../../precompiles/parachain-staking", default-features = false }
pallet-evm-precompile-vesting = { path = "../../precompiles/vesting", default-features = false }
pallet-evm-precompile-p256verify = { path = "../../precompiles/p256verify", default-features = false }
pallet-evm-precompile-ed25519 = { path = "../../precompiles/ed25519", default-features = false }
runtime-common = { path = "../common", default-features = false }
peaq-pallet-mor = { workspace = true, default-features = false }
xc-asset-config = { path = "../../pallets/xc-asset-config", default-features = false }
Expand Down Expand Up @@ -301,6 +302,7 @@ std = [
"pallet-evm-precompile-parachain-staking/std",
"pallet-evm-precompile-vesting/std",
"pallet-evm-precompile-p256verify/std",
"pallet-evm-precompile-ed25519/std",
"pallet-xcm/std",
"inflation-manager/std",
"pallet-message-queue/std",
Expand Down
8 changes: 8 additions & 0 deletions runtime/peaq-dev/src/precompiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use pallet_evm_precompile_balances_erc20::{Erc20BalancesPrecompile, Erc20Metadat
use pallet_evm_precompile_batch::BatchPrecompile;
use pallet_evm_precompile_blake2::Blake2F;
use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing};
use pallet_evm_precompile_ed25519::Ed25519Verify;
use pallet_evm_precompile_modexp::Modexp;
use pallet_evm_precompile_p256verify::P256Verify;
use pallet_evm_precompile_parachain_staking::ParachainStakingPrecompile;
Expand Down Expand Up @@ -109,6 +110,13 @@ pub type PeaqPrecompiles<R> = PrecompileSetBuilder<
ECRecoverPublicKey,
(CallableByContract, CallableByPrecompile),
>,
// Ed25519 signature verification (peaq-specific address; Ed25519 has no ecosystem
// standard). Output matches P256VERIFY: 32-byte 1 = valid, empty = invalid.
PrecompileAt<
AddressU64<1027>,
Ed25519Verify,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2048>,
PeaqDIDPrecompile<R>,
Expand Down
2 changes: 2 additions & 0 deletions runtime/peaq/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ pallet-evm-precompile-xcm-utils = { path = "../../precompiles/xcm-utils", defaul
pallet-evm-precompile-parachain-staking = { path = "../../precompiles/parachain-staking", default-features = false }
pallet-evm-precompile-vesting = { path = "../../precompiles/vesting", default-features = false }
pallet-evm-precompile-p256verify = { path = "../../precompiles/p256verify", default-features = false }
pallet-evm-precompile-ed25519 = { path = "../../precompiles/ed25519", default-features = false }
pallet-evm-precompile-balances-erc20 = { path = "../../precompiles/balances-erc20", default-features = false}
runtime-common = { path = "../common", default-features = false }
pallet-scheduler = { workspace = true, default-features = false }
Expand Down Expand Up @@ -296,6 +297,7 @@ std = [
"pallet-evm-precompile-parachain-staking/std",
"pallet-evm-precompile-vesting/std",
"pallet-evm-precompile-p256verify/std",
"pallet-evm-precompile-ed25519/std",
"pallet-xcm/std",
"inflation-manager/std",
"pallet-message-queue/std",
Expand Down
8 changes: 8 additions & 0 deletions runtime/peaq/src/precompiles.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ use pallet_evm_precompile_balances_erc20::{Erc20BalancesPrecompile, Erc20Metadat
use pallet_evm_precompile_batch::BatchPrecompile;
use pallet_evm_precompile_blake2::Blake2F;
use pallet_evm_precompile_bn128::{Bn128Add, Bn128Mul, Bn128Pairing};
use pallet_evm_precompile_ed25519::Ed25519Verify;
use pallet_evm_precompile_modexp::Modexp;
use pallet_evm_precompile_p256verify::P256Verify;
use pallet_evm_precompile_parachain_staking::ParachainStakingPrecompile;
Expand Down Expand Up @@ -109,6 +110,13 @@ pub type PeaqPrecompiles<R> = PrecompileSetBuilder<
ECRecoverPublicKey,
(CallableByContract, CallableByPrecompile),
>,
// Ed25519 signature verification (peaq-specific address; Ed25519 has no ecosystem
// standard). Output matches P256VERIFY: 32-byte 1 = valid, empty = invalid.
PrecompileAt<
AddressU64<1027>,
Ed25519Verify,
(CallableByContract, CallableByPrecompile),
>,
PrecompileAt<
AddressU64<2048>,
PeaqDIDPrecompile<R>,
Expand Down
Loading