referrerpolicy=no-referrer-when-downgrade

pallet_revive/evm/api/
signature.rs

1// This file is part of Substrate.
2
3// Copyright (C) Parity Technologies (UK) Ltd.
4// SPDX-License-Identifier: Apache-2.0
5
6// Licensed under the Apache License, Version 2.0 (the "License");
7// you may not use this file except in compliance with the License.
8// You may obtain a copy of the License at
9//
10// 	http://www.apache.org/licenses/LICENSE-2.0
11//
12// Unless required by applicable law or agreed to in writing, software
13// distributed under the License is distributed on an "AS IS" BASIS,
14// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15// See the License for the specific language governing permissions and
16// limitations under the License.
17//! Ethereum signature utilities
18
19use super::*;
20use sp_core::{H160, U256};
21use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
22
23impl TransactionLegacySigned {
24	/// Get the recovery ID from the signed transaction.
25	/// See https://eips.ethereum.org/EIPS/eip-155
26	fn extract_recovery_id(&self) -> Option<u8> {
27		if let Some(chain_id) = self.transaction_legacy_unsigned.chain_id {
28			// self.v - chain_id * 2 - 35
29			let v: u64 = self.v.try_into().ok()?;
30			let chain_id: u64 = chain_id.try_into().ok()?;
31			let r = v.checked_sub(chain_id.checked_mul(2)?)?.checked_sub(35)?;
32			r.try_into().ok()
33		} else {
34			self.v.try_into().ok()
35		}
36	}
37}
38
39impl TransactionUnsigned {
40	/// Extract the unsigned transaction from a signed transaction.
41	pub fn from_signed(tx: TransactionSigned) -> Self {
42		match tx {
43			TransactionSigned::TransactionLegacySigned(signed) =>
44				Self::TransactionLegacyUnsigned(signed.transaction_legacy_unsigned),
45			TransactionSigned::Transaction7702Signed(signed) =>
46				Self::Transaction7702Unsigned(signed.transaction_7702_unsigned),
47			TransactionSigned::Transaction4844Signed(signed) =>
48				Self::Transaction4844Unsigned(signed.transaction_4844_unsigned),
49			TransactionSigned::Transaction1559Signed(signed) =>
50				Self::Transaction1559Unsigned(signed.transaction_1559_unsigned),
51			TransactionSigned::Transaction2930Signed(signed) =>
52				Self::Transaction2930Unsigned(signed.transaction_2930_unsigned),
53		}
54	}
55
56	/// Create a signed transaction from an [`TransactionUnsigned`] and a signature.
57	pub fn with_signature(self, signature: [u8; 65]) -> TransactionSigned {
58		let r = U256::from_big_endian(&signature[..32]);
59		let s = U256::from_big_endian(&signature[32..64]);
60		let recovery_id = signature[64];
61
62		match self {
63			TransactionUnsigned::Transaction7702Unsigned(transaction_7702_unsigned) =>
64				Transaction7702Signed {
65					transaction_7702_unsigned,
66					r,
67					s,
68					v: None,
69					y_parity: U256::from(recovery_id),
70				}
71				.into(),
72			TransactionUnsigned::Transaction2930Unsigned(transaction_2930_unsigned) =>
73				Transaction2930Signed {
74					transaction_2930_unsigned,
75					r,
76					s,
77					v: None,
78					y_parity: U256::from(recovery_id),
79				}
80				.into(),
81			TransactionUnsigned::Transaction1559Unsigned(transaction_1559_unsigned) =>
82				Transaction1559Signed {
83					transaction_1559_unsigned,
84					r,
85					s,
86					v: None,
87					y_parity: U256::from(recovery_id),
88				}
89				.into(),
90
91			TransactionUnsigned::Transaction4844Unsigned(transaction_4844_unsigned) =>
92				Transaction4844Signed {
93					transaction_4844_unsigned,
94					r,
95					s,
96					y_parity: U256::from(recovery_id),
97				}
98				.into(),
99
100			TransactionUnsigned::TransactionLegacyUnsigned(transaction_legacy_unsigned) => {
101				let v = transaction_legacy_unsigned
102					.chain_id
103					.map(|chain_id| {
104						chain_id
105							.saturating_mul(U256::from(2))
106							.saturating_add(U256::from(35u32 + recovery_id as u32))
107					})
108					.unwrap_or_else(|| U256::from(27u32 + recovery_id as u32));
109
110				TransactionLegacySigned { transaction_legacy_unsigned, r, s, v }.into()
111			},
112		}
113	}
114}
115
116impl TransactionSigned {
117	/// Get the raw 65 bytes signature from the signed transaction.
118	pub fn raw_signature(&self) -> Result<[u8; 65], ()> {
119		use TransactionSigned::*;
120		let (r, s, v) = match self {
121			TransactionLegacySigned(tx) => (tx.r, tx.s, tx.extract_recovery_id().ok_or(())?),
122			Transaction7702Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
123			Transaction4844Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
124			Transaction1559Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
125			Transaction2930Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
126		};
127		let mut sig = [0u8; 65];
128		r.write_as_big_endian(sig[0..32].as_mut());
129		s.write_as_big_endian(sig[32..64].as_mut());
130		sig[64] = v;
131		Ok(sig)
132	}
133
134	/// Recover the Ethereum address, from a signed transaction.
135	pub fn recover_eth_address(&self) -> Result<H160, ()> {
136		use TransactionSigned::*;
137
138		let mut s = rlp::RlpStream::new();
139		match self {
140			TransactionLegacySigned(tx) => {
141				let tx = &tx.transaction_legacy_unsigned;
142				s.append(tx);
143			},
144			Transaction7702Signed(tx) => {
145				let tx = &tx.transaction_7702_unsigned;
146				s.append(&tx.r#type.value());
147				s.append(tx);
148			},
149			Transaction4844Signed(tx) => {
150				let tx = &tx.transaction_4844_unsigned;
151				s.append(&tx.r#type.value());
152				s.append(tx);
153			},
154			Transaction1559Signed(tx) => {
155				let tx = &tx.transaction_1559_unsigned;
156				s.append(&tx.r#type.value());
157				s.append(tx);
158			},
159			Transaction2930Signed(tx) => {
160				let tx = &tx.transaction_2930_unsigned;
161				s.append(&tx.r#type.value());
162				s.append(tx);
163			},
164		}
165		let bytes = s.out().to_vec();
166		let signature = self.raw_signature()?;
167
168		let hash = keccak_256(&bytes);
169		let mut addr = H160::default();
170		let pk = secp256k1_ecdsa_recover(&signature, &hash).map_err(|_| ())?;
171		addr.assign_from_slice(&keccak_256(&pk[..])[12..]);
172		Ok(addr)
173	}
174}
175
176#[test]
177fn sign_and_recover_work() {
178	use crate::evm::TransactionUnsigned;
179	let txs = [
180		// Legacy
181		"f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808026a07b2e762a17a71a46b422e60890a04512cf0d907ccf6b78b5bd6e6977efdc2bf5a01ea673d50bbe7c2236acb498ceb8346a8607c941f0b8cbcde7cf439aa9369f1f",
182		//// type 1: EIP2930
183		"01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0c45a61b3d1d00169c649e7326e02857b850efb96e587db4b9aad29afc80d0752a070ae1eb47ab4097dbed2f19172ae286492621b46ac737ee6c32fb18a00c94c9c",
184		// type 2: EIP1559
185		"02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a055d72bbc3047d4b9d3e4b8099f187143202407746118204cc2e0cb0c85a68baea04f6ef08a1418c70450f53398d9f0f2d78d9e9d6b8a80cba886b67132c4a744f2",
186		// type 3: EIP4844
187		"03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000001a0672b8bac466e2cf1be3148c030988d40d582763ecebbc07700dfc93bb070d8a4a07c635887005b11cb58964c04669ac2857fa633aa66f662685dadfd8bcacb0f21",
188	];
189	let account = Account::from_secret_key(hex_literal::hex!(
190		"a872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f"
191	));
192
193	for tx in txs {
194		let raw_tx = alloy_core::hex::decode(tx).unwrap();
195		let tx = TransactionSigned::decode(&raw_tx).unwrap();
196
197		let address = tx.recover_eth_address();
198		assert_eq!(address.unwrap(), account.address());
199
200		let unsigned = TransactionUnsigned::from_signed(tx.clone());
201		let signed = account.sign_transaction(unsigned);
202		assert_eq!(tx, signed);
203	}
204}