Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Claim Account to use Eip-712 #1755

Merged
merged 20 commits into from
Jan 12, 2022
Merged
Show file tree
Hide file tree
Changes from 19 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
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions modules/evm-accounts/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ orml-traits = { path = "../../orml/traits", default-features = false }

primitives = { package = "acala-primitives", path = "../../primitives", default-features = false }
module-support = { path = "../support", default-features = false }
module-evm-utiltity-macro = { path = "../evm-utiltity/macro" }

[dev-dependencies]
pallet-balances = { git = "https://github.com/paritytech/substrate", branch = "polkadot-v0.9.13" }
Expand Down
107 changes: 57 additions & 50 deletions modules/evm-accounts/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -34,16 +34,18 @@ use frame_support::{
transactional,
};
use frame_system::{ensure_signed, pallet_prelude::*};
use module_evm_utiltity_macro::keccak256;
use module_support::AddressMapping;
use orml_traits::currency::TransferAll;
use primitives::{evm::EvmAddress, AccountIndex};
use sp_core::{crypto::AccountId32, ecdsa};
use primitives::{evm::EvmAddress, to_bytes, AccountIndex};
use sp_core::crypto::AccountId32;
use sp_core::{H160, H256};
use sp_io::{
crypto::secp256k1_ecdsa_recover,
hashing::{blake2_256, keccak_256},
};
use sp_runtime::{
traits::{LookupError, StaticLookup},
traits::{LookupError, StaticLookup, Zero},
MultiAddress,
};
use sp_std::{marker::PhantomData, vec::Vec};
Expand All @@ -55,7 +57,8 @@ pub mod weights;
pub use module::*;
pub use weights::WeightInfo;

pub type EcdsaSignature = ecdsa::Signature;
/// A signature (a 512-bit value, plus 8 bits for recovery ID).
pub type Eip712Signature = [u8; 65];

#[frame_support::pallet]
pub mod module {
Expand All @@ -71,6 +74,10 @@ pub mod module {
/// Mapping from address to account id.
type AddressMapping: AddressMapping<Self::AccountId>;

/// Chain ID of EVM.
#[pallet::constant]
type ChainId: Get<u64>;

/// Merge free balance from source to dest.
type TransferAll: TransferAll<Self::AccountId>;

Expand Down Expand Up @@ -136,7 +143,7 @@ pub mod module {
pub fn claim_account(
origin: OriginFor<T>,
eth_address: EvmAddress,
eth_signature: EcdsaSignature,
eth_signature: Eip712Signature,
) -> DispatchResult {
let who = ensure_signed(origin)?;

Expand All @@ -148,8 +155,7 @@ pub mod module {
);

// recover evm address from signature
let address = Self::eth_recover(&eth_signature, &who.using_encoded(to_ascii_hex), &[][..])
.ok_or(Error::<T>::BadSignature)?;
let address = Self::verify_eip712_signature(&who, &eth_signature).ok_or(Error::<T>::BadSignature)?;
ensure!(eth_address == address, Error::<T>::InvalidSignature);

// check if the evm padded address already exists
Expand Down Expand Up @@ -193,34 +199,6 @@ pub mod module {
}

impl<T: Config> Pallet<T> {
// Constructs the message that Ethereum RPC's `personal_sign` and `eth_sign`
// would sign.
pub fn ethereum_signable_message(what: &[u8], extra: &[u8]) -> Vec<u8> {
let prefix = b"acala evm:";
let mut l = prefix.len() + what.len() + extra.len();
let mut rev = Vec::new();
while l > 0 {
rev.push(b'0' + (l % 10) as u8);
l /= 10;
}
let mut v = b"\x19Ethereum Signed Message:\n".to_vec();
v.extend(rev.into_iter().rev());
v.extend_from_slice(&prefix[..]);
v.extend_from_slice(what);
v.extend_from_slice(extra);
v
}

// Attempts to recover the Ethereum address from a message signature signed by
// using the Ethereum RPC's `personal_sign` and `eth_sign`.
pub fn eth_recover(s: &EcdsaSignature, what: &[u8], extra: &[u8]) -> Option<EvmAddress> {
let msg = keccak_256(&Self::ethereum_signable_message(what, extra));
let mut res = EvmAddress::default();
res.0
.copy_from_slice(&keccak_256(&secp256k1_ecdsa_recover(&s.0, &msg).ok()?[..])[12..]);
Some(res)
}

#[cfg(any(feature = "runtime-benchmarks", feature = "std"))]
// Returns an Etherum public key derived from an Ethereum secret key.
pub fn eth_public(secret: &libsecp256k1::SecretKey) -> libsecp256k1::PublicKey {
Expand All @@ -236,16 +214,57 @@ impl<T: Config> Pallet<T> {

#[cfg(any(feature = "runtime-benchmarks", feature = "std"))]
// Constructs a message and signs it.
pub fn eth_sign(secret: &libsecp256k1::SecretKey, what: &[u8], extra: &[u8]) -> EcdsaSignature {
let msg = keccak_256(&Self::ethereum_signable_message(&to_ascii_hex(what)[..], extra));
pub fn eth_sign(secret: &libsecp256k1::SecretKey, who: &T::AccountId) -> Eip712Signature {
let msg = keccak_256(&Self::eip712_signable_message(who));
let (sig, recovery_id) = libsecp256k1::sign(&libsecp256k1::Message::parse(&msg), secret);
let mut r = [0u8; 65];
r[0..64].copy_from_slice(&sig.serialize()[..]);
r[64] = recovery_id.serialize();
EcdsaSignature::from_slice(&r)
r
}

fn verify_eip712_signature(who: &T::AccountId, sig: &[u8; 65]) -> Option<H160> {
let msg = Self::eip712_signable_message(who);
let msg_hash = keccak_256(msg.as_slice());

recover_signer(sig, &msg_hash)
}

// Eip-712 message to be signed
fn eip712_signable_message(who: &T::AccountId) -> Vec<u8> {
let domain_separator = Self::evm_account_domain_separator();
let payload_hash = Self::evm_account_payload_hash(who);

let mut msg = b"\x19\x01".to_vec();
msg.extend_from_slice(&domain_separator);
msg.extend_from_slice(&payload_hash);
msg
}

fn evm_account_payload_hash(who: &T::AccountId) -> [u8; 32] {
let tx_type_hash = keccak256!("Transaction(bytes substrateAddress)");
let mut tx_msg = tx_type_hash.to_vec();
tx_msg.extend_from_slice(&keccak_256(&who.encode()));
keccak_256(tx_msg.as_slice())
}

fn evm_account_domain_separator() -> [u8; 32] {
let domain_hash = keccak256!("EIP712Domain(string name,string version,uint256 chainId,bytes32 salt)");
let mut domain_seperator_msg = domain_hash.to_vec();
domain_seperator_msg.extend_from_slice(keccak256!("Acala EVM claim")); // name
domain_seperator_msg.extend_from_slice(keccak256!("1")); // version
domain_seperator_msg.extend_from_slice(&to_bytes(T::ChainId::get())); // chain id
domain_seperator_msg.extend_from_slice(frame_system::Pallet::<T>::block_hash(T::BlockNumber::zero()).as_ref()); // genesis block hash
keccak_256(domain_seperator_msg.as_slice())
}
}

fn recover_signer(sig: &[u8; 65], msg_hash: &[u8; 32]) -> Option<H160> {
secp256k1_ecdsa_recover(sig, msg_hash)
.map(|pubkey| H160::from(H256::from_slice(&keccak_256(&pubkey))))
.ok()
}

// Creates a an EvmAddress from an AccountId by appending the bytes "evm:" to
// the account_id and hashing it.
fn account_to_default_evm_address(account_id: &impl Encode) -> EvmAddress {
Expand Down Expand Up @@ -345,15 +364,3 @@ impl<T: Config> StaticLookup for Pallet<T> {
MultiAddress::Id(a)
}
}

/// Converts the given binary data into ASCII-encoded hex. It will be twice
/// the length.
pub fn to_ascii_hex(data: &[u8]) -> Vec<u8> {
let mut r = Vec::with_capacity(data.len() * 2);
let mut push_nibble = |n| r.push(if n < 10 { b'0' + n } else { b'a' - 10 + n });
for &b in data.iter() {
push_nibble(b / 16);
push_nibble(b % 16);
}
r
}
1 change: 1 addition & 0 deletions modules/evm-accounts/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,7 @@ pub type AdaptedBasicCurrency = orml_currencies::BasicCurrencyAdapter<Runtime, B
impl Config for Runtime {
type Event = Event;
type Currency = Balances;
type ChainId = ();
type AddressMapping = EvmAddressMapping<Runtime>;
type TransferAll = Currencies;
type WeightInfo = ();
Expand Down
26 changes: 9 additions & 17 deletions modules/evm-accounts/src/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ fn claim_account_work() {
assert_ok!(EvmAccountsModule::claim_account(
Origin::signed(ALICE),
EvmAccountsModule::eth_address(&alice()),
EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..])
EvmAccountsModule::eth_sign(&alice(), &ALICE)
));
System::assert_last_event(Event::EvmAccountsModule(crate::Event::ClaimAccount {
account_id: ALICE,
Expand All @@ -51,44 +51,36 @@ fn claim_account_should_not_work() {
EvmAccountsModule::claim_account(
Origin::signed(ALICE),
EvmAccountsModule::eth_address(&bob()),
EvmAccountsModule::eth_sign(&bob(), &ALICE.encode(), &vec![1][..])
EvmAccountsModule::eth_sign(&bob(), &BOB)
),
Error::<Runtime>::InvalidSignature
);
assert_noop!(
EvmAccountsModule::claim_account(
Origin::signed(ALICE),
EvmAccountsModule::eth_address(&bob()),
EvmAccountsModule::eth_sign(&bob(), &BOB.encode(), &[][..])
),
Error::<Runtime>::InvalidSignature
);
assert_noop!(
EvmAccountsModule::claim_account(
Origin::signed(ALICE),
EvmAccountsModule::eth_address(&bob()),
EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..])
EvmAccountsModule::eth_sign(&alice(), &ALICE)
),
Error::<Runtime>::InvalidSignature
);
assert_ok!(EvmAccountsModule::claim_account(
Origin::signed(ALICE),
EvmAccountsModule::eth_address(&alice()),
EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..])
EvmAccountsModule::eth_sign(&alice(), &ALICE)
));
assert_noop!(
EvmAccountsModule::claim_account(
Origin::signed(ALICE),
EvmAccountsModule::eth_address(&alice()),
EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..])
EvmAccountsModule::eth_sign(&alice(), &ALICE)
),
Error::<Runtime>::AccountIdHasMapped
);
assert_noop!(
EvmAccountsModule::claim_account(
Origin::signed(BOB),
EvmAccountsModule::eth_address(&alice()),
EvmAccountsModule::eth_sign(&alice(), &BOB.encode(), &[][..])
EvmAccountsModule::eth_sign(&alice(), &BOB)
),
Error::<Runtime>::EthAddressHasMapped
);
Expand All @@ -112,7 +104,7 @@ fn evm_get_account_id() {
assert_ok!(EvmAccountsModule::claim_account(
Origin::signed(ALICE),
EvmAccountsModule::eth_address(&alice()),
EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..])
EvmAccountsModule::eth_sign(&alice(), &ALICE)
));

assert_eq!(EvmAddressMapping::<Runtime>::get_account_id(&evm_account), ALICE);
Expand Down Expand Up @@ -140,7 +132,7 @@ fn account_to_evm() {
assert_ok!(EvmAccountsModule::claim_account(
Origin::signed(ALICE),
alice_evm_account,
EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..])
EvmAccountsModule::eth_sign(&alice(), &ALICE)
));

assert_eq!(EvmAddressMapping::<Runtime>::get_account_id(&alice_evm_account), ALICE);
Expand Down Expand Up @@ -185,7 +177,7 @@ fn account_to_evm_with_create_default() {
EvmAccountsModule::claim_account(
Origin::signed(ALICE),
alice_evm_account,
EvmAccountsModule::eth_sign(&alice(), &ALICE.encode(), &[][..])
EvmAccountsModule::eth_sign(&alice(), &ALICE)
),
Error::<Runtime>::AccountIdHasMapped
);
Expand Down
6 changes: 6 additions & 0 deletions primitives/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ pub mod unchecked_extrinsic;

use codec::{Decode, Encode, MaxEncodedLen};
use scale_info::TypeInfo;
use sp_core::U256;
use sp_runtime::{
generic,
traits::{BlakeTwo256, CheckedDiv, IdentifyAccount, Saturating, Verify, Zero},
Expand Down Expand Up @@ -205,3 +206,8 @@ pub fn convert_decimals_from_evm<B: Zero + Saturating + CheckedDiv + PartialEq +
None
}
}

/// Convert any type that implements Into<U256> into byte representation ([u8, 32])
pub fn to_bytes<T: Into<U256>>(value: T) -> [u8; 32] {
Into::<[u8; 32]>::into(value.into())
}
9 changes: 3 additions & 6 deletions primitives/src/unchecked_extrinsic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
// You should have received a copy of the GNU General Public License
// along with this program. If not, see <https://www.gnu.org/licenses/>.

use crate::{evm::EthereumTransactionMessage, signature::AcalaMultiSignature, Address, Balance};
use crate::{evm::EthereumTransactionMessage, signature::AcalaMultiSignature, to_bytes, Address, Balance};
use codec::{Decode, Encode};
use frame_support::{
log,
Expand All @@ -26,7 +26,7 @@ use frame_support::{
use module_evm_utiltity::ethereum::{EIP1559TransactionMessage, LegacyTransactionMessage, TransactionAction};
use module_evm_utiltity_macro::keccak256;
use scale_info::TypeInfo;
use sp_core::{H160, H256, U256};
use sp_core::{H160, H256};
use sp_io::{crypto::secp256k1_ecdsa_recover, hashing::keccak_256};
use sp_runtime::{
generic::{CheckedExtrinsic, UncheckedExtrinsic},
Expand Down Expand Up @@ -93,10 +93,6 @@ impl<Call, Extra: SignedExtension, ConvertTx, StorageDepositPerByte, TxFeePerGas
}
}

fn to_bytes<T: Into<U256>>(value: T) -> [u8; 32] {
Into::<[u8; 32]>::into(value.into())
}

impl<Call, Extra, ConvertTx, StorageDepositPerByte, TxFeePerGas, Lookup> Checkable<Lookup>
for AcalaUncheckedExtrinsic<Call, Extra, ConvertTx, StorageDepositPerByte, TxFeePerGas>
where
Expand Down Expand Up @@ -346,6 +342,7 @@ mod tests {
use super::*;
use hex_literal::hex;
use module_evm_utiltity::ethereum::AccessListItem;
use sp_core::U256;
use std::{ops::Add, str::FromStr};

#[test]
Expand Down
1 change: 1 addition & 0 deletions runtime/acala/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1144,6 +1144,7 @@ impl module_evm_accounts::Config for Runtime {
type Currency = Balances;
type AddressMapping = EvmAddressMapping<Runtime>;
type TransferAll = Currencies;
type ChainId = ChainId;
type WeightInfo = weights::module_evm_accounts::WeightInfo<Runtime>;
}

Expand Down
11 changes: 5 additions & 6 deletions runtime/acala/src/weights/module_evm_accounts.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
//! Autogenerated weights for module_evm_accounts
//!
//! THIS FILE WAS AUTO-GENERATED USING THE SUBSTRATE BENCHMARK CLI VERSION 4.0.0-dev
//! DATE: 2021-10-30, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! DATE: 2022-01-10, STEPS: `50`, REPEAT: 20, LOW RANGE: `[]`, HIGH RANGE: `[]`
//! EXECUTION: Some(Wasm), WASM-EXECUTION: Compiled, CHAIN: Some("acala-latest"), DB CACHE: 128

// Executed Command:
Expand All @@ -28,15 +28,14 @@
// --chain=acala-latest
// --steps=50
// --repeat=20
// --pallet=*
// --pallet=module-evm-accounts
// --extrinsic=*
// --execution=wasm
// --wasm-execution=compiled
// --heap-pages=4096
// --template=./templates/runtime-weight-template.hbs
// --output=./runtime/acala/src/weights/


#![cfg_attr(rustfmt, rustfmt_skip)]
#![allow(unused_parens)]
#![allow(unused_imports)]
Expand All @@ -48,12 +47,12 @@ use sp_std::marker::PhantomData;
pub struct WeightInfo<T>(PhantomData<T>);
impl<T: frame_system::Config> module_evm_accounts::WeightInfo for WeightInfo<T> {
fn claim_account() -> Weight {
(241_242_000 as Weight)
.saturating_add(T::DbWeight::get().reads(3 as Weight))
(269_354_000 as Weight)
.saturating_add(T::DbWeight::get().reads(4 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
}
fn claim_default_account() -> Weight {
(29_492_000 as Weight)
(29_347_000 as Weight)
.saturating_add(T::DbWeight::get().reads(1 as Weight))
.saturating_add(T::DbWeight::get().writes(2 as Weight))
}
Expand Down
1 change: 1 addition & 0 deletions runtime/common/src/mock.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ impl module_evm_accounts::Config for TestRuntime {
type Currency = Balances;
type AddressMapping = EvmAddressMapping<TestRuntime>;
type TransferAll = Currencies;
type ChainId = ChainId;
type WeightInfo = ();
}

Expand Down
Loading