From 3433c5f20524416f1d4225d7961f40b4300c4006 Mon Sep 17 00:00:00 2001 From: Craig Raw Date: Wed, 4 Nov 2020 12:09:17 +0200 Subject: [PATCH] improve invalid wallet error messaging, fix public psbt --- .../com/sparrowwallet/drongo/psbt/PSBT.java | 7 ++- .../sparrowwallet/drongo/psbt/PSBTInput.java | 2 +- .../wallet/InvalidKeystoreException.java | 15 ++++++ .../drongo/wallet/InvalidWalletException.java | 15 ++++++ .../sparrowwallet/drongo/wallet/Keystore.java | 48 +++++++++++++++---- .../sparrowwallet/drongo/wallet/Wallet.java | 47 +++++++++++++----- 6 files changed, 107 insertions(+), 27 deletions(-) create mode 100644 src/main/java/com/sparrowwallet/drongo/wallet/InvalidKeystoreException.java create mode 100644 src/main/java/com/sparrowwallet/drongo/wallet/InvalidWalletException.java diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java index b234729..5832a2b 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java @@ -533,16 +533,15 @@ public class PSBT { try { PSBT publicCopy = new PSBT(serialize()); publicCopy.extendedPublicKeys.clear(); + publicCopy.globalProprietary.clear(); for(PSBTInput psbtInput : publicCopy.getPsbtInputs()) { - psbtInput.clearPrivateFields(); + psbtInput.getDerivedPublicKeys().clear(); + psbtInput.getProprietary().clear(); } for(PSBTOutput psbtOutput : publicCopy.getPsbtOutputs()) { psbtOutput.getDerivedPublicKeys().clear(); psbtOutput.getProprietary().clear(); } - if(publicCopy.isFinalized()) { - publicCopy.transaction = publicCopy.extractTransaction(); - } return publicCopy; } catch(PSBTParseException e) { diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java index 8179408..a5a426c 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java @@ -538,7 +538,7 @@ public class PSBTInput { return getWitnessUtxo() != null ? getWitnessUtxo() : (getNonWitnessUtxo() != null ? getNonWitnessUtxo().getOutputs().get(vout) : null); } - public void clearPrivateFields() { + public void clearNonFinalFields() { partialSignatures.clear(); sigHash = null; redeemScript = null; diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/InvalidKeystoreException.java b/src/main/java/com/sparrowwallet/drongo/wallet/InvalidKeystoreException.java new file mode 100644 index 0000000..e6d1c7c --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/InvalidKeystoreException.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.drongo.wallet; + +public class InvalidKeystoreException extends Exception { + public InvalidKeystoreException() { + super(); + } + + public InvalidKeystoreException(String msg) { + super(msg); + } + + public InvalidKeystoreException(String msg, Throwable throwable) { + super(msg, throwable); + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/InvalidWalletException.java b/src/main/java/com/sparrowwallet/drongo/wallet/InvalidWalletException.java new file mode 100644 index 0000000..34dff61 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/InvalidWalletException.java @@ -0,0 +1,15 @@ +package com.sparrowwallet.drongo.wallet; + +public class InvalidWalletException extends Exception { + public InvalidWalletException() { + super(); + } + + public InvalidWalletException(String msg) { + super(msg); + } + + public InvalidWalletException(String msg, Throwable throwable) { + super(msg, throwable); + } +} diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java b/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java index a59d52c..116a952 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Keystore.java @@ -130,25 +130,55 @@ public class Keystore { } public boolean isValid() { - if(label == null || source == null || walletModel == null || keyDerivation == null || extendedPublicKey == null) { + try { + checkKeystore(); + } catch(InvalidKeystoreException e) { return false; } - if(label.isEmpty() || label.replace(" ", "").length() > 16) { - return false; + return true; + } + + public void checkKeystore() throws InvalidKeystoreException { + if(label == null) { + throw new InvalidKeystoreException("No label specified"); + } + + if(source == null) { + throw new InvalidKeystoreException("No source specified"); + } + + if(walletModel == null) { + throw new InvalidKeystoreException("No wallet model specified"); + } + + if(keyDerivation == null) { + throw new InvalidKeystoreException("No key derivation specified"); + } + + if(extendedPublicKey == null) { + throw new InvalidKeystoreException("No extended public key specified"); + } + + if(label.isEmpty()) { + throw new InvalidKeystoreException("Label too short"); + } + + if(label.replace(" ", "").length() > 16) { + throw new InvalidKeystoreException("Label too long"); } if(keyDerivation.getDerivationPath() == null || keyDerivation.getDerivationPath().isEmpty() || !KeyDerivation.isValid(keyDerivation.getDerivationPath())) { - return false; + throw new InvalidKeystoreException("Invalid key derivation path of " + keyDerivation.getDerivationPath()); } if(keyDerivation.getMasterFingerprint() == null || keyDerivation.getMasterFingerprint().length() != 8 || !Utils.isHex(keyDerivation.getMasterFingerprint())) { - return false; + throw new InvalidKeystoreException("Invalid master fingerprint of " + keyDerivation.getMasterFingerprint()); } if(source == KeystoreSource.SW_SEED) { if(seed == null) { - return false; + throw new InvalidKeystoreException("Source of " + source + " but no seed is present"); } if(!seed.isEncrypted()) { @@ -158,15 +188,13 @@ public class Keystore { DeterministicKey derivedKeyPublicOnly = derivedKey.dropPrivateBytes().dropParent(); ExtendedKey xpub = new ExtendedKey(derivedKeyPublicOnly, derivedKey.getParentFingerprint(), derivation.isEmpty() ? ChildNumber.ZERO : derivation.get(derivation.size() - 1)); if(!xpub.equals(getExtendedPublicKey())) { - return false; + throw new InvalidKeystoreException("Specified extended public key does not match public key derived from seed"); } } catch(MnemonicException e) { - return false; + throw new InvalidKeystoreException("Invalid mnemonic specified for seed", e); } } } - - return true; } public Keystore copy() { diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java index 1e53dd8..777e2e5 100644 --- a/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java +++ b/src/main/java/com/sparrowwallet/drongo/wallet/Wallet.java @@ -764,7 +764,7 @@ public class Wallet { psbtInput.setFinalScriptSig(finalizedTxInput.getScriptSig()); psbtInput.setFinalScriptWitness(finalizedTxInput.getWitness()); - psbtInput.clearPrivateFields(); + psbtInput.clearNonFinalFields(); } } } @@ -799,43 +799,66 @@ public class Wallet { } public boolean isValid() { - if(policyType == null || scriptType == null || defaultPolicy == null || keystores.isEmpty()) { + try { + checkWallet(); + } catch(InvalidWalletException e) { return false; } + return true; + } + + public void checkWallet() throws InvalidWalletException { + if(policyType == null) { + throw new InvalidWalletException("No policy type specified"); + } + + if(scriptType == null) { + throw new InvalidWalletException("No script type specified"); + } + + if(defaultPolicy == null) { + throw new InvalidWalletException("No default policy specified"); + } + + if(keystores.isEmpty()) { + throw new InvalidWalletException("No keystores specified"); + } + if(!ScriptType.getScriptTypesForPolicyType(policyType).contains(scriptType)) { - return false; + throw new InvalidWalletException("Script type of " + scriptType + " is not valid for a policy type of " + policyType); } int numSigs; try { numSigs = defaultPolicy.getNumSignaturesRequired(); } catch (Exception e) { - return false; + throw new InvalidWalletException("Cannot determine number of required signatures to sign a transaction"); } if(policyType.equals(PolicyType.SINGLE) && (numSigs != 1 || keystores.size() != 1)) { - return false; + throw new InvalidWalletException(policyType + " wallet needs " + numSigs + " and has " + keystores.size() + " keystores"); } if(policyType.equals(PolicyType.MULTI) && (numSigs < 1 || numSigs > keystores.size())) { - return false; + throw new InvalidWalletException(policyType + " wallet needs " + numSigs + " and has " + keystores.size() + " keystores"); } if(containsDuplicateKeystoreLabels()) { - return false; + throw new InvalidWalletException("Wallet keystores have duplicate labels"); } for(Keystore keystore : keystores) { - if(!keystore.isValid()) { - return false; + try { + keystore.checkKeystore(); + } catch(InvalidKeystoreException e) { + throw new InvalidWalletException("Keystore " + keystore.getLabel() + " is invalid (" + e.getMessage() + ")", e); } + if(derivationMatchesAnotherScriptType(keystore.getKeyDerivation().getDerivationPath())) { - return false; + throw new InvalidWalletException("Keystore " + keystore.getLabel() + " derivation of " + keystore.getKeyDerivation().getDerivationPath() + " in " + scriptType.getName() + " wallet matches another default script type."); } } - - return true; } public boolean derivationMatchesAnotherScriptType(String derivationPath) {