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) { public TransactionWitness(Transaction transaction, List<TransactionSignature> signatures, Script witnessScript) {
setParent(transaction); setParent(transaction);
this.pushes = new ArrayList<>(); 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)) { if(ScriptType.MULTISIG.isScriptType(witnessScript)) {
pushes.add(new byte[] { ScriptOpCodes.OP_0 }); pushes.add(new byte[0]);
} }
for(TransactionSignature signature : signatures) { for(TransactionSignature signature : signatures) {
pushes.add(signature.encodeToBitcoin()); pushes.add(signature.encodeToBitcoin());
@ -95,7 +96,7 @@ public class TransactionWitness extends ChildMessage {
protected void bitcoinSerializeToStream(OutputStream stream) throws IOException { protected void bitcoinSerializeToStream(OutputStream stream) throws IOException {
stream.write(new VarInt(pushes.size()).encode()); stream.write(new VarInt(pushes.size()).encode());
for (int i = 0; i < pushes.size(); i++) { for(int i = 0; i < pushes.size(); i++) {
byte[] push = pushes.get(i); byte[] push = pushes.get(i);
if(push.length == 1 && push[0] == 0) { if(push.length == 1 && push[0] == 0) {
stream.write(push); stream.write(push);

View file

@ -386,7 +386,7 @@ public class PSBTInput {
//All partial sigs are already verified //All partial sigs are already verified
int reqSigs = getSigningScript().getNumRequiredSignatures(); int reqSigs = getSigningScript().getNumRequiredSignatures();
int sigs = getPartialSignatures().size(); int sigs = getPartialSignatures().size();
return sigs == reqSigs; return sigs >= reqSigs;
} catch(NonStandardScriptException e) { } catch(NonStandardScriptException e) {
return false; return false;
} }
@ -457,6 +457,31 @@ public class PSBTInput {
return false; 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() { public Script getSigningScript() {
Script signingScript = getUtxo().getScript(); Script signingScript = getUtxo().getScript();

View file

@ -1,9 +1,12 @@
package com.sparrowwallet.drongo.wallet; package com.sparrowwallet.drongo.wallet;
import com.sparrowwallet.drongo.KeyPurpose;
import com.sparrowwallet.drongo.crypto.ECKey; import com.sparrowwallet.drongo.crypto.ECKey;
import com.sparrowwallet.drongo.policy.Miniscript; import com.sparrowwallet.drongo.policy.Miniscript;
import com.sparrowwallet.drongo.policy.Policy; import com.sparrowwallet.drongo.policy.Policy;
import com.sparrowwallet.drongo.policy.PolicyType; 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.ScriptType;
import com.sparrowwallet.drongo.protocol.TransactionSignature; import com.sparrowwallet.drongo.protocol.TransactionSignature;
import com.sparrowwallet.drongo.psbt.PSBT; 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 * 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 { public class FinalizingPSBTWallet extends Wallet {
private final Map<PSBTInput, WalletNode> signedInputNodes = new LinkedHashMap<>(); private final Map<PSBTInput, WalletNode> signedInputNodes = new LinkedHashMap<>();
@ -25,15 +28,43 @@ public class FinalizingPSBTWallet extends Wallet {
public FinalizingPSBTWallet(PSBT psbt) { public FinalizingPSBTWallet(PSBT psbt) {
super("Finalizing PSBT Wallet"); 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++) { for(int i = 0; i < psbt.getPsbtInputs().size(); i++) {
PSBTInput psbtInput = psbt.getPsbtInputs().get(i); PSBTInput psbtInput = psbt.getPsbtInputs().get(i);
Set<ECKey> keys = psbtInput.getPartialSignatures().keySet(); Set<ECKey> keys = psbtInput.getPartialSignatures().keySet();
WalletNode signedNode = new WalletNode("m/" + i);
WalletNode signedNode = signedNodes.get(i);
signedInputNodes.put(psbtInput, signedNode); signedInputNodes.put(psbtInput, signedNode);
signedNodeKeys.put(signedNode, new ArrayList<>(keys)); 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); setPolicyType(numSignatures == 1 ? PolicyType.SINGLE : PolicyType.MULTI);
@ -73,4 +104,15 @@ public class FinalizingPSBTWallet extends Wallet {
public List<ECKey> getPubKeys(WalletNode node) { public List<ECKey> getPubKeys(WalletNode node) {
return signedNodeKeys.get(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()) { for(Map.Entry<PSBTInput, WalletNode> signingEntry : signingNodes.entrySet()) {
ECKey privKey = keystore.getKey(signingEntry.getValue()); ECKey privKey = keystore.getKey(signingEntry.getValue());
PSBTInput psbtInput = signingEntry.getKey(); PSBTInput psbtInput = signingEntry.getKey();
if(!psbtInput.isSigned()) {
psbtInput.sign(privKey); psbtInput.sign(privKey);
} }
} }
} }
} }
}
public void finalise(PSBT psbt) { public void finalise(PSBT psbt) {
int threshold = getDefaultPolicy().getNumSignaturesRequired(); int threshold = getDefaultPolicy().getNumSignaturesRequired();
@ -680,8 +683,8 @@ public class Wallet {
finalizedTxInput = getScriptType().addSpendingInput(transaction, utxo, pubKey, transactionSignature); finalizedTxInput = getScriptType().addSpendingInput(transaction, utxo, pubKey, transactionSignature);
} else if(getPolicyType().equals(PolicyType.MULTI)) { } else if(getPolicyType().equals(PolicyType.MULTI)) {
List<ECKey> pubKeys = getPubKeys(signingNode); List<ECKey> pubKeys = getPubKeys(signingNode);
List<TransactionSignature> signatures = pubKeys.stream().map(psbtInput::getPartialSignature).collect(Collectors.toList()); List<TransactionSignature> signatures = pubKeys.stream().map(psbtInput::getPartialSignature).filter(Objects::nonNull).collect(Collectors.toList());
if(pubKeys.size() != signatures.size()) { if(signatures.size() < threshold) {
throw new IllegalArgumentException("Pubkeys of partial signatures do not match wallet pubkeys"); throw new IllegalArgumentException("Pubkeys of partial signatures do not match wallet pubkeys");
} }