mirror of
https://github.com/sparrowwallet/drongo.git
synced 2024-12-26 10:06:45 +00:00
signing fixes
This commit is contained in:
parent
4b4a980a9b
commit
4c5166a6ea
4 changed files with 82 additions and 11 deletions
|
@ -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);
|
||||||
|
|
|
@ -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();
|
||||||
|
|
||||||
|
|
|
@ -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]);
|
||||||
|
}
|
||||||
}
|
}
|
|
@ -637,7 +637,10 @@ 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();
|
||||||
psbtInput.sign(privKey);
|
|
||||||
|
if(!psbtInput.isSigned()) {
|
||||||
|
psbtInput.sign(privKey);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in a new issue