diff --git a/core/src/main/java/org/bitcoinj/core/Address.java b/core/src/main/java/org/bitcoinj/core/Address.java index 2b9d6a127..0e43293f5 100644 --- a/core/src/main/java/org/bitcoinj/core/Address.java +++ b/core/src/main/java/org/bitcoinj/core/Address.java @@ -24,6 +24,7 @@ import java.io.ObjectOutputStream; import org.bitcoinj.params.Networks; import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptPattern; import javax.annotation.Nullable; @@ -74,7 +75,7 @@ public class Address extends VersionedChecksummedBytes { /** Returns an Address that represents the script hash extracted from the given scriptPubKey */ public static Address fromP2SHScript(NetworkParameters params, Script scriptPubKey) { - checkArgument(scriptPubKey.isPayToScriptHash(), "Not a P2SH script"); + checkArgument(ScriptPattern.isPayToScriptHash(scriptPubKey), "Not a P2SH script"); return fromP2SHHash(params, scriptPubKey.getPubKeyHash()); } diff --git a/core/src/main/java/org/bitcoinj/core/BloomFilter.java b/core/src/main/java/org/bitcoinj/core/BloomFilter.java index 0e3d66151..6751eb404 100644 --- a/core/src/main/java/org/bitcoinj/core/BloomFilter.java +++ b/core/src/main/java/org/bitcoinj/core/BloomFilter.java @@ -19,6 +19,8 @@ package org.bitcoinj.core; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptChunk; +import org.bitcoinj.script.ScriptPattern; + import com.google.common.base.Objects; import com.google.common.collect.Lists; @@ -330,7 +332,7 @@ public class BloomFilter extends Message { if (!chunk.isPushData()) continue; if (contains(chunk.data)) { - boolean isSendingToPubKeys = script.isSentToRawPubKey() || script.isSentToMultiSig(); + boolean isSendingToPubKeys = ScriptPattern.isPayToPubKey(script) || ScriptPattern.isSentToMultisig(script); if (flag == BloomUpdate.UPDATE_ALL || (flag == BloomUpdate.UPDATE_P2PUBKEY_ONLY && isSendingToPubKeys)) insert(output.getOutPointFor().unsafeBitcoinSerialize()); found = true; diff --git a/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java b/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java index d1f2810dd..eaf1ae95e 100644 --- a/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java +++ b/core/src/main/java/org/bitcoinj/core/FullPrunedBlockChain.java @@ -19,6 +19,7 @@ package org.bitcoinj.core; import org.bitcoinj.script.Script; import org.bitcoinj.script.Script.VerifyFlag; +import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.store.BlockStoreException; import org.bitcoinj.store.FullPrunedBlockStore; import org.bitcoinj.utils.*; @@ -269,7 +270,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain { // TODO: Check we're not spending the genesis transaction here. Bitcoin Core won't allow it. valueIn = valueIn.add(prevOut.getValue()); if (verifyFlags.contains(VerifyFlag.P2SH)) { - if (prevOut.getScript().isPayToScriptHash()) + if (ScriptPattern.isPayToScriptHash(prevOut.getScript())) sigOps += Script.getP2SHSigOpCount(in.getScriptBytes()); if (sigOps > Block.MAX_BLOCK_SIGOPS) throw new VerificationException("Too many P2SH SigOps in block"); @@ -397,7 +398,7 @@ public class FullPrunedBlockChain extends AbstractBlockChain { throw new VerificationException("Tried to spend coinbase at depth " + (newBlock.getHeight() - prevOut.getHeight())); valueIn = valueIn.add(prevOut.getValue()); if (verifyFlags.contains(VerifyFlag.P2SH)) { - if (prevOut.getScript().isPayToScriptHash()) + if (ScriptPattern.isPayToScriptHash(prevOut.getScript())) sigOps += Script.getP2SHSigOpCount(in.getScriptBytes()); if (sigOps > Block.MAX_BLOCK_SIGOPS) throw new VerificationException("Too many P2SH SigOps in block"); diff --git a/core/src/main/java/org/bitcoinj/core/PeerGroup.java b/core/src/main/java/org/bitcoinj/core/PeerGroup.java index 777dbab21..848d14807 100644 --- a/core/src/main/java/org/bitcoinj/core/PeerGroup.java +++ b/core/src/main/java/org/bitcoinj/core/PeerGroup.java @@ -200,7 +200,7 @@ public class PeerGroup implements TransactionBroadcaster { // filter. In case (1), we need to retransmit the filter to the connected peers. In case (2), we don't // and shouldn't, we should just recalculate and cache the new filter for next time. for (TransactionOutput output : tx.getOutputs()) { - if (output.getScriptPubKey().isSentToRawPubKey() && output.isMine(wallet)) { + if (ScriptPattern.isPayToPubKey(output.getScriptPubKey()) && output.isMine(wallet)) { if (tx.getConfidence().getConfidenceType() == TransactionConfidence.ConfidenceType.BUILDING) recalculateFastCatchupAndFilter(FilterRecalculateMode.SEND_IF_CHANGED); else diff --git a/core/src/main/java/org/bitcoinj/core/Transaction.java b/core/src/main/java/org/bitcoinj/core/Transaction.java index d5ce18f61..15824cc14 100644 --- a/core/src/main/java/org/bitcoinj/core/Transaction.java +++ b/core/src/main/java/org/bitcoinj/core/Transaction.java @@ -24,6 +24,7 @@ import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptError; import org.bitcoinj.script.ScriptException; import org.bitcoinj.script.ScriptOpCodes; +import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.signers.TransactionSigner; import org.bitcoinj.utils.ExchangeRate; import org.bitcoinj.wallet.Wallet; @@ -704,7 +705,7 @@ public class Transaction extends ChildMessage { final TransactionOutput connectedOutput = outpoint.getConnectedOutput(); if (connectedOutput != null) { Script scriptPubKey = connectedOutput.getScriptPubKey(); - if (scriptPubKey.isSentToAddress() || scriptPubKey.isPayToScriptHash()) { + if (ScriptPattern.isPayToPubKeyHash(scriptPubKey) || ScriptPattern.isPayToScriptHash(scriptPubKey)) { s.append(" hash160:"); s.append(Utils.HEX.encode(scriptPubKey.getPubKeyHash())); } @@ -819,9 +820,9 @@ public class Transaction extends ChildMessage { Sha256Hash hash = hashForSignature(inputs.size() - 1, scriptPubKey, sigHash, anyoneCanPay); ECKey.ECDSASignature ecSig = sigKey.sign(hash); TransactionSignature txSig = new TransactionSignature(ecSig, sigHash, anyoneCanPay); - if (scriptPubKey.isSentToRawPubKey()) + if (ScriptPattern.isPayToPubKey(scriptPubKey)) input.setScriptSig(ScriptBuilder.createInputScript(txSig)); - else if (scriptPubKey.isSentToAddress()) + else if (ScriptPattern.isPayToPubKeyHash(scriptPubKey)) input.setScriptSig(ScriptBuilder.createInputScript(txSig, sigKey)); else throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Don't know how to sign for this kind of scriptPubKey: " + scriptPubKey); diff --git a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java index 5392f72ab..dfcb70479 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionOutPoint.java @@ -141,10 +141,10 @@ public class TransactionOutPoint extends ChildMessage { TransactionOutput connectedOutput = getConnectedOutput(); checkNotNull(connectedOutput, "Input is not connected so cannot retrieve key"); Script connectedScript = connectedOutput.getScriptPubKey(); - if (connectedScript.isSentToAddress()) { + if (ScriptPattern.isPayToPubKeyHash(connectedScript)) { byte[] addressBytes = connectedScript.getPubKeyHash(); return keyBag.findKeyFromPubHash(addressBytes); - } else if (connectedScript.isSentToRawPubKey()) { + } else if (ScriptPattern.isPayToPubKey(connectedScript)) { byte[] pubkeyBytes = connectedScript.getPubKey(); return keyBag.findKeyFromPubKey(pubkeyBytes); } else { @@ -164,13 +164,13 @@ public class TransactionOutPoint extends ChildMessage { TransactionOutput connectedOutput = getConnectedOutput(); checkNotNull(connectedOutput, "Input is not connected so cannot retrieve key"); Script connectedScript = connectedOutput.getScriptPubKey(); - if (connectedScript.isSentToAddress()) { + if (ScriptPattern.isPayToPubKeyHash(connectedScript)) { byte[] addressBytes = connectedScript.getPubKeyHash(); return RedeemData.of(keyBag.findKeyFromPubHash(addressBytes), connectedScript); - } else if (connectedScript.isSentToRawPubKey()) { + } else if (ScriptPattern.isPayToPubKey(connectedScript)) { byte[] pubkeyBytes = connectedScript.getPubKey(); return RedeemData.of(keyBag.findKeyFromPubKey(pubkeyBytes), connectedScript); - } else if (connectedScript.isPayToScriptHash()) { + } else if (ScriptPattern.isPayToScriptHash(connectedScript)) { byte[] scriptHash = connectedScript.getPubKeyHash(); return keyBag.findRedeemDataFromScriptHash(scriptHash); } else { diff --git a/core/src/main/java/org/bitcoinj/core/TransactionOutput.java b/core/src/main/java/org/bitcoinj/core/TransactionOutput.java index 7b1bf616b..5da8dbd41 100644 --- a/core/src/main/java/org/bitcoinj/core/TransactionOutput.java +++ b/core/src/main/java/org/bitcoinj/core/TransactionOutput.java @@ -130,7 +130,7 @@ public class TransactionOutput extends ChildMessage { */ @Nullable public Address getAddressFromP2PKHScript(NetworkParameters networkParameters) throws ScriptException{ - if (getScriptPubKey().isSentToAddress()) + if (ScriptPattern.isPayToPubKeyHash(getScriptPubKey())) return getScriptPubKey().getToAddress(networkParameters); return null; @@ -150,7 +150,7 @@ public class TransactionOutput extends ChildMessage { */ @Nullable public Address getAddressFromP2SH(NetworkParameters networkParameters) throws ScriptException{ - if (getScriptPubKey().isPayToScriptHash()) + if (ScriptPattern.isPayToScriptHash(getScriptPubKey())) return getScriptPubKey().getToAddress(networkParameters); return null; @@ -212,7 +212,7 @@ public class TransactionOutput extends ChildMessage { */ public boolean isDust() { // Transactions that are OP_RETURN can't be dust regardless of their value. - if (getScriptPubKey().isOpReturn()) + if (ScriptPattern.isOpReturn(getScriptPubKey())) return false; return getValue().isLessThan(getMinNonDustValue()); } @@ -321,10 +321,10 @@ public class TransactionOutput extends ChildMessage { public boolean isMine(TransactionBag transactionBag) { try { Script script = getScriptPubKey(); - if (script.isSentToRawPubKey()) { + if (ScriptPattern.isPayToPubKey(script)) { byte[] pubkey = script.getPubKey(); return transactionBag.isPubKeyMine(pubkey); - } if (script.isPayToScriptHash()) { + } if (ScriptPattern.isPayToScriptHash(script)) { return transactionBag.isPayToScriptHashMine(script.getPubKeyHash()); } else { byte[] pubkeyHash = script.getPubKeyHash(); @@ -346,11 +346,11 @@ public class TransactionOutput extends ChildMessage { Script script = getScriptPubKey(); StringBuilder buf = new StringBuilder("TxOut of "); buf.append(Coin.valueOf(value).toFriendlyString()); - if (script.isSentToAddress() || script.isPayToScriptHash()) + if (ScriptPattern.isPayToPubKeyHash(script) || ScriptPattern.isPayToScriptHash(script)) buf.append(" to ").append(script.getToAddress(params)); - else if (script.isSentToRawPubKey()) + else if (ScriptPattern.isPayToPubKey(script)) buf.append(" to pubkey ").append(Utils.HEX.encode(script.getPubKey())); - else if (script.isSentToMultiSig()) + else if (ScriptPattern.isSentToMultisig(script)) buf.append(" to multisig"); else buf.append(" (unknown type)"); diff --git a/core/src/main/java/org/bitcoinj/script/Script.java b/core/src/main/java/org/bitcoinj/script/Script.java index e99c3a1fd..7230040b3 100644 --- a/core/src/main/java/org/bitcoinj/script/Script.java +++ b/core/src/main/java/org/bitcoinj/script/Script.java @@ -226,22 +226,12 @@ public class Script { } } - /** - * Returns true if this script is of the form OP_CHECKSIG. This form was originally intended for transactions - * where the peers talked to each other directly via TCP/IP, but has fallen out of favor with time due to that mode - * of operation being susceptible to man-in-the-middle attacks. It is still used in coinbase outputs and can be - * useful more exotic types of transaction, but today most payments are to addresses. - */ + @Deprecated public boolean isSentToRawPubKey() { return ScriptPattern.isPayToPubKey(this); } - /** - * Returns true if this script is of the form DUP HASH160 EQUALVERIFY CHECKSIG, ie, payment to an - * address like 1VayNert3x1KzbpzMGt2qdqrAThiRovi8. This form was originally intended for the case where you wish - * to send somebody money with a written code because their node is offline, but over time has become the standard - * way to make payments due to the short and recognizable base58 form addresses come in. - */ + @Deprecated public boolean isSentToAddress() { return ScriptPattern.isPayToPubKeyHash(this); } @@ -259,9 +249,9 @@ public class Script { * */ public byte[] getPubKeyHash() throws ScriptException { - if (isSentToAddress()) + if (ScriptPattern.isPayToPubKeyHash(this)) return chunks.get(2).data; - else if (isPayToScriptHash()) + else if (ScriptPattern.isPayToScriptHash(this)) return chunks.get(1).data; else throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not in the standard scriptPubKey form"); @@ -300,7 +290,7 @@ public class Script { * @throws ScriptException */ public byte[] getCLTVPaymentChannelSenderPubKey() throws ScriptException { - if (!isSentToCLTVPaymentChannel()) { + if (!ScriptPattern.isSentToCltvPaymentChannel(this)) { throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not a standard CHECKLOCKTIMVERIFY transaction: " + this); } return chunks.get(8).data; @@ -312,14 +302,14 @@ public class Script { * @throws ScriptException */ public byte[] getCLTVPaymentChannelRecipientPubKey() throws ScriptException { - if (!isSentToCLTVPaymentChannel()) { + if (!ScriptPattern.isSentToCltvPaymentChannel(this)) { throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not a standard CHECKLOCKTIMVERIFY transaction: " + this); } return chunks.get(1).data; } public BigInteger getCLTVPaymentChannelExpiry() { - if (!isSentToCLTVPaymentChannel()) { + if (!ScriptPattern.isSentToCltvPaymentChannel(this)) { throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not a standard CHECKLOCKTIMEVERIFY transaction: " + this); } return castToBigInteger(chunks.get(4).data, 5, false); @@ -350,11 +340,11 @@ public class Script { * showing addresses rather than pubkeys. */ public Address getToAddress(NetworkParameters params, boolean forcePayToPubKey) throws ScriptException { - if (isSentToAddress()) + if (ScriptPattern.isPayToPubKeyHash(this)) return new Address(params, getPubKeyHash()); - else if (isPayToScriptHash()) + else if (ScriptPattern.isPayToScriptHash(this)) return Address.fromP2SHScript(params, this); - else if (forcePayToPubKey && isSentToRawPubKey()) + else if (forcePayToPubKey && ScriptPattern.isPayToPubKey(this)) return ECKey.fromPublicOnly(getPubKey()).toAddress(params); else throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Cannot cast this script to a pay-to-address type"); @@ -435,12 +425,12 @@ public class Script { * It is expected that this program later on will be updated with proper signatures. */ public Script createEmptyInputScript(@Nullable ECKey key, @Nullable Script redeemScript) { - if (isSentToAddress()) { + if (ScriptPattern.isPayToPubKeyHash(this)) { checkArgument(key != null, "Key required to create pay-to-address input script"); return ScriptBuilder.createInputScript(null, key); - } else if (isSentToRawPubKey()) { + } else if (ScriptPattern.isPayToPubKey(this)) { return ScriptBuilder.createInputScript(null); - } else if (isPayToScriptHash()) { + } else if (ScriptPattern.isPayToScriptHash(this)) { checkArgument(redeemScript != null, "Redeem script required to create P2SH input script"); return ScriptBuilder.createP2SHMultiSigInputScript(null, redeemScript); } else { @@ -454,12 +444,12 @@ public class Script { public Script getScriptSigWithSignature(Script scriptSig, byte[] sigBytes, int index) { int sigsPrefixCount = 0; int sigsSuffixCount = 0; - if (isPayToScriptHash()) { + if (ScriptPattern.isPayToScriptHash(this)) { sigsPrefixCount = 1; // OP_0 * sigsSuffixCount = 1; - } else if (isSentToMultiSig()) { + } else if (ScriptPattern.isSentToMultisig(this)) { sigsPrefixCount = 1; // OP_0 * - } else if (isSentToAddress()) { + } else if (ScriptPattern.isPayToPubKeyHash(this)) { sigsSuffixCount = 1; // } return ScriptBuilder.updateScriptWithSignature(scriptSig, sigBytes, index, sigsPrefixCount, sigsSuffixCount); @@ -511,7 +501,7 @@ public class Script { * @throws ScriptException if the script type is not understood or is pay to address or is P2SH (run this method on the "Redeem script" instead). */ public List getPubKeys() { - if (!isSentToMultiSig()) + if (!ScriptPattern.isSentToMultisig(this)) throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Only usable for multisig scripts."); ArrayList result = Lists.newArrayList(); @@ -620,14 +610,14 @@ public class Script { * Returns number of signatures required to satisfy this script. */ public int getNumberOfSignaturesRequiredToSpend() { - if (isSentToMultiSig()) { + if (ScriptPattern.isSentToMultisig(this)) { // for N of M CHECKMULTISIG script we will need N signatures to spend ScriptChunk nChunk = chunks.get(0); return Script.decodeFromOpN(nChunk.opcode); - } else if (isSentToAddress() || isSentToRawPubKey()) { + } else if (ScriptPattern.isPayToPubKeyHash(this) || ScriptPattern.isPayToPubKey(this)) { // pay-to-address and pay-to-pubkey require single sig return 1; - } else if (isPayToScriptHash()) { + } else if (ScriptPattern.isPayToScriptHash(this)) { throw new IllegalStateException("For P2SH number of signatures depends on redeem script"); } else { throw new IllegalStateException("Unsupported script type"); @@ -639,17 +629,17 @@ public class Script { * be required for certain types of script to estimate target size. */ public int getNumberOfBytesRequiredToSpend(@Nullable ECKey pubKey, @Nullable Script redeemScript) { - if (isPayToScriptHash()) { + if (ScriptPattern.isPayToScriptHash(this)) { // scriptSig: [sig] [sig...] checkArgument(redeemScript != null, "P2SH script requires redeemScript to be spent"); return redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + redeemScript.getProgram().length; - } else if (isSentToMultiSig()) { + } else if (ScriptPattern.isSentToMultisig(this)) { // scriptSig: OP_0 [sig] [sig...] return getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + 1; - } else if (isSentToRawPubKey()) { + } else if (ScriptPattern.isPayToPubKey(this)) { // scriptSig: return SIG_SIZE; - } else if (isSentToAddress()) { + } else if (ScriptPattern.isPayToPubKeyHash(this)) { // scriptSig: int uncompressedPubKeySize = 65; return SIG_SIZE + (pubKey != null ? pubKey.getPubKey().length : uncompressedPubKeySize); @@ -658,30 +648,17 @@ public class Script { } } - /** - *

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 - * spending input to provide a program matching that hash. This rule is "soft enforced" by the network as it does - * not exist in Bitcoin Core. It means blocks containing P2SH transactions that don't match - * correctly are considered valid, but won't be mined upon, so they'll be rapidly re-orgd out of the chain. This - * logic is defined by BIP 16.

- * - *

bitcoinj does not support creation of P2SH transactions today. The goal of P2SH is to allow short addresses - * even for complex scripts (eg, multi-sig outputs) so they are convenient to work with in things like QRcodes or - * with copy/paste, and also to minimize the size of the unspent output set (which improves performance of the - * Bitcoin system).

- */ + @Deprecated public boolean isPayToScriptHash() { return ScriptPattern.isPayToScriptHash(this); } - /** - * Returns whether this script matches the format used for multisig outputs: [n] [keys...] [m] CHECKMULTISIG - */ + @Deprecated public boolean isSentToMultiSig() { return ScriptPattern.isSentToMultisig(this); } + @Deprecated public boolean isSentToCLTVPaymentChannel() { return ScriptPattern.isSentToCltvPaymentChannel(this); } @@ -795,6 +772,7 @@ public class Script { return Utils.decodeMPI(Utils.reverseBytes(chunk), false); } + @Deprecated public boolean isOpReturn() { return ScriptPattern.isOpReturn(this); } @@ -1643,7 +1621,7 @@ public class Script { // overall scalability and performance. // TODO: Check if we can take out enforceP2SH if there's a checkpoint at the enforcement block. - if (verifyFlags.contains(VerifyFlag.P2SH) && scriptPubKey.isPayToScriptHash()) { + if (verifyFlags.contains(VerifyFlag.P2SH) && ScriptPattern.isPayToScriptHash(scriptPubKey)) { for (ScriptChunk chunk : chunks) if (chunk.isOpCode() && chunk.opcode > OP_16) throw new ScriptException(ScriptError.SCRIPT_ERR_SIG_PUSHONLY, "Attempted to spend a P2SH scriptPubKey with a script that contained script ops"); @@ -1674,11 +1652,11 @@ public class Script { */ public ScriptType getScriptType() { ScriptType type = ScriptType.NO_TYPE; - if (isSentToAddress()) { + if (ScriptPattern.isPayToPubKeyHash(this)) { type = ScriptType.P2PKH; - } else if (isSentToRawPubKey()) { + } else if (ScriptPattern.isPayToPubKey(this)) { type = ScriptType.PUB_KEY; - } else if (isPayToScriptHash()) { + } else if (ScriptPattern.isPayToScriptHash(this)) { type = ScriptType.P2SH; } return type; diff --git a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java index 92d86f637..1aaa209ce 100644 --- a/core/src/main/java/org/bitcoinj/script/ScriptPattern.java +++ b/core/src/main/java/org/bitcoinj/script/ScriptPattern.java @@ -28,6 +28,12 @@ import static org.bitcoinj.script.ScriptOpCodes.*; * This is a Script pattern matcher with some typical script patterns */ public class ScriptPattern { + /** + * Returns true if this script is of the form DUP HASH160 EQUALVERIFY CHECKSIG, ie, payment to an + * address like 1VayNert3x1KzbpzMGt2qdqrAThiRovi8. This form was originally intended for the case where you wish + * to send somebody money with a written code because their node is offline, but over time has become the standard + * way to make payments due to the short and recognizable base58 form addresses come in. + */ public static boolean isPayToPubKeyHash(Script script) { List chunks = script.chunks; return chunks.size() == 5 && @@ -39,6 +45,11 @@ public class ScriptPattern { chunks.get(4).equalsOpCode(OP_CHECKSIG); } + /** + *

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 + * spending input to provide a program matching that hash. + */ public static boolean isPayToScriptHash(Script script) { List chunks = script.chunks; // We check for the effective serialized form because BIP16 defines a P2SH output using an exact byte @@ -54,6 +65,12 @@ public class ScriptPattern { chunks.get(2).equalsOpCode(OP_EQUAL); } + /** + * Returns true if this script is of the form OP_CHECKSIG. This form was originally intended for transactions + * where the peers talked to each other directly via TCP/IP, but has fallen out of favor with time due to that mode + * of operation being susceptible to man-in-the-middle attacks. It is still used in coinbase outputs and can be + * useful more exotic types of transaction, but today most payments are to addresses. + */ public static boolean isPayToPubKey(Script script) { List chunks = script.chunks; return chunks.size() == 2 && @@ -63,6 +80,9 @@ public class ScriptPattern { chunks.get(0).data.length > 1; } + /** + * Returns whether this script matches the format used for multisig outputs: [n] [keys...] [m] CHECKMULTISIG + */ public static boolean isSentToMultisig(Script script) { List chunks = script.chunks; if (chunks.size() < 4) return false; diff --git a/core/src/main/java/org/bitcoinj/signers/CustomTransactionSigner.java b/core/src/main/java/org/bitcoinj/signers/CustomTransactionSigner.java index 081bf9df1..1bac4230d 100644 --- a/core/src/main/java/org/bitcoinj/signers/CustomTransactionSigner.java +++ b/core/src/main/java/org/bitcoinj/signers/CustomTransactionSigner.java @@ -21,6 +21,7 @@ import org.bitcoinj.crypto.ChildNumber; import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptException; +import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.wallet.KeyBag; import org.bitcoinj.wallet.RedeemData; import org.slf4j.Logger; @@ -58,7 +59,7 @@ public abstract class CustomTransactionSigner extends StatelessTransactionSigner continue; } Script scriptPubKey = txOut.getScriptPubKey(); - if (!scriptPubKey.isPayToScriptHash()) { + if (!ScriptPattern.isPayToScriptHash(scriptPubKey)) { log.warn("CustomTransactionSigner works only with P2SH transactions"); return false; } diff --git a/core/src/main/java/org/bitcoinj/signers/MissingSigResolutionSigner.java b/core/src/main/java/org/bitcoinj/signers/MissingSigResolutionSigner.java index a4c7c5c17..41bdea73b 100644 --- a/core/src/main/java/org/bitcoinj/signers/MissingSigResolutionSigner.java +++ b/core/src/main/java/org/bitcoinj/signers/MissingSigResolutionSigner.java @@ -21,6 +21,7 @@ import org.bitcoinj.core.TransactionInput; import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptChunk; +import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.wallet.KeyBag; import org.bitcoinj.wallet.Wallet; import org.slf4j.Logger; @@ -66,8 +67,8 @@ public class MissingSigResolutionSigner extends StatelessTransactionSigner { Script scriptPubKey = txIn.getConnectedOutput().getScriptPubKey(); Script inputScript = txIn.getScriptSig(); - if (scriptPubKey.isPayToScriptHash() || scriptPubKey.isSentToMultiSig()) { - int sigSuffixCount = scriptPubKey.isPayToScriptHash() ? 1 : 0; + if (ScriptPattern.isPayToScriptHash(scriptPubKey) || ScriptPattern.isSentToMultisig(scriptPubKey)) { + int sigSuffixCount = ScriptPattern.isPayToScriptHash(scriptPubKey) ? 1 : 0; // all chunks except the first one (OP_0) and the last (redeem script) are signatures for (int j = 1; j < inputScript.getChunks().size() - sigSuffixCount; j++) { ScriptChunk scriptChunk = inputScript.getChunks().get(j); diff --git a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java index 6a4cd9f24..5874add2a 100644 --- a/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java +++ b/core/src/main/java/org/bitcoinj/wallet/KeyChainGroup.java @@ -236,7 +236,7 @@ public class KeyChainGroup implements KeyBag { DeterministicKeyChain chain = getActiveKeyChain(); if (chain.isMarried()) { Script outputScript = chain.freshOutputScript(purpose); - checkState(outputScript.isPayToScriptHash()); // Only handle P2SH for now + checkState(ScriptPattern.isPayToScriptHash(outputScript)); // Only handle P2SH for now Address freshAddress = Address.fromP2SHScript(params, outputScript); maybeLookaheadScripts(); currentAddresses.put(purpose, freshAddress); diff --git a/core/src/main/java/org/bitcoinj/wallet/KeyTimeCoinSelector.java b/core/src/main/java/org/bitcoinj/wallet/KeyTimeCoinSelector.java index cb7a2599e..f4069eb8e 100644 --- a/core/src/main/java/org/bitcoinj/wallet/KeyTimeCoinSelector.java +++ b/core/src/main/java/org/bitcoinj/wallet/KeyTimeCoinSelector.java @@ -19,6 +19,7 @@ package org.bitcoinj.wallet; import org.bitcoinj.core.*; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptException; +import org.bitcoinj.script.ScriptPattern; import com.google.common.collect.Lists; import org.slf4j.Logger; @@ -61,9 +62,9 @@ public class KeyTimeCoinSelector implements CoinSelector { // We ignore any other kind of exotic output on the assumption we can't spend it ourselves. final Script scriptPubKey = output.getScriptPubKey(); ECKey controllingKey; - if (scriptPubKey.isSentToRawPubKey()) { + if (ScriptPattern.isPayToPubKey(scriptPubKey)) { controllingKey = wallet.findKeyFromPubKey(scriptPubKey.getPubKey()); - } else if (scriptPubKey.isSentToAddress()) { + } else if (ScriptPattern.isPayToPubKeyHash(scriptPubKey)) { controllingKey = wallet.findKeyFromPubHash(scriptPubKey.getPubKeyHash()); } else { log.info("Skipping tx output {} because it's not of simple form.", output); diff --git a/core/src/main/java/org/bitcoinj/wallet/RedeemData.java b/core/src/main/java/org/bitcoinj/wallet/RedeemData.java index 162667ec0..de6f18f5a 100644 --- a/core/src/main/java/org/bitcoinj/wallet/RedeemData.java +++ b/core/src/main/java/org/bitcoinj/wallet/RedeemData.java @@ -18,6 +18,7 @@ package org.bitcoinj.wallet; import org.bitcoinj.core.ECKey; import org.bitcoinj.script.Script; +import org.bitcoinj.script.ScriptPattern; import java.util.ArrayList; import java.util.Collections; @@ -53,7 +54,7 @@ public class RedeemData { * to spend such inputs and provided program should be a proper CHECKSIG program. */ public static RedeemData of(ECKey key, Script program) { - checkArgument(program.isSentToAddress() || program.isSentToRawPubKey()); + checkArgument(ScriptPattern.isPayToPubKeyHash(program) || ScriptPattern.isPayToPubKey(program)); return key != null ? new RedeemData(Collections.singletonList(key), program) : null; } diff --git a/core/src/main/java/org/bitcoinj/wallet/Wallet.java b/core/src/main/java/org/bitcoinj/wallet/Wallet.java index 757f61082..615f34cea 100644 --- a/core/src/main/java/org/bitcoinj/wallet/Wallet.java +++ b/core/src/main/java/org/bitcoinj/wallet/Wallet.java @@ -974,7 +974,7 @@ public class Wallet extends BaseTaggableObject try { List

addresses = new LinkedList<>(); for (Script script : watchedScripts) - if (script.isSentToAddress()) + if (ScriptPattern.isPayToPubKeyHash(script)) addresses.add(script.getToAddress(params)); return addresses; } finally { @@ -1078,13 +1078,13 @@ public class Wallet extends BaseTaggableObject for (TransactionOutput o : tx.getOutputs()) { try { Script script = o.getScriptPubKey(); - if (script.isSentToRawPubKey()) { + if (ScriptPattern.isPayToPubKey(script)) { byte[] pubkey = script.getPubKey(); keyChainGroup.markPubKeyAsUsed(pubkey); - } else if (script.isSentToAddress()) { + } else if (ScriptPattern.isPayToPubKeyHash(script)) { byte[] pubkeyHash = script.getPubKeyHash(); keyChainGroup.markPubKeyHashAsUsed(pubkeyHash); - } else if (script.isPayToScriptHash()) { + } else if (ScriptPattern.isPayToScriptHash(script)) { Address a = Address.fromP2SHScript(tx.getParams(), script); keyChainGroup.markP2SHAddressAsUsed(a); } @@ -3986,7 +3986,7 @@ public class Wallet extends BaseTaggableObject for (TransactionOutput output : req.tx.getOutputs()) { if (output.isDust()) throw new DustySendRequested(); - if (output.getScriptPubKey().isOpReturn()) + if (ScriptPattern.isOpReturn(output.getScriptPubKey())) ++opReturnCount; } if (opReturnCount > 1) // Only 1 OP_RETURN per transaction allowed. @@ -4187,23 +4187,23 @@ public class Wallet extends BaseTaggableObject * false if the form of the script is not known or if the script is OP_RETURN. */ public boolean canSignFor(Script script) { - if (script.isSentToRawPubKey()) { + if (ScriptPattern.isPayToPubKey(script)) { byte[] pubkey = script.getPubKey(); ECKey key = findKeyFromPubKey(pubkey); return key != null && (key.isEncrypted() || key.hasPrivKey()); - } if (script.isPayToScriptHash()) { + } if (ScriptPattern.isPayToScriptHash(script)) { RedeemData data = findRedeemDataFromScriptHash(script.getPubKeyHash()); return data != null && canSignFor(data.redeemScript); - } else if (script.isSentToAddress()) { + } else if (ScriptPattern.isPayToPubKeyHash(script)) { ECKey key = findKeyFromPubHash(script.getPubKeyHash()); return key != null && (key.isEncrypted() || key.hasPrivKey()); - } else if (script.isSentToMultiSig()) { + } else if (ScriptPattern.isSentToMultisig(script)) { for (ECKey pubkey : script.getPubKeys()) { ECKey key = findKeyFromPubKey(pubkey.getPubKey()); if (key != null && (key.isEncrypted() || key.hasPrivKey())) return true; } - } else if (script.isSentToCLTVPaymentChannel()) { + } else if (ScriptPattern.isSentToCltvPaymentChannel(script)) { // Any script for which we are the recipient or sender counts. byte[] sender = script.getCLTVPaymentChannelSenderPubKey(); ECKey senderKey = findKeyFromPubKey(sender); @@ -4724,7 +4724,7 @@ public class Wallet extends BaseTaggableObject // Returns true if the output is one that won't be selected by a data element matching in the scriptSig. private boolean isTxOutputBloomFilterable(TransactionOutput out) { Script script = out.getScriptPubKey(); - boolean isScriptTypeSupported = script.isSentToRawPubKey() || script.isPayToScriptHash(); + boolean isScriptTypeSupported = ScriptPattern.isPayToPubKey(script) || ScriptPattern.isPayToScriptHash(script); return (isScriptTypeSupported && myUnspents.contains(out)) || watchedScripts.contains(script); } @@ -4981,10 +4981,10 @@ public class Wallet extends BaseTaggableObject Script script = output.getScriptPubKey(); ECKey key = null; Script redeemScript = null; - if (script.isSentToAddress()) { + if (ScriptPattern.isPayToPubKeyHash(script)) { key = findKeyFromPubHash(script.getPubKeyHash()); checkNotNull(key, "Coin selection includes unspendable outputs"); - } else if (script.isPayToScriptHash()) { + } else if (ScriptPattern.isPayToScriptHash(script)) { redeemScript = findRedeemDataFromScriptHash(script.getPubKeyHash()).redeemScript; checkNotNull(redeemScript, "Coin selection includes unspendable outputs"); } diff --git a/core/src/test/java/org/bitcoinj/core/FullBlockTestGenerator.java b/core/src/test/java/org/bitcoinj/core/FullBlockTestGenerator.java index 20b2251b6..a87975dc8 100644 --- a/core/src/test/java/org/bitcoinj/core/FullBlockTestGenerator.java +++ b/core/src/test/java/org/bitcoinj/core/FullBlockTestGenerator.java @@ -23,6 +23,7 @@ import org.bitcoinj.crypto.TransactionSignature; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptException; +import org.bitcoinj.script.ScriptPattern; import com.google.common.base.Preconditions; @@ -1818,7 +1819,7 @@ public class FullBlockTestGenerator { input.setScriptSig(new ScriptBuilder().op(OP_1).build()); } else { // Sign input - checkState(prevOut.scriptPubKey.isSentToRawPubKey()); + checkState(ScriptPattern.isPayToPubKey(prevOut.scriptPubKey)); Sha256Hash hash = t.hashForSignature(0, prevOut.scriptPubKey, SigHash.ALL, false); input.setScriptSig(ScriptBuilder.createInputScript( new TransactionSignature(coinbaseOutKey.sign(hash), SigHash.ALL, false)) diff --git a/core/src/test/java/org/bitcoinj/protocols/channels/ChannelConnectionTest.java b/core/src/test/java/org/bitcoinj/protocols/channels/ChannelConnectionTest.java index 4edeb3d2a..94a313354 100644 --- a/core/src/test/java/org/bitcoinj/protocols/channels/ChannelConnectionTest.java +++ b/core/src/test/java/org/bitcoinj/protocols/channels/ChannelConnectionTest.java @@ -18,6 +18,7 @@ package org.bitcoinj.protocols.channels; import org.bitcoinj.core.*; import org.bitcoinj.protocols.channels.PaymentChannelClient.VersionSelector; +import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.testing.TestWithWallet; import org.bitcoinj.utils.Threading; import org.bitcoinj.wallet.Wallet; @@ -540,9 +541,9 @@ public class ChannelConnectionTest extends TestWithWallet { newClientStates.deserializeWalletExtension(wallet, clientStoredChannels.serializeWalletExtension()); broadcastTxPause.release(); if (isMultiSigContract()) { - assertTrue(broadcasts.take().getOutput(0).getScriptPubKey().isSentToMultiSig()); + assertTrue(ScriptPattern.isSentToMultisig(broadcasts.take().getOutput(0).getScriptPubKey())); } else { - assertTrue(broadcasts.take().getOutput(0).getScriptPubKey().isPayToScriptHash()); + assertTrue(ScriptPattern.isPayToScriptHash(broadcasts.take().getOutput(0).getScriptPubKey())); } broadcastTxPause.release(); assertEquals(TransactionConfidence.Source.SELF, broadcasts.take().getConfidence().getSource()); diff --git a/core/src/test/java/org/bitcoinj/protocols/channels/PaymentChannelStateTest.java b/core/src/test/java/org/bitcoinj/protocols/channels/PaymentChannelStateTest.java index 92221a197..61eb07830 100644 --- a/core/src/test/java/org/bitcoinj/protocols/channels/PaymentChannelStateTest.java +++ b/core/src/test/java/org/bitcoinj/protocols/channels/PaymentChannelStateTest.java @@ -19,6 +19,7 @@ package org.bitcoinj.protocols.channels; import org.bitcoinj.core.*; import org.bitcoinj.script.Script; import org.bitcoinj.script.ScriptBuilder; +import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.testing.TestWithWallet; import org.bitcoinj.wallet.SendRequest; import org.bitcoinj.wallet.Wallet; @@ -255,12 +256,12 @@ public class PaymentChannelStateTest extends TestWithWallet { assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change. Script script = multisigContract.getOutput(0).getScriptPubKey(); if (versionSelector == PaymentChannelClient.VersionSelector.VERSION_1) { - assertTrue(script.isSentToMultiSig()); + assertTrue(ScriptPattern.isSentToMultisig(script)); } else { - assertTrue(script.isPayToScriptHash()); + assertTrue(ScriptPattern.isPayToScriptHash(script)); } script = multisigContract.getOutput(1).getScriptPubKey(); - assertTrue(script.isSentToAddress()); + assertTrue(ScriptPattern.isPayToPubKeyHash(script)); assertTrue(wallet.getPendingTransactions().contains(multisigContract)); // Provide the server with the multisig contract and simulate successful propagation/acceptance. @@ -380,12 +381,12 @@ public class PaymentChannelStateTest extends TestWithWallet { assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change. Script script = multisigContract.getOutput(0).getScriptPubKey(); if (versionSelector == PaymentChannelClient.VersionSelector.VERSION_1) { - assertTrue(script.isSentToMultiSig()); + assertTrue(ScriptPattern.isSentToMultisig(script)); } else { - assertTrue(script.isPayToScriptHash()); + assertTrue(ScriptPattern.isPayToScriptHash(script)); } script = multisigContract.getOutput(1).getScriptPubKey(); - assertTrue(script.isSentToAddress()); + assertTrue(ScriptPattern.isPayToPubKeyHash(script)); assertTrue(wallet.getPendingTransactions().contains(multisigContract)); // Provide the server with the multisig contract and simulate successful propagation/acceptance. @@ -856,12 +857,12 @@ public class PaymentChannelStateTest extends TestWithWallet { assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change. Script script = multisigContract.getOutput(0).getScriptPubKey(); if (versionSelector == PaymentChannelClient.VersionSelector.VERSION_1) { - assertTrue(script.isSentToMultiSig()); + assertTrue(ScriptPattern.isSentToMultisig(script)); } else { - assertTrue(script.isPayToScriptHash()); + assertTrue(ScriptPattern.isPayToScriptHash(script)); } script = multisigContract.getOutput(1).getScriptPubKey(); - assertTrue(script.isSentToAddress()); + assertTrue(ScriptPattern.isPayToPubKeyHash(script)); assertTrue(wallet.getPendingTransactions().contains(multisigContract)); // Provide the server with the multisig contract and simulate successful propagation/acceptance. @@ -950,12 +951,12 @@ public class PaymentChannelStateTest extends TestWithWallet { assertEquals(2, multisigContract.getOutputs().size()); // One multi-sig, one change. Script script = multisigContract.getOutput(0).getScriptPubKey(); if (versionSelector == PaymentChannelClient.VersionSelector.VERSION_1) { - assertTrue(script.isSentToMultiSig()); + assertTrue(ScriptPattern.isSentToMultisig(script)); } else { - assertTrue(script.isPayToScriptHash()); + assertTrue(ScriptPattern.isPayToScriptHash(script)); } script = multisigContract.getOutput(1).getScriptPubKey(); - assertTrue(script.isSentToAddress()); + assertTrue(ScriptPattern.isPayToPubKeyHash(script)); assertTrue(wallet.getPendingTransactions().contains(multisigContract)); // Provide the server with the multisig contract and simulate successful propagation/acceptance. diff --git a/core/src/test/java/org/bitcoinj/script/ScriptTest.java b/core/src/test/java/org/bitcoinj/script/ScriptTest.java index 984c1bbf3..f2f55a62a 100644 --- a/core/src/test/java/org/bitcoinj/script/ScriptTest.java +++ b/core/src/test/java/org/bitcoinj/script/ScriptTest.java @@ -87,13 +87,13 @@ public class ScriptTest { @Test public void testMultiSig() throws Exception { List keys = Lists.newArrayList(new ECKey(), new ECKey(), new ECKey()); - assertTrue(ScriptBuilder.createMultiSigOutputScript(2, keys).isSentToMultiSig()); + assertTrue(ScriptPattern.isSentToMultisig(ScriptBuilder.createMultiSigOutputScript(2, keys))); Script script = ScriptBuilder.createMultiSigOutputScript(3, keys); - assertTrue(script.isSentToMultiSig()); + assertTrue(ScriptPattern.isSentToMultisig(script)); List pubkeys = new ArrayList<>(3); for (ECKey key : keys) pubkeys.add(ECKey.fromPublicOnly(key.getPubKeyPoint())); assertEquals(script.getPubKeys(), pubkeys); - assertFalse(ScriptBuilder.createOutputScript(new ECKey()).isSentToMultiSig()); + assertFalse(ScriptPattern.isSentToMultisig(ScriptBuilder.createOutputScript(new ECKey()))); try { // Fail if we ask for more signatures than keys. Script.createMultiSigOutputScript(4, keys); @@ -113,14 +113,14 @@ public class ScriptTest { @Test public void testP2SHOutputScript() throws Exception { Address p2shAddress = Address.fromBase58(MainNetParams.get(), "35b9vsyH1KoFT5a5KtrKusaCcPLkiSo1tU"); - assertTrue(ScriptBuilder.createOutputScript(p2shAddress).isPayToScriptHash()); + assertTrue(ScriptPattern.isPayToScriptHash(ScriptBuilder.createOutputScript(p2shAddress))); } @Test public void testIp() throws Exception { byte[] bytes = HEX.decode("41043e96222332ea7848323c08116dddafbfa917b8e37f0bdf63841628267148588a09a43540942d58d49717ad3fabfe14978cf4f0a8b84d2435dad16e9aa4d7f935ac"); Script s = new Script(bytes); - assertTrue(s.isSentToRawPubKey()); + assertTrue(ScriptPattern.isPayToPubKey(s)); } @Test @@ -432,7 +432,7 @@ public class ScriptTest { @Test public void testCLTVPaymentChannelOutput() { Script script = ScriptBuilder.createCLTVPaymentChannelOutput(BigInteger.valueOf(20), new ECKey(), new ECKey()); - assertTrue("script is locktime-verify", script.isSentToCLTVPaymentChannel()); + assertTrue("script is locktime-verify", ScriptPattern.isSentToCltvPaymentChannel(script)); } @Test diff --git a/tools/src/main/java/org/bitcoinj/tools/WalletTool.java b/tools/src/main/java/org/bitcoinj/tools/WalletTool.java index ea355e12f..255272ea0 100644 --- a/tools/src/main/java/org/bitcoinj/tools/WalletTool.java +++ b/tools/src/main/java/org/bitcoinj/tools/WalletTool.java @@ -27,6 +27,7 @@ import org.bitcoinj.protocols.payments.PaymentProtocolException; import org.bitcoinj.protocols.payments.PaymentSession; import org.bitcoinj.script.ScriptBuilder; import org.bitcoinj.script.ScriptException; +import org.bitcoinj.script.ScriptPattern; import org.bitcoinj.store.*; import org.bitcoinj.uri.BitcoinURI; import org.bitcoinj.uri.BitcoinURIParseException; @@ -831,7 +832,7 @@ public class WalletTool { } TransactionOutput lockTimeVerifyOutput = null; for (TransactionOutput out : lockTimeVerify.getOutputs()) { - if (out.getScriptPubKey().isSentToCLTVPaymentChannel()) { + if (ScriptPattern.isSentToCltvPaymentChannel(out.getScriptPubKey())) { lockTimeVerifyOutput = out; } } @@ -935,7 +936,7 @@ public class WalletTool { } TransactionOutput lockTimeVerifyOutput = null; for (TransactionOutput out : lockTimeVerify.getOutputs()) { - if (out.getScriptPubKey().isSentToCLTVPaymentChannel()) { + if (ScriptPattern.isSentToCltvPaymentChannel(out.getScriptPubKey())) { lockTimeVerifyOutput = out; } }