All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.bitcoinj.script.ScriptBuilder Maven / Gradle / Ivy

There is a newer version: 21.1.2
Show newest version
/*
 * 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. */ 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 the given redeem script. */ public static Script createP2SHOutputScript(Script redeemScript) { byte[] hash = Utils.sha256hash160(redeemScript.getProgram()); return ScriptBuilder.createP2SHOutputScript(hash); } /** * Creates a P2SH output script with given public keys and threshold. Given public keys will be placed in * redeem script in the lexicographical sorting order. */ public static Script createP2SHOutputScript(int threshold, List pubkeys) { Script redeemScript = createRedeemScript(threshold, pubkeys); return createP2SHOutputScript(redeemScript); } /** * Creates redeem script with given public keys and threshold. Given public keys will be placed in * redeem script in the lexicographical sorting order. */ 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(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy