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

            
22
extern crate alloc;
23

            
24
use alloc::vec::Vec;
25
use core::marker::PhantomData;
26
use fp_evm::{
27
	ExitError, ExitSucceed, Precompile, PrecompileHandle, PrecompileOutput, PrecompileResult,
28
};
29
use frame_support::{traits::Get, weights::Weight};
30
use p256::ecdsa::{signature::hazmat::PrehashVerifier, Signature, VerifyingKey};
31

            
32
pub struct P256Verify<W: Get<Weight>>(PhantomData<W>);
33

            
34
impl<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
8
	fn handle_cost(handle: &mut impl PrecompileHandle) -> Result<(), ExitError> {
41
8
		let weight = W::get();
42
8
		handle.record_external_cost(Some(weight.ref_time()), None, None)
43
8
	}
44

            
45
	/// (Signed payload) Hash of the original message
46
	/// 32 bytes of the signed data hash
47
	#[inline]
48
4
	fn message_hash(input: &[u8]) -> &[u8] {
49
4
		&input[..32]
50
4
	}
51

            
52
	/// r and s signature components
53
	#[inline]
54
4
	fn signature(input: &[u8]) -> &[u8] {
55
4
		&input[32..96]
56
4
	}
57

            
58
	/// x and y coordinates of the public key
59
	#[inline]
60
4
	fn public_key(input: &[u8]) -> &[u8] {
61
4
		&input[96..160]
62
4
	}
63

            
64
	/// Extract and validate signature from input
65
8
	fn verify_from_input(input: &[u8]) -> Option<()> {
66
8
		// Input data: 160 bytes of data including:
67
8
		// - 32 bytes of the signed data hash
68
8
		// - 32 bytes of the r component of the signature
69
8
		// - 32 bytes of the s component of the signature
70
8
		// - 32 bytes of the x coordinate of the public key
71
8
		// - 32 bytes of the y coordinate of the public key
72
8
		if input.len() != Self::INPUT_LENGTH {
73
4
			return None;
74
4
		}
75
4

            
76
4
		let message_hash = Self::message_hash(input);
77
4
		let signature = Self::signature(input);
78
4
		let public_key = Self::public_key(input);
79
4

            
80
4
		let mut uncompressed_pk = [0u8; 65];
81
4
		// (0x04) prefix indicates the public key is in its uncompressed from
82
4
		uncompressed_pk[0] = 0x04;
83
4
		uncompressed_pk[1..].copy_from_slice(public_key);
84

            
85
		// Will only fail if the signature is not exactly 64 bytes
86
4
		let signature = Signature::from_slice(signature).ok()?;
87

            
88
4
		let public_key = VerifyingKey::from_sec1_bytes(&uncompressed_pk).ok()?;
89

            
90
4
		public_key.verify_prehash(message_hash, &signature).ok()
91
8
	}
92
}
93

            
94
/// Implements RIP-7212 P256VERIFY precompile.
95
/// https://github.com/ethereum/RIPs/blob/master/RIPS/rip-7212.md
96
impl<W: Get<Weight>> Precompile for P256Verify<W> {
97
8
	fn execute(handle: &mut impl PrecompileHandle) -> PrecompileResult {
98
8
		Self::handle_cost(handle)?;
99

            
100
8
		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
2
			let mut result = [0u8; 32];
103
2
			result[31] = 1;
104
2

            
105
2
			result.to_vec()
106
		} else {
107
			// If the signature verification process fails, it does not return any output data.
108
6
			Vec::new()
109
		};
110

            
111
8
		Ok(PrecompileOutput {
112
8
			exit_status: ExitSucceed::Returned,
113
8
			output: result.to_vec(),
114
8
		})
115
8
	}
116
}
117

            
118
#[cfg(test)]
119
mod 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
5
	fn prepare_handle(input: Vec<u8>, cost: u64) -> impl PrecompileHandle {
131
5
		let context: Context = Context {
132
5
			address: Default::default(),
133
5
			caller: Default::default(),
134
5
			apparent_value: From::from(0),
135
5
		};
136
5

            
137
5
		let mut handle = MockHandle::new(Default::default(), context);
138
5
		handle.input = input;
139
5
		handle.gas_limit = cost;
140
5

            
141
5
		handle
142
5
	}
143

            
144
	#[test]
145
1
	fn test_valid_signature() {
146
1
		let inputs = vec![
147
1
			(
148
1
				true,
149
1
				hex!(
150
1
					"b5a77e7a90aa14e0bf5f337f06f597148676424fae26e175c6e5621c34351955289f"
151
1
					"319789da424845c9eac935245fcddd805950e2f02506d09be7e411199556d2621444"
152
1
					"75b1fa46ad85250728c600c53dfd10f8b3f4adf140e27241aec3c2da3a81046703fc"
153
1
					"cf468b48b145f939efdbb96c3786db712b3113bb2488ef286cdcef8afe82d200a5bb"
154
1
					"36b5462166e8ce77f2d831a52ef2135b2af188110beaefb1"
155
1
				)
156
1
				.to_vec(),
157
1
			),
158
1
			(
159
1
				true,
160
1
				hex!(
161
1
					"4cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73b"
162
1
					"d4903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03"
163
1
					"009df8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c61"
164
1
					"8202fcfe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4"
165
1
					"ca6ca971a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"
166
1
				)
167
1
				.to_vec(),
168
1
			),
169
1
			(
170
1
				false,
171
1
				hex!(
172
1
					"afec5769b5cf4e310a7d150508e82fb8e3eda1c2c94c61492d3bd8aea99e06c9e22466"
173
1
					"e928fdccef0de49e3503d2657d00494a00e764fd437bdafa05f5922b1fbbb77c6817cc"
174
1
					"f50748419477e843d5bac67e6a70e97dde5a57e0c983b777e1ad31a80482dadf89de63"
175
1
					"02b1988c82c29544c9c07bb910596158f6062517eb089a2f54c9a0f348752950094d32"
176
1
					"28d3b940258c75fe2a413cb70baa21dc2e352fc5"
177
1
				)
178
1
				.to_vec(),
179
1
			),
180
1
			(
181
1
				false,
182
1
				hex!(
183
1
					"3cee90eb86eaa050036147a12d49004b6b9c72bd725d39d4785011fe190f0b4da73bd4"
184
1
					"903f0ce3b639bbbf6e8e80d16931ff4bcf5993d58468e8fb19086e8cac36dbcd03009d"
185
1
					"f8c59286b162af3bd7fcc0450c9aa81be5d10d312af6c66b1d604aebd3099c618202fc"
186
1
					"fe16ae7770b0c49ab5eadf74b754204a3bb6060e44eff37618b065f9832de4ca6ca971"
187
1
					"a7a1adc826d0f7c00181a5fb2ddf79ae00b4e10e"
188
1
				)
189
1
				.to_vec(),
190
1
			),
191
1
			(false, hex!("4cee90eb86eaa050036147a12d49004b6a").to_vec()),
192
1
		];
193
6
		for input in inputs {
194
5
			let cost = 3450;
195
5
			let mut handle = prepare_handle(input.1.clone(), cost);
196
5

            
197
5
			let mut success_result = [0u8; 32];
198
5
			success_result[31] = 1;
199
5

            
200
5
			let unsuccessful_result = Vec::<u8>::new();
201
5

            
202
5
			match (input.0, P256Verify::<DummyWeight>::execute(&mut handle)) {
203
2
				(true, Ok(result)) => assert_eq!(result.output, success_result.to_vec()),
204
3
				(false, Ok(result)) => assert_eq!(result.output, unsuccessful_result),
205
				(_, Err(_)) => panic!("Test not expected to fail for input: {:?}", input),
206
			}
207
		}
208
1
	}
209
}