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
27const ETHEREUM_MESSAGE_PREFIX: &[u8; 26] = b"\x19Ethereum Signed Message:\n";
29
30pub trait AccountAddressMapper<AccountId> {
32 fn to_account_id(public_key_or_address: &[u8]) -> AccountId;
34
35 fn to_bytes32(public_key_or_address: &[u8]) -> [u8; 32];
37
38 fn to_ethereum_address(account_id: AccountId) -> H160;
40
41 fn is_ethereum_address(account_id: &AccountId) -> bool;
43}
44
45pub 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 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 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 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 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#[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 Ed25519(ed25519::Signature),
124 Sr25519(sr25519::Signature),
126 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#[derive(Eq, PartialEq, Ord, PartialOrd, Clone, Encode, Decode, RuntimeDebug, TypeInfo)]
200#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
201pub enum UnifiedSigner {
202 Ed25519(ed25519::Public),
204 Sr25519(sr25519::Public),
206 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
220impl<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 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 let message_prefixed = eth_message_hash(msg.get());
368 if verify_signature(signature.as_ref(), &message_prefixed, signer) {
369 return true
370 }
371
372 let hashed = sp_io::hashing::keccak_256(msg.get());
375 verify_signature(signature.as_ref(), &hashed, signer)
376}
377
378pub fn get_eip712_encoding_prefix(verifier_contract_address: &str, chain_id: u32) -> Box<[u8]> {
380 lazy_static! {
381 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 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 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 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 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 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 let eth = from_hex("0x1111111111111111111111111111111111111111").expect("should work");
525
526 let account_id = EthereumAddressMapper::to_account_id(ð);
528 let bytes = EthereumAddressMapper::to_bytes32(ð);
529 let reversed = EthereumAddressMapper::to_ethereum_address(account_id.clone());
530
531 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 let eth = from_hex("0x1111111111111111111111111111111111111111111111111111111111111111")
544 .expect("should work");
545
546 let account_id = EthereumAddressMapper::to_account_id(ð);
548 let bytes = EthereumAddressMapper::to_bytes32(ð);
549
550 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 let public_key= from_hex("0x15b5e4aeac2086ee96ab2292ee2720da0b2d3c43b5c699ccdbfd38387e2f71dc167075a80a32fe2c78d7d8780ef1b2095810f12001fa2fcedcd1ffb0aa2ee2c7").expect("should work");
560
561 let account_id = EthereumAddressMapper::to_account_id(&public_key);
563 let bytes = EthereumAddressMapper::to_bytes32(&public_key);
564
565 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 let public_key= from_hex("0x0415b5e4aeac2086ee96ab2292ee2720da0b2d3c43b5c699ccdbfd38387e2f71dc167075a80a32fe2c78d7d8780ef1b2095810f12001fa2fcedcd1ffb0aa2ee2c7").expect("should work");
579
580 let account_id = EthereumAddressMapper::to_account_id(&public_key);
582 let bytes = EthereumAddressMapper::to_bytes32(&public_key);
583
584 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 let public_key = from_hex(
597 "0x010415b5e4aeac2086ee96ab2292ee2720da0b2d3c43b5c699ccdbfd38387e2f71dc167075a801",
598 )
599 .expect("should work");
600
601 let account_id = EthereumAddressMapper::to_account_id(&public_key);
603 let bytes = EthereumAddressMapper::to_bytes32(&public_key);
604
605 let expected_address = vec![0u8; 32]; 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}