Skip to content
Merged
Show file tree
Hide file tree
Changes from 5 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
23 changes: 23 additions & 0 deletions prdoc/pr_8757.prdoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
title: Allow sending transactions from an Ethereum address derived account id
doc:
- audience: Runtime Dev
description: |-
We always allowed signing transactions using an Bitcoin/Eth style SECP256k1 key. The account in this case is simply the blake2 hash of the public key.

This address derivation is problematic: It requires the public key in order to derive the account id. On Ethereum you simply can't know the public key of an address. This is why the mapping in pallet_revive is defined as `address <-> account_id`.

This PR adds a new signature variant that allows signing a transaction with an account id as origin that matches this mapping.

## Why is this important?

### Example1
A wallet contains an SECP256k1 key and wants to interact with native Polkadot APIs. It can sign the transaction using this key. However, without this change the origin of that transaction will be different than the one it would appear under if it had signed an Ethereum transaction.

### Example2
A chain using an Ethereum style address (like Mythical) wants to send some tokens to one of their users account on AssetHub. How would they know what is the address of that user on AssetHub? With this change they can just pad the address with `0xEE` and rely on the fact that the user can interact with AssetHub using their existing key.

## Why a new variant?
We can't modify the existing variant. Otherwise the same signature would suddenly map to a different account making people lose access to their funds. Instead, we add a new variant that adds control over an additional account for the same signature.
crates:
- name: sp-runtime
bump: major
60 changes: 58 additions & 2 deletions substrate/primitives/runtime/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,8 @@ pub enum MultiSignature {
Sr25519(sr25519::Signature),
/// An ECDSA/SECP256k1 signature.
Ecdsa(ecdsa::Signature),
/// An ECDSA/SECP256k1 signature but with a different address derivation.
Eth(ecdsa::Signature),
}

impl From<ed25519::Signature> for MultiSignature {
Expand Down Expand Up @@ -364,6 +366,13 @@ pub enum MultiSigner {
Sr25519(sr25519::Public),
/// An SECP256k1/ECDSA identity (actually, the Blake2 hash of the compressed pub key).
Ecdsa(ecdsa::Public),
/// Same as `Ecdsa` but its account id is derived based off its eth address instead of its
/// pubkey.
///
/// This is important so that the address matches the address to address mapping in
/// `pallet_revive`. This means that the same public key controls two accounts. But
/// this is already the case due to `pallet_revive`'s address mapping.
Eth(ecdsa::Public),
Comment thread
athei marked this conversation as resolved.
Outdated
Comment thread
athei marked this conversation as resolved.
Outdated
}

impl FromEntropy for MultiSigner {
Expand All @@ -390,6 +399,7 @@ impl AsRef<[u8]> for MultiSigner {
Self::Ed25519(ref who) => who.as_ref(),
Self::Sr25519(ref who) => who.as_ref(),
Self::Ecdsa(ref who) => who.as_ref(),
Self::Eth(ref who) => who.as_ref(),
}
}
}
Expand All @@ -401,6 +411,17 @@ impl traits::IdentifyAccount for MultiSigner {
Self::Ed25519(who) => <[u8; 32]>::from(who).into(),
Self::Sr25519(who) => <[u8; 32]>::from(who).into(),
Self::Ecdsa(who) => sp_io::hashing::blake2_256(who.as_ref()).into(),
Self::Eth(who) => {
// It is important that the account id is based off the eth address rather
// than its pubkey. This is because in many cases we don't know the pubkey
// of an eth account.
let eth_address = &sp_io::hashing::keccak_256(who.as_ref())[12..];
Comment thread
athei marked this conversation as resolved.
// This is by convention: `pallet_revive` maps eth addresses to account ids
// by filling up the additional 12 bytes with 0xEE.
let mut address = [0xEE; 32];
address[..20].copy_from_slice(eth_address);
address.into()
Comment thread
pgherveou marked this conversation as resolved.
},
}
}
}
Expand Down Expand Up @@ -463,6 +484,7 @@ impl std::fmt::Display for MultiSigner {
Self::Ed25519(who) => write!(fmt, "ed25519: {}", who),
Self::Sr25519(who) => write!(fmt, "sr25519: {}", who),
Self::Ecdsa(who) => write!(fmt, "ecdsa: {}", who),
Self::Eth(who) => write!(fmt, "eth: {}", who),
}
}
}
Expand All @@ -479,6 +501,13 @@ impl Verify for MultiSignature {
sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m)
.map_or(false, |pubkey| sp_io::hashing::blake2_256(&pubkey) == who)
},
Self::Eth(sig) => {
let m = sp_io::hashing::blake2_256(msg.get());
Comment thread
athei marked this conversation as resolved.
Outdated
sp_io::crypto::secp256k1_ecdsa_recover_compressed(sig.as_ref(), &m)
.map_or(false, |pubkey| {
&MultiSigner::Eth(pubkey.into()).into_account() == signer
})
},
}
}
}
Expand Down Expand Up @@ -1113,7 +1142,7 @@ mod tests {

use super::*;
use codec::{Decode, Encode};
use sp_core::crypto::Pair;
use sp_core::{crypto::Pair, hex2array};
use sp_io::TestExternalities;
use sp_state_machine::create_proof_check_backend;

Expand Down Expand Up @@ -1191,11 +1220,38 @@ mod tests {
let multi_sig = MultiSignature::from(signature);
let multi_signer = MultiSigner::from(pair.public());
assert!(multi_sig.verify(msg, &multi_signer.into_account()));
}

let multi_signer = MultiSigner::from(pair.public());
#[test]
fn multi_signature_eth_verify_works() {
let msg = &b"test-message"[..];
let (pair, _) = ecdsa::Pair::generate();

let signature = pair.sign(&msg);
assert!(ecdsa::Pair::verify(&signature, msg, &pair.public()));

let multi_sig = MultiSignature::Eth(signature);
let multi_signer = MultiSigner::Eth(pair.public());
assert!(multi_sig.verify(msg, &multi_signer.into_account()));
}

#[test]
fn multi_signer_eth_address_works() {
let pair = ecdsa::Pair::from_seed(&[0x42; 32]);
let ecdsa = MultiSigner::Ecdsa(pair.public()).into_account();
let eth = MultiSigner::Eth(pair.public()).into_account();

assert_eq!(&<AccountId32 as AsRef<[u8; 32]>>::as_ref(&eth)[20..], &[0xEE; 12]);
assert_eq!(
ecdsa,
hex2array!("ff241710529476ac87c67b66ccdc42f95a14b49a896164839fe675dc6f579614").into(),
);
assert_eq!(
eth,
hex2array!("2714c48edc39bc2714729e6530760d62344d6698eeeeeeeeeeeeeeeeeeeeeeee").into(),
);
}

#[test]
fn execute_and_generate_proof_works() {
use codec::Encode;
Expand Down