From ed2ac967e7d44e83e61dfaf9cd13e4f8f591434f Mon Sep 17 00:00:00 2001 From: borngraced Date: Mon, 2 Jan 2023 22:14:01 +0100 Subject: [PATCH 1/4] validate input_tx is 'receiverSpend' in extract_secret Signed-off-by: borngraced --- mm2src/coins/eth.rs | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index bf2b93fef4..2b17000145 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -127,6 +127,10 @@ const GAS_PRICE_APPROXIMATION_PERCENT_ON_TRADE_PREIMAGE: u64 = 7; /// Lifetime of generated signed message for gui-auth requests const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; +/// recieverSpend contract call bytes signature. +/// https://www.4byte.directory/signatures/?bytes4_signature=02ed292b. +const RECEIVERSPEND_BYTES_SIGNATURE: &str = "02ed292b"; + lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); @@ -1134,6 +1138,17 @@ impl SwapOps for EthCoin { async fn extract_secret(&self, _secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { let unverified: UnverifiedTransaction = try_s!(rlp::decode(spend_tx)); + + // Contract call expected to be receiverSpend (02ed292b). + let contract_call_sig = hex::encode(&unverified.data[0..4]); + if contract_call_sig != RECEIVERSPEND_BYTES_SIGNATURE { + return ERR!( + "Expected 'receiverSpend' call: ({}) contract but found {}", + RECEIVERSPEND_BYTES_SIGNATURE, + contract_call_sig + ); + } + let function = try_s!(SWAP_CONTRACT.function("receiverSpend")); let tokens = try_s!(function.decode_input(&unverified.data)); if tokens.len() < 3 { From 7385c098ad07d7b7d6ae2a6e4f458249b3e07b33 Mon Sep 17 00:00:00 2001 From: borngraced Date: Mon, 2 Jan 2023 22:19:40 +0100 Subject: [PATCH 2/4] fix inline doc --- mm2src/coins/eth.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 2b17000145..6d1b09c804 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -1143,7 +1143,7 @@ impl SwapOps for EthCoin { let contract_call_sig = hex::encode(&unverified.data[0..4]); if contract_call_sig != RECEIVERSPEND_BYTES_SIGNATURE { return ERR!( - "Expected 'receiverSpend' call: ({}) contract but found {}", + "Expected 'receiverSpend' contract call signature: ({}) but found {}", RECEIVERSPEND_BYTES_SIGNATURE, contract_call_sig ); From cc7fd4a50b6e0b040235abbb9289104e47ceda6f Mon Sep 17 00:00:00 2001 From: borngraced Date: Tue, 3 Jan 2023 04:03:15 +0100 Subject: [PATCH 3/4] unit test --- mm2src/coins/eth.rs | 14 +++++++------- mm2src/coins/eth/eth_tests.rs | 26 +++++++++++++++++++++++--- 2 files changed, 30 insertions(+), 10 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 6d1b09c804..0e8c00dd07 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -39,6 +39,7 @@ use ethkey::{sign, verify_address}; use futures::compat::Future01CompatExt; use futures::future::{join_all, select, Either, FutureExt, TryFutureExt}; use futures01::Future; +use hex::encode; use http::StatusCode; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; @@ -1138,18 +1139,17 @@ impl SwapOps for EthCoin { async fn extract_secret(&self, _secret_hash: &[u8], spend_tx: &[u8]) -> Result, String> { let unverified: UnverifiedTransaction = try_s!(rlp::decode(spend_tx)); + let function = try_s!(SWAP_CONTRACT.function("receiverSpend")); - // Contract call expected to be receiverSpend (02ed292b). - let contract_call_sig = hex::encode(&unverified.data[0..4]); - if contract_call_sig != RECEIVERSPEND_BYTES_SIGNATURE { + // Validate contract call; expected to be receiverSpend (02ed292b). + if unverified.data[0..4] != function.short_signature() { return ERR!( - "Expected 'receiverSpend' contract call signature: ({}) but found {}", + "Expected 'receiverSpend' contract call signature: {}, found {}", RECEIVERSPEND_BYTES_SIGNATURE, - contract_call_sig + encode(&unverified.data[0..4]) ); - } + }; - let function = try_s!(SWAP_CONTRACT.function("receiverSpend")); let tokens = try_s!(function.decode_input(&unverified.data)); if tokens.len() < 3 { return ERR!("Invalid arguments in 'receiverSpend' call: {:?}", tokens); diff --git a/mm2src/coins/eth/eth_tests.rs b/mm2src/coins/eth/eth_tests.rs index 713899489a..19f91b5a89 100644 --- a/mm2src/coins/eth/eth_tests.rs +++ b/mm2src/coins/eth/eth_tests.rs @@ -1405,7 +1405,7 @@ fn test_eth_extract_secret() { })); // raw transaction bytes of https://ropsten.etherscan.io/tx/0xcb7c14d3ff309996d582400369393b6fa42314c52245115d4a3f77f072c36da9 - let tx_hex = &[ + let tx_bytes = &[ 249, 1, 9, 37, 132, 119, 53, 148, 0, 131, 2, 73, 240, 148, 123, 193, 187, 221, 106, 10, 114, 47, 201, 191, 252, 73, 201, 33, 182, 133, 236, 184, 75, 148, 128, 184, 164, 2, 237, 41, 43, 188, 96, 248, 252, 165, 132, 81, 30, 243, 34, 85, 165, 46, 224, 176, 90, 137, 30, 19, 123, 224, 67, 83, 53, 74, 57, 148, 140, 95, 45, 70, 147, 0, 0, @@ -1418,13 +1418,33 @@ fn test_eth_extract_secret() { 100, 189, 72, 74, 221, 144, 66, 170, 68, 121, 29, 105, 19, 194, 35, 245, 196, 131, 236, 29, 105, 101, 30, ]; - let secret = block_on(coin.extract_secret(&[0u8; 20], tx_hex.as_slice())); + let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice())); assert!(secret.is_ok()); let expect_secret = &[ 168, 151, 11, 232, 224, 253, 63, 180, 26, 114, 23, 184, 27, 10, 161, 80, 178, 251, 73, 204, 80, 174, 97, 118, 149, 204, 186, 187, 243, 185, 19, 128, ]; - assert_eq!(expect_secret.as_slice(), &secret.unwrap()) + assert_eq!(expect_secret.as_slice(), &secret.unwrap()); + + // Test for unexpected contract signature + // raw transaction bytes of ethPayment contract https://etherscan + // .io/tx/0x0869be3e5d4456a29d488a533ad6c118620fef450f36778aecf31d356ff8b41f + let tx_bytes = [ + 248, 240, 3, 133, 1, 42, 5, 242, 0, 131, 2, 73, 240, 148, 133, 0, 175, 192, 188, 82, 20, 114, 128, 130, 22, 51, + 38, 194, 255, 12, 115, 244, 168, 113, 135, 110, 205, 245, 24, 127, 34, 254, 184, 132, 21, 44, 243, 175, 73, 33, + 143, 82, 117, 16, 110, 27, 133, 82, 200, 114, 233, 42, 140, 198, 35, 21, 201, 249, 187, 180, 20, 46, 148, 40, + 9, 228, 193, 130, 71, 199, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 152, 41, 132, 9, 201, 73, 19, 94, 237, 137, 35, + 61, 4, 194, 207, 239, 152, 75, 175, 245, 157, 174, 10, 214, 161, 207, 67, 70, 87, 246, 231, 212, 47, 216, 119, + 68, 237, 197, 125, 141, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 93, 72, 125, 102, 28, 159, 180, 237, 198, 97, 87, 80, 82, 200, 104, 40, 245, + 221, 7, 28, 122, 104, 91, 99, 1, 159, 140, 25, 131, 101, 74, 87, 50, 168, 146, 187, 90, 160, 51, 1, 123, 247, + 6, 108, 165, 181, 188, 40, 56, 47, 211, 229, 221, 73, 5, 15, 89, 81, 117, 225, 216, 108, 98, 226, 119, 232, 94, + 184, 42, 106, + ]; + let secret = block_on(coin.extract_secret(&[0u8; 20], tx_bytes.as_slice())) + .err() + .unwrap(); + assert!(secret.contains("Expected 'receiverSpend' contract call signature")); } #[test] From cf325f7f9c8b65ef5d3b9eb500d37e3d57824e96 Mon Sep 17 00:00:00 2001 From: borngraced Date: Tue, 3 Jan 2023 17:08:33 +0100 Subject: [PATCH 4/4] minor changes Signed-off-by: borngraced --- mm2src/coins/eth.rs | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/mm2src/coins/eth.rs b/mm2src/coins/eth.rs index 0e8c00dd07..76cb530bcf 100644 --- a/mm2src/coins/eth.rs +++ b/mm2src/coins/eth.rs @@ -39,7 +39,6 @@ use ethkey::{sign, verify_address}; use futures::compat::Future01CompatExt; use futures::future::{join_all, select, Either, FutureExt, TryFutureExt}; use futures01::Future; -use hex::encode; use http::StatusCode; use mm2_core::mm_ctx::{MmArc, MmWeak}; use mm2_err_handle::prelude::*; @@ -128,10 +127,6 @@ const GAS_PRICE_APPROXIMATION_PERCENT_ON_TRADE_PREIMAGE: u64 = 7; /// Lifetime of generated signed message for gui-auth requests const GUI_AUTH_SIGNED_MESSAGE_LIFETIME_SEC: i64 = 90; -/// recieverSpend contract call bytes signature. -/// https://www.4byte.directory/signatures/?bytes4_signature=02ed292b. -const RECEIVERSPEND_BYTES_SIGNATURE: &str = "02ed292b"; - lazy_static! { pub static ref SWAP_CONTRACT: Contract = Contract::load(SWAP_CONTRACT_ABI.as_bytes()).unwrap(); pub static ref ERC20_CONTRACT: Contract = Contract::load(ERC20_ABI.as_bytes()).unwrap(); @@ -1141,12 +1136,15 @@ impl SwapOps for EthCoin { let unverified: UnverifiedTransaction = try_s!(rlp::decode(spend_tx)); let function = try_s!(SWAP_CONTRACT.function("receiverSpend")); - // Validate contract call; expected to be receiverSpend (02ed292b). - if unverified.data[0..4] != function.short_signature() { + // Validate contract call; expected to be receiverSpend. + // https://www.4byte.directory/signatures/?bytes4_signature=02ed292b. + let expected_signature = function.short_signature(); + let actual_signature = &unverified.data[0..4]; + if actual_signature != expected_signature { return ERR!( - "Expected 'receiverSpend' contract call signature: {}, found {}", - RECEIVERSPEND_BYTES_SIGNATURE, - encode(&unverified.data[0..4]) + "Expected 'receiverSpend' contract call signature: {:?}, found {:?}", + expected_signature, + actual_signature ); };