mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
allow psbt signature verification to be deferred
This commit is contained in:
parent
567294a4b0
commit
42ffeb9565
3 changed files with 64 additions and 18 deletions
|
@ -16,7 +16,7 @@ import java.nio.ByteBuffer;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
|
|
||||||
import static com.sparrowwallet.drongo.psbt.PSBTEntry.*;
|
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.*;
|
import static com.sparrowwallet.drongo.psbt.PSBTOutput.*;
|
||||||
|
|
||||||
public class PSBT {
|
public class PSBT {
|
||||||
|
@ -169,11 +169,15 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
public PSBT(byte[] psbt) throws PSBTParseException {
|
public PSBT(byte[] psbt) throws PSBTParseException {
|
||||||
this.psbtBytes = psbt;
|
this(psbt, true);
|
||||||
parse();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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 seenInputs = 0;
|
||||||
int seenOutputs = 0;
|
int seenOutputs = 0;
|
||||||
|
|
||||||
|
@ -214,7 +218,7 @@ public class PSBT {
|
||||||
seenInputs++;
|
seenInputs++;
|
||||||
if (seenInputs == inputs) {
|
if (seenInputs == inputs) {
|
||||||
currentState = STATE_OUTPUTS;
|
currentState = STATE_OUTPUTS;
|
||||||
parseInputEntries(inputEntryLists);
|
parseInputEntries(inputEntryLists, verifySignatures);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case STATE_OUTPUTS:
|
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) {
|
for(List<PSBTEntry> inputEntries : inputEntryLists) {
|
||||||
PSBTEntry duplicate = findDuplicateKey(inputEntries);
|
PSBTEntry duplicate = findDuplicateKey(inputEntries);
|
||||||
if(duplicate != null) {
|
if(duplicate != null) {
|
||||||
|
@ -325,9 +329,11 @@ public class PSBT {
|
||||||
int inputIndex = this.psbtInputs.size();
|
int inputIndex = this.psbtInputs.size();
|
||||||
PSBTInput input = new PSBTInput(inputEntries, transaction, inputIndex);
|
PSBTInput input = new PSBTInput(inputEntries, transaction, inputIndex);
|
||||||
|
|
||||||
|
if(verifySignatures) {
|
||||||
boolean verified = input.verifySignatures();
|
boolean verified = input.verifySignatures();
|
||||||
if(!verified && input.getPartialSignatures().size() > 0) {
|
if(!verified && input.getPartialSignatures().size() > 0) {
|
||||||
throw new PSBTParseException("Unverifiable partial signatures provided");
|
throw new PSBTSignatureException("Unverifiable partial signatures provided");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this.psbtInputs.add(input);
|
this.psbtInputs.add(input);
|
||||||
|
@ -379,6 +385,19 @@ public class PSBT {
|
||||||
return fee;
|
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() {
|
public boolean hasSignatures() {
|
||||||
for(PSBTInput psbtInput : getPsbtInputs()) {
|
for(PSBTInput psbtInput : getPsbtInputs()) {
|
||||||
if(!psbtInput.getPartialSignatures().isEmpty() || psbtInput.getFinalScriptSig() != null || psbtInput.getFinalScriptWitness() != null) {
|
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 {
|
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)) {
|
if (!isPSBT(strPSBT)) {
|
||||||
throw new PSBTParseException("Provided string is not a PSBT");
|
throw new PSBTParseException("Provided string is not a PSBT");
|
||||||
}
|
}
|
||||||
|
@ -639,6 +662,6 @@ public class PSBT {
|
||||||
}
|
}
|
||||||
|
|
||||||
byte[] psbtBytes = Utils.hexToBytes(strPSBT);
|
byte[] psbtBytes = Utils.hexToBytes(strPSBT);
|
||||||
return new PSBT(psbtBytes);
|
return new PSBT(psbtBytes, verifySignatures);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -136,12 +136,16 @@ public class PSBTInput {
|
||||||
throw new PSBTParseException("Witness UTXO provided but redeem script is not P2WPKH or P2WSH");
|
throw new PSBTParseException("Witness UTXO provided but redeem script is not P2WPKH or P2WSH");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(scriptPubKey == null || !P2SH.isScriptType(scriptPubKey)) {
|
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");
|
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())) {
|
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()));
|
throw new PSBTParseException("Redeem script hash does not match transaction output script pubkey hash " + Utils.bytesToHex(scriptPubKey.getPubKeyHash()));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
this.redeemScript = redeemScript;
|
this.redeemScript = redeemScript;
|
||||||
log.debug("Found input redeem script hex " + Utils.bytesToHex(redeemScript.getProgram()) + " script " + redeemScript);
|
log.debug("Found input redeem script hex " + Utils.bytesToHex(redeemScript.getProgram()) + " script " + redeemScript);
|
||||||
|
@ -156,7 +160,7 @@ public class PSBTInput {
|
||||||
pubKeyHash = this.witnessUtxo.getScript().getPubKeyHash();
|
pubKeyHash = this.witnessUtxo.getScript().getPubKeyHash();
|
||||||
}
|
}
|
||||||
if(pubKeyHash == null) {
|
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)) {
|
} 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));
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean verifySignatures() throws PSBTParseException {
|
boolean verifySignatures() throws PSBTSignatureException {
|
||||||
SigHash localSigHash = getSigHash();
|
SigHash localSigHash = getSigHash();
|
||||||
if(localSigHash == null) {
|
if(localSigHash == null) {
|
||||||
//Assume SigHash.ALL
|
//Assume SigHash.ALL
|
||||||
|
@ -443,7 +447,7 @@ public class PSBTInput {
|
||||||
for(ECKey sigPublicKey : getPartialSignatures().keySet()) {
|
for(ECKey sigPublicKey : getPartialSignatures().keySet()) {
|
||||||
TransactionSignature signature = getPartialSignature(sigPublicKey);
|
TransactionSignature signature = getPartialSignature(sigPublicKey);
|
||||||
if(!sigPublicKey.verify(hash, signature)) {
|
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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue