Browse Source

add fee attack checking (#31)

V1.2.0-release
JunZhang 5 years ago
committed by GitHub
parent
commit
f3179dca96
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 4
      app/src/main/java/com/cobo/cold/DataRepository.java
  2. 3
      app/src/main/java/com/cobo/cold/db/dao/TxDao.java
  3. 4
      app/src/main/java/com/cobo/cold/ui/fragment/BaseFragment.java
  4. 78
      app/src/main/java/com/cobo/cold/ui/fragment/main/FeeAttackChecking.java
  5. 19
      app/src/main/java/com/cobo/cold/ui/fragment/main/TxConfirmFragment.java
  6. 12
      app/src/main/java/com/cobo/cold/ui/fragment/main/TxFragment.java
  7. 20
      app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumTxConfirmFragment.java
  8. 13
      app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumTxFragment.java
  9. 39
      app/src/main/java/com/cobo/cold/viewmodel/TxConfirmViewModel.java
  10. 12
      app/src/main/res/navigation/nav_graph_main.xml
  11. 3
      app/src/main/res/values-zh-rCN/strings.xml
  12. 3
      app/src/main/res/values/strings.xml

4
app/src/main/java/com/cobo/cold/DataRepository.java

@ -130,6 +130,10 @@ public class DataRepository {
return mDb.txDao().loadElectrumTxsSync(coinId);
}
public List<TxEntity> loadAllTxSync(String coinId) {
return mDb.txDao().loadTxsSync(coinId);
}
public LiveData<TxEntity> loadTx(String txId) {
return mDb.txDao().load(txId);
}

3
app/src/main/java/com/cobo/cold/db/dao/TxDao.java

@ -36,6 +36,9 @@ public interface TxDao {
@Query("SELECT * FROM txs where coinId = :coinId and signId = 'electrum_sign_id' ORDER BY timeStamp DESC")
List<TxEntity> loadElectrumTxsSync(String coinId);
@Query("SELECT * FROM txs where coinId = :coinId")
List<TxEntity> loadTxsSync(String coinId);
@Insert(onConflict = OnConflictStrategy.REPLACE)
void insert(TxEntity tx);

4
app/src/main/java/com/cobo/cold/ui/fragment/BaseFragment.java

@ -142,5 +142,9 @@ public abstract class BaseFragment<T extends ViewDataBinding> extends Fragment {
public void navigate(@IdRes int id, Bundle data) {
NavHostFragment.findNavController(this).navigate(id, data);
}
public AppCompatActivity getHostActivity() {
return mActivity;
}
}

78
app/src/main/java/com/cobo/cold/ui/fragment/main/FeeAttackChecking.java

@ -0,0 +1,78 @@
package com.cobo.cold.ui.fragment.main;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import androidx.databinding.DataBindingUtil;
import androidx.navigation.Navigation;
import com.cobo.cold.R;
import com.cobo.cold.databinding.CommonModalBinding;
import com.cobo.cold.db.entity.TxEntity;
import com.cobo.cold.ui.fragment.BaseFragment;
import com.cobo.cold.ui.modal.ModalDialog;
import java.util.Objects;
import static com.cobo.cold.ui.fragment.main.TxFragment.KEY_TX_ID;
import static com.cobo.cold.viewmodel.ElectrumViewModel.ELECTRUM_SIGN_ID;
public class FeeAttackChecking {
public static final String KEY_DUPLICATE_TX = "key_duplicate_tx";
public interface FeeAttackCheckingResult {
int NORMAL = 1;
int DUPLICATE_TX = 2;
int SAME_OUTPUTS = 3;
}
private BaseFragment fragment;
public FeeAttackChecking(BaseFragment fragment) {
this.fragment = fragment;
}
public void showFeeAttackWarning() {
ModalDialog modalDialog = ModalDialog.newInstance();
CommonModalBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(fragment.getHostActivity()), R.layout.common_modal,
null, false);
modalDialog.setBinding(binding);
binding.title.setText(R.string.abnormal_tx);
binding.subTitle.setText(R.string.fee_attack_warning);
binding.confirm.setText(R.string.know);
binding.confirm.setOnClickListener(v -> modalDialog.dismiss());
modalDialog.show(fragment.getHostActivity().getSupportFragmentManager(),"");
}
private void navigateToSignedTx(String txId, String signId) {
Bundle bundle = new Bundle();
bundle.putString(KEY_TX_ID, txId);
bundle.putBoolean(KEY_DUPLICATE_TX,true);
if (ELECTRUM_SIGN_ID.equals(signId)) {
Navigation.findNavController(Objects.requireNonNull(fragment.getView()))
.navigate(R.id.action_to_electrumTxFragment, bundle);
} else {
Navigation.findNavController(Objects.requireNonNull(fragment.getView()))
.navigate(R.id.action_to_txFragment, bundle);
}
}
public void showDuplicateTx(TxEntity tx) {
ModalDialog modalDialog = ModalDialog.newInstance();
CommonModalBinding binding = DataBindingUtil.inflate(
LayoutInflater.from(fragment.getHostActivity()), R.layout.common_modal,
null, false);
modalDialog.setBinding(binding);
binding.title.setText(R.string.broadcast_tx);
binding.close.setVisibility(View.GONE);
binding.subTitle.setText(R.string.already_signed);
binding.confirm.setText(R.string.broadcast_tx);
binding.confirm.setOnClickListener(v -> {
modalDialog.dismiss();
navigateToSignedTx(tx.getTxId(), tx.getSignId());
});
modalDialog.show(fragment.getHostActivity().getSupportFragmentManager(),"");
}
}

19
app/src/main/java/com/cobo/cold/ui/fragment/main/TxConfirmFragment.java

@ -57,6 +57,9 @@ import java.util.Objects;
import static com.cobo.cold.ui.fragment.Constants.KEY_NAV_ID;
import static com.cobo.cold.ui.fragment.main.BroadcastTxFragment.KEY_TXID;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.FeeAttackCheckingResult.DUPLICATE_TX;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.FeeAttackCheckingResult.NORMAL;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.FeeAttackCheckingResult.SAME_OUTPUTS;
public class TxConfirmFragment extends BaseFragment<TxConfirmFragmentBinding> {
@ -72,6 +75,8 @@ public class TxConfirmFragment extends BaseFragment<TxConfirmFragmentBinding> {
private SigningDialog signingDialog;
private TxEntity txEntity;
private ModalDialog addingAddressDialog;
private int feeAttackCheckingState;
private FeeAttackChecking feeAttackChecking;
@Override
protected int setView() {
@ -93,6 +98,10 @@ public class TxConfirmFragment extends BaseFragment<TxConfirmFragmentBinding> {
}
private void handleSign() {
if (feeAttackCheckingState == SAME_OUTPUTS) {
feeAttackChecking.showFeeAttackWarning();
return;
}
boolean fingerprintSignEnable = Utilities.isFingerprintSignEnable(mActivity);
if (txEntity != null) {
if (FeatureFlags.ENABLE_WHITE_LIST) {
@ -171,6 +180,16 @@ public class TxConfirmFragment extends BaseFragment<TxConfirmFragmentBinding> {
navigateUp();
}
});
viewModel.feeAttackChecking().observe(this, state -> {
feeAttackCheckingState = state;
if (state != NORMAL) {
feeAttackChecking = new FeeAttackChecking(this);
}
if(state == DUPLICATE_TX) {
feeAttackChecking.showDuplicateTx(viewModel.getPreviousSignTx());
}
});
}
private void refreshMemoDisplay() {

12
app/src/main/java/com/cobo/cold/ui/fragment/main/TxFragment.java

@ -26,6 +26,7 @@ import android.text.style.ForegroundColorSpan;
import android.view.View;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.fragment.NavHostFragment;
import com.cobo.coinlib.utils.Coins;
import com.cobo.cold.R;
@ -44,6 +45,8 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.KEY_DUPLICATE_TX;
public class TxFragment extends BaseFragment<TxBinding> {
@ -58,7 +61,14 @@ public class TxFragment extends BaseFragment<TxBinding> {
@Override
protected void init(View view) {
Bundle data = Objects.requireNonNull(getArguments());
mBinding.toolbar.setNavigationOnClickListener(v -> navigateUp());
mBinding.toolbar.setNavigationOnClickListener(v -> {
if (data.getBoolean(KEY_DUPLICATE_TX)) {
NavHostFragment.findNavController(this)
.popBackStack(R.id.assetListFragment, false);
} else {
navigateUp();
}
});
CoinListViewModel viewModel = ViewModelProviders.of(mActivity).get(CoinListViewModel.class);
viewModel.loadTx(data.getString(KEY_TX_ID)).observe(this, txEntity -> {
mBinding.setTx(txEntity);

20
app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumTxConfirmFragment.java

@ -42,6 +42,7 @@ import com.cobo.cold.databinding.ProgressModalBinding;
import com.cobo.cold.db.entity.TxEntity;
import com.cobo.cold.encryptioncore.utils.ByteFormatter;
import com.cobo.cold.ui.fragment.BaseFragment;
import com.cobo.cold.ui.fragment.main.FeeAttackChecking;
import com.cobo.cold.ui.fragment.main.TransactionItem;
import com.cobo.cold.ui.fragment.main.TransactionItemAdapter;
import com.cobo.cold.ui.modal.ModalDialog;
@ -66,6 +67,9 @@ import java.util.Objects;
import static com.cobo.cold.ui.fragment.Constants.KEY_NAV_ID;
import static com.cobo.cold.ui.fragment.main.BroadcastTxFragment.KEY_TXID;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.FeeAttackCheckingResult.DUPLICATE_TX;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.FeeAttackCheckingResult.NORMAL;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.FeeAttackCheckingResult.SAME_OUTPUTS;
import static com.cobo.cold.viewmodel.ElectrumViewModel.exportSuccess;
import static com.cobo.cold.viewmodel.ElectrumViewModel.hasSdcard;
import static com.cobo.cold.viewmodel.ElectrumViewModel.showNoSdcardModal;
@ -86,6 +90,8 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
private ModalDialog addingAddressDialog;
private String txnData;
private List<String> changeAddress = new ArrayList<>();
private int feeAttackCheckingState;
private FeeAttackChecking feeAttackChecking;
public static void showExportTxnDialog(AppCompatActivity activity, String txId, String hex,
Runnable onExportSuccess) {
@ -151,6 +157,10 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
}
private void handleSign() {
if (feeAttackCheckingState == SAME_OUTPUTS) {
feeAttackChecking.showFeeAttackWarning();
return;
}
boolean fingerprintSignEnable = Utilities.isFingerprintSignEnable(mActivity);
if (txEntity != null) {
if (FeatureFlags.ENABLE_WHITE_LIST) {
@ -235,6 +245,16 @@ public class ElectrumTxConfirmFragment extends BaseFragment<ElectrumTxConfirmFra
navigateUp();
}
});
viewModel.feeAttackChecking().observe(this, state -> {
feeAttackCheckingState = state;
if (state != NORMAL) {
feeAttackChecking = new FeeAttackChecking(this);
}
if(state == DUPLICATE_TX) {
feeAttackChecking.showDuplicateTx(viewModel.getPreviousSignTx());
}
});
}
private void refreshAmount() {

13
app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/ElectrumTxFragment.java

@ -25,6 +25,7 @@ import android.text.style.ForegroundColorSpan;
import android.view.View;
import androidx.lifecycle.ViewModelProviders;
import androidx.navigation.fragment.NavHostFragment;
import com.cobo.coinlib.utils.Base43;
import com.cobo.coinlib.utils.Coins;
@ -46,13 +47,14 @@ import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.KEY_DUPLICATE_TX;
import static com.cobo.cold.ui.fragment.main.electrum.ElectrumBroadcastTxFragment.showElectrumInfo;
import static com.cobo.cold.ui.fragment.main.electrum.ElectrumTxConfirmFragment.showExportTxnDialog;
public class ElectrumTxFragment extends BaseFragment<ElectrumTxBinding> {
public static final String KEY_TX_ID = "txid";
private static final String KEY_TX_ID = "txid";
private TxEntity txEntity;
private List<String> changeAddress = new ArrayList<>();
@ -64,7 +66,14 @@ public class ElectrumTxFragment extends BaseFragment<ElectrumTxBinding> {
@Override
protected void init(View view) {
Bundle data = Objects.requireNonNull(getArguments());
mBinding.toolbar.setNavigationOnClickListener(v -> navigateUp());
mBinding.toolbar.setNavigationOnClickListener(v -> {
if (data.getBoolean(KEY_DUPLICATE_TX)) {
NavHostFragment.findNavController(this)
.popBackStack(R.id.assetListFragment, false);
} else {
navigateUp();
}
});
CoinListViewModel viewModel = ViewModelProviders.of(mActivity).get(CoinListViewModel.class);
viewModel.loadTx(data.getString(KEY_TX_ID)).observe(this, txEntity -> {
mBinding.setTx(txEntity);

39
app/src/main/java/com/cobo/cold/viewmodel/TxConfirmViewModel.java

@ -76,6 +76,9 @@ import java.util.concurrent.Executors;
import java.util.stream.Stream;
import static com.cobo.coinlib.coins.BTC.Electrum.TxUtils.isMasterPublicKeyMatch;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.FeeAttackCheckingResult.DUPLICATE_TX;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.FeeAttackCheckingResult.NORMAL;
import static com.cobo.cold.ui.fragment.main.FeeAttackChecking.FeeAttackCheckingResult.SAME_OUTPUTS;
import static com.cobo.cold.viewmodel.ElectrumViewModel.ELECTRUM_SIGN_ID;
import static com.cobo.cold.viewmodel.ElectrumViewModel.adapt;
@ -90,10 +93,13 @@ public class TxConfirmViewModel extends AndroidViewModel {
private final MutableLiveData<TxEntity> observableTx = new MutableLiveData<>();
private final MutableLiveData<Exception> parseTxException = new MutableLiveData<>();
private final MutableLiveData<Boolean> addingAddress = new MutableLiveData<>();
private final MutableLiveData<Integer> feeAttachCheckingResult = new MutableLiveData<>();
private AbsTx transaction;
private String coinCode;
private final MutableLiveData<String> signState = new MutableLiveData<>();
private AuthenticateModal.OnVerify.VerifyToken token;
private TxEntity previousSignedTx;
public TxConfirmViewModel(@NonNull Application application) {
super(application);
@ -123,18 +129,49 @@ public class TxConfirmViewModel extends AndroidViewModel {
if (transaction instanceof UtxoTx) {
if (!checkChangeAddress(transaction)) {
observableTx.postValue(null);
parseTxException.postValue(new InvalidTransactionException("invlid change address"));
parseTxException.postValue(new InvalidTransactionException("invalid change address"));
return;
}
}
TxEntity tx = generateTxEntity(object);
observableTx.postValue(tx);
if (Coins.BTC.coinCode().equals(transaction.getCoinCode())) {
feeAttackChecking(tx);
}
} catch (JSONException e) {
e.printStackTrace();
}
});
}
public TxEntity getPreviousSignTx() {
return previousSignedTx;
}
private void feeAttackChecking(TxEntity txEntity) {
AppExecutors.getInstance().diskIO().execute(() -> {
String inputs = txEntity.getFrom();
String outputs = txEntity.getTo();
List<TxEntity> txs = mRepository.loadAllTxSync(Coins.BTC.coinId());
for (TxEntity tx : txs) {
if (inputs.equals(tx.getFrom()) && outputs.equals(tx.getTo())) {
previousSignedTx = tx;
feeAttachCheckingResult.postValue(DUPLICATE_TX);
break;
} else if (outputs.equals(tx.getTo())) {
feeAttachCheckingResult.postValue(SAME_OUTPUTS);
break;
} else {
feeAttachCheckingResult.postValue(NORMAL);
}
}
});
}
public LiveData<Integer> feeAttackChecking() {
return feeAttachCheckingResult;
}
private TxEntity generateTxEntity(JSONObject object) throws JSONException {
TxEntity tx = new TxEntity();
NumberFormat nf = NumberFormat.getInstance();

12
app/src/main/res/navigation/nav_graph_main.xml

@ -307,6 +307,12 @@
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@id/action_to_txFragment"
app:destination="@id/txFragment"/>
<action
android:id="@id/action_to_electrumTxFragment"
app:destination="@id/electrumTxFragment" />
</fragment>
<fragment
@ -335,6 +341,12 @@
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@android:anim/slide_in_left"
app:popExitAnim="@android:anim/slide_out_right" />
<action
android:id="@id/action_to_txFragment"
app:destination="@id/txFragment" />
<action
android:id="@id/action_to_electrumTxFragment"
app:destination="@id/electrumTxFragment" />
</fragment>
<fragment

3
app/src/main/res/values-zh-rCN/strings.xml

@ -319,4 +319,7 @@
<string name="dice_roll_guide"><![CDATA[请注意:<br><br> 1. 至少投掷50次,才能生成助记词;建议您最少投掷99次,获得256位的随机熵值;Cobo 金库不限制您的最大投掷次数。<br>2. 建议您使用<font color="#00cdc3">赌场级骰子<font/>(六个面向上的概率相同),操作过程中确保骰子在空中得到足够的翻转,且避免总是从骰子特定面朝上进行投掷。]]></string>
<string name="start">开始</string>
<string name="pattern_lock_hide_track">为防偷窥,解锁时将隐藏轨迹</string>
<string name="abnormal_tx">异常交易</string>
<string name="fee_attack_warning">检测到该笔交易有风险,不能对其签名,您可前往:https://cobo.com/hardware-wallet/XXX 了解详情,您也可邮件联系客服:support@cobo.com</string>
<string name="already_signed">检测到您已对该笔交易签名,您可直接广播该交易</string>
</resources>

3
app/src/main/res/values/strings.xml

@ -343,4 +343,7 @@
<string name="dice_roll_guide"><![CDATA[How to use dice to generate keys:<br><br> 1. Roll the dice at least 50 times. Roll 99 times to obtain 256-bit entropy (recommended). There is no limit to the number of rolls you can use.<br>2. We recommend you use <font color="#00cdc3">casino dice<font/> to increase the entropy of each dice throw.]]></string>
<string name="start">Start</string>
<string name="pattern_lock_hide_track">To prevent peeping, track will be hidden when unlocked</string>
<string name="abnormal_tx">Abnormal Transaction</string>
<string name="fee_attack_warning">Detect that the transaction is risky and cannot be signed. You can go to: https// for details, or contact us: support@cobo.com</string>
<string name="already_signed">You have signed the transaction, can broadcast the transaction directly</string>
</resources>

Loading…
Cancel
Save