account/
lib.rs

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
23use parity_scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
24use scale_info::TypeInfo;
25use sha3::{Digest, Keccak256};
26use sp_core::{ecdsa, H160};
27
28pub 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
36pub 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)]
57pub struct AccountId20(pub [u8; 20]);
58
59impl_serde::impl_fixed_hash_serde!(AccountId20, 20);
60
61#[cfg(feature = "std")]
62impl 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
71impl core::fmt::Debug for AccountId20 {
72	fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
73		write!(f, "{:?}", H160(self.0))
74	}
75}
76
77impl From<[u8; 20]> for AccountId20 {
78	fn from(bytes: [u8; 20]) -> Self {
79		Self(bytes)
80	}
81}
82
83impl From<AccountId20> for [u8; 20] {
84	fn from(value: AccountId20) -> Self {
85		value.0
86	}
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.
93impl From<[u8; 32]> for AccountId20 {
94	fn from(bytes: [u8; 32]) -> Self {
95		let mut buffer = [0u8; 20];
96		buffer.copy_from_slice(&bytes[..20]);
97		Self(buffer)
98	}
99}
100impl 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
107impl From<H160> for AccountId20 {
108	fn from(h160: H160) -> Self {
109		Self(h160.0)
110	}
111}
112
113impl From<AccountId20> for H160 {
114	fn from(value: AccountId20) -> Self {
115		H160(value.0)
116	}
117}
118
119#[cfg(feature = "std")]
120impl std::str::FromStr for AccountId20 {
121	type Err = &'static str;
122	fn from_str(input: &str) -> Result<Self, Self::Err> {
123		H160::from_str(input)
124			.map(Into::into)
125			.map_err(|_| "invalid hex address.")
126	}
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`.
145pub struct EthereumSignature(ecdsa::Signature);
146
147impl From<ecdsa::Signature> for EthereumSignature {
148	fn from(x: ecdsa::Signature) -> Self {
149		EthereumSignature(x)
150	}
151}
152
153impl 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
169impl 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))]
209pub struct EthereumSigner([u8; 20]);
210
211impl sp_runtime::traits::IdentifyAccount for EthereumSigner {
212	type AccountId = AccountId20;
213	fn into_account(self) -> AccountId20 {
214		AccountId20(self.0)
215	}
216}
217
218impl From<[u8; 20]> for EthereumSigner {
219	fn from(x: [u8; 20]) -> Self {
220		EthereumSigner(x)
221	}
222}
223
224impl From<ecdsa::Public> for EthereumSigner {
225	fn from(x: ecdsa::Public) -> Self {
226		let decompressed = libsecp256k1::PublicKey::parse_slice(
227			&x.0,
228			Some(libsecp256k1::PublicKeyFormat::Compressed),
229		)
230		.expect("Wrong compressed public key provided")
231		.serialize();
232		let mut m = [0u8; 64];
233		m.copy_from_slice(&decompressed[1..65]);
234		let account = H160::from_slice(&Keccak256::digest(m).as_slice()[12..32]);
235		EthereumSigner(account.into())
236	}
237}
238
239impl 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")]
249impl 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)]
256mod tests {
257	use super::*;
258	use sp_core::{ecdsa, Pair, H256};
259	use sp_runtime::traits::IdentifyAccount;
260
261	#[test]
262	fn test_account_derivation_1() {
263		// Test from https://asecuritysite.com/encryption/ethadd
264		let secret_key =
265			hex::decode("502f97299c472b88754accd412b7c9a6062ef3186fba0c0388365e1edec24875")
266				.unwrap();
267		let mut expected_hex_account = [0u8; 20];
268		hex::decode_to_slice(
269			"976f8456e4e2034179b284a23c0e0c8f6d3da50c",
270			&mut expected_hex_account,
271		)
272		.expect("example data is 20 bytes of valid hex");
273
274		let public_key = ecdsa::Pair::from_seed_slice(&secret_key).unwrap().public();
275		let account: EthereumSigner = public_key.into();
276		let expected_account = AccountId20::from(expected_hex_account);
277		assert_eq!(account.into_account(), expected_account);
278	}
279	#[test]
280	fn test_account_derivation_2() {
281		// Test from https://asecuritysite.com/encryption/ethadd
282		let secret_key =
283			hex::decode("0f02ba4d7f83e59eaa32eae9c3c4d99b68ce76decade21cdab7ecce8f4aef81a")
284				.unwrap();
285		let mut expected_hex_account = [0u8; 20];
286		hex::decode_to_slice(
287			"420e9f260b40af7e49440cead3069f8e82a5230f",
288			&mut expected_hex_account,
289		)
290		.expect("example data is 20 bytes of valid hex");
291
292		let public_key = ecdsa::Pair::from_seed_slice(&secret_key).unwrap().public();
293		let account: EthereumSigner = public_key.into();
294		let expected_account = AccountId20::from(expected_hex_account);
295		assert_eq!(account.into_account(), expected_account);
296	}
297	#[test]
298	fn test_account_derivation_3() {
299		let m = hex::decode("c5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470")
300			.unwrap();
301		let old = AccountId20(H160::from(H256::from_slice(Keccak256::digest(&m).as_slice())).0);
302		let new = AccountId20(H160::from_slice(&Keccak256::digest(&m).as_slice()[12..32]).0);
303		assert_eq!(new, old);
304	}
305}