diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java index 14f3086..38af19c 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java @@ -16,7 +16,7 @@ import java.nio.ByteBuffer; import java.util.*; import static com.sparrowwallet.drongo.psbt.PSBTEntry.*; -import static com.sparrowwallet.drongo.psbt.PSBTInput.PSBT_IN_BIP32_DERIVATION; +import static com.sparrowwallet.drongo.psbt.PSBTInput.*; import static com.sparrowwallet.drongo.psbt.PSBTOutput.*; public class PSBT { @@ -169,11 +169,15 @@ public class PSBT { } public PSBT(byte[] psbt) throws PSBTParseException { - this.psbtBytes = psbt; - parse(); + this(psbt, true); } - private void parse() throws PSBTParseException { + public PSBT(byte[] psbt, boolean verifySignatures) throws PSBTParseException { + this.psbtBytes = psbt; + parse(verifySignatures); + } + + private void parse(boolean verifySignatures) throws PSBTParseException { int seenInputs = 0; int seenOutputs = 0; @@ -214,7 +218,7 @@ public class PSBT { seenInputs++; if (seenInputs == inputs) { currentState = STATE_OUTPUTS; - parseInputEntries(inputEntryLists); + parseInputEntries(inputEntryLists, verifySignatures); } break; case STATE_OUTPUTS: @@ -315,7 +319,7 @@ public class PSBT { } } - private void parseInputEntries(List> inputEntryLists) throws PSBTParseException { + private void parseInputEntries(List> inputEntryLists, boolean verifySignatures) throws PSBTParseException { for(List inputEntries : inputEntryLists) { PSBTEntry duplicate = findDuplicateKey(inputEntries); if(duplicate != null) { @@ -325,9 +329,11 @@ public class PSBT { int inputIndex = this.psbtInputs.size(); PSBTInput input = new PSBTInput(inputEntries, transaction, inputIndex); - boolean verified = input.verifySignatures(); - if(!verified && input.getPartialSignatures().size() > 0) { - throw new PSBTParseException("Unverifiable partial signatures provided"); + if(verifySignatures) { + boolean verified = input.verifySignatures(); + if(!verified && input.getPartialSignatures().size() > 0) { + throw new PSBTSignatureException("Unverifiable partial signatures provided"); + } } this.psbtInputs.add(input); @@ -379,6 +385,19 @@ public class PSBT { return fee; } + public void verifySignatures() throws PSBTSignatureException { + for(PSBTInput input : getPsbtInputs()) { + boolean verified = input.verifySignatures(); + if(!verified) { + if(input.getPartialSignatures().size() > 0) { + throw new PSBTSignatureException("Unverifiable partial signatures provided"); + } + + throw new PSBTSignatureException("No UTXO data provided"); + } + } + } + public boolean hasSignatures() { for(PSBTInput psbtInput : getPsbtInputs()) { if(!psbtInput.getPartialSignatures().isEmpty() || psbtInput.getFinalScriptSig() != null || psbtInput.getFinalScriptWitness() != null) { @@ -630,6 +649,10 @@ public class PSBT { } public static PSBT fromString(String strPSBT) throws PSBTParseException { + return fromString(strPSBT, true); + } + + public static PSBT fromString(String strPSBT, boolean verifySignatures) throws PSBTParseException { if (!isPSBT(strPSBT)) { throw new PSBTParseException("Provided string is not a PSBT"); } @@ -639,6 +662,6 @@ public class PSBT { } byte[] psbtBytes = Utils.hexToBytes(strPSBT); - return new PSBT(psbtBytes); + return new PSBT(psbtBytes, verifySignatures); } } diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java index bdd7645..a569328 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java @@ -136,11 +136,15 @@ public class PSBTInput { throw new PSBTParseException("Witness UTXO provided but redeem script is not P2WPKH or P2WSH"); } } - if(scriptPubKey == null || !P2SH.isScriptType(scriptPubKey)) { - throw new PSBTParseException("PSBT provided a redeem script for a transaction output that does not need one"); - } - if(!Arrays.equals(Utils.sha256hash160(redeemScript.getProgram()), scriptPubKey.getPubKeyHash())) { - throw new PSBTParseException("Redeem script hash does not match transaction output script pubkey hash " + Utils.bytesToHex(scriptPubKey.getPubKeyHash())); + if(scriptPubKey == null) { + log.warn("PSBT provided a redeem script for a transaction output that was not provided"); + } else { + if(!P2SH.isScriptType(scriptPubKey)) { + throw new PSBTParseException("PSBT provided a redeem script for a transaction output that does not need one"); + } + if(!Arrays.equals(Utils.sha256hash160(redeemScript.getProgram()), scriptPubKey.getPubKeyHash())) { + throw new PSBTParseException("Redeem script hash does not match transaction output script pubkey hash " + Utils.bytesToHex(scriptPubKey.getPubKeyHash())); + } } this.redeemScript = redeemScript; @@ -156,7 +160,7 @@ public class PSBTInput { pubKeyHash = this.witnessUtxo.getScript().getPubKeyHash(); } if(pubKeyHash == null) { - throw new PSBTParseException("Witness script provided without P2WSH witness utxo or P2SH redeem script"); + log.warn("Witness script provided without P2WSH witness utxo or P2SH redeem script"); } else if(!Arrays.equals(Sha256Hash.hash(witnessScript.getProgram()), pubKeyHash)) { throw new PSBTParseException("Witness script hash does not match provided pay to script hash " + Utils.bytesToHex(pubKeyHash)); } @@ -428,7 +432,7 @@ public class PSBTInput { return false; } - boolean verifySignatures() throws PSBTParseException { + boolean verifySignatures() throws PSBTSignatureException { SigHash localSigHash = getSigHash(); if(localSigHash == null) { //Assume SigHash.ALL @@ -443,7 +447,7 @@ public class PSBTInput { for(ECKey sigPublicKey : getPartialSignatures().keySet()) { TransactionSignature signature = getPartialSignature(sigPublicKey); if(!sigPublicKey.verify(hash, signature)) { - throw new PSBTParseException("Partial signature does not verify against provided public key"); + throw new PSBTSignatureException("Partial signature does not verify against provided public key"); } } diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTSignatureException.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTSignatureException.java new file mode 100644 index 0000000..a29f686 --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTSignatureException.java @@ -0,0 +1,19 @@ +package com.sparrowwallet.drongo.psbt; + +public class PSBTSignatureException extends PSBTParseException { + public PSBTSignatureException() { + super(); + } + + public PSBTSignatureException(String message) { + super(message); + } + + public PSBTSignatureException(Throwable cause) { + super(cause); + } + + public PSBTSignatureException(String message, Throwable cause) { + super(message, cause); + } +}