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			},
46			TransactionSigned::Transaction7702Signed(signed) => {
47				Self::Transaction7702Unsigned(signed.transaction_7702_unsigned)
48			},
49			TransactionSigned::Transaction4844Signed(signed) => {
50				Self::Transaction4844Unsigned(signed.transaction_4844_unsigned)
51			},
52			TransactionSigned::Transaction1559Signed(signed) => {
53				Self::Transaction1559Unsigned(signed.transaction_1559_unsigned)
54			},
55			TransactionSigned::Transaction2930Signed(signed) => {
56				Self::Transaction2930Unsigned(signed.transaction_2930_unsigned)
57			},
58		}
59	}
60
61	/// Create a signed transaction from an [`TransactionUnsigned`] and a signature.
62	pub fn with_signature(self, signature: [u8; 65]) -> TransactionSigned {
63		let r = U256::from_big_endian(&signature[..32]);
64		let s = U256::from_big_endian(&signature[32..64]);
65		let recovery_id = signature[64];
66
67		match self {
68			TransactionUnsigned::Transaction7702Unsigned(transaction_7702_unsigned) => {
69				Transaction7702Signed {
70					transaction_7702_unsigned,
71					r,
72					s,
73					v: None,
74					y_parity: U256::from(recovery_id),
75				}
76				.into()
77			},
78			TransactionUnsigned::Transaction2930Unsigned(transaction_2930_unsigned) => {
79				Transaction2930Signed {
80					transaction_2930_unsigned,
81					r,
82					s,
83					v: None,
84					y_parity: U256::from(recovery_id),
85				}
86				.into()
87			},
88			TransactionUnsigned::Transaction1559Unsigned(transaction_1559_unsigned) => {
89				Transaction1559Signed {
90					transaction_1559_unsigned,
91					r,
92					s,
93					v: None,
94					y_parity: U256::from(recovery_id),
95				}
96				.into()
97			},
98
99			TransactionUnsigned::Transaction4844Unsigned(transaction_4844_unsigned) => {
100				Transaction4844Signed {
101					transaction_4844_unsigned,
102					r,
103					s,
104					y_parity: U256::from(recovery_id),
105				}
106				.into()
107			},
108
109			TransactionUnsigned::TransactionLegacyUnsigned(transaction_legacy_unsigned) => {
110				let v = transaction_legacy_unsigned
111					.chain_id
112					.map(|chain_id| {
113						chain_id
114							.saturating_mul(U256::from(2))
115							.saturating_add(U256::from(35u32 + recovery_id as u32))
116					})
117					.unwrap_or_else(|| U256::from(27u32 + recovery_id as u32));
118
119				TransactionLegacySigned { transaction_legacy_unsigned, r, s, v }.into()
120			},
121		}
122	}
123}
124
125impl TransactionSigned {
126	/// Get the raw 65 bytes signature from the signed transaction.
127	pub fn raw_signature(&self) -> Result<[u8; 65], ()> {
128		use TransactionSigned::*;
129		let (r, s, v) = match self {
130			TransactionLegacySigned(tx) => (tx.r, tx.s, tx.extract_recovery_id().ok_or(())?),
131			Transaction7702Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
132			Transaction4844Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
133			Transaction1559Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
134			Transaction2930Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
135		};
136		let mut sig = [0u8; 65];
137		r.write_as_big_endian(sig[0..32].as_mut());
138		s.write_as_big_endian(sig[32..64].as_mut());
139		sig[64] = v;
140		Ok(sig)
141	}
142
143	/// Recover the Ethereum address, from a signed transaction.
144	pub fn recover_eth_address(&self) -> Result<H160, ()> {
145		use TransactionSigned::*;
146
147		let mut s = rlp::RlpStream::new();
148		match self {
149			TransactionLegacySigned(tx) => {
150				let tx = &tx.transaction_legacy_unsigned;
151				s.append(tx);
152			},
153			Transaction7702Signed(tx) => {
154				let tx = &tx.transaction_7702_unsigned;
155				s.append(&tx.r#type.value());
156				s.append(tx);
157			},
158			Transaction4844Signed(tx) => {
159				let tx = &tx.transaction_4844_unsigned;
160				s.append(&tx.r#type.value());
161				s.append(tx);
162			},
163			Transaction1559Signed(tx) => {
164				let tx = &tx.transaction_1559_unsigned;
165				s.append(&tx.r#type.value());
166				s.append(tx);
167			},
168			Transaction2930Signed(tx) => {
169				let tx = &tx.transaction_2930_unsigned;
170				s.append(&tx.r#type.value());
171				s.append(tx);
172			},
173		}
174		let bytes = s.out().to_vec();
175		let signature = self.raw_signature()?;
176
177		let hash = keccak_256(&bytes);
178		let mut addr = H160::default();
179		let pk = secp256k1_ecdsa_recover(&signature, &hash).map_err(|_| ())?;
180		addr.assign_from_slice(&keccak_256(&pk[..])[12..]);
181		Ok(addr)
182	}
183}
184
185#[test]
186fn sign_and_recover_work() {
187	use crate::evm::TransactionUnsigned;
188	let txs = [
189		// Legacy
190		"f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808026a07b2e762a17a71a46b422e60890a04512cf0d907ccf6b78b5bd6e6977efdc2bf5a01ea673d50bbe7c2236acb498ceb8346a8607c941f0b8cbcde7cf439aa9369f1f",
191		//// type 1: EIP2930
192		"01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0c45a61b3d1d00169c649e7326e02857b850efb96e587db4b9aad29afc80d0752a070ae1eb47ab4097dbed2f19172ae286492621b46ac737ee6c32fb18a00c94c9c",
193		// type 2: EIP1559
194		"02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a055d72bbc3047d4b9d3e4b8099f187143202407746118204cc2e0cb0c85a68baea04f6ef08a1418c70450f53398d9f0f2d78d9e9d6b8a80cba886b67132c4a744f2",
195		// type 3: EIP4844
196		"03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000001a0672b8bac466e2cf1be3148c030988d40d582763ecebbc07700dfc93bb070d8a4a07c635887005b11cb58964c04669ac2857fa633aa66f662685dadfd8bcacb0f21",
197	];
198	let account = Account::from_secret_key(hex_literal::hex!(
199		"a872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f"
200	));
201
202	for tx in txs {
203		let raw_tx = alloy_core::hex::decode(tx).unwrap();
204		let tx = TransactionSigned::decode(&raw_tx).unwrap();
205
206		let address = tx.recover_eth_address();
207		assert_eq!(address.unwrap(), account.address());
208
209		let unsigned = TransactionUnsigned::from_signed(tx.clone());
210		let signed = account.sign_transaction(unsigned);
211		assert_eq!(tx, signed);
212	}
213}