1
// Copyright 2019-2025 PureStake Inc.
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
//! The Ethereum Signature implementation.
18
//!
19
//! It includes the Verify and IdentifyAccount traits for the AccountId20
20

            
21
#![cfg_attr(not(feature = "std"), no_std)]
22

            
23
use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
24
use scale_info::TypeInfo;
25
use sha3::{Digest, Keccak256};
26
use sp_core::{ecdsa, H160};
27

            
28
pub use serde::{de::DeserializeOwned, Deserialize, Serialize};
29

            
30
//TODO Maybe this should be upstreamed into Frontier (And renamed accordingly) so that it can
31
// be used in palletEVM as well. It may also need more traits such as AsRef, AsMut, etc like
32
// AccountId32 has.
33

            
34
/// System account size in bytes = Pallet_Name_Hash (16) + Storage_name_hash (16) +
35
/// Blake2_128Concat (16) + AccountId (20) + AccountInfo (4 + 12 + AccountData (4* 16)) = 148
36
pub const SYSTEM_ACCOUNT_SIZE: u64 = 148;
37

            
38
/// The account type to be used in Moonbeam. It is a wrapper for 20 fixed bytes. We prefer to use
39
/// a dedicated type to prevent using arbitrary 20 byte arrays were AccountIds are expected. With
40
/// the introduction of the `scale-info` crate this benefit extends even to non-Rust tools like
41
/// Polkadot JS.
42

            
43
#[derive(
44
	Eq,
45
	PartialEq,
46
	Copy,
47
	Clone,
48
	Encode,
49
	Decode,
50
	TypeInfo,
51
	MaxEncodedLen,
52
	Default,
53
	PartialOrd,
54
	Ord,
55
	DecodeWithMemTracking,
56
)]
57
pub struct AccountId20(pub [u8; 20]);
58

            
59
impl_serde::impl_fixed_hash_serde!(AccountId20, 20);
60

            
61
#[cfg(feature = "std")]
62
impl std::fmt::Display for AccountId20 {
63
	//TODO This is a pretty quck-n-dirty implementation. Perhaps we should add
64
	// checksum casing here? I bet there is a crate for that.
65
	// Maybe this one https://github.com/miguelmota/rust-eth-checksum
66
	fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
67
		write!(f, "{:?}", self.0)
68
	}
69
}
70

            
71
impl core::fmt::Debug for AccountId20 {
72
720
	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
73
720
		write!(f, "{:?}", H160(self.0))
74
720
	}
75
}
76

            
77
impl From<[u8; 20]> for AccountId20 {
78
33882
	fn from(bytes: [u8; 20]) -> Self {
79
33882
		Self(bytes)
80
33882
	}
81
}
82

            
83
impl From<AccountId20> for [u8; 20] {
84
3186
	fn from(value: AccountId20) -> Self {
85
3186
		value.0
86
3186
	}
87
}
88

            
89
// NOTE: the implementation is lossy, and is intended to be used
90
// only to convert from Polkadot accounts to AccountId20.
91
// See https://github.com/moonbeam-foundation/moonbeam/pull/2315#discussion_r1205830577
92
// DO NOT USE IT FOR ANYTHING ELSE.
93
impl From<[u8; 32]> for AccountId20 {
94
1344
	fn from(bytes: [u8; 32]) -> Self {
95
1344
		let mut buffer = [0u8; 20];
96
1344
		buffer.copy_from_slice(&bytes[..20]);
97
1344
		Self(buffer)
98
1344
	}
99
}
100
impl From<sp_runtime::AccountId32> for AccountId20 {
101
	fn from(account: sp_runtime::AccountId32) -> Self {
102
		let bytes: &[u8; 32] = account.as_ref();
103
		Self::from(*bytes)
104
	}
105
}
106

            
107
impl From<H160> for AccountId20 {
108
109508
	fn from(h160: H160) -> Self {
109
109508
		Self(h160.0)
110
109508
	}
111
}
112

            
113
impl From<AccountId20> for H160 {
114
32092
	fn from(value: AccountId20) -> Self {
115
32092
		H160(value.0)
116
32092
	}
117
}
118

            
119
#[cfg(feature = "std")]
120
impl std::str::FromStr for AccountId20 {
121
	type Err = &'static str;
122
72
	fn from_str(input: &str) -> Result<Self, Self::Err> {
123
72
		H160::from_str(input)
124
72
			.map(Into::into)
125
72
			.map_err(|_| "invalid hex address.")
126
72
	}
127
}
128

            
129
#[derive(
130
	Eq,
131
	PartialEq,
132
	Clone,
133
	Encode,
134
	Decode,
135
	sp_core::RuntimeDebug,
136
	TypeInfo,
137
	Serialize,
138
	Deserialize,
139
	DecodeWithMemTracking,
140
)]
141

            
142
// Note: the inner `ecdsa::Signature` is blake2-tagged, but `EthereumSignature::verify`
143
// does its own keccak hashing and never calls the tag-dependent `.recover()` method.
144
// If that invariant changes, the inner type should be switched to `KeccakSignature`.
145
pub struct EthereumSignature(ecdsa::Signature);
146

            
147
impl From<ecdsa::Signature> for EthereumSignature {
148
	fn from(x: ecdsa::Signature) -> Self {
149
		EthereumSignature(x)
150
	}
151
}
152

            
153
impl From<sp_runtime::MultiSignature> for EthereumSignature {
154
	fn from(signature: sp_runtime::MultiSignature) -> Self {
155
		match signature {
156
			sp_runtime::MultiSignature::Ed25519(_) => {
157
				panic!("Ed25519 not supported for EthereumSignature")
158
			}
159
			sp_runtime::MultiSignature::Sr25519(_) => {
160
				panic!("Sr25519 not supported for EthereumSignature")
161
			}
162
			sp_runtime::MultiSignature::Ecdsa(sig) => Self(sig),
163
			// Same byte layout as `ecdsa::Signature` — see note on the struct.
164
			sp_runtime::MultiSignature::Eth(sig) => Self(ecdsa::Signature::from(sig.0)),
165
		}
166
	}
167
}
168

            
169
impl sp_runtime::traits::Verify for EthereumSignature {
170
	type Signer = EthereumSigner;
171
	fn verify<L: sp_runtime::traits::Lazy<[u8]>>(&self, mut msg: L, signer: &AccountId20) -> bool {
172
		let mut m = [0u8; 32];
173
		m.copy_from_slice(Keccak256::digest(msg.get()).as_slice());
174
		match sp_io::crypto::secp256k1_ecdsa_recover(self.0.as_ref(), &m) {
175
			Ok(pubkey) => {
176
				AccountId20(H160::from_slice(&Keccak256::digest(pubkey).as_slice()[12..32]).0)
177
					== *signer
178
			}
179
			Err(sp_io::EcdsaVerifyError::BadRS) => {
180
				log::error!(target: "evm", "Error recovering: Incorrect value of R or S");
181
				false
182
			}
183
			Err(sp_io::EcdsaVerifyError::BadV) => {
184
				log::error!(target: "evm", "Error recovering: Incorrect value of V");
185
				false
186
			}
187
			Err(sp_io::EcdsaVerifyError::BadSignature) => {
188
				log::error!(target: "evm", "Error recovering: Invalid signature");
189
				false
190
			}
191
		}
192
	}
193
}
194

            
195
/// Public key for an Ethereum / Moonbeam compatible account
196
#[derive(
197
	Eq,
198
	PartialEq,
199
	Ord,
200
	PartialOrd,
201
	Clone,
202
	Encode,
203
	Decode,
204
	sp_core::RuntimeDebug,
205
	TypeInfo,
206
	DecodeWithMemTracking,
207
)]
208
#[cfg_attr(feature = "std", derive(serde::Serialize, serde::Deserialize))]
209
pub struct EthereumSigner([u8; 20]);
210

            
211
impl sp_runtime::traits::IdentifyAccount for EthereumSigner {
212
	type AccountId = AccountId20;
213
2
	fn into_account(self) -> AccountId20 {
214
2
		AccountId20(self.0)
215
2
	}
216
}
217

            
218
impl From<[u8; 20]> for EthereumSigner {
219
	fn from(x: [u8; 20]) -> Self {
220
		EthereumSigner(x)
221
	}
222
}
223

            
224
impl From<ecdsa::Public> for EthereumSigner {
225
2
	fn from(x: ecdsa::Public) -> Self {
226
2
		let decompressed = libsecp256k1::PublicKey::parse_slice(
227
2
			&x.0,
228
2
			Some(libsecp256k1::PublicKeyFormat::Compressed),
229
2
		)
230
2
		.expect("Wrong compressed public key provided")
231
2
		.serialize();
232
2
		let mut m = [0u8; 64];
233
2
		m.copy_from_slice(&decompressed[1..65]);
234
2
		let account = H160::from_slice(&Keccak256::digest(m).as_slice()[12..32]);
235
2
		EthereumSigner(account.into())
236
2
	}
237
}
238

            
239
impl From<libsecp256k1::PublicKey> for EthereumSigner {
240
	fn from(x: libsecp256k1::PublicKey) -> Self {
241
		let mut m = [0u8; 64];
242
		m.copy_from_slice(&x.serialize()[1..65]);
243
		let account = H160::from_slice(&Keccak256::digest(m).as_slice()[12..32]);
244
		EthereumSigner(account.into())
245
	}
246
}
247

            
248
#[cfg(feature = "std")]
249
impl std::fmt::Display for EthereumSigner {
250
	fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
251
		write!(fmt, "ethereum signature: {:?}", H160::from_slice(&self.0))
252
	}
253
}
254

            
255
#[cfg(test)]
256
mod tests {
257
	use super::*;
258
	use sp_core::{ecdsa, Pair, H256};
259
	use sp_runtime::traits::IdentifyAccount;
260

            
261
	#[test]
262
1
	fn test_account_derivation_1() {
263
		// Test from https://asecuritysite.com/encryption/ethadd
264
1
		let secret_key =
265
1
			hex::decode("502f97299c472b88754accd412b7c9a6062ef3186fba0c0388365e1edec24875")
266
1
				.unwrap();
267
1
		let mut expected_hex_account = [0u8; 20];
268
1
		hex::decode_to_slice(
269
			"976f8456e4e2034179b284a23c0e0c8f6d3da50c",
270
1
			&mut expected_hex_account,
271
		)
272
1
		.expect("example data is 20 bytes of valid hex");
273

            
274
1
		let public_key = ecdsa::Pair::from_seed_slice(&secret_key).unwrap().public();
275
1
		let account: EthereumSigner = public_key.into();
276
1
		let expected_account = AccountId20::from(expected_hex_account);
277
1
		assert_eq!(account.into_account(), expected_account);
278
1
	}
279
	#[test]
280
1
	fn test_account_derivation_2() {
281
		// Test from https://asecuritysite.com/encryption/ethadd
282
1
		let secret_key =
283
1
			hex::decode("0f02ba4d7f83e59eaa32eae9c3c4d99b68ce76decade21cdab7ecce8f4aef81a")
284
1
				.unwrap();
285
1
		let mut expected_hex_account = [0u8; 20];
286
1
		hex::decode_to_slice(
287
			"420e9f260b40af7e49440cead3069f8e82a5230f",
288
1
			&mut expected_hex_account,
289
		)
290
1
		.expect("example data is 20 bytes of valid hex");
291

            
292
1
		let public_key = ecdsa::Pair::from_seed_slice(&secret_key).unwrap().public();
293
1
		let account: EthereumSigner = public_key.into();
294
1
		let expected_account = AccountId20::from(expected_hex_account);
295
1
		assert_eq!(account.into_account(), expected_account);
296
1
	}
297
	#[test]
298
1
	fn test_account_derivation_3() {
299
1
		let m = hex::decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
300
1
			.unwrap();
301
1
		let old = AccountId20(H160::from(H256::from_slice(Keccak256::digest(&m).as_slice())).0);
302
1
		let new = AccountId20(H160::from_slice(&Keccak256::digest(&m).as_slice()[12..32]).0);
303
1
		assert_eq!(new, old);
304
1
	}
305
}