You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

251 lines
8.5 KiB

/*
* 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 <http://www.gnu.org/licenses/>.
*/
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<String> pwd1 = new ObservableField<>("");
private final ObservableField<String> pwd2 = new ObservableField<>("");
private final ObservableField<String> webAuthCode = new ObservableField<>("");
private final ObservableField<Integer> mnemonicCount = new ObservableField<>(24);
private final MutableLiveData<Integer> vaultCreateState = new MutableLiveData<>(VAULT_STATE_NOT_CREATE);
private final MutableLiveData<String> 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<String> getWebAuthCode() {
return webAuthCode;
}
public MutableLiveData<Integer> getVaultCreateState() {
return vaultCreateState;
}
public ObservableField<String> getPwd1() {
return pwd1;
}
public ObservableField<String> getPwd2() {
return pwd2;
}
@NonNull
public ObservableField<Integer> 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<String> getMnemonic() {
return mnemonic;
}
public void presetData(List<CoinEntity> 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<List<CoinEntity>> getCoins() {
return mRepository.reloadCoins();
}
public enum PasswordValidationResult {
RESULT_OK,
RESULT_NOT_MATCH,
RESULT_TOO_SHORT,
RESULT_INPUT_WRONG,
}
}