fp_account/
lib.rs

1// This file is part of Frontier.
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
18#![cfg_attr(not(feature = "std"), no_std)]
19#![warn(unused_crate_dependencies)]
20
21extern crate alloc;
22
23use alloc::string::{String, ToString};
24use core::fmt;
25
26use scale_codec::{Decode, DecodeWithMemTracking, Encode, MaxEncodedLen};
27use scale_info::TypeInfo;
28// Substrate
29use sp_core::{crypto::AccountId32, ecdsa, RuntimeDebug, H160, H256};
30use sp_io::hashing::keccak_256;
31use sp_runtime::MultiSignature;
32
33// Polkadot / XCM
34use xcm::latest::{Junction, Location};
35
36/// A fully Ethereum-compatible `AccountId`.
37/// Conforms to H160 address and ECDSA key standards.
38/// Alternative to H256->H160 mapping.
39#[derive(Copy, Clone, Eq, PartialEq, Ord, PartialOrd, Default, Hash)]
40#[derive(Encode, Decode, DecodeWithMemTracking, MaxEncodedLen, TypeInfo)]
41pub struct AccountId20(pub [u8; 20]);
42
43#[cfg(feature = "serde")]
44impl_serde::impl_fixed_hash_serde!(AccountId20, 20);
45
46#[cfg(feature = "std")]
47impl std::str::FromStr for AccountId20 {
48	type Err = &'static str;
49
50	fn from_str(s: &str) -> Result<Self, Self::Err> {
51		H160::from_str(s)
52			.map(Into::into)
53			.map_err(|_| "invalid hex address.")
54	}
55}
56
57impl fmt::Display for AccountId20 {
58	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
59		let address = hex::encode(self.0).trim_start_matches("0x").to_lowercase();
60		let address_hash = hex::encode(keccak_256(address.as_bytes()));
61
62		let checksum: String =
63			address
64				.char_indices()
65				.fold(String::from("0x"), |mut acc, (index, address_char)| {
66					let n = u16::from_str_radix(&address_hash[index..index + 1], 16)
67						.expect("Keccak256 hashed; qed");
68
69					if n > 7 {
70						// make char uppercase if ith character is 9..f
71						acc.push_str(&address_char.to_uppercase().to_string())
72					} else {
73						// already lowercased
74						acc.push(address_char)
75					}
76
77					acc
78				});
79		write!(f, "{checksum}")
80	}
81}
82
83impl fmt::Debug for AccountId20 {
84	fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
85		write!(f, "{:?}", H160(self.0))
86	}
87}
88
89impl From<[u8; 20]> for AccountId20 {
90	fn from(bytes: [u8; 20]) -> Self {
91		Self(bytes)
92	}
93}
94
95impl<'a> TryFrom<&'a [u8]> for AccountId20 {
96	type Error = ();
97	fn try_from(x: &'a [u8]) -> Result<AccountId20, ()> {
98		if x.len() == 20 {
99			let mut data = [0; 20];
100			data.copy_from_slice(x);
101			Ok(AccountId20(data))
102		} else {
103			Err(())
104		}
105	}
106}
107
108impl From<AccountId20> for [u8; 20] {
109	fn from(val: AccountId20) -> Self {
110		val.0
111	}
112}
113
114impl From<H160> for AccountId20 {
115	fn from(h160: H160) -> Self {
116		Self(h160.0)
117	}
118}
119
120impl From<AccountId20> for H160 {
121	fn from(val: AccountId20) -> Self {
122		H160(val.0)
123	}
124}
125
126impl AsRef<[u8]> for AccountId20 {
127	fn as_ref(&self) -> &[u8] {
128		&self.0[..]
129	}
130}
131
132impl AsMut<[u8]> for AccountId20 {
133	fn as_mut(&mut self) -> &mut [u8] {
134		&mut self.0[..]
135	}
136}
137
138impl AsRef<[u8; 20]> for AccountId20 {
139	fn as_ref(&self) -> &[u8; 20] {
140		&self.0
141	}
142}
143
144impl AsMut<[u8; 20]> for AccountId20 {
145	fn as_mut(&mut self) -> &mut [u8; 20] {
146		&mut self.0
147	}
148}
149
150impl From<ecdsa::Public> for AccountId20 {
151	fn from(pk: ecdsa::Public) -> Self {
152		let decompressed = libsecp256k1::PublicKey::parse_compressed(&pk.0)
153			.expect("Wrong compressed public key provided")
154			.serialize();
155		let mut m = [0u8; 64];
156		m.copy_from_slice(&decompressed[1..65]);
157		let account = H160::from(H256::from(keccak_256(&m)));
158		Self(account.into())
159	}
160}
161
162impl From<[u8; 32]> for AccountId20 {
163	fn from(bytes: [u8; 32]) -> Self {
164		let mut buffer = [0u8; 20];
165		buffer.copy_from_slice(&bytes[..20]);
166		Self(buffer)
167	}
168}
169
170impl From<AccountId32> for AccountId20 {
171	fn from(account: AccountId32) -> Self {
172		let bytes: &[u8; 32] = account.as_ref();
173		Self::from(*bytes)
174	}
175}
176
177impl From<AccountId20> for Location {
178	fn from(id: AccountId20) -> Self {
179		Junction::AccountKey20 {
180			network: None,
181			key: id.into(),
182		}
183		.into()
184	}
185}
186
187#[derive(Clone, Eq, PartialEq)]
188#[derive(
189	RuntimeDebug,
190	Encode,
191	Decode,
192	DecodeWithMemTracking,
193	MaxEncodedLen,
194	TypeInfo
195)]
196#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
197pub struct EthereumSignature(ecdsa::Signature);
198
199impl sp_runtime::traits::Verify for EthereumSignature {
200	type Signer = EthereumSigner;
201	fn verify<L: sp_runtime::traits::Lazy<[u8]>>(&self, mut msg: L, signer: &AccountId20) -> bool {
202		let m = keccak_256(msg.get());
203		match sp_io::crypto::secp256k1_ecdsa_recover(self.0.as_ref(), &m) {
204			Ok(pubkey) => AccountId20(H160::from(H256::from(keccak_256(&pubkey))).0) == *signer,
205			Err(sp_io::EcdsaVerifyError::BadRS) => {
206				log::error!(target: "evm", "Error recovering: Incorrect value of R or S");
207				false
208			}
209			Err(sp_io::EcdsaVerifyError::BadV) => {
210				log::error!(target: "evm", "Error recovering: Incorrect value of V");
211				false
212			}
213			Err(sp_io::EcdsaVerifyError::BadSignature) => {
214				log::error!(target: "evm", "Error recovering: Invalid signature");
215				false
216			}
217		}
218	}
219}
220
221impl From<MultiSignature> for EthereumSignature {
222	fn from(signature: MultiSignature) -> Self {
223		match signature {
224			MultiSignature::Ed25519(_) => {
225				panic!("Ed25519 not supported for EthereumSignature")
226			}
227			MultiSignature::Sr25519(_) => {
228				panic!("Sr25519 not supported for EthereumSignature")
229			}
230			MultiSignature::Ecdsa(sig) => Self(sig),
231		}
232	}
233}
234
235impl EthereumSignature {
236	pub fn new(s: ecdsa::Signature) -> Self {
237		Self(s)
238	}
239}
240
241#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
242#[derive(RuntimeDebug, Encode, Decode, MaxEncodedLen, TypeInfo)]
243#[repr(transparent)]
244pub struct EthereumSigner([u8; 20]);
245
246impl From<[u8; 20]> for EthereumSigner {
247	fn from(x: [u8; 20]) -> Self {
248		EthereumSigner(x)
249	}
250}
251
252impl sp_runtime::traits::IdentifyAccount for EthereumSigner {
253	type AccountId = AccountId20;
254	fn into_account(self) -> AccountId20 {
255		AccountId20(self.0)
256	}
257}
258
259impl fmt::Display for EthereumSigner {
260	fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
261		write!(fmt, "{:?}", H160::from(self.0))
262	}
263}
264
265impl From<ecdsa::Public> for EthereumSigner {
266	fn from(pk: ecdsa::Public) -> Self {
267		let decompressed = libsecp256k1::PublicKey::parse_compressed(&pk.0)
268			.expect("Wrong compressed public key provided")
269			.serialize();
270		let mut m = [0u8; 64];
271		m.copy_from_slice(&decompressed[1..65]);
272		let account = H160::from(H256::from(keccak_256(&m)));
273		EthereumSigner(account.into())
274	}
275}
276
277#[cfg(test)]
278mod tests {
279	use super::*;
280	use sp_core::{ecdsa, Pair, H256};
281	use sp_runtime::traits::IdentifyAccount;
282
283	#[test]
284	fn test_derive_from_secret_key() {
285		let sk = hex::decode("eb3d6b0b0c794f6fd8964b4a28df99d4baa5f9c8d33603c4cc62504daa259358")
286			.unwrap();
287		let hex_acc: [u8; 20] = hex::decode("98fa2838ee6471ae87135880f870a785318e6787")
288			.unwrap()
289			.try_into()
290			.unwrap();
291		let acc = AccountId20::from(hex_acc);
292
293		let pk = ecdsa::Pair::from_seed_slice(&sk).unwrap().public();
294		let signer: EthereumSigner = pk.into();
295
296		assert_eq!(signer.into_account(), acc);
297	}
298
299	#[test]
300	fn test_from_h160() {
301		let m = hex::decode("28490327ff4e60d44b8aadf5478266422ed01232cc712c2d617e5c650ca15b85")
302			.unwrap();
303		let old: AccountId20 = H160::from(H256::from(keccak_256(&m))).into();
304		let new: AccountId20 = H160::from_slice(&keccak_256(&m)[12..32]).into();
305		assert_eq!(new, old);
306	}
307
308	#[test]
309	fn test_account_display() {
310		let pk = ecdsa::Pair::from_string("//Alice", None)
311			.expect("static values are valid; qed")
312			.public();
313		let signer: EthereumSigner = pk.into();
314		let account: AccountId20 = signer.into_account();
315		let account_fmt = format!("{account}");
316		assert_eq!(account_fmt, "0xE04CC55ebEE1cBCE552f250e85c57B70B2E2625b");
317	}
318}