From eab42c0f0580452968f579bba3904bfc6480ae0f Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Thu, 12 Aug 2021 17:48:36 +0200 Subject: [PATCH] support for standardized child wallet accounts --- .../sparrowwallet/drongo/wallet/Payment.java | 13 +++ .../drongo/wallet/StandardAccount.java | 48 ++++++++++ .../sparrowwallet/drongo/wallet/Wallet.java | 88 +++++++++++++++++++ .../drongo/wallet/WalletTransaction.java | 16 +++- 4 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/drongo/wallet/StandardAccount.java diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Payment.java b/src/main/java/com/sparrowwallet/drongo/wallet/Payment.java index ce3998b..18f2d99 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Payment.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Payment.java @@ -7,6 +7,7 @@ public class Payment { private String label; private long amount; private boolean sendMax; + private Type type = Type.DEFAULT; public Payment(Address address, String label, long amount, boolean sendMax) { this.address = address; @@ -46,4 +47,16 @@ public class Payment { public void setSendMax(boolean sendMax) { this.sendMax = sendMax; } + + public Type getType() { + return type; + } + + public void setType(Type type) { + this.type = type; + } + + public enum Type { + DEFAULT, WHIRLPOOL_FEE; + } } diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/StandardAccount.java b/src/main/java/com/sparrowwallet/drongo/wallet/StandardAccount.java new file mode 100644 index 0000000..b6778b7 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/StandardAccount.java @@ -0,0 +1,48 @@ +package com.sparrowwallet.drongo.wallet; + +import com.sparrowwallet.drongo.crypto.ChildNumber; + +import java.util.List; + +public enum StandardAccount { + ACCOUNT_0("Account #0", new ChildNumber(0, true)), + ACCOUNT_1("Account #1", new ChildNumber(1, true)), + ACCOUNT_2("Account #2", new ChildNumber(2, true)), + ACCOUNT_3("Account #3", new ChildNumber(3, true)), + ACCOUNT_4("Account #4", new ChildNumber(4, true)), + ACCOUNT_5("Account #5", new ChildNumber(5, true)), + ACCOUNT_6("Account #6", new ChildNumber(6, true)), + ACCOUNT_7("Account #7", new ChildNumber(7, true)), + ACCOUNT_8("Account #8", new ChildNumber(8, true)), + ACCOUNT_9("Account #9", new ChildNumber(9, true)), + WHIRLPOOL_PREMIX("Premix", new ChildNumber(2147483645, true)), + WHIRLPOOL_POSTMIX("Postmix", new ChildNumber(2147483646, true)), + WHIRLPOOL_BADBANK("Badbank", new ChildNumber(2147483644, true)); + + public static final List WHIRLPOOL_ACCOUNTS = List.of(WHIRLPOOL_PREMIX, WHIRLPOOL_POSTMIX, WHIRLPOOL_BADBANK); + + StandardAccount(String name, ChildNumber childNumber) { + this.name = name; + this.childNumber = childNumber; + } + + private final String name; + private final ChildNumber childNumber; + + public String getName() { + return name; + } + + public ChildNumber getChildNumber() { + return childNumber; + } + + public int getAccountNumber() { + return childNumber.num(); + } + + @Override + public String toString() { + return name; + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java index 406e320..1f6abf5 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java @@ -1,9 +1,11 @@ package com.sparrowwallet.drongo.wallet; import com.sparrowwallet.drongo.BitcoinUnit; +import com.sparrowwallet.drongo.KeyDerivation; import com.sparrowwallet.drongo.KeyPurpose; import com.sparrowwallet.drongo.Network; import com.sparrowwallet.drongo.address.Address; +import com.sparrowwallet.drongo.crypto.ChildNumber; import com.sparrowwallet.drongo.crypto.ECKey; import com.sparrowwallet.drongo.crypto.Key; import com.sparrowwallet.drongo.policy.Policy; @@ -77,6 +79,92 @@ public class Wallet extends Persistable { return getMasterWallet().getName(); } + public Wallet addChildWallet(StandardAccount standardAccount) { + Wallet childWallet = this.copy(); + + if(!isMasterWallet()) { + throw new IllegalStateException("Cannot add child wallet to existing child wallet"); + } + + if(!childWallet.containsPrivateKeys()) { + throw new IllegalStateException("Cannot derive child wallet xpub without private keys"); + } + + if(childWallet.isEncrypted()) { + throw new IllegalStateException("Cannot derive child wallet xpub from encrypted wallet"); + } + + childWallet.setId(null); + childWallet.setName(standardAccount.getName()); + childWallet.purposeNodes.clear(); + childWallet.transactions.clear(); + childWallet.storedBlockHeight = null; + childWallet.gapLimit = null; + childWallet.birthDate = null; + + for(Keystore keystore : childWallet.getKeystores()) { + KeyDerivation keyDerivation = keystore.getKeyDerivation(); + List childDerivation = new ArrayList<>(keyDerivation.getDerivation().subList(0, keyDerivation.getDerivation().size() - 1)); + childDerivation.add(standardAccount.getChildNumber()); + + try { + Keystore derivedKeystore = Keystore.fromSeed(keystore.getSeed(), childDerivation); + keystore.setKeyDerivation(derivedKeystore.getKeyDerivation()); + keystore.setExtendedPublicKey(derivedKeystore.getExtendedPublicKey()); + keystore.getSeed().setPassphrase(keystore.getSeed().getPassphrase()); + } catch(Exception e) { + throw new IllegalStateException("Cannot derive keystore for " + standardAccount + " account", e); + } + } + + childWallet.setMasterWallet(this); + getChildWallets().add(childWallet); + return childWallet; + } + + public Wallet getChildWallet(StandardAccount account) { + for(Wallet childWallet : getChildWallets()) { + for(Keystore keystore : childWallet.getKeystores()) { + if(keystore.getKeyDerivation().getDerivation().get(keystore.getKeyDerivation().getDerivation().size() - 1).equals(account.getChildNumber())) { + return childWallet; + } + } + } + + return null; + } + + public StandardAccount getStandardAccountType() { + int accountIndex = getAccountIndex(); + return Arrays.stream(StandardAccount.values()).filter(standardAccount -> standardAccount.getChildNumber().num() == accountIndex).findFirst().orElse(null); + } + + public int getAccountIndex() { + int index = -1; + + for(Keystore keystore : getKeystores()) { + int keystoreAccount = getScriptType().getAccount(keystore.getKeyDerivation().getDerivationPath()); + if(keystoreAccount != -1 && (index == -1 || keystoreAccount == index)) { + index = keystoreAccount; + } + } + + return index; + } + + public boolean isWhirlpoolMasterWallet() { + if(!isMasterWallet()) { + return false; + } + + Set whirlpoolAccounts = new HashSet<>(Set.of(StandardAccount.WHIRLPOOL_PREMIX, StandardAccount.WHIRLPOOL_POSTMIX, StandardAccount.WHIRLPOOL_BADBANK)); + for(Wallet childWallet : getChildWallets()) { + whirlpoolAccounts.remove(childWallet.getStandardAccountType()); + } + + return whirlpoolAccounts.isEmpty(); + } + public void setName(String name) { this.name = name; } diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/WalletTransaction.java b/src/main/java/com/sparrowwallet/drongo/wallet/WalletTransaction.java index 8c5bce1..e6aa32e 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/WalletTransaction.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/WalletTransaction.java @@ -98,8 +98,20 @@ public class WalletTransaction { } public boolean isConsolidationSend(Payment payment) { - if(payment.getAddress() != null && getWallet() != null) { - return getWallet().isWalletOutputScript(payment.getAddress().getOutputScript()); + return isWalletSend(getWallet(), payment); + } + + public boolean isPremixSend(Payment payment) { + return isWalletSend(getWallet().getChildWallet(StandardAccount.WHIRLPOOL_PREMIX), payment); + } + + public boolean isBadbankSend(Payment payment) { + return isWalletSend(getWallet().getChildWallet(StandardAccount.WHIRLPOOL_BADBANK), payment); + } + + public boolean isWalletSend(Wallet wallet, Payment payment) { + if(payment.getAddress() != null && wallet != null) { + return wallet.isWalletOutputScript(payment.getAddress().getOutputScript()); } return false;