support for better combining and finalising psbts

This commit is contained in:
Craig Raw 2020-07-30 09:59:32 +02:00
parent 3ce2394813
commit 4b4a980a9b
4 changed files with 116 additions and 10 deletions

View file

@ -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() { public boolean hasWitnesses() {
for(TransactionInput in : inputs) { for(TransactionInput in : inputs) {
if(in.hasWitness()) { if(in.hasWitness()) {

View file

@ -393,6 +393,16 @@ public class PSBT {
return true; return true;
} }
public boolean isFinalized() {
for(PSBTInput psbtInput : getPsbtInputs()) {
if(psbtInput.getFinalScriptSig() != null || psbtInput.getFinalScriptWitness() != null) {
return true;
}
}
return false;
}
private List<PSBTEntry> getGlobalEntries() { private List<PSBTEntry> getGlobalEntries() {
List<PSBTEntry> entries = new ArrayList<>(); List<PSBTEntry> entries = new ArrayList<>();
@ -462,10 +472,8 @@ public class PSBT {
throw new IllegalArgumentException("Provided PSBT does contain a matching global transaction"); throw new IllegalArgumentException("Provided PSBT does contain a matching global transaction");
} }
for(PSBTInput psbtInput : psbt.getPsbtInputs()) { if(isFinalized() || psbt.isFinalized()) {
if(psbtInput.getFinalScriptSig() != null || psbtInput.getFinalScriptWitness() != null) { throw new IllegalArgumentException("Cannot combine an already finalised PSBT");
throw new IllegalArgumentException("Cannot combine an already finalised PSBT");
}
} }
if(psbt.getVersion() != null) { if(psbt.getVersion() != null) {
@ -499,12 +507,14 @@ public class PSBT {
} }
} }
if(hasWitness && !transaction.isSegwit()) { Transaction finalTransaction = new Transaction(transaction.bitcoinSerialize());
transaction.setSegwitVersion(1);
if(hasWitness && !finalTransaction.isSegwit()) {
finalTransaction.setSegwitVersion(1);
} }
for(int i = 0; i < transaction.getInputs().size(); i++) { for(int i = 0; i < finalTransaction.getInputs().size(); i++) {
TransactionInput txInput = transaction.getInputs().get(i); TransactionInput txInput = finalTransaction.getInputs().get(i);
PSBTInput psbtInput = getPsbtInputs().get(i); PSBTInput psbtInput = getPsbtInputs().get(i);
txInput.setScriptBytes(psbtInput.getFinalScriptSig().getProgram()); txInput.setScriptBytes(psbtInput.getFinalScriptSig().getProgram());
@ -512,12 +522,12 @@ public class PSBT {
if(psbtInput.getFinalScriptWitness() != null) { if(psbtInput.getFinalScriptWitness() != null) {
txInput.setWitness(psbtInput.getFinalScriptWitness()); txInput.setWitness(psbtInput.getFinalScriptWitness());
} else { } else {
txInput.setWitness(new TransactionWitness(transaction)); txInput.setWitness(new TransactionWitness(finalTransaction));
} }
} }
} }
return transaction; return finalTransaction;
} }
public List<PSBTInput> getPsbtInputs() { public List<PSBTInput> getPsbtInputs() {

View file

@ -395,6 +395,16 @@ public class PSBTInput {
} }
} }
public Collection<TransactionSignature> getSignatures() {
if(getFinalScriptWitness() != null) {
return getFinalScriptWitness().getSignatures();
} else if(getFinalScriptSig() != null) {
return getFinalScriptSig().getSignatures();
} else {
return getPartialSignatures().values();
}
}
public boolean sign(ECKey privKey) { public boolean sign(ECKey privKey) {
SigHash localSigHash = getSigHash(); SigHash localSigHash = getSigHash();
if(localSigHash == null) { if(localSigHash == null) {

View file

@ -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<PSBTInput, WalletNode> signedInputNodes = new LinkedHashMap<>();
private final Map<WalletNode, List<ECKey>> signedNodeKeys = new LinkedHashMap<>();
private int numSignatures;
public FinalizingPSBTWallet(PSBT psbt) {
super("Finalizing PSBT Wallet");
Map<PSBTInput, WalletNode> signingNodes = new LinkedHashMap<>();
for(int i = 0; i < psbt.getPsbtInputs().size(); i++) {
PSBTInput psbtInput = psbt.getPsbtInputs().get(i);
Set<ECKey> 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<PSBTInput, List<Keystore>> getSignedKeystores(PSBT psbt) {
Map<PSBTInput, List<Keystore>> signedKeystores = new LinkedHashMap<>();
for(PSBTInput psbtInput : psbt.getPsbtInputs()) {
Collection<TransactionSignature> 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<PSBTInput, WalletNode> getSigningNodes(PSBT psbt) {
return signedInputNodes;
}
@Override
public ECKey getPubKey(WalletNode node) {
return signedNodeKeys.get(node).get(0);
}
@Override
public List<ECKey> getPubKeys(WalletNode node) {
return signedNodeKeys.get(node);
}
}