diff --git a/integration-tests/src/tests/ethereum_support.rs b/integration-tests/src/tests/ethereum_support.rs index 8b1378917..9ecfee8cc 100644 --- a/integration-tests/src/tests/ethereum_support.rs +++ b/integration-tests/src/tests/ethereum_support.rs @@ -1 +1,41 @@ +use crate::*; +use hex_literal::hex; +use sp_runtime::traits::Convert; +generate_accounts!(ETH_BUYER); + +#[test] +fn test_hardcoded_signatures() { + let polimec_account: PolimecAccountId = ETH_BUYER.into(); + let project_id = 0; + + // Values generated with `https://github.com/lrazovic/ethsigner` + let polimec_account_ss58 = polimec_runtime::SS58Converter::convert(polimec_account.clone()); + dbg!(polimec_account_ss58); + let ethereum_account: [u8; 20] = hex!("FCAd0B19bB29D4674531d6f115237E16AfCE377c"); + let signature: [u8; 65] = hex!("4fa35369a2d654112d3fb419e24dc0d7d61b7e3f23936d6d4df0ac8608fa4530795971d4d1967da60853aa974ad57252a521f97bcd5a68ddea5f8959a5c60b471c"); + + PolimecNet::execute_with(|| { + assert_ok!(PolimecFunding::verify_receiving_account_signature( + &polimec_account, + project_id, + &Junction::AccountKey20 { network: Some(NetworkId::Ethereum { chain_id: 1 }), key: ethereum_account }, + signature, + )); + }); + + let polkadot_signature: [u8; 64] = hex!("7efee88bb61b74c91e6dc0ad48ea5b0118db77a579da8a8a753933d76cdc9e029c11f32a51b00fd3a1e3ce5b56cd1e275b179d4b195e7d527eebc60680291b81"); + let mut signature: [u8; 65] = [0u8; 65]; + signature[..64].copy_from_slice(polkadot_signature.as_slice()); + signature[64] = 0; + let polkadot_address: [u8; 32] = hex!("d43593c715fdd31c61141abd04a99fd6822c8558854ccde39a5684e7a56da27d"); + + PolimecNet::execute_with(|| { + assert_ok!(PolimecFunding::verify_receiving_account_signature( + &polimec_account, + project_id, + &Junction::AccountId32 { network: Some(NetworkId::Polkadot), id: polkadot_address }, + signature, + )); + }); +} diff --git a/pallets/funding/src/functions/misc.rs b/pallets/funding/src/functions/misc.rs index 974e6aa5f..2750b009c 100644 --- a/pallets/funding/src/functions/misc.rs +++ b/pallets/funding/src/functions/misc.rs @@ -448,16 +448,12 @@ impl Pallet { .ok_or(Error::::BadReceiverAccountSignature)?; let message_bytes = message_to_sign.into_bytes(); match receiver_account { - Junction::AccountId32 { network, id } => - if network.is_none() { - let signature = SrSignature::from_slice(&signature_bytes[..64]) - .map_err(|_| Error::::BadReceiverAccountSignature)?; - let public = SrPublic::from_slice(id).map_err(|_| Error::::BadReceiverAccountSignature)?; - ensure!( - signature.verify(message_bytes.as_slice(), &public), - Error::::BadReceiverAccountSignature - ); - }, + Junction::AccountId32 { network, id } if *network == Some(NetworkId::Polkadot) => { + let signature = SrSignature::from_slice(&signature_bytes[..64]) + .map_err(|_| Error::::BadReceiverAccountSignature)?; + let public = SrPublic::from_slice(id).map_err(|_| Error::::BadReceiverAccountSignature)?; + ensure!(signature.verify(message_bytes.as_slice(), &public), Error::::BadReceiverAccountSignature); + }, Junction::AccountKey20 { network, key } if *network == Some(NetworkId::Ethereum { chain_id: 1 }) => { let message_length = message_bytes.len().to_string().into_bytes(); diff --git a/pallets/funding/src/instantiator/calculations.rs b/pallets/funding/src/instantiator/calculations.rs index ebbaf9398..ed3263152 100644 --- a/pallets/funding/src/instantiator/calculations.rs +++ b/pallets/funding/src/instantiator/calculations.rs @@ -4,6 +4,7 @@ use crate::{MultiplierOf, ParticipationMode}; use core::cmp::Ordering; use itertools::GroupBy; use polimec_common::{ProvideAssetPrice, USD_DECIMALS}; +use sp_core::{ecdsa, hexdisplay::AsBytesRef, keccak_256, sr25519, Pair}; impl< T: Config, @@ -795,4 +796,49 @@ impl< T::CommunityRoundDuration::get() + One::one() } + + pub fn eth_key_and_sig_from( + &mut self, + s: &str, + project_id: ProjectId, + polimec_account: AccountIdOf, + ) -> (Junction, [u8; 65]) { + let message_to_sign = self.execute(|| Pallet::::get_message_to_sign(polimec_account, project_id)).unwrap(); + let message_to_sign = message_to_sign.into_bytes(); + let ecdsa_pair = ecdsa::Pair::from_string(s, None).unwrap(); + let message_length = message_to_sign.len(); + let message_prefix = format!("\x19Ethereum Signed Message:\n{}", message_length).into_bytes(); + let expected_message = [&message_prefix[..], &message_to_sign[..]].concat(); + let signature = ecdsa_pair.sign_prehashed(&keccak_256(&expected_message)); + let mut signature_bytes = [0u8; 65]; + signature_bytes[..65].copy_from_slice(signature.as_bytes_ref()); + + match signature_bytes[64] { + 0x00 => signature_bytes[64] = 27, + 0x01 => signature_bytes[64] = 28, + _v => unreachable!("Recovery bit should be always either 0 or 1"), + } + + let compressed_public_key = ecdsa_pair.public().to_raw(); + let public_uncompressed = k256::ecdsa::VerifyingKey::from_sec1_bytes(&compressed_public_key).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(); + let junction = + Junction::AccountKey20 { network: Some(Ethereum { chain_id: 1 }), key: derived_ethereum_account }; + + (junction, signature_bytes) + } + + // pub fn polkadot_key_and_sig_from(&self, s: &str, project_id: ProjectId, polimec_account: AccountIdOf) -> (Junction, [u8; 65]) { + // let message_to_sign = self.execute(|| Pallet::::get_message_to_sign(polimec_account, project_id)).unwrap(); + // let message_to_sign = message_to_sign.into_bytes(); + // + // let sr_pair = sr25519::Pair::from_seed(&[69u8; 32]).unwrap(); + // let signature = sr_pair.sign(&message_to_sign); + // let mut signature_bytes = [0u8; 65]; + // signature_bytes[..64].copy_from_slice(signature.as_bytes_ref()); + // let junction = Junction::AccountId32 { network: None, id: sr_pair.public().to_raw() }; + // (junction, signature_bytes) + // } } diff --git a/pallets/funding/src/tests/2_evaluation.rs b/pallets/funding/src/tests/2_evaluation.rs index 444962df4..233776536 100644 --- a/pallets/funding/src/tests/2_evaluation.rs +++ b/pallets/funding/src/tests/2_evaluation.rs @@ -690,6 +690,37 @@ mod evaluate_extrinsic { pre_slash_treasury_balance + ::EvaluatorSlash::get() * frozen_amount ); } + + #[test] + fn evaluate_on_ethereum_project() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + + let mut project_metadata = default_project_metadata(ISSUER_1); + project_metadata.participants_account_type = ParticipantsAccountType::Ethereum; + + let project_id = inst.create_evaluating_project(project_metadata.clone(), ISSUER_1, None); + let jwt = get_mock_jwt_with_cid( + EVALUATOR_1, + InvestorType::Retail, + generate_did_from_account(EVALUATOR_1), + project_metadata.clone().policy_ipfs_cid.unwrap(), + ); + + let ethereum_account = [1u8; 20]; + + inst.execute(|| { + PolimecFunding::evaluate_with_receiving_account( + RuntimeOrigin::signed(EVALUATOR_1), + jwt, + project_id, + 500 * USD_UNIT, + Junction::AccountKey20 { network: Ethereum { chain_id: 1 }, key: ethereum_account }, + ) + }); + } + + #[test] + fn evaluate_with_different_receiver_polkadot_account() {} } #[cfg(test)] diff --git a/pallets/funding/src/tests/misc.rs b/pallets/funding/src/tests/misc.rs index 6536016bf..4195cddd2 100644 --- a/pallets/funding/src/tests/misc.rs +++ b/pallets/funding/src/tests/misc.rs @@ -351,6 +351,67 @@ mod helper_functions { assert_close_enough!(expected, calculated, Perquintill::from_float(0.999)); } } + + #[test] + fn get_message_to_sign() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let account = 69u64; + let project_id = 4u32; + let message_to_sign = inst.execute(|| PolimecFunding::get_message_to_sign(account, project_id)).unwrap(); + + const EXPECTED_MESSAGE: &str = + "polimec account: 57qWuK1HShHMA5o1TX7Q6Xhino5iNwf9qgiSBdkQZMNYddKs - project id: 4 - nonce: 0"; + assert_eq!(&message_to_sign, EXPECTED_MESSAGE); + } + + #[test] + fn verify_receiving_account_signature() { + let mut inst = MockInstantiator::new(Some(RefCell::new(new_test_ext()))); + let message_to_sign = inst.execute(|| PolimecFunding::get_message_to_sign(BUYER_1, 1)).unwrap(); + let message_to_sign = message_to_sign.into_bytes(); + + // Polkadot verification + let sr_pair = sr25519::Pair::from_seed_slice(&[69u8; 32]).unwrap(); + let signature = sr_pair.sign(&message_to_sign); + let mut signature_bytes = [0u8; 65]; + signature_bytes[..64].copy_from_slice(signature.as_bytes_ref()); + let junction = Junction::AccountId32 { network: None, id: sr_pair.public().to_raw() }; + assert_ok!(inst.execute(|| PolimecFunding::verify_receiving_account_signature( + &BUYER_1, + 1, + &junction, + signature_bytes + ))); + + // Ethereum verification + let ecdsa_pair = ecdsa::Pair::from_seed_slice(&[69u8; 32]).unwrap(); + let message_length = message_to_sign.len(); + let message_prefix = format!("\x19Ethereum Signed Message:\n{}", message_length).into_bytes(); + let expected_message = [&message_prefix[..], &message_to_sign[..]].concat(); + let signature = ecdsa_pair.sign_prehashed(&keccak_256(&expected_message)); + let mut signature_bytes = [0u8; 65]; + signature_bytes[..65].copy_from_slice(signature.as_bytes_ref()); + + match signature_bytes[64] { + 0x00 => signature_bytes[64] = 27, + 0x01 => signature_bytes[64] = 28, + _v => unreachable!("Recovery bit should be always either 0 or 1"), + } + + let compressed_public_key = ecdsa_pair.public().to_raw(); + let public_uncompressed = k256::ecdsa::VerifyingKey::from_sec1_bytes(&compressed_public_key).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(); + let junction = + Junction::AccountKey20 { network: Some(Ethereum { chain_id: 1 }), key: derived_ethereum_account }; + assert_ok!(inst.execute(|| PolimecFunding::verify_receiving_account_signature( + &BUYER_1, + 1, + &junction, + signature_bytes + ))); + } } // logic of small functions that extrinsics use to process data or interact with storage