signing fixes

This commit is contained in:
Craig Raw 2020-07-30 15:57:50 +02:00
parent 4b4a980a9b
commit 4c5166a6ea
4 changed files with 82 additions and 11 deletions

View file

@ -24,8 +24,9 @@ public class TransactionWitness extends ChildMessage {
public TransactionWitness(Transaction transaction, List<TransactionSignature> signatures, Script witnessScript) {
setParent(transaction);
this.pushes = new ArrayList<>();
//If a multisig witness script, add a zero byte witness element to handle the multisig off by one bug
if(ScriptType.MULTISIG.isScriptType(witnessScript)) {
pushes.add(new byte[] { ScriptOpCodes.OP_0 });
pushes.add(new byte[0]);
}
for(TransactionSignature signature : signatures) {
pushes.add(signature.encodeToBitcoin());

View file

@ -386,7 +386,7 @@ public class PSBTInput {
//All partial sigs are already verified
int reqSigs = getSigningScript().getNumRequiredSignatures();
int sigs = getPartialSignatures().size();
return sigs == reqSigs;
return sigs >= reqSigs;
} catch(NonStandardScriptException e) {
return false;
}
@ -457,6 +457,31 @@ public class PSBTInput {
return false;
}
public ScriptType getScriptType() {
Script signingScript = getUtxo().getScript();
boolean p2sh = false;
if(P2SH.isScriptType(signingScript)) {
p2sh = true;
if(getRedeemScript() != null) {
signingScript = getRedeemScript();
} else if(getFinalScriptSig() != null) {
signingScript = getFinalScriptSig().getFirstNestedScript();
} else {
return null;
}
}
if(P2WPKH.isScriptType(signingScript)) {
return p2sh ? P2SH_P2WPKH : P2WPKH;
} else if(P2WSH.isScriptType(signingScript)) {
return p2sh ? P2SH_P2WSH : P2WSH;
}
return ScriptType.getType(signingScript);
}
public Script getSigningScript() {
Script signingScript = getUtxo().getScript();

View file

@ -1,9 +1,12 @@
package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.KeyPurpose;
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.NonStandardScriptException;
import com.sparrowwallet.drongo.protocol.Script;
import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.protocol.TransactionSignature;
import com.sparrowwallet.drongo.psbt.PSBT;
@ -15,7 +18,7 @@ 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
*
* It is used when the normal wallet is not available.
*/
public class FinalizingPSBTWallet extends Wallet {
private final Map<PSBTInput, WalletNode> signedInputNodes = new LinkedHashMap<>();
@ -25,15 +28,43 @@ public class FinalizingPSBTWallet extends Wallet {
public FinalizingPSBTWallet(PSBT psbt) {
super("Finalizing PSBT Wallet");
Map<PSBTInput, WalletNode> signingNodes = new LinkedHashMap<>();
if(!psbt.isSigned() || psbt.isFinalized()) {
throw new IllegalArgumentException("Only a fully signed, unfinalized PSBT can be used");
}
WalletNode purposeNode = getNode(KeyPurpose.RECEIVE);
List<WalletNode> signedNodes = new ArrayList<>(purposeNode.getChildren());
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);
WalletNode signedNode = signedNodes.get(i);
signedInputNodes.put(psbtInput, signedNode);
signedNodeKeys.put(signedNode, new ArrayList<>(keys));
numSignatures = keys.size();
setScriptType(ScriptType.getType(psbtInput.getUtxo().getScript()));
ScriptType scriptType = psbtInput.getScriptType();
if(scriptType == null || (getScriptType() != null && scriptType.equals(getScriptType()))) {
throw new IllegalArgumentException("Cannot determine a single script type from the PSBT");
} else {
setScriptType(scriptType);
}
try {
Script signingScript = psbtInput.getSigningScript();
int sigsRequired = signingScript.getNumRequiredSignatures();
if(numSignatures > 0 && sigsRequired != numSignatures) {
throw new IllegalArgumentException("Different number of signatures required in PSBT inputs");
} else {
numSignatures = sigsRequired;
}
if(ScriptType.MULTISIG.isScriptType(signingScript)) {
signedNodeKeys.put(signedNode, Arrays.asList(ScriptType.MULTISIG.getPublicKeysFromScript(signingScript)));
}
} catch(NonStandardScriptException e) {
throw new IllegalArgumentException(e.getMessage());
}
}
setPolicyType(numSignatures == 1 ? PolicyType.SINGLE : PolicyType.MULTI);
@ -73,4 +104,15 @@ public class FinalizingPSBTWallet extends Wallet {
public List<ECKey> getPubKeys(WalletNode node) {
return signedNodeKeys.get(node);
}
@Override
public Script getOutputScript(WalletNode node) {
for(Map.Entry<PSBTInput, WalletNode> entry : signedInputNodes.entrySet()) {
if(node.equals(entry.getValue())) {
return entry.getKey().getUtxo().getScript();
}
}
return new Script(new byte[10]);
}
}

View file

@ -637,11 +637,14 @@ public class Wallet {
for(Map.Entry<PSBTInput, WalletNode> signingEntry : signingNodes.entrySet()) {
ECKey privKey = keystore.getKey(signingEntry.getValue());
PSBTInput psbtInput = signingEntry.getKey();
if(!psbtInput.isSigned()) {
psbtInput.sign(privKey);
}
}
}
}
}
public void finalise(PSBT psbt) {
int threshold = getDefaultPolicy().getNumSignaturesRequired();
@ -680,8 +683,8 @@ public class Wallet {
finalizedTxInput = getScriptType().addSpendingInput(transaction, utxo, pubKey, transactionSignature);
} else if(getPolicyType().equals(PolicyType.MULTI)) {
List<ECKey> pubKeys = getPubKeys(signingNode);
List<TransactionSignature> signatures = pubKeys.stream().map(psbtInput::getPartialSignature).collect(Collectors.toList());
if(pubKeys.size() != signatures.size()) {
List<TransactionSignature> signatures = pubKeys.stream().map(psbtInput::getPartialSignature).filter(Objects::nonNull).collect(Collectors.toList());
if(signatures.size() < threshold) {
throw new IllegalArgumentException("Pubkeys of partial signatures do not match wallet pubkeys");
}