common_primitives/
signatures.rs

1#![cfg_attr(not(feature = "std"), no_std)]
2#[cfg(feature = "serde")]
3use frame_support::{Deserialize, Serialize};
4use frame_support::{
5	__private::{codec, RuntimeDebug},
6	pallet_prelude::{Decode, Encode, MaxEncodedLen, TypeInfo},
7};
8use lazy_static::lazy_static;
9use parity_scale_codec::{alloc::string::ToString, DecodeWithMemTracking};
10use sp_core::{
11	bytes::from_hex,
12	crypto,
13	crypto::{AccountId32, FromEntropy},
14	ecdsa, ed25519,
15	hexdisplay::HexDisplay,
16	sr25519, ByteArray, H256,
17};
18use sp_runtime::{
19	traits,
20	traits::{Lazy, Verify},
21	MultiSignature,
22};
23extern crate alloc;
24use crate::{msa::H160, utils::to_abi_compatible_number};
25use alloc::boxed::Box;
26
27/// Ethereum message prefix eip-191
28const ETHEREUM_MESSAGE_PREFIX: &[u8; 26] = b"\x19Ethereum Signed Message:\n";
29
30/// A trait that allows mapping of raw bytes to AccountIds
31pub trait AccountAddressMapper<AccountId> {
32	/// mapping to the desired address
33	fn to_account_id(public_key_or_address: &[u8]) -> AccountId;
34
35	/// mapping to bytes of a public key or an address
36	fn to_bytes32(public_key_or_address: &[u8]) -> [u8; 32];
37
38	/// reverses an accountId to it's 20 byte ethereum address
39	fn to_ethereum_address(account_id: AccountId) -> H160;
40
41	/// returns whether `account_id` converts to a valid Ethereum address
42	fn is_ethereum_address(account_id: &AccountId) -> bool;
43}
44
45/// converting raw address bytes to 32 bytes Ethereum compatible addresses
46pub struct EthereumAddressMapper;
47
48impl AccountAddressMapper<AccountId32> for EthereumAddressMapper {
49	fn to_account_id(public_key_or_address: &[u8]) -> AccountId32 {
50		Self::to_bytes32(public_key_or_address).into()
51	}
52
53	/// In this function we are trying to convert different types of valid identifiers to valid
54	/// Substrate supported 32 bytes AccountIds
55	/// ref: <https://github.com/paritytech/polkadot-sdk/blob/79b28b3185d01f2e43e098b1f57372ed9df64adf/substrate/frame/revive/src/address.rs#L84-L90>
56	/// This function have 4 types of valid inputs
57	/// 1. 20 byte ETH address which gets appended with 12 bytes of 0xEE
58	/// 2. 32 byte address is returned unchanged
59	/// 3. 64 bytes Secp256k1 public key is converted to ETH address based on <https://asecuritysite.com/encryption/ethadd> and appended 12 bytes of 0xEE
60	/// 4. 65 bytes Secp256k1 public key is also converted to ETH address after skipping first byte and appended 12 bytes of 0xEE
61	///    Anything else is invalid and would return default (all zeros) 32 bytes.
62	fn to_bytes32(public_key_or_address: &[u8]) -> [u8; 32] {
63		let mut hashed = [0u8; 32];
64		match public_key_or_address.len() {
65			20 => {
66				hashed[..20].copy_from_slice(public_key_or_address);
67			},
68			32 => {
69				hashed[..].copy_from_slice(public_key_or_address);
70				return hashed;
71			},
72			64 => {
73				let hashed_full = sp_io::hashing::keccak_256(public_key_or_address);
74				// Copy bytes 12..32 (20 bytes) from hashed_full to the beginning of hashed
75				hashed[..20].copy_from_slice(&hashed_full[12..]);
76			},
77			65 => {
78				let hashed_full = sp_io::hashing::keccak_256(&public_key_or_address[1..]);
79				// Copy bytes 12..32 (20 bytes) from hashed_full to the beginning of hashed
80				hashed[..20].copy_from_slice(&hashed_full[12..]);
81			},
82			_ => {
83				log::error!("Invalid public key size provided for {public_key_or_address:?}");
84				return [0u8; 32];
85			},
86		};
87
88		// Fill the rest (12 bytes) with 0xEE
89		hashed[20..].fill(0xEE);
90		hashed
91	}
92
93	fn to_ethereum_address(account_id: AccountId32) -> H160 {
94		let mut eth_address = [0u8; 20];
95		if Self::is_ethereum_address(&account_id) {
96			eth_address[..].copy_from_slice(&account_id.as_slice()[0..20]);
97		} else {
98			log::error!("Incompatible ethereum account id is provided {account_id:?}");
99		}
100		eth_address.into()
101	}
102
103	fn is_ethereum_address(account_id: &AccountId32) -> bool {
104		account_id.as_slice()[20..] == *[0xEE; 12].as_slice()
105	}
106}
107
108/// Signature verify that can work with any known signature types.
109#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
110#[derive(
111	Eq,
112	PartialEq,
113	Clone,
114	Encode,
115	Decode,
116	DecodeWithMemTracking,
117	MaxEncodedLen,
118	RuntimeDebug,
119	TypeInfo,
120)]
121pub enum UnifiedSignature {
122	/// An Ed25519 signature.
123	Ed25519(ed25519::Signature),
124	/// An Sr25519 signature.
125	Sr25519(sr25519::Signature),
126	/// An ECDSA/SECP256k1 signature compatible with Ethereum
127	Ecdsa(ecdsa::Signature),
128}
129
130impl From<ed25519::Signature> for UnifiedSignature {
131	fn from(x: ed25519::Signature) -> Self {
132		Self::Ed25519(x)
133	}
134}
135
136impl TryFrom<UnifiedSignature> for ed25519::Signature {
137	type Error = ();
138	fn try_from(m: UnifiedSignature) -> Result<Self, Self::Error> {
139		if let UnifiedSignature::Ed25519(x) = m {
140			Ok(x)
141		} else {
142			Err(())
143		}
144	}
145}
146
147impl From<sr25519::Signature> for UnifiedSignature {
148	fn from(x: sr25519::Signature) -> Self {
149		Self::Sr25519(x)
150	}
151}
152
153impl TryFrom<UnifiedSignature> for sr25519::Signature {
154	type Error = ();
155	fn try_from(m: UnifiedSignature) -> Result<Self, Self::Error> {
156		if let UnifiedSignature::Sr25519(x) = m {
157			Ok(x)
158		} else {
159			Err(())
160		}
161	}
162}
163
164impl From<ecdsa::Signature> for UnifiedSignature {
165	fn from(x: ecdsa::Signature) -> Self {
166		Self::Ecdsa(x)
167	}
168}
169
170impl TryFrom<UnifiedSignature> for ecdsa::Signature {
171	type Error = ();
172	fn try_from(m: UnifiedSignature) -> Result<Self, Self::Error> {
173		if let UnifiedSignature::Ecdsa(x) = m {
174			Ok(x)
175		} else {
176			Err(())
177		}
178	}
179}
180
181impl Verify for UnifiedSignature {
182	type Signer = UnifiedSigner;
183	fn verify<L: Lazy<[u8]>>(&self, msg: L, signer: &AccountId32) -> bool {
184		match (self, signer) {
185			(Self::Ed25519(ref sig), who) => match ed25519::Public::from_slice(who.as_ref()) {
186				Ok(signer) => sig.verify(msg, &signer),
187				Err(()) => false,
188			},
189			(Self::Sr25519(ref sig), who) => match sr25519::Public::from_slice(who.as_ref()) {
190				Ok(signer) => sig.verify(msg, &signer),
191				Err(()) => false,
192			},
193			(Self::Ecdsa(ref sig), who) => check_ethereum_signature(sig, msg, who),
194		}
195	}
196}
197
198/// Public key for any known crypto algorithm.
199#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
200#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
201pub enum UnifiedSigner {
202	/// An Ed25519 identity.
203	Ed25519(ed25519::Public),
204	/// An Sr25519 identity.
205	Sr25519(sr25519::Public),
206	/// An SECP256k1/ECDSA identity (12 bytes of zeros + 20 bytes of ethereum address).
207	Ecdsa(ecdsa::Public),
208}
209
210impl FromEntropy for UnifiedSigner {
211	fn from_entropy(input: &mut impl codec::Input) -> Result<Self, codec::Error> {
212		Ok(match input.read_byte()? % 3 {
213			0 => Self::Ed25519(FromEntropy::from_entropy(input)?),
214			1 => Self::Sr25519(FromEntropy::from_entropy(input)?),
215			2.. => Self::Ecdsa(FromEntropy::from_entropy(input)?),
216		})
217	}
218}
219
220/// NOTE: This implementations is required by `SimpleAddressDeterminer`,
221/// we convert the hash into some AccountId, it's fine to use any scheme.
222impl<T: Into<H256>> crypto::UncheckedFrom<T> for UnifiedSigner {
223	fn unchecked_from(x: T) -> Self {
224		ed25519::Public::unchecked_from(x.into()).into()
225	}
226}
227
228impl AsRef<[u8]> for UnifiedSigner {
229	fn as_ref(&self) -> &[u8] {
230		match *self {
231			Self::Ed25519(ref who) => who.as_ref(),
232			Self::Sr25519(ref who) => who.as_ref(),
233			Self::Ecdsa(ref who) => who.as_ref(),
234		}
235	}
236}
237
238impl traits::IdentifyAccount for UnifiedSigner {
239	type AccountId = AccountId32;
240	fn into_account(self) -> AccountId32 {
241		match self {
242			Self::Ed25519(who) => <[u8; 32]>::from(who).into(),
243			Self::Sr25519(who) => <[u8; 32]>::from(who).into(),
244			Self::Ecdsa(who) => {
245				let decompressed_result = libsecp256k1::PublicKey::parse_slice(
246					who.as_ref(),
247					Some(libsecp256k1::PublicKeyFormat::Compressed),
248				);
249				match decompressed_result {
250					Ok(public_key) => {
251						// calculating ethereum address compatible with `pallet-revive`
252						let decompressed = public_key.serialize();
253						EthereumAddressMapper::to_account_id(&decompressed)
254					},
255					Err(_) => {
256						log::error!("Invalid compressed public key provided");
257						AccountId32::new([0u8; 32])
258					},
259				}
260			},
261		}
262	}
263}
264
265impl From<ed25519::Public> for UnifiedSigner {
266	fn from(x: ed25519::Public) -> Self {
267		Self::Ed25519(x)
268	}
269}
270
271impl TryFrom<UnifiedSigner> for ed25519::Public {
272	type Error = ();
273	fn try_from(m: UnifiedSigner) -> Result<Self, Self::Error> {
274		if let UnifiedSigner::Ed25519(x) = m {
275			Ok(x)
276		} else {
277			Err(())
278		}
279	}
280}
281
282impl From<sr25519::Public> for UnifiedSigner {
283	fn from(x: sr25519::Public) -> Self {
284		Self::Sr25519(x)
285	}
286}
287
288impl TryFrom<UnifiedSigner> for sr25519::Public {
289	type Error = ();
290	fn try_from(m: UnifiedSigner) -> Result<Self, Self::Error> {
291		if let UnifiedSigner::Sr25519(x) = m {
292			Ok(x)
293		} else {
294			Err(())
295		}
296	}
297}
298
299impl From<ecdsa::Public> for UnifiedSigner {
300	fn from(x: ecdsa::Public) -> Self {
301		Self::Ecdsa(x)
302	}
303}
304
305impl TryFrom<UnifiedSigner> for ecdsa::Public {
306	type Error = ();
307	fn try_from(m: UnifiedSigner) -> Result<Self, Self::Error> {
308		if let UnifiedSigner::Ecdsa(x) = m {
309			Ok(x)
310		} else {
311			Err(())
312		}
313	}
314}
315
316#[cfg(feature = "std")]
317impl std::fmt::Display for UnifiedSigner {
318	fn fmt(&self, fmt: &mut std::fmt::Formatter) -> std::fmt::Result {
319		match *self {
320			Self::Ed25519(ref who) => write!(fmt, "ed25519: {who:?}"),
321			Self::Sr25519(ref who) => write!(fmt, "sr25519: {who:?}"),
322			Self::Ecdsa(ref who) => write!(fmt, "ecdsa: {who:?}"),
323		}
324	}
325}
326
327impl Into<UnifiedSignature> for MultiSignature {
328	fn into(self: MultiSignature) -> UnifiedSignature {
329		match self {
330			MultiSignature::Ed25519(who) => UnifiedSignature::Ed25519(who),
331			MultiSignature::Sr25519(who) => UnifiedSignature::Sr25519(who),
332			MultiSignature::Ecdsa(who) => UnifiedSignature::Ecdsa(who),
333		}
334	}
335}
336
337fn check_secp256k1_signature(signature: &[u8; 65], msg: &[u8; 32], signer: &AccountId32) -> bool {
338	match sp_io::crypto::secp256k1_ecdsa_recover(signature, msg) {
339		Ok(pubkey) => {
340			let hashed = EthereumAddressMapper::to_bytes32(&pubkey);
341			log::debug!(target:"ETHEREUM", "eth hashed={:?} signer={:?}",
342				HexDisplay::from(&hashed),HexDisplay::from(<dyn AsRef<[u8; 32]>>::as_ref(signer)),
343			);
344			&hashed == <dyn AsRef<[u8; 32]>>::as_ref(signer)
345		},
346		_ => false,
347	}
348}
349
350fn eth_message_hash(message: &[u8]) -> [u8; 32] {
351	let only_len = (message.len() as u32).to_string().into_bytes();
352	let concatenated = [ETHEREUM_MESSAGE_PREFIX.as_slice(), only_len.as_slice(), message].concat();
353	log::debug!(target:"ETHEREUM", "prefixed {concatenated:?}");
354	sp_io::hashing::keccak_256(concatenated.as_slice())
355}
356
357fn check_ethereum_signature<L: Lazy<[u8]>>(
358	signature: &ecdsa::Signature,
359	mut msg: L,
360	signer: &AccountId32,
361) -> bool {
362	let verify_signature = |signature: &[u8; 65], payload: &[u8; 32], signer: &AccountId32| {
363		check_secp256k1_signature(signature, payload, signer)
364	};
365
366	// signature of ethereum prefixed message eip-191
367	let message_prefixed = eth_message_hash(msg.get());
368	if verify_signature(signature.as_ref(), &message_prefixed, signer) {
369		return true
370	}
371
372	// PolkadotJs raw payload signatures
373	// or Ethereum based EIP-712 compatible signatures
374	let hashed = sp_io::hashing::keccak_256(msg.get());
375	verify_signature(signature.as_ref(), &hashed, signer)
376}
377
378/// returns the ethereum encoded prefix and domain separator for EIP-712 signatures
379pub fn get_eip712_encoding_prefix(verifier_contract_address: &str, chain_id: u32) -> Box<[u8]> {
380	lazy_static! {
381		// domain separator
382		static ref DOMAIN_TYPE_HASH: [u8; 32] = sp_io::hashing::keccak_256(
383			b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)",
384		);
385
386		static ref DOMAIN_NAME: [u8; 32] = sp_io::hashing::keccak_256(b"Frequency");
387		static ref DOMAIN_VERSION: [u8; 32] = sp_io::hashing::keccak_256(b"1");
388	}
389	let compatible_chain_id: [u8; 32] = to_abi_compatible_number(chain_id);
390	let verifier_contract: [u8; 20] = from_hex(verifier_contract_address)
391		.unwrap_or_default()
392		.try_into()
393		.unwrap_or_default();
394
395	// eip-712 prefix 0x1901
396	let eip_712_prefix = [25, 1];
397
398	let mut zero_prefixed_verifier_contract = [0u8; 32];
399	zero_prefixed_verifier_contract[12..].copy_from_slice(&verifier_contract);
400
401	let domain_separator = sp_io::hashing::keccak_256(
402		&[
403			DOMAIN_TYPE_HASH.as_slice(),
404			DOMAIN_NAME.as_slice(),
405			DOMAIN_VERSION.as_slice(),
406			compatible_chain_id.as_slice(),
407			&zero_prefixed_verifier_contract,
408		]
409		.concat(),
410	);
411	let combined = [eip_712_prefix.as_slice(), domain_separator.as_slice()].concat();
412	combined.into_boxed_slice()
413}
414
415#[cfg(test)]
416mod tests {
417	use crate::{
418		handles::ClaimHandlePayload,
419		node::EIP712Encode,
420		signatures::{UnifiedSignature, UnifiedSigner},
421	};
422	use impl_serde::serialize::from_hex;
423	use sp_core::{ecdsa, sr25519, Pair};
424	use sp_runtime::{
425		traits::{IdentifyAccount, Verify},
426		AccountId32,
427	};
428
429	use super::{AccountAddressMapper, EthereumAddressMapper};
430
431	#[test]
432	fn polkadot_ecdsa_should_not_work_due_to_using_wrong_hash() {
433		let msg = &b"test-message"[..];
434		let (pair, _) = ecdsa::Pair::generate();
435
436		let signature = pair.sign(msg);
437		let unified_sig = UnifiedSignature::from(signature);
438		let unified_signer = UnifiedSigner::from(pair.public());
439		assert!(!unified_sig.verify(msg, &unified_signer.into_account()));
440	}
441
442	#[test]
443	fn ethereum_prefixed_eip191_signatures_should_work() {
444		// payload is random and the signature is generated over that payload by a standard EIP-191 signer
445		let payload = b"test eip-191 message payload";
446		let signature_raw = from_hex("0x276dcc9c69da24dd8441ba3acc9b60d8aae0cb39f0bc5ad92c723a31bf11575031d860978280191a0a97a1f74336ca0c79a8b1b3aab013fb58a27f113b73b2081b").expect("Should convert");
447		let unified_signature = UnifiedSignature::from(ecdsa::Signature::from_raw(
448			signature_raw.try_into().expect("should convert"),
449		));
450
451		let public_key = ecdsa::Public::from_raw(
452			from_hex("0x03be5b145e12c5fb95151374ed919eb445ade57637d729dd2d73bf161d4bc10329")
453				.expect("should convert")
454				.try_into()
455				.expect("invalid size"),
456		);
457		let unified_signer = UnifiedSigner::from(public_key);
458		assert!(unified_signature.verify(&payload[..], &unified_signer.into_account()));
459	}
460
461	#[test]
462	fn ethereum_raw_signatures_should_work() {
463		// payload is random and the signature is generated over that payload by PolkadotJs and ethereum keypair
464		let payload = from_hex("0x0a0300e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e028c7d0a3500000000830000000100000026c1147602cf6557f4e0068a78cd4b22b6f6b03e106d05618cde8537e4ffe454b63f7774106903a22684c02eeebe2fdc903ac945bf25962fd9d05e7e0ddfb44f00").expect("Should convert");
465		let signature_raw = from_hex("0xd740c8294967b36236c5e05861a55bad75d0866c4a6f63d4918a39769a9582b872299a3411cc0f31b5f631261d669fc21ce427ee23999a91df5f0e74dfbbfc6c00").expect("Should convert");
466		let unified_signature = UnifiedSignature::from(ecdsa::Signature::from_raw(
467			signature_raw.try_into().expect("should convert"),
468		));
469
470		let public_key = ecdsa::Public::from_raw(
471			from_hex("0x025b107c7f38d5ac7d618e626f9fa57eec683adf373b1352cd20e5e5c684747079")
472				.expect("should convert")
473				.try_into()
474				.expect("invalid size"),
475		);
476		let unified_signer = UnifiedSigner::from(public_key);
477		assert!(unified_signature.verify(&payload[..], &unified_signer.into_account()));
478	}
479
480	#[test]
481	fn ethereum_eip712_signatures_for_claim_handle_payload_should_work() {
482		let payload = ClaimHandlePayload { base_handle: b"Alice".to_vec(), expiration: 100u32 };
483		let encoded_payload = payload.encode_eip_712(420420420u32);
484
485		// following signature is generated via Metamask using the same input to check compatibility
486		let signature_raw = from_hex("0x832d1f6870118f5fc6e3cc314152b87dc452bd607581f16b1e39142b553260f8397e80c9f7733aecf1bd46d4e84ad333c648e387b069fa93b4b1ca4fa0fd406b1c").expect("Should convert");
487		let unified_signature = UnifiedSignature::from(ecdsa::Signature::from_raw(
488			signature_raw.try_into().expect("should convert"),
489		));
490
491		// Non-compressed public key associated with the keypair used in Metamask
492		// 0x509540919faacf9ab52146c9aa40db68172d83777250b28e4679176e49ccdd9fa213197dc0666e85529d6c9dda579c1295d61c417f01505765481e89a4016f02
493		let public_key = ecdsa::Public::from_raw(
494			from_hex("0x02509540919faacf9ab52146c9aa40db68172d83777250b28e4679176e49ccdd9f")
495				.expect("should convert")
496				.try_into()
497				.expect("invalid size"),
498		);
499		let unified_signer = UnifiedSigner::from(public_key);
500		assert!(unified_signature.verify(&encoded_payload[..], &unified_signer.into_account()));
501	}
502
503	#[test]
504	fn ethereum_invalid_signatures_should_fail() {
505		let payload = from_hex("0x0a0300e659a7a1628cdd93febc04a4e0646ea20e9f5f0ce097d9a05290d4a9e054df4e028c7d0a3500000000830000000100000026c1147602cf6557f4e0068a78cd4b22b6f6b03e106d05618cde8537e4ffe4548de1bcb12a1d42e58b218a7abb03cb629111625cf3449640d837c5aa98b87d8e00").expect("Should convert");
506		let signature_raw = from_hex("0x9633e747bcd951bdb9d98ff84c65562e1f62bd059c578a942859e1695f2472aa0dbaab48c28f6dbc795baa73c27252d97e8dc2170fd7d69694d5cd1863fb968c01").expect("Should convert");
507		let unified_signature = UnifiedSignature::from(ecdsa::Signature::from_raw(
508			signature_raw.try_into().expect("should convert"),
509		));
510
511		let public_key = ecdsa::Public::from_raw(
512			from_hex("0x025b107c7f38d5ac7d618e626f9fa57eec683adf373b1352cd20e5e5c684747079")
513				.expect("should convert")
514				.try_into()
515				.expect("invalid size"),
516		);
517		let unified_signer = UnifiedSigner::from(public_key);
518		assert!(!unified_signature.verify(&payload[..], &unified_signer.into_account()));
519	}
520
521	#[test]
522	fn ethereum_address_mapper_should_work_as_expected_for_eth_20_bytes_addresses() {
523		// arrange
524		let eth = from_hex("0x1111111111111111111111111111111111111111").expect("should work");
525
526		// act
527		let account_id = EthereumAddressMapper::to_account_id(&eth);
528		let bytes = EthereumAddressMapper::to_bytes32(&eth);
529		let reversed = EthereumAddressMapper::to_ethereum_address(account_id.clone());
530
531		// assert
532		let expected_address =
533			from_hex("0x1111111111111111111111111111111111111111eeeeeeeeeeeeeeeeeeeeeeee")
534				.expect("should be hex");
535		assert_eq!(account_id, AccountId32::new(expected_address.clone().try_into().unwrap()));
536		assert_eq!(bytes.to_vec(), expected_address);
537		assert_eq!(reversed.0.to_vec(), eth);
538	}
539
540	#[test]
541	fn ethereum_address_mapper_should_return_the_same_value_for_32_byte_addresses() {
542		// arrange
543		let eth = from_hex("0x1111111111111111111111111111111111111111111111111111111111111111")
544			.expect("should work");
545
546		// act
547		let account_id = EthereumAddressMapper::to_account_id(&eth);
548		let bytes = EthereumAddressMapper::to_bytes32(&eth);
549
550		// assert
551		assert_eq!(account_id, AccountId32::new(eth.clone().try_into().unwrap()));
552		assert_eq!(bytes.to_vec(), eth);
553	}
554
555	#[test]
556	fn ethereum_address_mapper_should_return_the_ethereum_address_with_suffixes_for_64_byte_public_keys(
557	) {
558		// arrange
559		let public_key= from_hex("0x15b5e4aeac2086ee96ab2292ee2720da0b2d3c43b5c699ccdbfd38387e2f71dc167075a80a32fe2c78d7d8780ef1b2095810f12001fa2fcedcd1ffb0aa2ee2c7").expect("should work");
560
561		// act
562		let account_id = EthereumAddressMapper::to_account_id(&public_key);
563		let bytes = EthereumAddressMapper::to_bytes32(&public_key);
564
565		// assert
566		// 0x917B536617B0A42B2ABE85AC88788825F29F0B29 is eth address associated with above public_key
567		let expected_address =
568			from_hex("0x917B536617B0A42B2ABE85AC88788825F29F0B29eeeeeeeeeeeeeeeeeeeeeeee")
569				.expect("should be hex");
570		assert_eq!(account_id, AccountId32::new(expected_address.clone().try_into().unwrap()));
571		assert_eq!(bytes.to_vec(), expected_address);
572	}
573
574	#[test]
575	fn ethereum_address_mapper_should_return_the_ethereum_address_with_suffixes_for_65_byte_public_keys(
576	) {
577		// arrange
578		let public_key= from_hex("0x0415b5e4aeac2086ee96ab2292ee2720da0b2d3c43b5c699ccdbfd38387e2f71dc167075a80a32fe2c78d7d8780ef1b2095810f12001fa2fcedcd1ffb0aa2ee2c7").expect("should work");
579
580		// act
581		let account_id = EthereumAddressMapper::to_account_id(&public_key);
582		let bytes = EthereumAddressMapper::to_bytes32(&public_key);
583
584		// assert
585		// 0x917B536617B0A42B2ABE85AC88788825F29F0B29 is eth address associated with above public_key
586		let expected_address =
587			from_hex("0x917B536617B0A42B2ABE85AC88788825F29F0B29eeeeeeeeeeeeeeeeeeeeeeee")
588				.expect("should be hex");
589		assert_eq!(account_id, AccountId32::new(expected_address.clone().try_into().unwrap()));
590		assert_eq!(bytes.to_vec(), expected_address);
591	}
592
593	#[test]
594	fn ethereum_address_mapper_should_return_the_default_zero_values_for_any_invalid_length() {
595		// arrange
596		let public_key = from_hex(
597			"0x010415b5e4aeac2086ee96ab2292ee2720da0b2d3c43b5c699ccdbfd38387e2f71dc167075a801",
598		)
599		.expect("should work");
600
601		// act
602		let account_id = EthereumAddressMapper::to_account_id(&public_key);
603		let bytes = EthereumAddressMapper::to_bytes32(&public_key);
604
605		// assert
606		let expected_address = vec![0u8; 32]; // zero default values
607		assert_eq!(account_id, AccountId32::new(expected_address.clone().try_into().unwrap()));
608		assert_eq!(bytes.to_vec(), expected_address);
609	}
610
611	#[test]
612	fn ethereum_address_mapper_is_ethereum_address_correctly_detects() {
613		let valid_eth_address =
614			from_hex("0x917B536617B0A42B2ABE85AC88788825F29F0B29eeeeeeeeeeeeeeeeeeeeeeee")
615				.expect("should be hex");
616		let valid_addr32 = AccountId32::new(valid_eth_address.clone().try_into().unwrap());
617
618		assert!(EthereumAddressMapper::is_ethereum_address(&valid_addr32));
619
620		let (pair, _) = sr25519::Pair::generate();
621		let random_addr32 = AccountId32::from(pair.public());
622		assert!(!EthereumAddressMapper::is_ethereum_address(&random_addr32));
623	}
624}