Browse Source

add fee attack checking (#32)

V1.2.0-btc-release
JunZhang 5 years ago
committed by GitHub
parent
commit
e46b213815
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. 82
      app/src/main/java/com/cobo/cold/ui/fragment/main/FeeAttackChecking.java
  5. 3
      app/src/main/java/com/cobo/cold/ui/fragment/main/PsbtSignedTxFragment.java
  6. 19
      app/src/main/java/com/cobo/cold/ui/fragment/main/TxConfirmFragment.java
  7. 12
      app/src/main/java/com/cobo/cold/ui/fragment/main/TxFragment.java
  8. 11
      app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/SignedTxFragment.java
  9. 22
      app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/UnsignedTxFragment.java
  10. 39
      app/src/main/java/com/cobo/cold/viewmodel/TxConfirmViewModel.java
  11. 24
      app/src/main/res/navigation/nav_graph_main.xml
  12. 5
      app/src/main/res/values-zh-rCN/strings.xml
  13. 4
      app/src/main/res/values/strings.xml

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

@ -140,6 +140,10 @@ public class DataRepository {
return mDb.txDao().loadWasabiTxsSync(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

@ -39,6 +39,9 @@ public interface TxDao {
@Query("SELECT * FROM txs where coinId = :coinId and signId = 'wasabi_sign_id' ORDER BY timeStamp DESC")
List<TxEntity> loadWasabiTxsSync(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

@ -146,5 +146,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;
}
}

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

@ -0,0 +1,82 @@
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;
import static com.cobo.cold.viewmodel.PsbtViewModel.WASABI_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 if(WASABI_SIGN_ID.equals(signId)){
Navigation.findNavController(Objects.requireNonNull(fragment.getView()))
.navigate(R.id.action_to_psbtSignedTxFragment, 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(),"");
}
}

3
app/src/main/java/com/cobo/cold/ui/fragment/main/PsbtSignedTxFragment.java

@ -19,6 +19,7 @@ package com.cobo.cold.ui.fragment.main;
import android.view.View;
import com.cobo.cold.R;
import com.cobo.cold.db.entity.TxEntity;
import com.cobo.cold.ui.fragment.main.electrum.SignedTxFragment;
@ -34,6 +35,6 @@ public class PsbtSignedTxFragment extends SignedTxFragment {
@Override
protected void showExportDialog() {
showExportPsbtDialog(mActivity, txEntity.getTxId(),
txEntity.getSignedHex(), this::navigateUp);
txEntity.getSignedHex(), () -> popBackStack(R.id.assetFragment, false));
}
}

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<T> extends BaseFragment<TxConfirmFragmentBinding> {
@ -72,6 +75,8 @@ public class TxConfirmFragment<T> 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<T> 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) {
@ -169,6 +178,16 @@ public class TxConfirmFragment<T> 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 refreshAmount() {

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.assetFragment, false);
} else {
navigateUp();
}
});
CoinListViewModel viewModel = ViewModelProviders.of(mActivity).get(CoinListViewModel.class);
viewModel.loadTx(data.getString(KEY_TX_ID)).observe(this, txEntity -> {
mBinding.setTx(txEntity);

11
app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/SignedTxFragment.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;
@ -47,6 +48,7 @@ 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.UnsignedTxFragment.showExportTxnDialog;
@ -65,7 +67,14 @@ public class SignedTxFragment extends BaseFragment<SignedTxBinding> {
@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.assetFragment, false);
} else {
navigateUp();
}
});
String walletName = SupportedWatchWallet.getSupportedWatchWallet(mActivity)
.getWalletName(mActivity);
mBinding.txDetail.watchWallet.setText(walletName);

22
app/src/main/java/com/cobo/cold/ui/fragment/main/electrum/UnsignedTxFragment.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;
@ -68,10 +69,15 @@ 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.viewmodel.GlobalViewModel.exportSuccess;
import static com.cobo.cold.viewmodel.GlobalViewModel.hasSdcard;
import static com.cobo.cold.viewmodel.GlobalViewModel.showNoSdcardModal;
import static com.cobo.cold.viewmodel.GlobalViewModel.writeToSdcard;
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.TxConfirmViewModel.STATE_NONE;
public class UnsignedTxFragment extends BaseFragment<ElectrumTxConfirmFragmentBinding> {
@ -87,6 +93,8 @@ public class UnsignedTxFragment extends BaseFragment<ElectrumTxConfirmFragmentBi
private TxEntity txEntity;
private ModalDialog addingAddressDialog;
private List<String> changeAddress = new ArrayList<>();
private int feeAttackCheckingState;
private FeeAttackChecking feeAttackChecking;
static void showExportTxnDialog(AppCompatActivity activity, String txId, String hex,
Runnable onExportSuccess) {
@ -156,6 +164,10 @@ public class UnsignedTxFragment extends BaseFragment<ElectrumTxConfirmFragmentBi
}
private void handleSign() {
if (feeAttackCheckingState == SAME_OUTPUTS) {
feeAttackChecking.showFeeAttackWarning();
return;
}
boolean fingerprintSignEnable = Utilities.isFingerprintSignEnable(mActivity);
if (txEntity != null) {
if (FeatureFlags.ENABLE_WHITE_LIST) {
@ -236,6 +248,16 @@ public class UnsignedTxFragment extends BaseFragment<ElectrumTxConfirmFragmentBi
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 observeAddAddress() {

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

@ -79,6 +79,9 @@ import java.util.stream.Stream;
import static com.cobo.coinlib.coins.BTC.Electrum.TxUtils.isMasterPublicKeyMatch;
import static com.cobo.cold.viewmodel.AddAddressViewModel.AddAddressTask.getAddressType;
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;
import static com.cobo.cold.viewmodel.GlobalViewModel.getAccount;
@ -95,10 +98,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);
@ -128,18 +134,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();

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

@ -320,7 +320,11 @@
<fragment
android:id="@+id/psbtTxConfirmFragment"
android:name="com.cobo.cold.ui.fragment.main.PsbtTxConfirmFragment"
android:label="PsbtTxConfirmFragment"/>
android:label="PsbtTxConfirmFragment">
<action
android:id="@id/action_to_psbtSignedTxFragment"
app:destination="@id/psbtSignedTxFragment" />
</fragment>
<fragment
android:id="@+id/walletInfoFragment"
@ -437,6 +441,15 @@
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" />
<action
android:id="@id/action_to_psbtSignedTxFragment"
app:destination="@id/psbtSignedTxFragment" />
</fragment>
<fragment
@ -465,6 +478,15 @@
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" />
<action
android:id="@id/action_to_psbtSignedTxFragment"
app:destination="@id/psbtSignedTxFragment" />
</fragment>
<fragment

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

@ -409,4 +409,9 @@
<item><![CDATA[举例:<font color="#00cdc3">3<font/>kgdahdkhghahdgkhaldshg]]></item>
<item><![CDATA[举例:<font color="#00cdc3">bc1<font/>akjhdfhkashdhdhsaghlh]]></item>
</string-array>
<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>

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

@ -454,4 +454,8 @@
<string name="toggle_address_hint">Once you toggle the address format, your receiving addresses, exported wallet, and wallet info will all be changed to this format. You will need to export your wallet information again using the new address format if you have not already. Do you still want to change the format?</string>
<string name="toggle_later">Cancel</string>
<string name="toggle_confirm">Confirm</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