Skip to content

Commit

Permalink
Update runtime_api.rs
Browse files Browse the repository at this point in the history
  • Loading branch information
JuaniRios committed Nov 28, 2024
1 parent ccca122 commit 098e693
Show file tree
Hide file tree
Showing 2 changed files with 121 additions and 25 deletions.
144 changes: 120 additions & 24 deletions pallets/funding/src/functions/misc.rs
Original file line number Diff line number Diff line change
Expand Up @@ -438,6 +438,47 @@ impl<T: Config> Pallet<T> {
Some(message)
}

pub fn verify_ethereum_account(
mut signature_bytes: [u8; 65],
expected_ethereum_account: [u8; 20],
polimec_account: AccountIdOf<T>,
project_id: ProjectId,
) -> bool {
match signature_bytes[64] {
27 => signature_bytes[64] = 0x00,
28 => signature_bytes[64] = 0x01,
_v => return false,
}

let hashed_message = typed_data_v4::get_eip_712_message(
&T::SS58Conversion::convert(polimec_account.clone()),
project_id,
frame_system::Pallet::<T>::account_nonce(polimec_account),
);

let ecdsa_signature = EcdsaSignature::from_slice(&signature_bytes).unwrap();
let public_compressed: EcdsaPublic = ecdsa_signature.recover_prehashed(&hashed_message).unwrap();
let public_uncompressed = k256::ecdsa::VerifyingKey::from_sec1_bytes(&public_compressed).unwrap();
let public_uncompressed_point = public_uncompressed.to_encoded_point(false).to_bytes();
let derived_ethereum_account: [u8; 20] =
keccak_256(&public_uncompressed_point[1..])[12..32].try_into().unwrap();

derived_ethereum_account == expected_ethereum_account
}

pub fn verify_substrate_account(
signature_bytes: [u8; 65],
expected_substrate_account: [u8; 32],
polimec_account: AccountIdOf<T>,
project_id: ProjectId,
) -> bool {
let message_to_sign = Self::get_substrate_message_to_sign(polimec_account.clone(), project_id).unwrap();
let message_bytes = message_to_sign.into_bytes();
let signature = SrSignature::from_slice(&signature_bytes[..64]).unwrap();
let public = SrPublic::from_slice(&expected_substrate_account).unwrap();
signature.verify(message_bytes.as_slice(), &public)
}

pub fn verify_receiving_account_signature(
polimec_account: &AccountIdOf<T>,
project_id: ProjectId,
Expand All @@ -456,30 +497,16 @@ impl<T: Config> Pallet<T> {
);
},

Junction::AccountKey20 { network, key } if *network == Some(NetworkId::Ethereum { chain_id: 1 }) => {
let message_length = message_bytes.len().to_string().into_bytes();
let message_prefix = b"\x19Ethereum Signed Message:\n".to_vec();
let full_message = [&message_prefix[..], &message_length[..], &message_bytes[..]].concat();
let hashed_message = keccak_256(full_message.as_slice());

match signature_bytes[64] {
27 => signature_bytes[64] = 0x00,
28 => signature_bytes[64] = 0x01,
_v => return Err(Error::<T>::BadReceiverAccountSignature.into()),
}

// If a user specifies an AccountKey20, we assume they used the ECDSA crypto (secp256k1), so the signature is 65 bytes.
let signature = EcdsaSignature::from_slice(&signature_bytes)
.map_err(|_| Error::<T>::BadReceiverAccountSignature)?;
let public_compressed: EcdsaPublic =
signature.recover_prehashed(&hashed_message).ok_or(Error::<T>::BadReceiverAccountSignature)?;
let public_uncompressed = k256::ecdsa::VerifyingKey::from_sec1_bytes(&public_compressed)
.map_err(|_| Error::<T>::BadReceiverAccountSignature)?;
let public_uncompressed_point = public_uncompressed.to_encoded_point(false).to_bytes();
let derived_ethereum_account: [u8; 20] = keccak_256(&public_uncompressed_point[1..])[12..32]
.try_into()
.map_err(|_| Error::<T>::BadReceiverAccountSignature)?;
ensure!(*key == derived_ethereum_account, Error::<T>::BadReceiverAccountSignature);
Junction::AccountKey20 { key: expected_ethereum_account, .. } => {
ensure!(
Self::verify_ethereum_account(
signature_bytes,
*expected_ethereum_account,
polimec_account.clone(),
project_id,
),
Error::<T>::BadReceiverAccountSignature
);
},
_ => return Err(Error::<T>::UnsupportedReceiverAccountJunction.into()),
};
Expand All @@ -492,3 +519,72 @@ impl<T: Config> Pallet<T> {
<PriceProviderOf<T>>::get_decimals_aware_price(funding_asset_id, USD_DECIMALS, funding_asset_decimals)
}
}

pub mod typed_data_v4 {
use super::*;

/// Returns the first part needed for a typed data v4 message. It specifies the entity that requires the signature.
pub fn get_domain_separator(name: &str, version: &str, chain_id: u32, verifying_contract: &str) -> [u8; 32] {
let encoded_domain_type =
keccak_256(b"EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)");
let encoded_name = keccak_256(name.as_bytes());
let encoded_version = keccak_256(version.as_bytes());

// TODO: Handle panics from copy_from_slice
// u32 should be converted to u256 in big endian notation
let chain_id_bytes: [u8; 4] = chain_id.to_be_bytes();
let mut encoded_chain_id: [u8; 32] = [0u8; 32];
encoded_chain_id[28..].copy_from_slice(&chain_id_bytes);
// Should be 32 bytes to comply with ABI encoding, so we pad with zeroes to the left
let mut encoded_contract: [u8; 32] = [0u8; 32];
encoded_contract[12..].copy_from_slice(hex::decode(verifying_contract).unwrap().as_slice());

let mut data = Vec::new();
data.extend_from_slice(&encoded_domain_type);
data.extend_from_slice(&encoded_name);
data.extend_from_slice(&encoded_version);
data.extend_from_slice(&encoded_chain_id);
data.extend_from_slice(&encoded_contract);

keccak_256(&data)
}

/// Returns the second part needed for a typed data v4 message. It specifies the message details with type information.
pub fn get_message(polimec_account: &str, project_id: u32, nonce: u32) -> [u8; 32] {
let encoded_message_type =
keccak_256(b"ParticipationAuthorization(string polimecAccount,uint32 projectId,uint32 nonce)");

let encoded_polimec_account_string = keccak_256(polimec_account.as_bytes());

let project_id_bytes: [u8; 4] = project_id.to_be_bytes();
let mut encoded_project_id: [u8; 32] = [0u8; 32];
encoded_project_id[28..].copy_from_slice(&project_id_bytes);

let nonce_bytes: [u8; 4] = nonce.to_be_bytes();
let mut encoded_nonce: [u8; 32] = [0u8; 32];
encoded_nonce[28..].copy_from_slice(&nonce_bytes);

let mut data = Vec::new();
data.extend_from_slice(&encoded_message_type);
data.extend_from_slice(&encoded_polimec_account_string);
data.extend_from_slice(&encoded_project_id);
data.extend_from_slice(&encoded_nonce);

let output = keccak_256(&data);

output
}

/// Returns the final message hash that will be signed by the user.
pub fn get_eip_712_message(polimec_account: &str, project_id: u32, nonce: u32) -> [u8; 32] {
let domain_separator = get_domain_separator("Polimec", "1", 1, "0000000000000000000000000000000000003344");
let message = get_message(polimec_account, project_id, nonce);

let mut data = Vec::new();
data.extend_from_slice(b"\x19\x01");
data.extend_from_slice(&domain_separator);
data.extend_from_slice(&message);

keccak_256(&data)
}
}
2 changes: 1 addition & 1 deletion pallets/funding/src/runtime_api.rs
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ sp_api::decl_runtime_apis! {
fn get_funding_asset_min_max_amounts(project_id: ProjectId, did: Did, funding_asset: AcceptedFundingAsset, investor_type: InvestorType) -> Option<(Balance, Balance)>;

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

}
Expand Down

0 comments on commit 098e693

Please sign in to comment.