diff --git a/core/src/main/java/com/google/bitcoin/core/Wallet.java b/core/src/main/java/com/google/bitcoin/core/Wallet.java index b94937ea..a988b683 100644 --- a/core/src/main/java/com/google/bitcoin/core/Wallet.java +++ b/core/src/main/java/com/google/bitcoin/core/Wallet.java @@ -491,7 +491,7 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha /** Returns the address used for change outputs. Note: this will probably go away in future. */ public Address getChangeAddress() { - return currentKey(KeyChain.KeyPurpose.CHANGE).toAddress(params); + return keychain.currentAddress(KeyChain.KeyPurpose.CHANGE); } /** @@ -3895,14 +3895,17 @@ public class Wallet extends BaseTaggableObject implements Serializable, BlockCha int size = 0; for (TransactionOutput output : selection.gathered) { try { - if (output.getScriptPubKey().isSentToAddress()) { - // Send-to-address spends usually take maximum pubkey.length (as it may be compressed or not) + 75 bytes - final ECKey key = findKeyFromPubHash(output.getScriptPubKey().getPubKeyHash()); - size += checkNotNull(key, "Coin selection includes unspendable outputs").getPubKey().length + 75; - } else if (output.getScriptPubKey().isSentToRawPubKey()) - size += 74; // Send-to-pubkey spends usually take maximum 74 bytes to spend - else - throw new IllegalStateException("Unknown output type returned in coin selection"); + Script script = output.getScriptPubKey(); + ECKey key = null; + Script redeemScript = null; + if (script.isSentToAddress()) { + key = findKeyFromPubHash(script.getPubKeyHash()); + checkNotNull(key, "Coin selection includes unspendable outputs"); + } else if (script.isPayToScriptHash()) { + redeemScript = keychain.findRedeemScriptFromPubHash(script.getPubKeyHash()); + checkNotNull(redeemScript, "Coin selection includes unspendable outputs"); + } + size += script.getNumberOfBytesRequiredToSpend(key, redeemScript); } catch (ScriptException e) { // If this happens it means an output script in a wallet tx could not be understood. That should never // happen, if it does it means the wallet has got into an inconsistent state. diff --git a/core/src/main/java/com/google/bitcoin/script/Script.java b/core/src/main/java/com/google/bitcoin/script/Script.java index 378d19d4..40dc1059 100644 --- a/core/src/main/java/com/google/bitcoin/script/Script.java +++ b/core/src/main/java/com/google/bitcoin/script/Script.java @@ -25,6 +25,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.spongycastle.crypto.digests.RIPEMD160Digest; +import javax.annotation.Nullable; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -54,6 +55,7 @@ import static com.google.common.base.Preconditions.checkState; public class Script { private static final Logger log = LoggerFactory.getLogger(Script.class); public static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes + public static final int SIG_SIZE = 75; // The program is a set of chunks where each element is either [opcode] or [data, data, data ...] protected List chunks; @@ -446,6 +448,36 @@ public class Script { return 0; } + /** + * Returns number of bytes required to spend this script. It accepts optional ECKey and redeemScript that may + * be required for certain types of script to estimate target size. + */ + public int getNumberOfBytesRequiredToSpend(@Nullable ECKey pubKey, @Nullable Script redeemScript) { + if (isPayToScriptHash()) { + // scriptSig: [sig] [sig...] + checkArgument(redeemScript != null, "P2SH script requires redeemScript to be spent"); + // for N of M CHECKMULTISIG redeem script we will need N signatures to spend + ScriptChunk nChunk = redeemScript.getChunks().get(0); + int n = Script.decodeFromOpN(nChunk.opcode); + return n * SIG_SIZE + getProgram().length; + } else if (isSentToMultiSig()) { + // scriptSig: OP_0 [sig] [sig...] + // for N of M CHECKMULTISIG script we will need N signatures to spend + ScriptChunk nChunk = chunks.get(0); + int n = Script.decodeFromOpN(nChunk.opcode); + return n * SIG_SIZE + 1; + } else if (isSentToRawPubKey()) { + // scriptSig: + return SIG_SIZE; + } else if (isSentToAddress()) { + // scriptSig: + int uncompressedPubKeySize = 65; + return SIG_SIZE + (pubKey != null ? pubKey.getPubKey().length : uncompressedPubKeySize); + } else { + throw new IllegalStateException("Unsupported script type"); + } + } + /** *

Whether or not this is a scriptPubKey representing a pay-to-script-hash output. In such outputs, the logic that * controls reclamation is not actually in the output at all. Instead there's just a hash, and it's up to the