/* * Copyright (c) 2020 Cobo * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * in the file COPYING. If not, see . */ package com.cobo.cold.viewmodel; import android.app.Application; import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.databinding.ObservableField; import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import com.cobo.coinlib.MnemonicUtils; import com.cobo.coinlib.utils.Bip39; import com.cobo.coinlib.utils.Coins; import com.cobo.cold.AppExecutors; import com.cobo.cold.DataRepository; import com.cobo.cold.MainApplication; import com.cobo.cold.callables.GetExtendedPublicKeyCallable; import com.cobo.cold.callables.GetRandomEntropyCallable; import com.cobo.cold.callables.GetVaultIdCallable; import com.cobo.cold.callables.UpdatePassphraseCallable; import com.cobo.cold.callables.WebAuthCallable; import com.cobo.cold.callables.WriteMnemonicCallable; import com.cobo.cold.db.entity.AccountEntity; import com.cobo.cold.db.entity.CoinEntity; import com.cobo.cold.util.HashUtil; import org.spongycastle.util.encoders.Hex; import java.util.List; import java.util.Objects; import java.util.concurrent.Executor; import java.util.concurrent.Executors; public class SetupVaultViewModel extends AndroidViewModel { private static final int VAULT_STATE_NOT_CREATE = 0; public static final int VAULT_STATE_CREATING = 1; public static final int VAULT_STATE_CREATED = 2; private final ObservableField pwd1 = new ObservableField<>(""); private final ObservableField pwd2 = new ObservableField<>(""); private final ObservableField webAuthCode = new ObservableField<>(""); private final ObservableField mnemonicCount = new ObservableField<>(24); private final MutableLiveData vaultCreateState = new MutableLiveData<>(VAULT_STATE_NOT_CREATE); private final MutableLiveData mnemonic = new MutableLiveData<>(""); private String vaultId; private final DataRepository mRepository; private String password; public SetupVaultViewModel(@NonNull Application application) { super(application); mRepository = ((MainApplication) application).getRepository(); } public void calcAuthCode(String data) { AppExecutors.getInstance().diskIO().execute(() -> { String authCode = new WebAuthCallable(data).call(); webAuthCode.set(format(authCode)); }); } private String format(String replace) { if (TextUtils.isEmpty(replace)) { return ""; } String regex = "(.{4})"; replace = replace.replaceAll(regex, "$1 "); return replace.trim(); } public ObservableField getWebAuthCode() { return webAuthCode; } public MutableLiveData getVaultCreateState() { return vaultCreateState; } public ObservableField getPwd1() { return pwd1; } public ObservableField getPwd2() { return pwd2; } @NonNull public ObservableField getMnemonicCount() { return mnemonicCount; } public void setMnemonicCount(int mnemonicCount) { this.mnemonicCount.set(mnemonicCount); } public void setPassword(String password) { this.password = password; } public boolean validateMnemonic(String mnemonic) { return Bip39.validateMnemonic(mnemonic); } public void writeMnemonic(String mnemonic) { AppExecutors.getInstance().diskIO().execute(() -> { vaultCreateState.postValue(VAULT_STATE_CREATING); new WriteMnemonicCallable(mnemonic, password).call(); vaultId = new GetVaultIdCallable().call(); mRepository.clearDb(); vaultCreateState.postValue(VAULT_STATE_CREATED); password = null; }); } public void updatePassphrase(String passphrase) { AppExecutors.getInstance().diskIO().execute(() -> { vaultCreateState.postValue(VAULT_STATE_CREATING); new UpdatePassphraseCallable(passphrase, password).call(); vaultId = new GetVaultIdCallable().call(); deleteHiddenVaultData(); vaultCreateState.postValue(VAULT_STATE_CREATED); password = null; }); } public String getVaultId() { return vaultId; } public PasswordValidationResult validatePassword() { if (Objects.requireNonNull(pwd1.get()).length() < 10) { return PasswordValidationResult.RESULT_TOO_SHORT; } else if (!validInput(Objects.requireNonNull(pwd1.get()))) { return PasswordValidationResult.RESULT_INPUT_WRONG; } else { return PasswordValidationResult.RESULT_OK; } } private boolean validInput(String s) { char[] chars = s.toCharArray(); boolean hasUpperCase = false; boolean hasLowerCase = false; boolean hasDigit = false; for (char c : chars) { if (Character.isDigit(c)) { hasDigit = true; continue; } if (Character.isUpperCase(c)) { hasUpperCase = true; continue; } if (Character.isLowerCase(c)) { hasLowerCase = true; } } return hasDigit && hasLowerCase && hasUpperCase; } public void generateRandomMnemonic() { Executor executor = Executors.newSingleThreadExecutor(); Runnable task = () -> { String entropy = new GetRandomEntropyCallable().call(); if (!MnemonicUtils.isValidateEntropy(Hex.decode(entropy))) { this.mnemonic.postValue(""); } else { this.mnemonic.postValue(Bip39.generateMnemonic(entropy)); } }; executor.execute(task); } public void generateMnemonicFromDiceRolls(byte[] diceRolls) { String entropy = Hex.toHexString(Objects.requireNonNull(HashUtil.sha256(diceRolls))); String mnemonic = Bip39.generateMnemonic(entropy); this.mnemonic.postValue(mnemonic); } public LiveData getMnemonic() { return mnemonic; } public void presetData(List coins, final Runnable onComplete) { AppExecutors.getInstance().diskIO().execute(() -> { for (CoinEntity coin : coins) { CoinEntity coinEntity = mRepository.loadCoinSync(coin.getCoinId()); if (coinEntity != null) { continue; } String xPub = new GetExtendedPublicKeyCallable(coin.getAccounts().get(0).getHdPath()).call(); coin.setExPub(xPub); long id = mRepository.insertCoin(coin); coin.setId(id); boolean isFirstAccount = true; for (AccountEntity account : coin.getAccounts()) { if (!isFirstAccount) { xPub = new GetExtendedPublicKeyCallable(account.getHdPath()).call(); } isFirstAccount = false; account.setCoinId(id); account.setExPub(xPub); mRepository.insertAccount(account); if (!Coins.showPublicKey(coin.getCoinCode())) { new AddAddressViewModel.AddAddressTask(coin, mRepository, null) .execute(coin.getCoinCode() + "-1"); } } } if (onComplete != null) { AppExecutors.getInstance().mainThread().execute(onComplete); } }); } private void deleteHiddenVaultData() { mRepository.deleteHiddenVaultData(); } public LiveData> getCoins() { return mRepository.reloadCoins(); } public enum PasswordValidationResult { RESULT_OK, RESULT_NOT_MATCH, RESULT_TOO_SHORT, RESULT_INPUT_WRONG, } }