allow psbt signature verification to be deferred

This commit is contained in:
Craig Raw 2021-05-28 11:07:59 +02:00
parent 567294a4b0
commit 42ffeb9565
3 changed files with 64 additions and 18 deletions

View file

@ -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<List<PSBTEntry>> inputEntryLists) throws PSBTParseException {
private void parseInputEntries(List<List<PSBTEntry>> inputEntryLists, boolean verifySignatures) throws PSBTParseException {
for(List<PSBTEntry> 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);
}
}

View file

@ -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");
}
}

View file

@ -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);
}
}