improve wallet import error messaging, handle p2sh-p2wpkh payjoin

This commit is contained in:
Craig Raw 2020-11-04 12:10:40 +02:00
parent 0aac8bbea7
commit bcf6f77340
6 changed files with 45 additions and 24 deletions

2
drongo

@ -1 +1 @@
Subproject commit 9c9836147ab77b28fed9b6bdc8eb1e14fd1e1217 Subproject commit 3433c5f20524416f1d4225d7961f40b4300c4006

View file

@ -6,10 +6,7 @@ import com.sparrowwallet.drongo.KeyDerivation;
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.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletModel;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
@ -75,8 +72,10 @@ public class CoboVaultSinglesig implements KeystoreFileImport, WalletImport {
wallet.getKeystores().add(keystore); wallet.getKeystores().add(keystore);
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, wallet.getKeystores(), null)); wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, wallet.getKeystores(), null));
if(!wallet.isValid()) { try {
throw new ImportException("Wallet is in an inconsistent state."); wallet.checkWallet();
} catch(InvalidWalletException e) {
throw new ImportException("Imported Cobo Vault wallet was invalid: " + e.getMessage());
} }
return wallet; return wallet;

View file

@ -8,10 +8,7 @@ import com.sparrowwallet.drongo.KeyDerivation;
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.ScriptType; import com.sparrowwallet.drongo.protocol.ScriptType;
import com.sparrowwallet.drongo.wallet.Keystore; import com.sparrowwallet.drongo.wallet.*;
import com.sparrowwallet.drongo.wallet.KeystoreSource;
import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletModel;
import java.io.File; import java.io.File;
import java.io.InputStream; import java.io.InputStream;
@ -101,8 +98,10 @@ public class ColdcardSinglesig implements KeystoreFileImport, WalletImport {
wallet.getKeystores().add(keystore); wallet.getKeystores().add(keystore);
wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, wallet.getKeystores(), null)); wallet.setDefaultPolicy(Policy.getPolicy(PolicyType.SINGLE, ScriptType.P2WPKH, wallet.getKeystores(), null));
if(!wallet.isValid()) { try {
throw new ImportException("Wallet is in an inconsistent state."); wallet.checkWallet();
} catch(InvalidWalletException e) {
throw new ImportException("Imported Coldcard wallet was invalid: " + e.getMessage());
} }
return wallet; return wallet;

View file

@ -201,8 +201,10 @@ public class Electrum implements KeystoreFileImport, WalletImport, WalletExport
wallet.updateTransactions(ew.transactions); wallet.updateTransactions(ew.transactions);
if(!wallet.isValid()) { try {
throw new IllegalStateException("Electrum wallet is in an inconsistent state."); wallet.checkWallet();
} catch(InvalidWalletException e) {
throw new IllegalStateException("Imported Electrum wallet was invalid: " + e.getMessage());
} }
return wallet; return wallet;

View file

@ -3,6 +3,7 @@ package com.sparrowwallet.sparrow.io;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.GsonBuilder; import com.google.gson.GsonBuilder;
import com.sparrowwallet.drongo.OutputDescriptor; import com.sparrowwallet.drongo.OutputDescriptor;
import com.sparrowwallet.drongo.wallet.InvalidWalletException;
import com.sparrowwallet.drongo.wallet.Wallet; import com.sparrowwallet.drongo.wallet.Wallet;
import com.sparrowwallet.drongo.wallet.WalletModel; import com.sparrowwallet.drongo.wallet.WalletModel;
@ -57,8 +58,10 @@ public class Specter implements WalletImport, WalletExport {
Wallet wallet = outputDescriptor.toWallet(); Wallet wallet = outputDescriptor.toWallet();
wallet.setName(specterWallet.label); wallet.setName(specterWallet.label);
if(!wallet.isValid()) { try {
throw new ImportException("Specter wallet file did not contain a valid wallet"); wallet.checkWallet();
} catch(InvalidWalletException e) {
throw new ImportException("Imported Specter wallet was invalid: " + e.getMessage());
} }
return wallet; return wallet;

View file

@ -36,6 +36,23 @@ public class Payjoin {
this.payjoinURI = payjoinURI; this.payjoinURI = payjoinURI;
this.wallet = wallet; this.wallet = wallet;
this.psbt = psbt.getPublicCopy(); this.psbt = psbt.getPublicCopy();
for(PSBTInput psbtInput : this.psbt.getPsbtInputs()) {
if(psbtInput.getUtxo() == null) {
throw new IllegalArgumentException("Original PSBT for payjoin transaction must have non_witness_utxo or witness_utxo fields for all inputs");
}
if(!psbtInput.getDerivedPublicKeys().isEmpty()) {
throw new IllegalArgumentException("Original PSBT for payjoin transaction must have no derived public keys for all inputs");
}
}
if(!this.psbt.isFinalized()) {
throw new IllegalArgumentException("Original PSBT for payjoin transaction must be finalized");
}
if(!this.psbt.getExtendedPublicKeys().isEmpty()) {
throw new IllegalArgumentException("Original PSBT for payjoin transaction must have no global xpubs");
}
} }
public PSBT requestPayjoinPSBT(boolean allowOutputSubstitution) throws PayjoinReceiverException { public PSBT requestPayjoinPSBT(boolean allowOutputSubstitution) throws PayjoinReceiverException {
@ -57,7 +74,7 @@ public class Payjoin {
long maxAdditionalFeeContribution = 0; long maxAdditionalFeeContribution = 0;
if(changeOutputIndex > -1) { if(changeOutputIndex > -1) {
appendQuery += "&additionalfeeoutputindex=" + changeOutputIndex; appendQuery += "&additionalfeeoutputindex=" + changeOutputIndex;
maxAdditionalFeeContribution = getAdditionalFeeContribution(psbt.getTransaction()); maxAdditionalFeeContribution = getAdditionalFeeContribution();
appendQuery += "&maxadditionalfeecontribution=" + maxAdditionalFeeContribution; appendQuery += "&maxadditionalfeecontribution=" + maxAdditionalFeeContribution;
} }
@ -158,8 +175,8 @@ public class Payjoin {
proposedPSBTInput.setWitnessUtxo(originalPSBTInput.getWitnessUtxo()); proposedPSBTInput.setWitnessUtxo(originalPSBTInput.getWitnessUtxo());
// We fill up information we had on the signed PSBT, so we can sign it. // We fill up information we had on the signed PSBT, so we can sign it.
proposedPSBTInput.getDerivedPublicKeys().putAll(originalPSBTInput.getDerivedPublicKeys()); proposedPSBTInput.getDerivedPublicKeys().putAll(originalPSBTInput.getDerivedPublicKeys());
proposedPSBTInput.setRedeemScript(originalPSBTInput.getRedeemScript()); proposedPSBTInput.setRedeemScript(originalPSBTInput.getFinalScriptSig().getFirstNestedScript());
proposedPSBTInput.setWitnessScript(originalPSBTInput.getWitnessScript()); proposedPSBTInput.setWitnessScript(originalPSBTInput.getFinalScriptWitness().getWitnessScript());
proposedPSBTInput.setSigHash(originalPSBTInput.getSigHash()); proposedPSBTInput.setSigHash(originalPSBTInput.getSigHash());
} else { } else {
// Verify the PSBT input is finalized // Verify the PSBT input is finalized
@ -220,7 +237,7 @@ public class Payjoin {
} }
// Make sure the actual contribution is only paying for fee incurred by additional inputs // Make sure the actual contribution is only paying for fee incurred by additional inputs
int additionalInputsCount = proposalTx.getInputs().size() - originalTx.getInputs().size(); int additionalInputsCount = proposalTx.getInputs().size() - originalTx.getInputs().size();
if(actualContribution > getSingleInputFee(originalTx) * additionalInputsCount) { if(actualContribution > getSingleInputFee() * additionalInputsCount) {
throw new PayjoinReceiverException("The actual contribution is not only paying for additional inputs"); throw new PayjoinReceiverException("The actual contribution is not only paying for additional inputs");
} }
} else if(allowOutputSubstitution && originalOutput.getKey().getScript().equals(payjoinURI.getAddress().getOutputScript())) { } else if(allowOutputSubstitution && originalOutput.getKey().getScript().equals(payjoinURI.getAddress().getOutputScript())) {
@ -259,11 +276,12 @@ public class Payjoin {
return -1; return -1;
} }
private long getAdditionalFeeContribution(Transaction transaction) { private long getAdditionalFeeContribution() {
return getSingleInputFee(transaction); return getSingleInputFee();
} }
private long getSingleInputFee(Transaction transaction) { private long getSingleInputFee() {
Transaction transaction = psbt.extractTransaction();
double feeRate = psbt.getFee().doubleValue() / transaction.getVirtualSize(); double feeRate = psbt.getFee().doubleValue() / transaction.getVirtualSize();
int vSize = 68; int vSize = 68;