crystal-wallet-android/app/src/main/java/cy/agorise/crystalwallet/models/GeneralCoinAccount.java

401 lines
13 KiB
Java

package cy.agorise.crystalwallet.models;
import android.content.Context;
import android.util.Log;
import com.google.gson.JsonObject;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.crypto.ChildNumber;
import org.bitcoinj.crypto.DeterministicKey;
import org.bitcoinj.crypto.HDKeyDerivation;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import cy.agorise.crystalwallet.enums.CryptoCoin;
import cy.agorise.crystalwallet.enums.CryptoNet;
/**
* Created by henry on 2/8/2018.
*/
public abstract class GeneralCoinAccount extends CryptoNetAccount {
/**
* The account number of the BIP-44
*/
protected int mAccountNumber;
/**
* The index of the last used external address
*/
protected int mLastExternalIndex;
/**
* The indes of the last used change address
*/
protected int mLastChangeIndex;
/**
* The account key, this is calculated as a cache
*/
protected DeterministicKey mAccountKey;
/**
* With this key we can calculate the external addresses
*/
protected DeterministicKey mExternalKey;
/**
* With this key we can calculate the change address
*/
protected DeterministicKey mChangeKey;
/**
* The keys for externals addresses
*/
protected HashMap<Integer, GeneralCoinAddress> mExternalKeys = new HashMap<Integer,GeneralCoinAddress>();
/**
* The keys for the change addresses
*/
protected HashMap<Integer, GeneralCoinAddress> mChangeKeys = new HashMap<Integer,GeneralCoinAddress>();
/**
* The list of transaction that involves this account
*/
protected List<GeneralTransaction> mTransactions = new ArrayList<GeneralTransaction>();
/**
* The Limit gap define in the BIP-44
*/
private final static int sAddressGap = 20;
/**
* is the coin number defined by the SLIP-44
*/
private final int mCoinNumber;
public GeneralCoinAccount(long mId, AccountSeed seed, int mAccountIndex, CryptoNet mCryptoNet, int mAccountNumber, int mLastExternalIndex, int mLastChangeIndex, int mCoinNumber) {
super(mId, seed.getId(), mAccountIndex, mCryptoNet);
this.mAccountNumber = mAccountNumber;
this.mLastExternalIndex = mLastExternalIndex;
this.mLastChangeIndex = mLastChangeIndex;
this.mCoinNumber = mCoinNumber;
calculateAddresses((DeterministicKey) seed.getPrivateKey());
}
/**
* Setter for the transactions of this account, this is used from the database
*/
public void setTransactions(List<GeneralTransaction> transactions) {
this.mTransactions = transactions;
}
/**
* Calculates each basic key, not the addresses keys using the BIP-44
*/
private void calculateAddresses(DeterministicKey masterKey) {
DeterministicKey purposeKey = HDKeyDerivation.deriveChildKey(masterKey,
new ChildNumber(44, true));
DeterministicKey coinKey = HDKeyDerivation.deriveChildKey(purposeKey,
new ChildNumber(this.mCoinNumber, true));
this.mAccountKey = HDKeyDerivation.deriveChildKey(coinKey,
new ChildNumber(this.mAccountNumber, true));
this.mExternalKey = HDKeyDerivation.deriveChildKey(this.mAccountKey,
new ChildNumber(0, false));
this.mChangeKey = HDKeyDerivation.deriveChildKey(this.mAccountKey,
new ChildNumber(1, false));
}
/**
* Calculate the external address keys until the index + gap
*/
public void calculateGapExternal() {
for (int i = 0; i < this.mLastExternalIndex + this.sAddressGap; i++) {
if (!this.mExternalKeys.containsKey(i)) {
this.mExternalKeys.put(i, new GeneralCoinAddress(this, false, i,
HDKeyDerivation.deriveChildKey(this.mExternalKey,
new ChildNumber(i, false))));
}
}
}
/**
* Calculate the change address keys until the index + gap
*/
public void calculateGapChange() {
for (int i = 0; i < this.mLastChangeIndex + this.sAddressGap; i++) {
if (!this.mChangeKeys.containsKey(i)) {
this.mChangeKeys.put(i, new GeneralCoinAddress(this, true, i,
HDKeyDerivation.deriveChildKey(this.mChangeKey,
new ChildNumber(i, false))));
}
}
}
//TODO check init address
/*public List<GeneralCoinAddress> getAddresses(SCWallDatabase db) {
//TODO check for used address
this.getNextReceiveAddress();
this.getNextChangeAddress();
this.calculateGapExternal();
this.calculateGapChange();
List<GeneralCoinAddress> addresses = new ArrayList();
addresses.addAll(this.mChangeKeys.values());
addresses.addAll(this.mExternalKeys.values());
this.saveAddresses(db);
return addresses;
}*/
/**
* Get the list of all the address, external and change addresses
* @return a list with all the addresses of this account
*/
public List<GeneralCoinAddress> getAddresses() {
List<GeneralCoinAddress> addresses = new ArrayList();
addresses.addAll(this.mChangeKeys.values());
addresses.addAll(this.mExternalKeys.values());
return addresses;
}
/**
* Charges the list of addresse of this account, this is used from the database
*/
public void loadAddresses(List<GeneralCoinAddress> addresses) {
for (GeneralCoinAddress address : addresses) {
if (address.isIsChange()) {
this.mChangeKeys.put(address.getIndex(), address);
} else {
this.mExternalKeys.put(address.getIndex(), address);
}
}
}
//TODO save address
/*public void saveAddresses(SCWallDatabase db) {
for (GeneralCoinAddress externalAddress : this.mExternalKeys.values()) {
if (externalAddress.getId() == -1) {
long id = db.putGeneralCoinAddress(externalAddress);
if(id != -1)
externalAddress.setId(id);
} else {
db.updateGeneralCoinAddress(externalAddress);
}
}
for (GeneralCoinAddress changeAddress : this.mChangeKeys.values()) {
if (changeAddress.getId() == -1) {
Log.i("SCW","change address id " + changeAddress.getId());
long id = db.putGeneralCoinAddress(changeAddress);
if(id != -1)
changeAddress.setId(id);
} else {
db.updateGeneralCoinAddress(changeAddress);
}
}
db.updateGeneralCoinAccount(this);
}*/
/**
* Getter of the account number
*/
public int getAccountNumber() {
return this.mAccountNumber;
}
/**
* Getter of the last external address used index
*/
public int getLastExternalIndex() {
return this.mLastExternalIndex;
}
/**
* Getter of the last change address used index
*/
public int getLastChangeIndex() {
return this.mLastChangeIndex;
}
/**
* Getter of the next receive address
* @return The next unused receive address to be used
*/
public abstract String getNextReceiveAddress();
/**
* Getter of the next change address
* @return The next unused change address to be used
*/
public abstract String getNextChangeAddress();
/**
* Transfer coin amount to another address
*
* @param toAddress The destination address
* @param coin the coin
* @param amount the amount to send in satoshi
* @param memo the memo, this can be empty
* @param context the android context
*/
public abstract void send(String toAddress, CryptoCoin coin, long amount, String memo,
Context context);
/**
* Transform this account into json object to be saved in the bin file, or any other file
*/
public JsonObject toJson() {
JsonObject answer = new JsonObject();
answer.addProperty("type", this.getCryptoNet().name());
answer.addProperty("name", this.getName());
answer.addProperty("accountNumber", this.mAccountNumber);
answer.addProperty("changeIndex", this.mLastChangeIndex);
answer.addProperty("externalIndex", this.mLastExternalIndex);
return answer;
}
/**
* Getter of the list of transactions
*/
public List<GeneralTransaction> getTransactions() {
List<GeneralTransaction> transactions = new ArrayList();
for (GeneralCoinAddress address : this.mExternalKeys.values()) {
for (GTxIO giotx : address.getTransactionInput()) {
if (!transactions.contains(giotx.getTransaction())) {
transactions.add(giotx.getTransaction());
}
}
for (GTxIO giotx : address.getTransactionOutput()) {
if (!transactions.contains(giotx.getTransaction())) {
transactions.add(giotx.getTransaction());
}
}
}
for (GeneralCoinAddress address : this.mChangeKeys.values()) {
for (GTxIO giotx : address.getTransactionInput()) {
if (!transactions.contains(giotx.getTransaction())) {
transactions.add(giotx.getTransaction());
}
}
for (GTxIO giotx : address.getTransactionOutput()) {
if (!transactions.contains(giotx.getTransaction())) {
transactions.add(giotx.getTransaction());
}
}
;
}
Collections.sort(transactions, new TransactionsCustomComparator());
return transactions;
}
public CryptoCoin getCryptoCoin(){
return CryptoCoin.valueOf(this.getCryptoNet().name());
}
/**
* Get the address as string of an adrees index
* @param index The index of the address
* @param change if it is change addres or is a external address
* @return The Address as string
*/
public abstract String getAddressString(int index, boolean change);
/**
* Get the GeneralCoinAddress object of an address
* @param index the index of the address
* @param change if it is change addres or is a external address
* @return The GeneralCoinAddress of the address
*/
public abstract GeneralCoinAddress getAddress(int index, boolean change);
/**
* Return the network parameters, this is used for the bitcoiinj library
*/
public abstract NetworkParameters getNetworkParam();
/**
* Triggers the event onBalanceChange
*/
public void balanceChange() {
this._fireOnChangeBalance(this.getBalance().get(0)); //TODO make it more genertic
}
public abstract List<CryptoNetBalance> getBalance();
public DeterministicKey getChangeKey() {
return mChangeKey;
}
public DeterministicKey getExternalKey() {
return mExternalKey;
}
/**
* Compare the transaction, to order it for the list of transaction
*/
public class TransactionsCustomComparator implements Comparator<GeneralTransaction> {
@Override
public int compare(GeneralTransaction o1, GeneralTransaction o2) {
return o1.getDate().compareTo(o2.getDate());
}
}
/**
* Add listener for the onChangebalance Event
*/
/*public void addChangeBalanceListener(ChangeBalanceListener listener) {
this.mChangeBalanceListeners.add(listener);
}*/
/**
* Fire the onChangeBalance event
*/
protected void _fireOnChangeBalance(CryptoNetBalance balance) {
/*for (ChangeBalanceListener listener : this.mChangeBalanceListeners) {
listener.balanceChange(balance);
}*/
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
GeneralCoinAccount that = (GeneralCoinAccount) o;
if (this.getCryptoNet() != that.getCryptoNet()) return false;
if (this.getAccountNumber() != that.getAccountNumber()) return false;
return this.mAccountKey != null ? this.mAccountKey.equals(that.mAccountKey)
: that.mAccountKey == null;
}
@Override
public int hashCode() {
int result = this.getAccountNumber();
result = 31 * result + (this.mAccountKey != null ? this.mAccountKey.hashCode() : 0);
return result;
}
/**
* Updates a transaction
*
* @param transaction The transaction to update
*/
public void updateTransaction(GeneralTransaction transaction){
// Checks if it has an external address
for (GeneralCoinAddress address : this.mExternalKeys.values()) {
if(address.updateTransaction(transaction)){
return;
}
}
for (GeneralCoinAddress address : this.mChangeKeys.values()) {
if(address.updateTransaction(transaction)){
return;
}
}
}
}