Skip to content

Commit 7f4bc03

Browse files
committed
Update runtime_api.rs
1 parent 33515bb commit 7f4bc03

File tree

2 files changed

+123
-26
lines changed

2 files changed

+123
-26
lines changed

pallets/funding/src/functions/misc.rs

+122-25
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ use sp_core::{
66
ecdsa::{Public as EcdsaPublic, Signature as EcdsaSignature},
77
keccak_256,
88
sr25519::{Public as SrPublic, Signature as SrSignature},
9-
ByteArray,
9+
ByteArray, KeccakHasher,
1010
};
1111
use sp_runtime::traits::Verify;
1212

@@ -438,6 +438,36 @@ impl<T: Config> Pallet<T> {
438438
Some(message)
439439
}
440440

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+
441471
pub fn verify_receiving_account_signature(
442472
polimec_account: &AccountIdOf<T>,
443473
project_id: ProjectId,
@@ -459,30 +489,18 @@ impl<T: Config> Pallet<T> {
459489
);
460490
},
461491

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"\x19Ethereum 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+
);
486504
},
487505
_ => return Err(Error::<T>::UnsupportedReceiverAccountJunction.into()),
488506
};
@@ -495,3 +513,82 @@ impl<T: Config> Pallet<T> {
495513
<PriceProviderOf<T>>::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals)
496514
}
497515
}
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+
}

pallets/funding/src/runtime_api.rs

+1-1
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,7 @@ sp_api::decl_runtime_apis! {
7474
fn get_funding_asset_min_max_amounts(project_id: ProjectId, did: Did, funding_asset: AcceptedFundingAsset, investor_type: InvestorType) -> Option<(Balance, Balance)>;
7575

7676
/// Gets the hex encoded bytes of the message needed to be signed by the receiving account to participate in the project.
77-
/// The message will first be prefixed with a string depending on the blockchain, hashed, and then signed.
77+
/// The message will first be prefixed with a blockchain-dependent string, then hashed, and then signed.
7878
fn get_message_to_sign_by_receiving_account(project_id: ProjectId, polimec_account: AccountIdOf<T>) -> Option<String>;
7979

8080
}

0 commit comments

Comments
 (0)