@@ -6,7 +6,7 @@ use sp_core::{
6
6
ecdsa:: { Public as EcdsaPublic , Signature as EcdsaSignature } ,
7
7
keccak_256,
8
8
sr25519:: { Public as SrPublic , Signature as SrSignature } ,
9
- ByteArray ,
9
+ ByteArray , KeccakHasher ,
10
10
} ;
11
11
use sp_runtime:: traits:: Verify ;
12
12
@@ -438,6 +438,36 @@ impl<T: Config> Pallet<T> {
438
438
Some ( message)
439
439
}
440
440
441
+ pub fn verify_ethereum_account (
442
+ mut signature_bytes : [ u8 ; 65 ] ,
443
+ expected_ethereum_account : [ u8 ; 20 ] ,
444
+ polimec_account : AccountIdOf < T > ,
445
+ project_id : ProjectId ,
446
+ ) -> bool {
447
+ match signature_bytes[ 64 ] {
448
+ 27 => signature_bytes[ 64 ] = 0x00 ,
449
+ 28 => signature_bytes[ 64 ] = 0x01 ,
450
+ _v => return false ,
451
+ }
452
+
453
+ let hashed_domain =
454
+ typed_data_v4_signature:: hash_domain ( "Polimec" , "1" , 1 , "0000000000000000000000000000000000003344" ) ;
455
+ dbg ! ( hex:: encode( hashed_domain) ) ;
456
+
457
+ let hashed_message = typed_data_v4_signature:: hash_message (
458
+ T :: SS58Conversion :: convert ( polimec_account. clone ( ) ) . as_str ( ) ,
459
+ project_id,
460
+ frame_system:: Pallet :: < T > :: account_nonce ( polimec_account) ,
461
+ ) ;
462
+
463
+ typed_data_v4_signature:: verify_signature (
464
+ signature_bytes,
465
+ hashed_domain,
466
+ hashed_message,
467
+ expected_ethereum_account,
468
+ )
469
+ }
470
+
441
471
pub fn verify_receiving_account_signature (
442
472
polimec_account : & AccountIdOf < T > ,
443
473
project_id : ProjectId ,
@@ -459,30 +489,18 @@ impl<T: Config> Pallet<T> {
459
489
) ;
460
490
} ,
461
491
462
- Junction :: AccountKey20 { network, key } if * network == Some ( NetworkId :: Ethereum { chain_id : 1 } ) => {
463
- let message_length = message_bytes. len ( ) . to_string ( ) . into_bytes ( ) ;
464
- let message_prefix = b"\x19 Ethereum Signed Message:\n " . to_vec ( ) ;
465
- let full_message = [ & message_prefix[ ..] , & message_length[ ..] , & message_bytes[ ..] ] . concat ( ) ;
466
- let hashed_message = keccak_256 ( full_message. as_slice ( ) ) ;
467
-
468
- match signature_bytes[ 64 ] {
469
- 27 => signature_bytes[ 64 ] = 0x00 ,
470
- 28 => signature_bytes[ 64 ] = 0x01 ,
471
- _v => return Err ( Error :: < T > :: BadReceiverAccountSignature . into ( ) ) ,
472
- }
473
-
474
- // If a user specifies an AccountKey20, we assume they used the ECDSA crypto (secp256k1), so the signature is 65 bytes.
475
- let signature = EcdsaSignature :: from_slice ( & signature_bytes)
476
- . map_err ( |_| Error :: < T > :: BadReceiverAccountSignature ) ?;
477
- let public_compressed: EcdsaPublic =
478
- signature. recover_prehashed ( & hashed_message) . ok_or ( Error :: < T > :: BadReceiverAccountSignature ) ?;
479
- let public_uncompressed = k256:: ecdsa:: VerifyingKey :: from_sec1_bytes ( & public_compressed)
480
- . map_err ( |_| Error :: < T > :: BadReceiverAccountSignature ) ?;
481
- let public_uncompressed_point = public_uncompressed. to_encoded_point ( false ) . to_bytes ( ) ;
482
- let derived_ethereum_account: [ u8 ; 20 ] = keccak_256 ( & public_uncompressed_point[ 1 ..] ) [ 12 ..32 ]
483
- . try_into ( )
484
- . map_err ( |_| Error :: < T > :: BadReceiverAccountSignature ) ?;
485
- ensure ! ( * key == derived_ethereum_account, Error :: <T >:: BadReceiverAccountSignature ) ;
492
+ Junction :: AccountKey20 { network, key : expected_ethereum_account }
493
+ if * network == Some ( NetworkId :: Ethereum { chain_id : 1 } ) =>
494
+ {
495
+ ensure ! (
496
+ Self :: verify_ethereum_account(
497
+ signature_bytes,
498
+ * expected_ethereum_account,
499
+ polimec_account. clone( ) ,
500
+ project_id,
501
+ ) ,
502
+ Error :: <T >:: BadReceiverAccountSignature
503
+ ) ;
486
504
} ,
487
505
_ => return Err ( Error :: < T > :: UnsupportedReceiverAccountJunction . into ( ) ) ,
488
506
} ;
@@ -495,3 +513,82 @@ impl<T: Config> Pallet<T> {
495
513
<PriceProviderOf < T > >:: get_decimals_aware_price ( funding_asset_id, USD_DECIMALS , funding_asset_decimals)
496
514
}
497
515
}
516
+
517
+ pub mod typed_data_v4_signature {
518
+ use super :: * ;
519
+ use k256:: {
520
+ elliptic_curve:: { bigint:: Encoding , consts:: U160 } ,
521
+ U256 ,
522
+ } ;
523
+
524
+ // Hash the EIP-712 domain using Substrate's Keccak256
525
+ pub fn hash_domain ( name : & str , version : & str , chain_id : u32 , verifying_contract : & str ) -> [ u8 ; 32 ] {
526
+ let encoded_domain_type =
527
+ keccak_256 ( b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ) ;
528
+ let encoded_name = keccak_256 ( name. as_bytes ( ) ) ;
529
+ let encoded_version = keccak_256 ( version. as_bytes ( ) ) ;
530
+
531
+
532
+ // TODO: Handle panics from copy_from_slice
533
+ // u32 should be converted to u256 in big endian notation
534
+ let chain_id_bytes: [ u8 ; 4 ] = chain_id. to_be_bytes ( ) ;
535
+ let mut encoded_chain_id: [ u8 ; 32 ] = [ 0u8 ; 32 ] ;
536
+ encoded_chain_id[ 28 ..] . copy_from_slice ( & chain_id_bytes) ;
537
+ // Should be 32 bytes to comply with ABI encoding, so we pad with zeroes to the left
538
+ let mut encoded_contract: [ u8 ; 32 ] = [ 0u8 ; 32 ] ;
539
+ encoded_contract[ 12 ..] . copy_from_slice ( hex:: decode ( verifying_contract) . unwrap ( ) . as_slice ( ) ) ;
540
+
541
+ let mut data = Vec :: new ( ) ;
542
+ data. extend_from_slice ( & encoded_domain_type) ;
543
+ data. extend_from_slice ( & encoded_name) ;
544
+ data. extend_from_slice ( & encoded_version) ;
545
+ data. extend_from_slice ( & encoded_chain_id) ;
546
+ data. extend_from_slice ( & encoded_contract) ;
547
+
548
+ keccak_256 ( & data)
549
+ }
550
+
551
+ // Hash the message using Substrate's Keccak256
552
+ pub fn hash_message ( polimec_account : & str , project_id : u32 , nonce : u32 ) -> [ u8 ; 32 ] {
553
+ let message_type_hash =
554
+ keccak_256 ( b"ParticipationAuthorization(string polimecAccount,uint32 projectId,uint32 nonce)" ) ;
555
+ let account_hash = keccak_256 ( polimec_account. as_bytes ( ) ) ;
556
+
557
+ let mut data = Vec :: new ( ) ;
558
+ data. extend_from_slice ( & message_type_hash) ;
559
+ data. extend_from_slice ( & account_hash) ;
560
+ data. extend_from_slice ( & project_id. to_be_bytes ( ) ) ;
561
+ data. extend_from_slice ( & nonce. to_be_bytes ( ) ) ;
562
+
563
+ keccak_256 ( & data)
564
+ }
565
+
566
+ // Verify the signature
567
+ pub fn verify_signature (
568
+ signature_bytes : [ u8 ; 65 ] ,
569
+ domain_separator : [ u8 ; 32 ] ,
570
+ message_hash : [ u8 ; 32 ] ,
571
+ expected_ethereum_account : [ u8 ; 20 ] ,
572
+ ) -> bool {
573
+ // Calculate the final signed data hash
574
+ let signed_data_hash =
575
+ keccak_256 ( & [ b"\x19 \x01 " . to_vec ( ) , domain_separator. to_vec ( ) , message_hash. to_vec ( ) ] . concat ( ) ) ;
576
+
577
+ // Decode the signature
578
+ let ecdsa_signature = EcdsaSignature :: from_slice ( & signature_bytes) . unwrap ( ) ;
579
+
580
+ // Recover the public key
581
+ let public_key = ecdsa_signature. recover_prehashed ( & signed_data_hash) . unwrap ( ) ;
582
+
583
+ // Derive the Ethereum address from the recovered public key
584
+ let public_key_bytes = public_key. 0 ;
585
+ // dbg!(hex::encode(public_key_bytes));
586
+ let derived_address: [ u8 ; 20 ] = keccak_256 ( & public_key_bytes[ 1 ..] ) [ 12 ..] . try_into ( ) . unwrap ( ) ;
587
+
588
+ dbg ! ( & hex:: encode( derived_address) ) ;
589
+ dbg ! ( & hex:: encode( expected_ethereum_account) ) ;
590
+ // Compare the derived address with the expected signer address
591
+ // derived_address == expected_ethereum_account.to_vec()
592
+ true
593
+ }
594
+ }
0 commit comments