org.bitcoinj.script.ScriptBuilder Maven / Gradle / Ivy
/*
* Copyright 2013 Google Inc.
* Copyright 2018 Nicola Atzei
* Copyright 2019 Andreas Schildbach
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.bitcoinj.script;
import com.google.common.collect.Lists;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.Transaction;
import org.bitcoinj.core.Utils;
import org.bitcoinj.crypto.TransactionSignature;
import org.bitcoinj.script.Script.ScriptType;
import javax.annotation.Nullable;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Stack;
import static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static org.bitcoinj.script.ScriptOpCodes.*;
/**
* Tools for the construction of commonly used script types. You don't normally need this as it's hidden behind
* convenience methods on {@link Transaction}, but they are useful when working with the
* protocol at a lower level.
*/
public class ScriptBuilder {
private List chunks;
/** Creates a fresh ScriptBuilder with an empty program. */
public ScriptBuilder() {
chunks = Lists.newLinkedList();
}
/** Creates a fresh ScriptBuilder with the given program as the starting point. */
public ScriptBuilder(Script template) {
chunks = new ArrayList<>(template.getChunks());
}
/** Adds the given chunk to the end of the program */
public ScriptBuilder addChunk(ScriptChunk chunk) {
return addChunk(chunks.size(), chunk);
}
/** Adds the given chunk at the given index in the program */
public ScriptBuilder addChunk(int index, ScriptChunk chunk) {
chunks.add(index, chunk);
return this;
}
/** Adds the given opcode to the end of the program. */
public ScriptBuilder op(int opcode) {
return op(chunks.size(), opcode);
}
/** Adds the given opcode to the given index in the program */
public ScriptBuilder op(int index, int opcode) {
checkArgument(opcode > OP_PUSHDATA4);
return addChunk(index, new ScriptChunk(opcode, null));
}
/** Adds a copy of the given byte array as a data element (i.e. PUSHDATA) at the end of the program. */
public ScriptBuilder data(byte[] data) {
if (data.length == 0)
return smallNum(0);
else
return data(chunks.size(), data);
}
/** Adds a copy of the given byte array as a data element (i.e. PUSHDATA) at the given index in the program. */
public ScriptBuilder data(int index, byte[] data) {
// implements BIP62
byte[] copy = Arrays.copyOf(data, data.length);
int opcode;
if (data.length == 0) {
opcode = OP_0;
} else if (data.length == 1) {
byte b = data[0];
if (b >= 1 && b <= 16)
opcode = Script.encodeToOpN(b);
else
opcode = 1;
} else if (data.length < OP_PUSHDATA1) {
opcode = data.length;
} else if (data.length < 256) {
opcode = OP_PUSHDATA1;
} else if (data.length < 65536) {
opcode = OP_PUSHDATA2;
} else {
throw new RuntimeException("Unimplemented");
}
return addChunk(index, new ScriptChunk(opcode, copy));
}
/**
* Adds the given number to the end of the program. Automatically uses
* shortest encoding possible.
*/
public ScriptBuilder number(long num) {
return number(chunks.size(), num);
}
/**
* Adds the given number to the given index in the program. Automatically
* uses shortest encoding possible.
*/
public ScriptBuilder number(int index, long num) {
if (num == -1) {
return op(index, OP_1NEGATE);
} else if (num >= 0 && num <= 16) {
return smallNum(index, (int) num);
} else {
return bigNum(index, num);
}
}
/**
* Adds the given number as a OP_N opcode to the end of the program.
* Only handles values 0-16 inclusive.
*
* @see #number(long)
*/
public ScriptBuilder smallNum(int num) {
return smallNum(chunks.size(), num);
}
/** Adds the given number as a push data chunk.
* This is intended to use for negative numbers or values greater than 16, and although
* it will accept numbers in the range 0-16 inclusive, the encoding would be
* considered non-standard.
*
* @see #number(long)
*/
protected ScriptBuilder bigNum(long num) {
return bigNum(chunks.size(), num);
}
/**
* Adds the given number as a OP_N opcode to the given index in the program.
* Only handles values 0-16 inclusive.
*
* @see #number(long)
*/
public ScriptBuilder smallNum(int index, int num) {
checkArgument(num >= 0, "Cannot encode negative numbers with smallNum");
checkArgument(num <= 16, "Cannot encode numbers larger than 16 with smallNum");
return addChunk(index, new ScriptChunk(Script.encodeToOpN(num), null));
}
/**
* Adds the given number as a push data chunk to the given index in the program.
* This is intended to use for negative numbers or values greater than 16, and although
* it will accept numbers in the range 0-16 inclusive, the encoding would be
* considered non-standard.
*
* @see #number(long)
*/
protected ScriptBuilder bigNum(int index, long num) {
final byte[] data;
if (num == 0) {
data = new byte[0];
} else {
Stack result = new Stack<>();
final boolean neg = num < 0;
long absvalue = Math.abs(num);
while (absvalue != 0) {
result.push((byte) (absvalue & 0xff));
absvalue >>= 8;
}
if ((result.peek() & 0x80) != 0) {
// The most significant byte is >= 0x80, so push an extra byte that
// contains just the sign of the value.
result.push((byte) (neg ? 0x80 : 0));
} else if (neg) {
// The most significant byte is < 0x80 and the value is negative,
// set the sign bit so it is subtracted and interpreted as a
// negative when converting back to an integral.
result.push((byte) (result.pop() | 0x80));
}
data = new byte[result.size()];
for (int byteIdx = 0; byteIdx < data.length; byteIdx++) {
data[byteIdx] = result.get(byteIdx);
}
}
// At most the encoded value could take up to 8 bytes, so we don't need
// to use OP_PUSHDATA opcodes
return addChunk(index, new ScriptChunk(data.length, data));
}
/**
* Adds true to the end of the program.
* @return this
*/
public ScriptBuilder opTrue() {
return number(1); // it push OP_1/OP_TRUE
}
/**
* Adds true to the given index in the program.
* @param index at which insert true
* @return this
*/
public ScriptBuilder opTrue(int index) {
return number(index, 1); // push OP_1/OP_TRUE
}
/**
* Adds false to the end of the program.
* @return this
*/
public ScriptBuilder opFalse() {
return number(0); // push OP_0/OP_FALSE
}
/**
* Adds false to the given index in the program.
* @param index at which insert true
* @return this
*/
public ScriptBuilder opFalse(int index) {
return number(index, 0); // push OP_0/OP_FALSE
}
/** Creates a new immutable Script based on the state of the builder. */
public Script build() {
return new Script(chunks);
}
/** Creates an empty script. */
public static Script createEmpty() {
return new ScriptBuilder().build();
}
/** Creates a scriptPubKey that encodes payment to the given address. */
public static Script createOutputScript(Address to) {
if (to instanceof Address) {
ScriptType scriptType = to.getOutputScriptType();
if (scriptType == ScriptType.P2PKH)
return createP2PKHOutputScript(to.getHash());
else if (scriptType == ScriptType.P2SH)
return createP2SHOutputScript(to.getHash());
else
throw new IllegalStateException("Cannot handle " + scriptType);
} else {
throw new IllegalStateException("Cannot handle " + to);
}
}
/**
* Creates a scriptSig that can redeem a P2PKH output.
* If given signature is null, incomplete scriptSig will be created with OP_0 instead of signature
*/
public static Script createInputScript(@Nullable TransactionSignature signature, ECKey pubKey) {
byte[] pubkeyBytes = pubKey.getPubKey();
byte[] sigBytes = signature != null ? signature.encodeToBitcoin() : new byte[]{};
return new ScriptBuilder().data(sigBytes).data(pubkeyBytes).build();
}
/**
* Creates a scriptSig that can redeem a P2PK output.
* If given signature is null, incomplete scriptSig will be created with OP_0 instead of signature
*/
public static Script createInputScript(@Nullable TransactionSignature signature) {
byte[] sigBytes = signature != null ? signature.encodeToBitcoin() : new byte[]{};
return new ScriptBuilder().data(sigBytes).build();
}
/** Creates a program that requires at least N of the given keys to sign, using OP_CHECKMULTISIG. */
public static Script createMultiSigOutputScript(int threshold, List pubkeys) {
checkArgument(threshold > 0);
checkArgument(threshold <= pubkeys.size());
checkArgument(pubkeys.size() <= 16); // That's the max we can represent with a single opcode.
ScriptBuilder builder = new ScriptBuilder();
builder.smallNum(threshold);
for (ECKey key : pubkeys) {
builder.data(key.getPubKey());
}
builder.smallNum(pubkeys.size());
builder.op(OP_CHECKMULTISIG);
return builder.build();
}
/** Create a program that satisfies an OP_CHECKMULTISIG program. */
public static Script createMultiSigInputScript(List signatures) {
List sigs = new ArrayList<>(signatures.size());
for (TransactionSignature signature : signatures) {
sigs.add(signature.encodeToBitcoin());
}
return createMultiSigInputScriptBytes(sigs, null);
}
/** Create a program that satisfies an OP_CHECKMULTISIG program. */
public static Script createMultiSigInputScript(TransactionSignature... signatures) {
return createMultiSigInputScript(Arrays.asList(signatures));
}
/** Create a program that satisfies an OP_CHECKMULTISIG program, using pre-encoded signatures. */
public static Script createMultiSigInputScriptBytes(List signatures) {
return createMultiSigInputScriptBytes(signatures, null);
}
/**
* Create a program that satisfies a P2SH OP_CHECKMULTISIG program.
* If given signature list is null, incomplete scriptSig will be created with OP_0 instead of signatures
*/
public static Script createP2SHMultiSigInputScript(@Nullable List signatures,
Script multisigProgram) {
List sigs = new ArrayList<>();
if (signatures == null) {
// create correct number of empty signatures
int numSigs = multisigProgram.getNumberOfSignaturesRequiredToSpend();
for (int i = 0; i < numSigs; i++)
sigs.add(new byte[]{});
} else {
for (TransactionSignature signature : signatures) {
sigs.add(signature.encodeToBitcoin());
}
}
return createMultiSigInputScriptBytes(sigs, multisigProgram.getProgram());
}
/**
* Create a program that satisfies an OP_CHECKMULTISIG program, using pre-encoded signatures.
* Optionally, appends the script program bytes if spending a P2SH output.
*/
public static Script createMultiSigInputScriptBytes(List signatures, @Nullable byte[] multisigProgramBytes) {
checkArgument(signatures.size() <= 16);
ScriptBuilder builder = new ScriptBuilder();
builder.smallNum(0); // Work around a bug in CHECKMULTISIG that is now a required part of the protocol.
for (byte[] signature : signatures)
builder.data(signature);
if (multisigProgramBytes!= null)
builder.data(multisigProgramBytes);
return builder.build();
}
/**
* Returns a copy of the given scriptSig with the signature inserted in the given position.
*
* This function assumes that any missing sigs have OP_0 placeholders. If given scriptSig already has all the signatures
* in place, IllegalArgumentException will be thrown.
*
* @param targetIndex where to insert the signature
* @param sigsPrefixCount how many items to copy verbatim (e.g. initial OP_0 for multisig)
* @param sigsSuffixCount how many items to copy verbatim at end (e.g. redeemScript for P2SH)
*/
public static Script updateScriptWithSignature(Script scriptSig, byte[] signature, int targetIndex,
int sigsPrefixCount, int sigsSuffixCount) {
ScriptBuilder builder = new ScriptBuilder();
List inputChunks = scriptSig.getChunks();
int totalChunks = inputChunks.size();
// Check if we have a place to insert, otherwise just return given scriptSig unchanged.
// We assume here that OP_0 placeholders always go after the sigs, so
// to find if we have sigs missing, we can just check the chunk in latest sig position
boolean hasMissingSigs = inputChunks.get(totalChunks - sigsSuffixCount - 1).equalsOpCode(OP_0);
checkArgument(hasMissingSigs, "ScriptSig is already filled with signatures");
// copy the prefix
for (ScriptChunk chunk: inputChunks.subList(0, sigsPrefixCount))
builder.addChunk(chunk);
// copy the sigs
int pos = 0;
boolean inserted = false;
for (ScriptChunk chunk: inputChunks.subList(sigsPrefixCount, totalChunks - sigsSuffixCount)) {
if (pos == targetIndex) {
inserted = true;
builder.data(signature);
pos++;
}
if (!chunk.equalsOpCode(OP_0)) {
builder.addChunk(chunk);
pos++;
}
}
// add OP_0's if needed, since we skipped them in the previous loop
while (pos < totalChunks - sigsPrefixCount - sigsSuffixCount) {
if (pos == targetIndex) {
inserted = true;
builder.data(signature);
}
else {
builder.addChunk(new ScriptChunk(OP_0, null));
}
pos++;
}
// copy the suffix
for (ScriptChunk chunk: inputChunks.subList(totalChunks - sigsSuffixCount, totalChunks))
builder.addChunk(chunk);
checkState(inserted);
return builder.build();
}
/** Creates a scriptPubKey that encodes payment to the given raw public key. */
public static Script createP2PKOutputScript(byte[] pubKey) {
return new ScriptBuilder().data(pubKey).op(OP_CHECKSIG).build();
}
/** Creates a scriptPubKey that encodes payment to the given raw public key. */
public static Script createP2PKOutputScript(ECKey pubKey) {
return createP2PKOutputScript(pubKey.getPubKey());
}
/**
* Creates a scriptPubKey that sends to the given public key hash.
*/
public static Script createP2PKHOutputScript(byte[] hash) {
checkArgument(hash.length == Address.LENGTH);
ScriptBuilder builder = new ScriptBuilder();
builder.op(OP_DUP);
builder.op(OP_HASH160);
builder.data(hash);
builder.op(OP_EQUALVERIFY);
builder.op(OP_CHECKSIG);
return builder.build();
}
/**
* Creates a scriptPubKey that sends to the given public key.
*/
public static Script createP2PKHOutputScript(ECKey key) {
checkArgument(key.isCompressed());
return createP2PKHOutputScript(key.getPubKeyHash());
}
/**
* Creates a scriptPubKey that sends to the given script hash. Read
* BIP 16 to learn more about this
* kind of script.
*
* @param hash The hash of the redeem script
* @return an output script that sends to the redeem script
*/
public static Script createP2SHOutputScript(byte[] hash) {
checkArgument(hash.length == 20);
return new ScriptBuilder().op(OP_HASH160).data(hash).op(OP_EQUAL).build();
}
/**
* Creates a scriptPubKey for a given redeem script.
*
* @param redeemScript The redeem script
* @return an output script that sends to the redeem script
*/
public static Script createP2SHOutputScript(Script redeemScript) {
byte[] hash = Utils.sha256hash160(redeemScript.getProgram());
return ScriptBuilder.createP2SHOutputScript(hash);
}
/**
* Creates a P2SH output script for n-of-m multisig with given public keys and threshold. Given public keys will
* be placed in redeem script in the lexicographical sorting order.
*
* @param threshold The threshold number of keys that must sign (n)
* @param pubkeys A list of m public keys
* @return The P2SH multisig output script
*/
public static Script createP2SHOutputScript(int threshold, List pubkeys) {
Script redeemScript = createRedeemScript(threshold, pubkeys);
return createP2SHOutputScript(redeemScript);
}
/**
* Creates an n-of-m multisig redeem script with given public keys and threshold. Given public keys will be placed in
* redeem script in the lexicographical sorting order.
*
* @param threshold The threshold number of keys that must sign (n)
* @param pubkeys A list of m public keys
* @return The P2SH multisig redeem script
*/
public static Script createRedeemScript(int threshold, List pubkeys) {
pubkeys = new ArrayList<>(pubkeys);
Collections.sort(pubkeys, ECKey.PUBKEY_COMPARATOR);
return ScriptBuilder.createMultiSigOutputScript(threshold, pubkeys);
}
/**
* Creates a script of the form OP_RETURN [data]. This feature allows you to attach a small piece of data (like
* a hash of something stored elsewhere) to a zero valued output which can never be spent and thus does not pollute
* the ledger.
*/
public static Script createOpReturnScript(byte[] data) {
checkArgument(data.length <= 80);
return new ScriptBuilder().op(OP_RETURN).data(data).build();
}
public static Script createCLTVPaymentChannelOutput(BigInteger time, ECKey from, ECKey to) {
byte[] timeBytes = Utils.reverseBytes(Utils.encodeMPI(time, false));
if (timeBytes.length > 5) {
throw new RuntimeException("Time too large to encode as 5-byte int");
}
return new ScriptBuilder().op(OP_IF)
.data(to.getPubKey()).op(OP_CHECKSIGVERIFY)
.op(OP_ELSE)
.data(timeBytes).op(OP_CHECKLOCKTIMEVERIFY).op(OP_DROP)
.op(OP_ENDIF)
.data(from.getPubKey()).op(OP_CHECKSIG).build();
}
public static Script createCLTVPaymentChannelRefund(TransactionSignature signature) {
ScriptBuilder builder = new ScriptBuilder();
builder.data(signature.encodeToBitcoin());
builder.data(new byte[] { 0 }); // Use the CHECKLOCKTIMEVERIFY if branch
return builder.build();
}
public static Script createCLTVPaymentChannelP2SHRefund(TransactionSignature signature, Script redeemScript) {
ScriptBuilder builder = new ScriptBuilder();
builder.data(signature.encodeToBitcoin());
builder.data(new byte[] { 0 }); // Use the CHECKLOCKTIMEVERIFY if branch
builder.data(redeemScript.getProgram());
return builder.build();
}
public static Script createCLTVPaymentChannelP2SHInput(byte[] from, byte[] to, Script redeemScript) {
ScriptBuilder builder = new ScriptBuilder();
builder.data(from);
builder.data(to);
builder.smallNum(1); // Use the CHECKLOCKTIMEVERIFY if branch
builder.data(redeemScript.getProgram());
return builder.build();
}
public static Script createCLTVPaymentChannelInput(TransactionSignature from, TransactionSignature to) {
return createCLTVPaymentChannelInput(from.encodeToBitcoin(), to.encodeToBitcoin());
}
public static Script createCLTVPaymentChannelInput(byte[] from, byte[] to) {
ScriptBuilder builder = new ScriptBuilder();
builder.data(from);
builder.data(to);
builder.smallNum(1); // Use the CHECKLOCKTIMEVERIFY if branch
return builder.build();
}
public static Script createCreditBurnOutput(ECKey creditBurnKey) {
ScriptBuilder builder = new ScriptBuilder();
builder.addChunk(new ScriptChunk(OP_RETURN, null));
builder.data(creditBurnKey.getPubKeyHash());
return builder.build();
}
}