Skip to content

Commit 5afc54b

Browse files
Merge pull request #1310 from galacticcouncil/fix_claim_account
fix: claim account with wrapped message
2 parents 3e1e75c + 28bc695 commit 5afc54b

7 files changed

Lines changed: 94 additions & 50 deletions

File tree

Cargo.lock

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

integration-tests/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "runtime-integration-tests"
3-
version = "1.61.0"
3+
version = "1.61.1"
44
description = "Integration tests"
55
authors = ["GalacticCouncil"]
66
edition = "2021"

integration-tests/src/evm.rs

Lines changed: 19 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -544,7 +544,7 @@ mod account_conversion {
544544
.pre_dispatch(&account, &call, &info, len);
545545
assert_ok!(&pre);
546546

547-
let result = call.clone().dispatch(RuntimeOrigin::signed(account.clone().into()));
547+
let result = call.clone().dispatch(RuntimeOrigin::signed(account.clone()));
548548
assert_ok!(result);
549549

550550
assert_ok!(
@@ -1249,7 +1249,7 @@ mod currency_precompile {
12491249
})
12501250
);
12511251

1252-
let stored = pallet_evm_accounts::Pallet::<Runtime>::get_allowance(HDX.into(), owner, spender);
1252+
let stored = pallet_evm_accounts::Pallet::<Runtime>::get_allowance(HDX, owner, spender);
12531253
assert_eq!(stored, amount);
12541254
});
12551255
}
@@ -1339,13 +1339,13 @@ mod currency_precompile {
13391339

13401340
approve(80u128 * UNITS);
13411341
assert_eq!(
1342-
pallet_evm_accounts::Pallet::<Runtime>::get_allowance(HDX.into(), owner, spender),
1342+
pallet_evm_accounts::Pallet::<Runtime>::get_allowance(HDX, owner, spender),
13431343
80u128 * UNITS
13441344
);
13451345

13461346
approve(0);
13471347
assert_eq!(
1348-
pallet_evm_accounts::Pallet::<Runtime>::get_allowance(HDX.into(), owner, spender),
1348+
pallet_evm_accounts::Pallet::<Runtime>::get_allowance(HDX, owner, spender),
13491349
0
13501350
);
13511351
});
@@ -2864,7 +2864,7 @@ mod account_marking {
28642864
10000 * UNITS as i128,
28652865
));
28662866

2867-
let state = frame_system::Pallet::<Runtime>::account(&user_acc.address());
2867+
let state = frame_system::Pallet::<Runtime>::account(user_acc.address());
28682868
assert_eq!(state.providers, 1);
28692869
assert_eq!(state.sufficients, 0);
28702870
assert_eq!(state.nonce, 0);
@@ -2914,7 +2914,7 @@ mod account_marking {
29142914
));
29152915

29162916
// Verify the nonce and sufficients were incremented through EVM transactions
2917-
let state = frame_system::Pallet::<Runtime>::account(&user_acc.address());
2917+
let state = frame_system::Pallet::<Runtime>::account(user_acc.address());
29182918
assert_eq!(state.providers, 1);
29192919
assert_eq!(state.sufficients, 1);
29202920
assert_eq!(state.nonce, 3,);
@@ -2979,7 +2979,7 @@ mod account_marking {
29792979
initial_amount as i128,
29802980
));
29812981

2982-
let state = frame_system::Pallet::<Runtime>::account(&user_acc.address());
2982+
let state = frame_system::Pallet::<Runtime>::account(user_acc.address());
29832983
assert_eq!(state.providers, 1);
29842984

29852985
assert_ok!(Omnipool::sell(
@@ -3042,7 +3042,7 @@ mod account_marking {
30423042
let free_hdx = Currencies::free_balance(HDX, &user_acc.address());
30433043
assert_eq!(free_hdx, 0);
30443044

3045-
let state = frame_system::Pallet::<Runtime>::account(&user_acc.address());
3045+
let state = frame_system::Pallet::<Runtime>::account(user_acc.address());
30463046
assert_eq!(state.providers, 0);
30473047
assert_eq!(state.sufficients, 1);
30483048
assert_eq!(state.nonce, 3);
@@ -3120,7 +3120,7 @@ mod account_marking {
31203120
initial_amount as i128,
31213121
));
31223122

3123-
let state = frame_system::Pallet::<Runtime>::account(&user_acc.address());
3123+
let state = frame_system::Pallet::<Runtime>::account(user_acc.address());
31243124
assert_eq!(state.providers, 1);
31253125

31263126
assert_ok!(Omnipool::sell(
@@ -3185,7 +3185,7 @@ mod account_marking {
31853185
let free_hdx = Currencies::free_balance(HDX, &user_acc.address());
31863186
assert!(free_hdx > 0);
31873187

3188-
let state = frame_system::Pallet::<Runtime>::account(&user_acc.address());
3188+
let state = frame_system::Pallet::<Runtime>::account(user_acc.address());
31893189
assert_eq!(state.providers, 1);
31903190
assert_eq!(state.sufficients, 1);
31913191
assert_eq!(state.nonce, 0);
@@ -3915,7 +3915,7 @@ fn raw_eip1559_eth_call_with_params(is_batch: bool, nonce: U256, input: Vec<u8>)
39153915
gas_limit: gas_limit.into(),
39163916
action: TransactionAction::Call(DISPATCH_ADDR),
39173917
value: U256::zero(),
3918-
input: input_data.clone().into(),
3918+
input: input_data.clone(),
39193919
access_list: vec![],
39203920
};
39213921

@@ -3937,7 +3937,7 @@ fn raw_eip1559_eth_call_with_params(is_batch: bool, nonce: U256, input: Vec<u8>)
39373937
gas_limit: gas_limit.into(),
39383938
action: TransactionAction::Call(DISPATCH_ADDR),
39393939
value: U256::zero(),
3940-
input: input_data.into(),
3940+
input: input_data,
39413941
access_list: vec![],
39423942
odd_y_parity,
39433943
r: H256::from(rs.r.b32()),
@@ -4043,7 +4043,7 @@ fn raw_legacy_eth_call_with_params(is_batch: bool, nonce: U256, input: Vec<u8>)
40434043
gas_limit: gas_limit.into(),
40444044
action: TransactionAction::Call(DISPATCH_ADDR),
40454045
value: U256::zero(),
4046-
input: input_data.clone().into(),
4046+
input: input_data.clone(),
40474047
chain_id: None,
40484048
};
40494049

@@ -4067,7 +4067,7 @@ fn raw_legacy_eth_call_with_params(is_batch: bool, nonce: U256, input: Vec<u8>)
40674067
gas_limit: gas_limit.into(),
40684068
action: TransactionAction::Call(DISPATCH_ADDR),
40694069
value: U256::zero(),
4070-
input: input_data.into(),
4070+
input: input_data,
40714071
signature,
40724072
};
40734073

@@ -4667,7 +4667,7 @@ mod evm_error_decoder {
46674667
..Config::default()
46684668
});
46694669

4670-
let _ = runner
4670+
runner
46714671
.run(&random_error_string(), |value| {
46724672
let call_result = CallResult {
46734673
exit_reason: ExitReason::Error(ExitError::Other("Some error".into())),
@@ -5003,7 +5003,7 @@ mod evm_error_decoder {
50035003

50045004
#[test]
50055005
fn test_scale_decode_malicious_payload() {
5006-
let malicious_payloads = vec![
5006+
let malicious_payloads = [
50075007
// Looks like Module error (discriminant 3) with crafted data
50085008
vec![0x03, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00],
50095009
// Looks like Other variant (discriminant 0) with invalid string data
@@ -5026,7 +5026,7 @@ mod evm_error_decoder {
50265026
}
50275027

50285028
#[test]
5029-
fn dispatch_decode_with_malformed_scawle_payloads_should_not_panic() {
5029+
fn dispatch_decode_with_malformed_scale_payloads_should_not_panic() {
50305030
// Test various malicious/malformed SCALE-encoded payloads
50315031
// that could trigger panics in decode_with_depth_limit
50325032
let test_cases = vec![
@@ -5062,12 +5062,12 @@ mod evm_error_decoder {
50625062
};
50635063

50645064
let _result = EvmErrorDecoder::convert(call_result.clone());
5065-
DispatchError::decode_with_depth_limit(MAX_DECODE_DEPTH, &mut &call_result.value[..]);
5065+
let _ = DispatchError::decode_with_depth_limit(MAX_DECODE_DEPTH, &mut &call_result.value[..]);
50665066
}
50675067
}
50685068

50695069
#[test]
5070-
fn dispatch_decode_cannot_pani_for_different_multi_byte_patterns() {
5070+
fn dispatch_decode_cannot_panic_for_different_multi_byte_patterns() {
50715071
for byte1 in [0x00, 0x03, 0x06, 0x07, 0xFF].iter() {
50725072
for byte2 in [0x00, 0xFF].iter() {
50735073
let call_result = CallResult {

pallets/evm-accounts/Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[package]
22
name = "pallet-evm-accounts"
3-
version = "1.4.2"
3+
version = "1.4.3"
44
authors = ["GalacticCouncil"]
55
edition = "2021"
66
license = "Apache-2.0"

pallets/evm-accounts/src/lib.rs

Lines changed: 30 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -236,16 +236,7 @@ pub mod pallet {
236236
{
237237
type Call = Call<T>;
238238

239-
fn validate_unsigned(source: TransactionSource, call: &Self::Call) -> TransactionValidity {
240-
match source {
241-
TransactionSource::External => {
242-
// receiving unsigned transaction from network - disallow
243-
return InvalidTransaction::Call.into();
244-
}
245-
TransactionSource::Local => {} // produced by off-chain worker
246-
TransactionSource::InBlock => {} // some other node included it in a block
247-
};
248-
239+
fn validate_unsigned(_source: TransactionSource, call: &Self::Call) -> TransactionValidity {
249240
let valid_tx = |user| {
250241
ValidTransaction::with_tag_prefix("evm-accounts")
251242
.priority(UNSIGNED_TXS_PRIORITY)
@@ -265,8 +256,8 @@ pub mod pallet {
265256
} => {
266257
// validate transaction
267258
match (
268-
Self::verify_claim_account(&account, asset_id.clone(), signature.clone()),
269-
Self::validate_bind_evm_address(&account, &Self::evm_address(&account)),
259+
Self::verify_claim_account(account, *asset_id, signature.clone()),
260+
Self::validate_bind_evm_address(account, &Self::evm_address(&account)),
270261
) {
271262
(Ok(()), Ok(())) => valid_tx(account),
272263
_ => InvalidTransaction::Call.into(),
@@ -480,31 +471,51 @@ where
480471
fn verify_claim_account(account: &T::AccountId, asset_id: T::AssetId, signature: Signature) -> DispatchResult {
481472
let msg = Self::create_claim_account_message(account, asset_id);
482473

483-
ensure!(
484-
signature.verify(msg.as_slice(), &account.clone().into()),
485-
Error::<T>::InvalidSignature
486-
);
474+
Self::validate_signature(msg.as_slice(), &signature, account)?;
487475

488476
T::FeeCurrency::is_payment_currency(asset_id)?;
489477

490478
ensure!(
491-
!frame_system::Pallet::<T>::account_exists(&account),
479+
!frame_system::Pallet::<T>::account_exists(account),
492480
Error::<T>::AccountAlreadyExists
493481
);
494482

495483
ensure!(
496-
T::Currency::balance(asset_id, &account) >= T::ExistentialDeposits::get(&asset_id),
484+
T::Currency::balance(asset_id, account) >= T::ExistentialDeposits::get(&asset_id),
497485
Error::<T>::InsufficientAssetBalance
498486
);
499487

500488
Ok(())
501489
}
502490

503491
/// Creates a message that can be used to prove ownership of an account.
504-
fn create_claim_account_message(account: &T::AccountId, asset_id: T::AssetId) -> Vec<u8> {
492+
pub fn create_claim_account_message(account: &T::AccountId, asset_id: T::AssetId) -> Vec<u8> {
505493
(MESSAGE_PREFIX, account.clone(), asset_id).encode()
506494
}
507495

496+
/// Validate a signature. Supports signatures on raw `data` or `data` wrapped in HTML `<Bytes>`.
497+
pub fn validate_signature(data: &[u8], signature: &Signature, signer: &T::AccountId) -> DispatchResult {
498+
// Happy path, user has signed the raw data.
499+
if signature.verify(data, &signer.clone().into()) {
500+
return Ok(());
501+
}
502+
// NOTE: for security reasons modern UIs implicitly wrap the data requested to sign into
503+
// `<Bytes> + data + </Bytes>`.
504+
let prefix = b"<Bytes>";
505+
let suffix = b"</Bytes>";
506+
let mut wrapped: Vec<u8> = Vec::with_capacity(data.len() + prefix.len() + suffix.len());
507+
wrapped.extend(prefix);
508+
wrapped.extend(data);
509+
wrapped.extend(suffix);
510+
511+
ensure!(
512+
signature.verify(&wrapped[..], &signer.clone().into()),
513+
Error::<T>::InvalidSignature
514+
);
515+
516+
Ok(())
517+
}
518+
508519
fn _is_evm_account(account_id: &[u8; 32]) -> bool {
509520
&account_id[0..4] == b"ETH\0" && account_id[24..32] == [0u8; 8]
510521
}

pallets/evm-accounts/src/mock.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ impl AccountFeeCurrency<AccountId> for FeeCurrencyMock {
7272
type AssetId = AssetId;
7373

7474
fn get(a: &AccountId) -> Self::AssetId {
75-
FEE_ASSET.with(|v| v.borrow().get(&a).copied()).unwrap_or_default()
75+
FEE_ASSET.with(|v| v.borrow().get(a).copied()).unwrap_or_default()
7676
}
7777
fn set(who: &AccountId, asset_id: Self::AssetId) -> DispatchResult {
7878
FEE_ASSET.with(|v| {

pallets/evm-accounts/src/tests.rs

Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ use frame_support::sp_runtime::traits::ValidateUnsigned;
2323
use frame_support::sp_runtime::traits::Zero;
2424
use frame_support::sp_runtime::DispatchError;
2525
use frame_support::unsigned::TransactionValidityError;
26-
use frame_support::{assert_noop, assert_ok, assert_storage_noop};
26+
use frame_support::{assert_err, assert_noop, assert_ok, assert_storage_noop};
2727
use hex_literal::hex;
2828
use mock::*;
2929
use orml_traits::MultiCurrency;
@@ -221,7 +221,7 @@ fn renounce_contract_deployer_should_remove_address_from_the_storage() {
221221
}
222222

223223
#[test]
224-
fn verify_signed_message_should_work() {
224+
fn verify_signed_message_should_work_if_message_not_wrapped() {
225225
ExtBuilder::default().build().execute_with(|| {
226226
// Arrange & Act
227227
let pair = sp_core::sr25519::Pair::from_seed_slice([1; 64].as_slice()).unwrap();
@@ -231,7 +231,31 @@ fn verify_signed_message_should_work() {
231231
let msg = EVMAccounts::create_claim_account_message(&account, HDX);
232232

233233
// Assert
234-
assert!(signature.verify(msg.as_slice(), &account.clone().into()));
234+
assert_ok!(EVMAccounts::validate_signature(msg.as_slice(), &signature, &account));
235+
});
236+
}
237+
238+
#[test]
239+
fn verify_signed_message_should_work_if_message_wrapped() {
240+
ExtBuilder::default().build().execute_with(|| {
241+
// Arrange & Act
242+
let pair = sp_core::sr25519::Pair::from_seed_slice([1; 64].as_slice()).unwrap();
243+
let account = frame_support::sp_runtime::MultiSigner::from(pair.public()).into_account();
244+
245+
let original_msg = EVMAccounts::create_claim_account_message(&account, HDX);
246+
let mut wrapped_msg = Vec::new();
247+
wrapped_msg.extend(b"<Bytes>");
248+
wrapped_msg.extend(original_msg);
249+
wrapped_msg.extend(b"</Bytes>");
250+
251+
let signature = Signature::Sr25519(pair.sign(wrapped_msg.as_slice()));
252+
253+
// Assert
254+
assert_ok!(EVMAccounts::validate_signature(
255+
wrapped_msg.as_slice(),
256+
&signature,
257+
&account
258+
));
235259
});
236260
}
237261

@@ -246,7 +270,10 @@ fn verify_signed_message_should_fail_if_different_account() {
246270
let msg = EVMAccounts::create_claim_account_message(&account, HDX);
247271

248272
// Assert
249-
assert_eq!(signature.verify(msg.as_slice(), &ALICE.into()), false);
273+
assert_err!(
274+
EVMAccounts::validate_signature(msg.as_slice(), &signature, &ALICE),
275+
Error::<Test>::InvalidSignature
276+
);
250277
});
251278
}
252279

@@ -261,7 +288,10 @@ fn verify_signed_message_should_fail_if_different_asset() {
261288
let msg = EVMAccounts::create_claim_account_message(&account, 1);
262289

263290
// Assert
264-
assert_eq!(signature.verify(msg.as_slice(), &account.into()), false);
291+
assert_err!(
292+
EVMAccounts::validate_signature(msg.as_slice(), &signature, &account),
293+
Error::<Test>::InvalidSignature
294+
);
265295
});
266296
}
267297

@@ -275,7 +305,10 @@ fn verify_signed_message_should_fail_if_different_message() {
275305
let signature = sign_message::<Test>(pair, &account, HDX);
276306

277307
// Assert
278-
assert_eq!(signature.verify("wrong message".as_bytes(), &account.into()), false);
308+
assert_err!(
309+
EVMAccounts::validate_signature("wrong message".as_bytes(), &signature, &account),
310+
Error::<Test>::InvalidSignature
311+
);
279312
});
280313
}
281314

@@ -470,7 +503,7 @@ fn validate_unsigned_should_pass_if_correct_call() {
470503

471504
// Act & Assert
472505
assert_storage_noop!({
473-
let res = EVMAccounts::validate_unsigned(TransactionSource::Local, &call);
506+
let res = EVMAccounts::validate_unsigned(TransactionSource::External, &call);
474507
assert_ok!(res);
475508
});
476509
});

0 commit comments

Comments
 (0)