1use super::*;
20use sp_core::{H160, U256};
21use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
22
23pub fn recover_eth_address_from_message(message: &[u8], signature: &[u8; 65]) -> Result<H160, ()> {
35 if !sp_core::ecdsa::is_signature_normalized(signature) {
36 log::debug!(target: "evm", "Rejected high-S ECDSA signature (EIP-2 violation)");
37 return Err(());
38 }
39
40 let hash = keccak_256(message);
41 let pk = secp256k1_ecdsa_recover(signature, &hash).map_err(|_| ())?;
42 let mut addr = H160::default();
43 addr.assign_from_slice(&keccak_256(&pk[..])[12..]);
44 Ok(addr)
45}
46
47impl TransactionLegacySigned {
48 fn extract_recovery_id(&self) -> Option<u8> {
51 if let Some(chain_id) = self.transaction_legacy_unsigned.chain_id {
52 let v: u64 = self.v.try_into().ok()?;
54 let chain_id: u64 = chain_id.try_into().ok()?;
55 let r = v.checked_sub(chain_id.checked_mul(2)?)?.checked_sub(35)?;
56 r.try_into().ok()
57 } else {
58 self.v.try_into().ok()
59 }
60 }
61}
62
63impl TransactionUnsigned {
64 pub fn from_signed(tx: TransactionSigned) -> Self {
66 match tx {
67 TransactionSigned::TransactionLegacySigned(signed) => {
68 Self::TransactionLegacyUnsigned(signed.transaction_legacy_unsigned)
69 },
70 TransactionSigned::Transaction7702Signed(signed) => {
71 Self::Transaction7702Unsigned(signed.transaction_7702_unsigned)
72 },
73 TransactionSigned::Transaction4844Signed(signed) => {
74 Self::Transaction4844Unsigned(signed.transaction_4844_unsigned)
75 },
76 TransactionSigned::Transaction1559Signed(signed) => {
77 Self::Transaction1559Unsigned(signed.transaction_1559_unsigned)
78 },
79 TransactionSigned::Transaction2930Signed(signed) => {
80 Self::Transaction2930Unsigned(signed.transaction_2930_unsigned)
81 },
82 }
83 }
84
85 pub fn with_signature(self, signature: [u8; 65]) -> TransactionSigned {
87 let r = U256::from_big_endian(&signature[..32]);
88 let s = U256::from_big_endian(&signature[32..64]);
89 let recovery_id = signature[64];
90
91 match self {
92 TransactionUnsigned::Transaction7702Unsigned(transaction_7702_unsigned) => {
93 Transaction7702Signed {
94 transaction_7702_unsigned,
95 r,
96 s,
97 v: None,
98 y_parity: U256::from(recovery_id),
99 }
100 .into()
101 },
102 TransactionUnsigned::Transaction2930Unsigned(transaction_2930_unsigned) => {
103 Transaction2930Signed {
104 transaction_2930_unsigned,
105 r,
106 s,
107 v: None,
108 y_parity: U256::from(recovery_id),
109 }
110 .into()
111 },
112 TransactionUnsigned::Transaction1559Unsigned(transaction_1559_unsigned) => {
113 Transaction1559Signed {
114 transaction_1559_unsigned,
115 r,
116 s,
117 v: None,
118 y_parity: U256::from(recovery_id),
119 }
120 .into()
121 },
122
123 TransactionUnsigned::Transaction4844Unsigned(transaction_4844_unsigned) => {
124 Transaction4844Signed {
125 transaction_4844_unsigned,
126 r,
127 s,
128 y_parity: U256::from(recovery_id),
129 }
130 .into()
131 },
132
133 TransactionUnsigned::TransactionLegacyUnsigned(transaction_legacy_unsigned) => {
134 let v = transaction_legacy_unsigned
135 .chain_id
136 .map(|chain_id| {
137 chain_id
138 .saturating_mul(U256::from(2))
139 .saturating_add(U256::from(35u32 + recovery_id as u32))
140 })
141 .unwrap_or_else(|| U256::from(27u32 + recovery_id as u32));
142
143 TransactionLegacySigned { transaction_legacy_unsigned, r, s, v }.into()
144 },
145 }
146 }
147}
148
149impl TransactionSigned {
150 pub fn raw_signature(&self) -> Result<[u8; 65], ()> {
152 use TransactionSigned::*;
153 let (r, s, v) = match self {
154 TransactionLegacySigned(tx) => (tx.r, tx.s, tx.extract_recovery_id().ok_or(())?),
155 Transaction7702Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
156 Transaction4844Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
157 Transaction1559Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
158 Transaction2930Signed(tx) => (tx.r, tx.s, tx.y_parity.try_into().map_err(|_| ())?),
159 };
160 let mut sig = [0u8; 65];
161 r.write_as_big_endian(sig[0..32].as_mut());
162 s.write_as_big_endian(sig[32..64].as_mut());
163 sig[64] = v;
164 Ok(sig)
165 }
166
167 pub fn recover_eth_address(&self) -> Result<H160, ()> {
169 use TransactionSigned::*;
170
171 let mut s = rlp::RlpStream::new();
172 match self {
173 TransactionLegacySigned(tx) => {
174 let tx = &tx.transaction_legacy_unsigned;
175 s.append(tx);
176 },
177 Transaction7702Signed(tx) => {
178 let tx = &tx.transaction_7702_unsigned;
179 s.append(&tx.r#type.value());
180 s.append(tx);
181 },
182 Transaction4844Signed(tx) => {
183 let tx = &tx.transaction_4844_unsigned;
184 s.append(&tx.r#type.value());
185 s.append(tx);
186 },
187 Transaction1559Signed(tx) => {
188 let tx = &tx.transaction_1559_unsigned;
189 s.append(&tx.r#type.value());
190 s.append(tx);
191 },
192 Transaction2930Signed(tx) => {
193 let tx = &tx.transaction_2930_unsigned;
194 s.append(&tx.r#type.value());
195 s.append(tx);
196 },
197 }
198 let bytes = s.out().to_vec();
199 let signature = self.raw_signature()?;
200
201 recover_eth_address_from_message(&bytes, &signature)
202 }
203}
204
205#[cfg(test)]
206mod tests {
207 use super::*;
208 use crate::evm::TransactionUnsigned;
209
210 #[test]
211 fn sign_and_recover_work() {
212 let txs = [
213 "f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808026a07b2e762a17a71a46b422e60890a04512cf0d907ccf6b78b5bd6e6977efdc2bf5a01ea673d50bbe7c2236acb498ceb8346a8607c941f0b8cbcde7cf439aa9369f1f",
215 "01f89b0180808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a0c45a61b3d1d00169c649e7326e02857b850efb96e587db4b9aad29afc80d0752a070ae1eb47ab4097dbed2f19172ae286492621b46ac737ee6c32fb18a00c94c9c",
217 "02f89c018080018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080a055d72bbc3047d4b9d3e4b8099f187143202407746118204cc2e0cb0c85a68baea04f6ef08a1418c70450f53398d9f0f2d78d9e9d6b8a80cba886b67132c4a744f2",
219 "03f8bf018002018301e24194095e7baea6a6c7c4c2dfeb977efac326af552d878080f838f7940000000000000000000000000000000000000001e1a0000000000000000000000000000000000000000000000000000000000000000080e1a0000000000000000000000000000000000000000000000000000000000000000001a0672b8bac466e2cf1be3148c030988d40d582763ecebbc07700dfc93bb070d8a4a07c635887005b11cb58964c04669ac2857fa633aa66f662685dadfd8bcacb0f21",
221 ];
222 let account = Account::from_secret_key(hex_literal::hex!(
223 "a872f6cbd25a0e04a08b1e21098017a9e6194d101d75e13111f71410c59cd57f"
224 ));
225
226 for tx in txs {
227 let raw_tx = alloy_core::hex::decode(tx).unwrap();
228 let tx = TransactionSigned::decode(&raw_tx).unwrap();
229
230 let address = tx.recover_eth_address();
231 assert_eq!(address.unwrap(), account.address());
232
233 let unsigned = TransactionUnsigned::from_signed(tx.clone());
234 let signed = account.sign_transaction(unsigned);
235 assert_eq!(tx, signed);
236 }
237 }
238
239 #[test]
240 fn high_s_signature_is_rejected() {
241 let order: [u8; 32] = [
242 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff,
243 0xff, 0xfe, 0xba, 0xae, 0xdc, 0xe6, 0xaf, 0x48, 0xa0, 0x3b, 0xbf, 0xd2, 0x5e, 0x8c,
244 0xd0, 0x36, 0x41, 0x41,
245 ];
246
247 let raw_tx = alloy_core::hex::decode(
248 "f86080808301e24194095e7baea6a6c7c4c2dfeb977efac326af552d87808026a07b2e762a17a71a46b422e60890a04512cf0d907ccf6b78b5bd6e6977efdc2bf5a01ea673d50bbe7c2236acb498ceb8346a8607c941f0b8cbcde7cf439aa9369f1f",
249 )
250 .unwrap();
251 let tx = TransactionSigned::decode(&raw_tx).unwrap();
252
253 assert!(tx.recover_eth_address().is_ok());
255
256 let unsigned = TransactionUnsigned::from_signed(tx.clone());
257 let sig = tx.raw_signature().unwrap();
258
259 let s_bytes: [u8; 32] = sig[32..64].try_into().unwrap();
260 let mut s_prime = [0u8; 32];
261 let mut borrow = 0i16;
262 for i in (0..32).rev() {
263 let diff = order[i] as i16 - s_bytes[i] as i16 - borrow;
264 if diff < 0 {
265 s_prime[i] = (diff + 256) as u8;
266 borrow = 1;
267 } else {
268 s_prime[i] = diff as u8;
269 borrow = 0;
270 }
271 }
272
273 let mut malleable_sig = [0u8; 65];
274 malleable_sig[0..32].copy_from_slice(&sig[0..32]);
275 malleable_sig[32..64].copy_from_slice(&s_prime);
276 malleable_sig[64] = sig[64] ^ 1;
277
278 let malleable_tx = unsigned.with_signature(malleable_sig);
279
280 assert!(
282 malleable_tx.recover_eth_address().is_err(),
283 "high-S signature should be rejected per EIP-2"
284 );
285 }
286}