Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
163 changes: 156 additions & 7 deletions common/primitives/src/signatures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,69 @@ use sp_runtime::{
traits::{Lazy, Verify},
MultiSignature,
};
extern crate alloc;

/// Ethereum message prefix eip-191
const ETHEREUM_MESSAGE_PREFIX: &[u8; 26] = b"\x19Ethereum Signed Message:\n";

/// A trait that allows mapping of raw bytes to AccountIds
pub trait AccountAddressMapper<AccountId> {
/// mapping to the desired address
fn to_account_id(public_key_or_address: &[u8]) -> AccountId;

/// mapping to bytes of a public key or an address
fn to_bytes32(public_key_or_address: &[u8]) -> [u8; 32];
}

/// converting raw address bytes to 32 bytes Ethereum compatible addresses
pub struct EthereumAddressMapper;

impl AccountAddressMapper<AccountId32> for EthereumAddressMapper {
fn to_account_id(public_key_or_address: &[u8]) -> AccountId32 {
Self::to_bytes32(public_key_or_address).into()
}

/// In this function we are trying to convert different types of valid identifiers to valid
/// Substrate supported 32 bytes AccountIds
/// ref: <https://github.com/paritytech/polkadot-sdk/blob/79b28b3185d01f2e43e098b1f57372ed9df64adf/substrate/frame/revive/src/address.rs#L84-L90>
/// This function have 4 types of valid inputs
/// 1. 20 byte ETH address which gets appended with 12 bytes of 0xEE
/// 2. 32 byte address is returned unchanged
/// 3. 64 bytes Secp256k1 public key is converted to ETH address based on <https://asecuritysite.com/encryption/ethadd> and appended 12 bytes of 0xEE
/// 4. 65 bytes Secp256k1 public key is also converted to ETH address after skipping first byte and appended 12 bytes of 0xEE
/// Anything else is invalid and would return default (all zeros) 32 bytes.
fn to_bytes32(public_key_or_address: &[u8]) -> [u8; 32] {
let mut hashed = [0u8; 32];
match public_key_or_address.len() {
20 => {
hashed[..20].copy_from_slice(public_key_or_address);
},
32 => {
hashed[..].copy_from_slice(public_key_or_address);
return hashed;
},
64 => {
let hashed_full = sp_io::hashing::keccak_256(public_key_or_address);
// Copy bytes 12..32 (20 bytes) from hashed_full to the beginning of hashed
hashed[..20].copy_from_slice(&hashed_full[12..]);
},
65 => {
let hashed_full = sp_io::hashing::keccak_256(&public_key_or_address[1..]);
// Copy bytes 12..32 (20 bytes) from hashed_full to the beginning of hashed
hashed[..20].copy_from_slice(&hashed_full[12..]);
},
_ => {
log::error!("Invalid public key size provided for {:?}", public_key_or_address);
return [0u8; 32];
},
};

// Fill the rest (12 bytes) with 0xEE
hashed[20..].fill(0xEE);
hashed
}
}

/// Signature verify that can work with any known signature types.
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[derive(
Expand Down Expand Up @@ -165,11 +224,9 @@ impl traits::IdentifyAccount for UnifiedSigner {
);
match decompressed_result {
Ok(public_key) => {
// calculating ethereum address prefixed with zeros
// calculating ethereum address compatible with `pallet-revive`
let decompressed = public_key.serialize();
let mut hashed = sp_io::hashing::keccak_256(&decompressed[1..65]);
hashed[..12].fill(0);
hashed.into()
EthereumAddressMapper::to_account_id(&decompressed)
},
Err(_) => {
log::error!("Invalid compressed public key provided");
Expand Down Expand Up @@ -256,8 +313,7 @@ impl Into<UnifiedSignature> for MultiSignature {
fn check_secp256k1_signature(signature: &[u8; 65], msg: &[u8; 32], signer: &AccountId32) -> bool {
match sp_io::crypto::secp256k1_ecdsa_recover(signature, msg) {
Ok(pubkey) => {
let mut hashed = sp_io::hashing::keccak_256(pubkey.as_ref());
hashed[..12].fill(0);
let hashed = EthereumAddressMapper::to_bytes32(&pubkey);
log::debug!(target:"ETHEREUM", "eth hashed={:?} signer={:?}",
HexDisplay::from(&hashed),HexDisplay::from(<dyn AsRef<[u8; 32]>>::as_ref(signer)),
);
Expand Down Expand Up @@ -299,7 +355,12 @@ mod tests {
use crate::signatures::{UnifiedSignature, UnifiedSigner};
use impl_serde::serialize::from_hex;
use sp_core::{ecdsa, Pair};
use sp_runtime::traits::{IdentifyAccount, Verify};
use sp_runtime::{
traits::{IdentifyAccount, Verify},
AccountId32,
};

use super::{AccountAddressMapper, EthereumAddressMapper};

#[test]
fn polkadot_ecdsa_should_not_work_due_to_using_wrong_hash() {
Expand Down Expand Up @@ -367,4 +428,92 @@ mod tests {
let unified_signer = UnifiedSigner::from(public_key);
assert!(!unified_signature.verify(&payload[..], &unified_signer.into_account()));
}

#[test]
fn ethereum_address_mapper_should_work_as_expected_for_eth_20_bytes_addresses() {
// arrange
let eth = from_hex("0x1111111111111111111111111111111111111111").expect("should work");

// act
let account_id = EthereumAddressMapper::to_account_id(&eth);
let bytes = EthereumAddressMapper::to_bytes32(&eth);

// assert
let expected_address =
from_hex("0x1111111111111111111111111111111111111111eeeeeeeeeeeeeeeeeeeeeeee")
.expect("should be hex");
assert_eq!(account_id, AccountId32::new(expected_address.clone().try_into().unwrap()));
assert_eq!(bytes.to_vec(), expected_address);
}

#[test]
fn ethereum_address_mapper_should_return_the_same_value_for_32_byte_addresses() {
// arrange
let eth = from_hex("0x1111111111111111111111111111111111111111111111111111111111111111")
.expect("should work");

// act
let account_id = EthereumAddressMapper::to_account_id(&eth);
let bytes = EthereumAddressMapper::to_bytes32(&eth);

// assert
assert_eq!(account_id, AccountId32::new(eth.clone().try_into().unwrap()));
assert_eq!(bytes.to_vec(), eth);
}

#[test]
fn ethereum_address_mapper_should_return_the_ethereum_address_with_suffixes_for_64_byte_public_keys(
) {
// arrange
let public_key= from_hex("0x15b5e4aeac2086ee96ab2292ee2720da0b2d3c43b5c699ccdbfd38387e2f71dc167075a80a32fe2c78d7d8780ef1b2095810f12001fa2fcedcd1ffb0aa2ee2c7").expect("should work");

// act
let account_id = EthereumAddressMapper::to_account_id(&public_key);
let bytes = EthereumAddressMapper::to_bytes32(&public_key);

// assert
// 0x917B536617B0A42B2ABE85AC88788825F29F0B29 is eth address associated with above public_key
let expected_address =
from_hex("0x917B536617B0A42B2ABE85AC88788825F29F0B29eeeeeeeeeeeeeeeeeeeeeeee")
.expect("should be hex");
assert_eq!(account_id, AccountId32::new(expected_address.clone().try_into().unwrap()));
assert_eq!(bytes.to_vec(), expected_address);
}

#[test]
fn ethereum_address_mapper_should_return_the_ethereum_address_with_suffixes_for_65_byte_public_keys(
) {
// arrange
let public_key= from_hex("0x0415b5e4aeac2086ee96ab2292ee2720da0b2d3c43b5c699ccdbfd38387e2f71dc167075a80a32fe2c78d7d8780ef1b2095810f12001fa2fcedcd1ffb0aa2ee2c7").expect("should work");

// act
let account_id = EthereumAddressMapper::to_account_id(&public_key);
let bytes = EthereumAddressMapper::to_bytes32(&public_key);

// assert
// 0x917B536617B0A42B2ABE85AC88788825F29F0B29 is eth address associated with above public_key
let expected_address =
from_hex("0x917B536617B0A42B2ABE85AC88788825F29F0B29eeeeeeeeeeeeeeeeeeeeeeee")
.expect("should be hex");
assert_eq!(account_id, AccountId32::new(expected_address.clone().try_into().unwrap()));
assert_eq!(bytes.to_vec(), expected_address);
}

#[test]
fn ethereum_address_mapper_should_return_the_default_zero_values_for_any_invalid_length() {
// arrange
let public_key = from_hex(
"0x010415b5e4aeac2086ee96ab2292ee2720da0b2d3c43b5c699ccdbfd38387e2f71dc167075a801",
)
.expect("should work");

// act
let account_id = EthereumAddressMapper::to_account_id(&public_key);
let bytes = EthereumAddressMapper::to_bytes32(&public_key);

// assert
let expected_address = vec![0u8; 32]; // zero default values
assert_eq!(account_id, AccountId32::new(expected_address.clone().try_into().unwrap()));
assert_eq!(bytes.to_vec(), expected_address);
}
}
14 changes: 8 additions & 6 deletions e2e/scaffolding/ethereum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,16 @@ export function getUnifiedAddress(pair: KeyringPair): string {
}

/**
* Returns ethereum style public key with prefixed zeros example: 0x00000000000000000000000019a701d23f0ee1748b5d5f883cb833943096c6c4
* Returns ethereum style public key with suffixed 0xee example: 0x19a701d23f0ee1748b5d5f883cb833943096c6c4eeeeeeeeeeeeeeeeeeeeeeee
* @param pair
*/
export function getUnifiedPublicKey(pair: KeyringPair): Uint8Array {
if ('ethereum' === pair.type) {
const publicKeyBytes = hexToU8a(ethereumEncode(pair.publicKey));
const ethAddressBytes = hexToU8a(ethereumEncode(pair.publicKey));
const suffix = new Uint8Array(12).fill(0xee);
const result = new Uint8Array(32);
result.fill(0, 0, 12);
result.set(publicKeyBytes, 12);
result.set(ethAddressBytes, 0);
result.set(suffix, 20);
return result;
}
if (pair.type === 'ecdsa') {
Expand Down Expand Up @@ -95,9 +96,10 @@ export function getKeyringPairFromSecp256k1PrivateKey(secretKey: Uint8Array): Ke
*/
function getSS58AccountFromEthereumAccount(accountId20Hex: string): string {
const addressBytes = hexToU8a(accountId20Hex);
const suffix = new Uint8Array(12).fill(0xee);
const result = new Uint8Array(32);
result.fill(0, 0, 12);
result.set(addressBytes, 12);
result.set(addressBytes, 0);
result.set(suffix, 20);
return encodeAddress(result);
}

Expand Down
2 changes: 1 addition & 1 deletion pallets/passkey/src/tests_v2.rs
Original file line number Diff line number Diff line change
Expand Up @@ -553,7 +553,7 @@ fn pre_dispatch_with_exceeding_weight_should_fail() {
fn passkey_example_should_work() {
new_test_ext().execute_with(|| {
// arrange
let account_id = AccountId32::new(from_hex("0x000000000000000000000000cf613044ccd8c1c60f561b99bd1fd2daef89625f").unwrap().try_into().unwrap());
let account_id = AccountId32::new(from_hex("0xcf613044ccd8c1c60f561b99bd1fd2daef89625feeeeeeeeeeeeeeeeeeeeeeee").unwrap().try_into().unwrap());
let pass_key_public_key = PasskeyPublicKey(from_hex("0x029bd263885e5eeaea31fa3b2e78ab1106d2cb1995045777fca3b38913a755d250").unwrap().try_into().unwrap());
let payload = PasskeyPayloadV2 {
passkey_public_key: pass_key_public_key,
Expand Down
3 changes: 0 additions & 3 deletions pallets/time-release/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,6 @@ use frame_system::{EnsureRoot, RawOrigin};
use sp_core::H256;
use sp_runtime::{traits::IdentityLookup, BuildStorage, Perbill};

use pallet_preimage;
use pallet_scheduler;

use crate as pallet_time_release;

pub type AccountId = u128;
Expand Down
20 changes: 9 additions & 11 deletions pallets/time-release/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ fn time_release_from_chain_spec_works() {

// Check that the release schedules built at genesis are correct
assert_eq!(
ReleaseSchedules::<Test>::get(&CHARLIE),
ReleaseSchedules::<Test>::get(CHARLIE),
vec![
ReleaseSchedule { start: 2u32, period: 3u32, period_count: 1u32, per_period: 5u64 },
ReleaseSchedule {
Expand Down Expand Up @@ -69,7 +69,7 @@ fn transfer_works() {
let schedule =
ReleaseSchedule { start: 0u32, period: 10u32, period_count: 1u32, per_period: 100u64 };
assert_ok!(TimeRelease::transfer(RuntimeOrigin::signed(ALICE), BOB, schedule.clone()));
assert_eq!(ReleaseSchedules::<Test>::get(&BOB), vec![schedule.clone()]);
assert_eq!(ReleaseSchedules::<Test>::get(BOB), vec![schedule.clone()]);
System::assert_last_event(RuntimeEvent::TimeRelease(crate::Event::ReleaseScheduleAdded {
from: ALICE,
to: BOB,
Expand Down Expand Up @@ -104,7 +104,7 @@ fn self_releasing() {

assert_ok!(TimeRelease::transfer(RuntimeOrigin::signed(ALICE), ALICE, schedule.clone()));

assert_eq!(ReleaseSchedules::<Test>::get(&ALICE), vec![schedule.clone()]);
assert_eq!(ReleaseSchedules::<Test>::get(ALICE), vec![schedule.clone()]);
System::assert_last_event(RuntimeEvent::TimeRelease(crate::Event::ReleaseScheduleAdded {
from: ALICE,
to: ALICE,
Expand Down Expand Up @@ -262,7 +262,7 @@ fn claim_for_works() {
),
20u64
);
assert!(ReleaseSchedules::<Test>::contains_key(&BOB));
assert!(ReleaseSchedules::<Test>::contains_key(BOB));

MockBlockNumberProvider::set(21);

Expand All @@ -276,7 +276,7 @@ fn claim_for_works() {
),
0
);
assert!(!ReleaseSchedules::<Test>::contains_key(&BOB));
assert!(!ReleaseSchedules::<Test>::contains_key(BOB));
});
}

Expand Down Expand Up @@ -359,19 +359,19 @@ fn multiple_release_schedule_claim_works() {
ReleaseSchedule { start: 0u32, period: 10u32, period_count: 3u32, per_period: 10u64 };
assert_ok!(TimeRelease::transfer(RuntimeOrigin::signed(ALICE), BOB, schedule2.clone()));

assert_eq!(ReleaseSchedules::<Test>::get(&BOB), vec![schedule, schedule2.clone()]);
assert_eq!(ReleaseSchedules::<Test>::get(BOB), vec![schedule, schedule2.clone()]);

MockBlockNumberProvider::set(21);

assert_ok!(TimeRelease::claim(RuntimeOrigin::signed(BOB)));

assert_eq!(ReleaseSchedules::<Test>::get(&BOB), vec![schedule2]);
assert_eq!(ReleaseSchedules::<Test>::get(BOB), vec![schedule2]);

MockBlockNumberProvider::set(31);

assert_ok!(TimeRelease::claim(RuntimeOrigin::signed(BOB)));

assert!(!ReleaseSchedules::<Test>::contains_key(&BOB));
assert!(!ReleaseSchedules::<Test>::contains_key(BOB));

assert_eq!(
<Test as Config>::Currency::balance_frozen(
Expand Down Expand Up @@ -909,9 +909,7 @@ fn alice_time_releases_schedule() {
// Bob thinks he can claim tokens because time-release transfer has started.
// However, Bob notices he still can't spend it. That is because he has not
// gone through 1 period =(.
MockBlockNumberProvider::set(
date_to_approximate_block_number(june_2024_release_start).into(),
);
MockBlockNumberProvider::set(date_to_approximate_block_number(june_2024_release_start));
// Doing a claim does not do anything
assert_ok!(TimeRelease::claim(RuntimeOrigin::signed(BOB)));
// Since the first issuance the total amount frozen increases by the new transfers: 24_996;
Expand Down
9 changes: 5 additions & 4 deletions runtime/frequency/src/ethereum.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
use common_primitives::signatures::{AccountAddressMapper, EthereumAddressMapper};
use core::{fmt::Debug, marker::PhantomData};
use parity_scale_codec::Codec;
use scale_info::StaticTypeInfo;
Expand Down Expand Up @@ -25,9 +26,9 @@ where
MultiAddress::Id(i) => Ok(i),
MultiAddress::Address20(acc20) => {
log::debug!(target: "ETHEREUM", "lookup 0x{:?}", HexDisplay::from(&acc20));
let mut buffer = [0u8; 32];
buffer[12..].copy_from_slice(&acc20);
let decoded = Self::Target::decode(&mut &buffer[..]).map_err(|_| LookupError)?;
let account_id_bytes = EthereumAddressMapper::to_bytes32(&acc20);
let decoded =
Self::Target::decode(&mut &account_id_bytes[..]).map_err(|_| LookupError)?;
Ok(decoded)
},
_ => Err(LookupError),
Expand Down Expand Up @@ -58,7 +59,7 @@ mod tests {

let converted = lookup.unwrap();
let expected = AccountId32::new(
from_hex("0x00000000000000000000000019a701d23f0ee1748b5d5f883cb833943096c6c4")
from_hex("0x19a701d23f0ee1748b5d5f883cb833943096c6c4eeeeeeeeeeeeeeeeeeeeeeee")
.expect("should convert")
.try_into()
.expect("invalid size"),
Expand Down
Loading