diff --git a/src/main/java/com/sparrowwallet/drongo/protocol/Transaction.java b/src/main/java/com/sparrowwallet/drongo/protocol/Transaction.java index e37caeb..62d8a62 100644 --- a/src/main/java/com/sparrowwallet/drongo/protocol/Transaction.java +++ b/src/main/java/com/sparrowwallet/drongo/protocol/Transaction.java @@ -153,6 +153,16 @@ public class Transaction extends ChildMessage { } } + public boolean hasScriptSigs() { + for(TransactionInput in : inputs) { + if(in.getScriptBytes().length > 0) { + return true; + } + } + + return false; + } + public boolean hasWitnesses() { for(TransactionInput in : inputs) { if(in.hasWitness()) { diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java index daf348f..d943699 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBT.java @@ -393,6 +393,16 @@ public class PSBT { return true; } + public boolean isFinalized() { + for(PSBTInput psbtInput : getPsbtInputs()) { + if(psbtInput.getFinalScriptSig() != null || psbtInput.getFinalScriptWitness() != null) { + return true; + } + } + + return false; + } + private List getGlobalEntries() { List entries = new ArrayList<>(); @@ -462,10 +472,8 @@ public class PSBT { throw new IllegalArgumentException("Provided PSBT does contain a matching global transaction"); } - for(PSBTInput psbtInput : psbt.getPsbtInputs()) { - if(psbtInput.getFinalScriptSig() != null || psbtInput.getFinalScriptWitness() != null) { - throw new IllegalArgumentException("Cannot combine an already finalised PSBT"); - } + if(isFinalized() || psbt.isFinalized()) { + throw new IllegalArgumentException("Cannot combine an already finalised PSBT"); } if(psbt.getVersion() != null) { @@ -499,12 +507,14 @@ public class PSBT { } } - if(hasWitness && !transaction.isSegwit()) { - transaction.setSegwitVersion(1); + Transaction finalTransaction = new Transaction(transaction.bitcoinSerialize()); + + if(hasWitness && !finalTransaction.isSegwit()) { + finalTransaction.setSegwitVersion(1); } - for(int i = 0; i < transaction.getInputs().size(); i++) { - TransactionInput txInput = transaction.getInputs().get(i); + for(int i = 0; i < finalTransaction.getInputs().size(); i++) { + TransactionInput txInput = finalTransaction.getInputs().get(i); PSBTInput psbtInput = getPsbtInputs().get(i); txInput.setScriptBytes(psbtInput.getFinalScriptSig().getProgram()); @@ -512,12 +522,12 @@ public class PSBT { if(psbtInput.getFinalScriptWitness() != null) { txInput.setWitness(psbtInput.getFinalScriptWitness()); } else { - txInput.setWitness(new TransactionWitness(transaction)); + txInput.setWitness(new TransactionWitness(finalTransaction)); } } } - return transaction; + return finalTransaction; } public List getPsbtInputs() { diff --git a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java index c44f44c..fa3d396 100644 --- a/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java +++ b/src/main/java/com/sparrowwallet/drongo/psbt/PSBTInput.java @@ -395,6 +395,16 @@ public class PSBTInput { } } + public Collection getSignatures() { + if(getFinalScriptWitness() != null) { + return getFinalScriptWitness().getSignatures(); + } else if(getFinalScriptSig() != null) { + return getFinalScriptSig().getSignatures(); + } else { + return getPartialSignatures().values(); + } + } + public boolean sign(ECKey privKey) { SigHash localSigHash = getSigHash(); if(localSigHash == null) { diff --git a/src/main/java/com/sparrowwallet/drongo/wallet/FinalizingPSBTWallet.java b/src/main/java/com/sparrowwallet/drongo/wallet/FinalizingPSBTWallet.java new file mode 100644 index 0000000..fd1c81c --- /dev/null +++ b/src/main/java/com/sparrowwallet/drongo/wallet/FinalizingPSBTWallet.java @@ -0,0 +1,76 @@ +package com.sparrowwallet.drongo.wallet; + +import com.sparrowwallet.drongo.crypto.ECKey; +import com.sparrowwallet.drongo.policy.Miniscript; +import com.sparrowwallet.drongo.policy.Policy; +import com.sparrowwallet.drongo.policy.PolicyType; +import com.sparrowwallet.drongo.protocol.ScriptType; +import com.sparrowwallet.drongo.protocol.TransactionSignature; +import com.sparrowwallet.drongo.psbt.PSBT; +import com.sparrowwallet.drongo.psbt.PSBTInput; + +import java.util.*; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * This is a special wallet that is used solely to finalize a fully signed PSBT by reading from the partial signatures and UTXO scriptPubKey + * + */ +public class FinalizingPSBTWallet extends Wallet { + private final Map signedInputNodes = new LinkedHashMap<>(); + private final Map> signedNodeKeys = new LinkedHashMap<>(); + private int numSignatures; + + public FinalizingPSBTWallet(PSBT psbt) { + super("Finalizing PSBT Wallet"); + + Map signingNodes = new LinkedHashMap<>(); + for(int i = 0; i < psbt.getPsbtInputs().size(); i++) { + PSBTInput psbtInput = psbt.getPsbtInputs().get(i); + Set keys = psbtInput.getPartialSignatures().keySet(); + WalletNode signedNode = new WalletNode("m/" + i); + signedInputNodes.put(psbtInput, signedNode); + signedNodeKeys.put(signedNode, new ArrayList<>(keys)); + numSignatures = keys.size(); + setScriptType(ScriptType.getType(psbtInput.getUtxo().getScript())); + } + + setPolicyType(numSignatures == 1 ? PolicyType.SINGLE : PolicyType.MULTI); + } + + @Override + public Map> getSignedKeystores(PSBT psbt) { + Map> signedKeystores = new LinkedHashMap<>(); + for(PSBTInput psbtInput : psbt.getPsbtInputs()) { + Collection signatures = psbtInput.getSignatures(); + signedKeystores.put(psbtInput, IntStream.range(1, signatures.size() + 1).mapToObj(i -> new Keystore("Keystore " + i)).collect(Collectors.toList())); + } + return signedKeystores; + } + + @Override + public Policy getDefaultPolicy() { + return new Policy(new Miniscript("")) { + @Override + public int getNumSignaturesRequired() { + return numSignatures; + } + }; + } + + @Override + public Map getSigningNodes(PSBT psbt) { + return signedInputNodes; + } + + @Override + public ECKey getPubKey(WalletNode node) { + return signedNodeKeys.get(node).get(0); + } + + @Override + public List getPubKeys(WalletNode node) { + return signedNodeKeys.get(node); + } +} \ No newline at end of file