pallet_evm_precompile_p256verify/
lib.rs

1// Copyright 2025 Moonbeam Foundation.
2// This file is part of Moonbeam.
3
4// Moonbeam is free software: you can redistribute it and/or modify
5// it under the terms of the GNU General Public License as published by
6// the Free Software Foundation, either version 3 of the License, or
7// (at your option) any later version.
8
9// Moonbeam is distributed in the hope that it will be useful,
10// but WITHOUT ANY WARRANTY; without even the implied warranty of
11// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12// GNU General Public License for more details.
13
14// You should have received a copy of the GNU General Public License
15// along with Moonbeam.  If not, see <http://www.gnu.org/licenses/>.
16
17// “secp256r1” is a specific elliptic curve, also known as “P-256”
18// and “prime256v1” curves.
19
20#![cfg_attr(not(feature = "std"), no_std)]
21
22extern crate alloc;
23
24use alloc::vec::Vec;
25use core::marker::PhantomData;
26use fp_evm::{
27	ExitError, ExitSucceed, Precompile, PrecompileHandle, PrecompileOutput, PrecompileResult,
28};
29use frame_support::{traits::Get, weights::Weight};
30use p256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey};
31
32pub struct P256Verify<W: Get<Weight>>(PhantomData<W>);
33
34impl<W: Get<Weight>> P256Verify<W> {
35	/// Expected input length (160 bytes)
36	const INPUT_LENGTH: usize = 160;
37
38	/// Handle gas costs
39	#[inline]
40	fn handle_cost(handle: &mut impl PrecompileHandle) -> Result<(), ExitError> {
41		let weight = W::get();
42		handle.record_external_cost(Some(weight.ref_time()), None, None)
43	}
44
45	/// (Signed payload) Hash of the original message
46	/// 32 bytes of the signed data hash
47	#[inline]
48	fn message_hash(input: &[u8]) -> &[u8] {
49		&input[..32]
50	}
51
52	/// r and s signature components
53	#[inline]
54	fn signature(input: &[u8]) -> &[u8] {
55		&input[32..96]
56	}
57
58	/// x and y coordinates of the public key
59	#[inline]
60	fn public_key(input: &[u8]) -> &[u8] {
61		&input[96..160]
62	}
63
64	/// Extract and validate signature from input
65	fn verify_from_input(input: &[u8]) -> Option<()> {
66		// Input data: 160 bytes of data including:
67		// - 32 bytes of the signed data hash
68		// - 32 bytes of the r component of the signature
69		// - 32 bytes of the s component of the signature
70		// - 32 bytes of the x coordinate of the public key
71		// - 32 bytes of the y coordinate of the public key
72		if input.len() != Self::INPUT_LENGTH {
73			return None;
74		}
75
76		let message_hash = Self::message_hash(input);
77		let signature = Self::signature(input);
78		let public_key = Self::public_key(input);
79
80		let mut uncompressed_pk = [0u8; 65];
81		// (0x04) prefix indicates the public key is in its uncompressed from
82		uncompressed_pk[0] = 0x04;
83		uncompressed_pk[1..].copy_from_slice(public_key);
84
85		// Will only fail if the signature is not exactly 64 bytes
86		let signature = Signature::from_slice(signature).ok()?;
87
88		let public_key = VerifyingKey::from_sec1_bytes(&uncompressed_pk).ok()?;
89
90		public_key.verify_prehash(message_hash, &signature).ok()
91	}
92}
93
94/// Implements RIP-7212 P256VERIFY precompile.
95/// https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md
96impl<W: Get<Weight>> Precompile for P256Verify<W> {
97	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
98		Self::handle_cost(handle)?;
99
100		let result = if Self::verify_from_input(handle.input()).is_some() {
101			// If the signature verification process succeeds, it returns 1 in 32 bytes format.
102			let mut result = [0u8; 32];
103			result[31] = 1;
104
105			result.to_vec()
106		} else {
107			// If the signature verification process fails, it does not return any output data.
108			Vec::new()
109		};
110
111		Ok(PrecompileOutput {
112			exit_status: ExitSucceed::Returned,
113			output: result.to_vec(),
114		})
115	}
116}
117
118#[cfg(test)]
119mod tests {
120	use super::*;
121	use fp_evm::Context;
122	use frame_support::parameter_types;
123	use hex_literal::hex;
124	use precompile_utils::testing::MockHandle;
125
126	parameter_types! {
127		pub const DummyWeight: Weight = Weight::from_parts(3450, 0);
128	}
129
130	fn prepare_handle(input: Vec<u8>, cost: u64) -> impl PrecompileHandle {
131		let context: Context = Context {
132			address: Default::default(),
133			caller: Default::default(),
134			apparent_value: From::from(0),
135		};
136
137		let mut handle = MockHandle::new(Default::default(), context);
138		handle.input = input;
139		handle.gas_limit = cost;
140
141		handle
142	}
143
144	#[test]
145	fn test_valid_signature() {
146		let inputs = vec![
147			(
148				true,
149				hex!(
150					"b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f"
151					"319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d2621444"
152					"75b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fc"
153					"cf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb"
154					"36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1"
155				)
156				.to_vec(),
157			),
158			(
159				true,
160				hex!(
161					"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73b"
162					"d4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03"
163					"009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c61"
164					"8202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4"
165					"ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"
166				)
167				.to_vec(),
168			),
169			(
170				false,
171				hex!(
172					"afec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466"
173					"e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817cc"
174					"f50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de63"
175					"02b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d32"
176					"28d3b940258c75fe2a413cb70baa21dc2e352fc5"
177				)
178				.to_vec(),
179			),
180			(
181				false,
182				hex!(
183					"3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4"
184					"903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009d"
185					"f8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fc"
186					"fe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971"
187					"a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"
188				)
189				.to_vec(),
190			),
191			(false, hex!("4cee90eb86eaa050036147a12d49004b6a").to_vec()),
192		];
193		for input in inputs {
194			let cost = 3450;
195			let mut handle = prepare_handle(input.1.clone(), cost);
196
197			let mut success_result = [0u8; 32];
198			success_result[31] = 1;
199
200			let unsuccessful_result = Vec::<u8>::new();
201
202			match (input.0, P256Verify::<DummyWeight>::execute(&mut handle)) {
203				(true, Ok(result)) => assert_eq!(result.output, success_result.to_vec()),
204				(false, Ok(result)) => assert_eq!(result.output, unsuccessful_result),
205				(_, Err(_)) => panic!("Test not expected to fail for input: {:?}", input),
206			}
207		}
208	}
209}