org.bitcoinj.script.Script Maven / Gradle / Ivy
Show all versions of didsdk Show documentation
/*
* Copyright 2011 Google Inc.
* Copyright 2012 Matt Corallo.
* Copyright 2014 Andreas Schildbach
* Copyright 2017 Nicola Atzei
*
* 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 static com.google.common.base.Preconditions.checkArgument;
import static com.google.common.base.Preconditions.checkState;
import static org.bitcoinj.script.ScriptOpCodes.OP_0;
import static org.bitcoinj.script.ScriptOpCodes.OP_1;
import static org.bitcoinj.script.ScriptOpCodes.OP_16;
import static org.bitcoinj.script.ScriptOpCodes.OP_1NEGATE;
import static org.bitcoinj.script.ScriptOpCodes.OP_CHECKMULTISIG;
import static org.bitcoinj.script.ScriptOpCodes.OP_CHECKMULTISIGVERIFY;
import static org.bitcoinj.script.ScriptOpCodes.OP_CHECKSIG;
import static org.bitcoinj.script.ScriptOpCodes.OP_CHECKSIGVERIFY;
import static org.bitcoinj.script.ScriptOpCodes.OP_INVALIDOPCODE;
import static org.bitcoinj.script.ScriptOpCodes.OP_PUSHDATA1;
import static org.bitcoinj.script.ScriptOpCodes.OP_PUSHDATA2;
import static org.bitcoinj.script.ScriptOpCodes.OP_PUSHDATA4;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.List;
import javax.annotation.Nullable;
import org.bitcoinj.core.Address;
import org.bitcoinj.core.ECKey;
import org.bitcoinj.core.LegacyAddress;
import org.bitcoinj.core.NetworkParameters;
import org.bitcoinj.core.SegwitAddress;
import org.bitcoinj.core.UnsafeByteArrayOutputStream;
import org.bitcoinj.core.Utils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
// TODO: Redesign this entire API to be more type safe and organised.
/**
* Programs embedded inside transactions that control redemption of payments.
*
* Bitcoin transactions don't specify what they do directly. Instead a
* small binary stack language is used to define programs that when evaluated return whether the transaction
* "accepts" or rejects the other transactions connected to it.
*
* In SPV mode, scripts are not run, because that would require all transactions to be available and lightweight
* clients don't have that data. In full mode, this class is used to run the interpreted language. It also has
* static methods for building scripts.
*/
public class Script {
/** Enumeration to encapsulate the type of this script. */
public enum ScriptType {
P2PKH(1), // pay to pubkey hash (aka pay to address)
P2PK(2), // pay to pubkey
P2SH(3), // pay to script hash
P2WPKH(4), // pay to witness pubkey hash
P2WSH(5); // pay to witness script hash
public final int id;
private ScriptType(int id) {
this.id = id;
}
}
/**
* Note currently only P2SH, DERSIG and NULLDUMMY are actually supported.
*/
public enum VerifyFlag {
P2SH, // Enable BIP16-style subscript evaluation.
STRICTENC, // Passing a non-strict-DER signature or one with undefined hashtype to a checksig operation causes script failure.
DERSIG, // Passing a non-strict-DER signature to a checksig operation causes script failure (softfork safe, BIP66 rule 1)
LOW_S, // Passing a non-strict-DER signature or one with S > order/2 to a checksig operation causes script failure
NULLDUMMY, // Verify dummy stack item consumed by CHECKMULTISIG is of zero-length.
SIGPUSHONLY, // Using a non-push operator in the scriptSig causes script failure (softfork safe, BIP62 rule 2).
MINIMALDATA, // Require minimal encodings for all push operations
DISCOURAGE_UPGRADABLE_NOPS, // Discourage use of NOPs reserved for upgrades (NOP1-10)
CLEANSTACK, // Require that only a single stack element remains after evaluation.
CHECKLOCKTIMEVERIFY, // Enable CHECKLOCKTIMEVERIFY operation
CHECKSEQUENCEVERIFY // Enable CHECKSEQUENCEVERIFY operation
}
public static final EnumSet ALL_VERIFY_FLAGS = EnumSet.allOf(VerifyFlag.class);
private static final Logger log = LoggerFactory.getLogger(Script.class);
public static final long MAX_SCRIPT_ELEMENT_SIZE = 520; // bytes
private static final int MAX_OPS_PER_SCRIPT = 201;
private static final int MAX_STACK_SIZE = 1000;
private static final int MAX_PUBKEYS_PER_MULTISIG = 20;
private static final int MAX_SCRIPT_SIZE = 10000;
public static final int SIG_SIZE = 75;
/** Max number of sigops allowed in a standard p2sh redeem script */
public static final int MAX_P2SH_SIGOPS = 15;
// The program is a set of chunks where each element is either [opcode] or [data, data, data ...]
protected List chunks;
// Unfortunately, scripts are not ever re-serialized or canonicalized when used in signature hashing. Thus we
// must preserve the exact bytes that we read off the wire, along with the parsed form.
protected byte[] program;
// Creation time of the associated keys in seconds since the epoch.
private long creationTimeSeconds;
/** Creates an empty script that serializes to nothing. */
private Script() {
chunks = new ArrayList<>();
}
// Used from ScriptBuilder.
Script(List chunks) {
this.chunks = Collections.unmodifiableList(new ArrayList<>(chunks));
creationTimeSeconds = Utils.currentTimeSeconds();
}
/**
* Construct a Script that copies and wraps the programBytes array. The array is parsed and checked for syntactic
* validity.
* @param programBytes Array of program bytes from a transaction.
*/
public Script(byte[] programBytes) throws ScriptException {
program = programBytes;
parse(programBytes);
creationTimeSeconds = 0;
}
public Script(byte[] programBytes, long creationTimeSeconds) throws ScriptException {
program = programBytes;
parse(programBytes);
this.creationTimeSeconds = creationTimeSeconds;
}
public long getCreationTimeSeconds() {
return creationTimeSeconds;
}
public void setCreationTimeSeconds(long creationTimeSeconds) {
this.creationTimeSeconds = creationTimeSeconds;
}
/**
* Returns the program opcodes as a string, for example "[1234] DUP HASH160", or "<empty>".
*/
@Override
public String toString() {
if (!chunks.isEmpty())
return Utils.SPACE_JOINER.join(chunks);
else
return "";
}
/** Returns the serialized program as a newly created byte array. */
public byte[] getProgram() {
try {
// Don't round-trip as Bitcoin Core doesn't and it would introduce a mismatch.
if (program != null)
return Arrays.copyOf(program, program.length);
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (ScriptChunk chunk : chunks) {
chunk.write(bos);
}
program = bos.toByteArray();
return program;
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
/** Returns an immutable list of the scripts parsed form. Each chunk is either an opcode or data element. */
public List getChunks() {
return Collections.unmodifiableList(chunks);
}
private static final ScriptChunk[] STANDARD_TRANSACTION_SCRIPT_CHUNKS = {
new ScriptChunk(ScriptOpCodes.OP_DUP, null),
new ScriptChunk(ScriptOpCodes.OP_HASH160, null),
new ScriptChunk(ScriptOpCodes.OP_EQUALVERIFY, null),
new ScriptChunk(ScriptOpCodes.OP_CHECKSIG, null),
};
/**
* To run a script, first we parse it which breaks it up into chunks representing pushes of data or logical
* opcodes. Then we can run the parsed chunks.
*
* The reason for this split, instead of just interpreting directly, is to make it easier
* to reach into a programs structure and pull out bits of data without having to run it.
* This is necessary to render the to addresses of transactions in a user interface.
* Bitcoin Core does something similar.
*/
private void parse(byte[] program) throws ScriptException {
chunks = new ArrayList<>(5); // Common size.
ByteArrayInputStream bis = new ByteArrayInputStream(program);
while (bis.available() > 0) {
int opcode = bis.read();
long dataToRead = -1;
if (opcode >= 0 && opcode < OP_PUSHDATA1) {
// Read some bytes of data, where how many is the opcode value itself.
dataToRead = opcode;
} else if (opcode == OP_PUSHDATA1) {
if (bis.available() < 1) throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Unexpected end of script");
dataToRead = bis.read();
} else if (opcode == OP_PUSHDATA2) {
// Read a short, then read that many bytes of data.
if (bis.available() < 2) throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Unexpected end of script");
dataToRead = Utils.readUint16FromStream(bis);
} else if (opcode == OP_PUSHDATA4) {
// Read a uint32, then read that many bytes of data.
// Though this is allowed, because its value cannot be > 520, it should never actually be used
if (bis.available() < 4) throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Unexpected end of script");
dataToRead = Utils.readUint32FromStream(bis);
}
ScriptChunk chunk;
if (dataToRead == -1) {
chunk = new ScriptChunk(opcode, null);
} else {
if (dataToRead > bis.available())
throw new ScriptException(ScriptError.SCRIPT_ERR_BAD_OPCODE, "Push of data element that is larger than remaining data");
byte[] data = new byte[(int)dataToRead];
checkState(dataToRead == 0 || bis.read(data, 0, (int)dataToRead) == dataToRead);
chunk = new ScriptChunk(opcode, data);
}
// Save some memory by eliminating redundant copies of the same chunk objects.
for (ScriptChunk c : STANDARD_TRANSACTION_SCRIPT_CHUNKS) {
if (c.equals(chunk)) chunk = c;
}
chunks.add(chunk);
}
}
/** @deprecated use {@link ScriptPattern#isP2PK(Script)} */
@Deprecated
public boolean isSentToRawPubKey() {
return ScriptPattern.isP2PK(this);
}
/** @deprecated use {@link ScriptPattern#isP2PKH(Script)} */
@Deprecated
public boolean isSentToAddress() {
return ScriptPattern.isP2PKH(this);
}
/**
* If the program somehow pays to a hash, returns the hash.
*
* Otherwise this method throws a ScriptException.
*/
public byte[] getPubKeyHash() throws ScriptException {
if (ScriptPattern.isP2PKH(this))
return ScriptPattern.extractHashFromP2PKH(this);
else if (ScriptPattern.isP2SH(this))
return ScriptPattern.extractHashFromP2SH(this);
else if (ScriptPattern.isP2WH(this))
return ScriptPattern.extractHashFromP2WH(this);
else
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script not in the standard scriptPubKey form");
}
/**
* Gets the destination address from this script, if it's in the required form.
*/
public Address getToAddress(NetworkParameters params) throws ScriptException {
return getToAddress(params, false);
}
/**
* Gets the destination address from this script, if it's in the required form.
*
* @param forcePayToPubKey
* If true, allow payToPubKey to be casted to the corresponding address. This is useful if you prefer
* showing addresses rather than pubkeys.
*/
public Address getToAddress(NetworkParameters params, boolean forcePayToPubKey) throws ScriptException {
if (ScriptPattern.isP2PKH(this))
return LegacyAddress.fromPubKeyHash(params, ScriptPattern.extractHashFromP2PKH(this));
else if (ScriptPattern.isP2SH(this))
return LegacyAddress.fromScriptHash(params, ScriptPattern.extractHashFromP2SH(this));
else if (forcePayToPubKey && ScriptPattern.isP2PK(this))
return LegacyAddress.fromKey(params, ECKey.fromPublicOnly(ScriptPattern.extractKeyFromP2PK(this)));
else if (ScriptPattern.isP2WH(this))
return SegwitAddress.fromHash(params, ScriptPattern.extractHashFromP2WH(this));
else
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Cannot cast this script to an address");
}
////////////////////// Interface for writing scripts from scratch ////////////////////////////////
/**
* Writes out the given byte buffer to the output stream with the correct opcode prefix
* To write an integer call writeBytes(out, Utils.reverseBytes(Utils.encodeMPI(val, false)));
*/
public static void writeBytes(OutputStream os, byte[] buf) throws IOException {
if (buf.length < OP_PUSHDATA1) {
os.write(buf.length);
os.write(buf);
} else if (buf.length < 256) {
os.write(OP_PUSHDATA1);
os.write(buf.length);
os.write(buf);
} else if (buf.length < 65536) {
os.write(OP_PUSHDATA2);
Utils.uint16ToByteStreamLE(buf.length, os);
os.write(buf);
} else {
throw new RuntimeException("Unimplemented");
}
}
/** Creates a program that requires at least N of the given keys to sign, using OP_CHECKMULTISIG. */
public static byte[] 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.
if (pubkeys.size() > 3) {
log.warn("Creating a multi-signature output that is non-standard: {} pubkeys, should be <= 3", pubkeys.size());
}
try {
ByteArrayOutputStream bits = new ByteArrayOutputStream();
bits.write(encodeToOpN(threshold));
for (ECKey key : pubkeys) {
writeBytes(bits, key.getPubKey());
}
bits.write(encodeToOpN(pubkeys.size()));
bits.write(OP_CHECKMULTISIG);
return bits.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e); // Cannot happen.
}
}
public static byte[] createInputScript(byte[] signature, byte[] pubkey) {
try {
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
ByteArrayOutputStream bits = new UnsafeByteArrayOutputStream(signature.length + pubkey.length + 2);
writeBytes(bits, signature);
writeBytes(bits, pubkey);
return bits.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static byte[] createInputScript(byte[] signature) {
try {
// TODO: Do this by creating a Script *first* then having the script reassemble itself into bytes.
ByteArrayOutputStream bits = new UnsafeByteArrayOutputStream(signature.length + 2);
writeBytes(bits, signature);
return bits.toByteArray();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
/**
* Returns a copy of the given scriptSig with the signature inserted in the given position.
*/
public Script getScriptSigWithSignature(Script scriptSig, byte[] sigBytes, int index) {
int sigsPrefixCount = 0;
int sigsSuffixCount = 0;
if (ScriptPattern.isP2SH(this)) {
sigsPrefixCount = 1; // OP_0 *
sigsSuffixCount = 1;
} else if (ScriptPattern.isSentToMultisig(this)) {
sigsPrefixCount = 1; // OP_0 *
} else if (ScriptPattern.isP2PKH(this)) {
sigsSuffixCount = 1; //
}
return ScriptBuilder.updateScriptWithSignature(scriptSig, sigBytes, index, sigsPrefixCount, sigsSuffixCount);
}
private int findKeyInRedeem(ECKey key) {
checkArgument(chunks.get(0).isOpCode()); // P2SH scriptSig
int numKeys = Script.decodeFromOpN(chunks.get(chunks.size() - 2).opcode);
for (int i = 0 ; i < numKeys ; i++) {
if (Arrays.equals(chunks.get(1 + i).data, key.getPubKey())) {
return i;
}
}
throw new IllegalStateException("Could not find matching key " + key.toString() + " in script " + this);
}
/**
* Returns a list of the keys required by this script, assuming a multi-sig 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 (!ScriptPattern.isSentToMultisig(this))
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Only usable for multisig scripts.");
ArrayList result = new ArrayList<>();
int numKeys = Script.decodeFromOpN(chunks.get(chunks.size() - 2).opcode);
for (int i = 0 ; i < numKeys ; i++)
result.add(ECKey.fromPublicOnly(chunks.get(1 + i).data));
return result;
}
////////////////////// Interface used during verification of transactions/blocks ////////////////////////////////
private static int getSigOpCount(List chunks, boolean accurate) throws ScriptException {
int sigOps = 0;
int lastOpCode = OP_INVALIDOPCODE;
for (ScriptChunk chunk : chunks) {
if (chunk.isOpCode()) {
switch (chunk.opcode) {
case OP_CHECKSIG:
case OP_CHECKSIGVERIFY:
sigOps++;
break;
case OP_CHECKMULTISIG:
case OP_CHECKMULTISIGVERIFY:
if (accurate && lastOpCode >= OP_1 && lastOpCode <= OP_16)
sigOps += decodeFromOpN(lastOpCode);
else
sigOps += 20;
break;
default:
break;
}
lastOpCode = chunk.opcode;
}
}
return sigOps;
}
public static int decodeFromOpN(int opcode) {
checkArgument((opcode == OP_0 || opcode == OP_1NEGATE) || (opcode >= OP_1 && opcode <= OP_16),
"decodeFromOpN called on non OP_N opcode: %s", ScriptOpCodes.getOpCodeName(opcode));
if (opcode == OP_0)
return 0;
else if (opcode == OP_1NEGATE)
return -1;
else
return opcode + 1 - OP_1;
}
public static int encodeToOpN(int value) {
checkArgument(value >= -1 && value <= 16, "encodeToOpN called for " + value + " which we cannot encode in an opcode.");
if (value == 0)
return OP_0;
else if (value == -1)
return OP_1NEGATE;
else
return value - 1 + OP_1;
}
/**
* Gets the count of regular SigOps in the script program (counting multisig ops as 20)
*/
public static int getSigOpCount(byte[] program) throws ScriptException {
Script script = new Script();
try {
script.parse(program);
} catch (ScriptException e) {
// Ignore errors and count up to the parse-able length
}
return getSigOpCount(script.chunks, false);
}
/**
* Gets the count of P2SH Sig Ops in the Script scriptSig
*/
public static long getP2SHSigOpCount(byte[] scriptSig) throws ScriptException {
Script script = new Script();
try {
script.parse(scriptSig);
} catch (ScriptException e) {
// Ignore errors and count up to the parse-able length
}
for (int i = script.chunks.size() - 1; i >= 0; i--)
if (!script.chunks.get(i).isOpCode()) {
Script subScript = new Script();
subScript.parse(script.chunks.get(i).data);
return getSigOpCount(subScript.chunks, true);
}
return 0;
}
/**
* Returns number of signatures required to satisfy this script.
*/
public int getNumberOfSignaturesRequiredToSpend() {
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 (ScriptPattern.isP2PKH(this) || ScriptPattern.isP2PK(this)) {
// P2PKH and P2PK require single sig
return 1;
} else if (ScriptPattern.isP2SH(this)) {
throw new IllegalStateException("For P2SH number of signatures depends on redeem script");
} else {
throw new IllegalStateException("Unsupported script type");
}
}
/**
* 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 (ScriptPattern.isP2SH(this)) {
// scriptSig: [sig] [sig...]
checkArgument(redeemScript != null, "P2SH script requires redeemScript to be spent");
return redeemScript.getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + redeemScript.getProgram().length;
} else if (ScriptPattern.isSentToMultisig(this)) {
// scriptSig: OP_0 [sig] [sig...]
return getNumberOfSignaturesRequiredToSpend() * SIG_SIZE + 1;
} else if (ScriptPattern.isP2PK(this)) {
// scriptSig:
return SIG_SIZE;
} else if (ScriptPattern.isP2PKH(this)) {
// scriptSig:
int uncompressedPubKeySize = 65; // very conservative
return SIG_SIZE + (pubKey != null ? pubKey.getPubKey().length : uncompressedPubKeySize);
} else if (ScriptPattern.isP2WPKH(this)) {
// scriptSig is empty
// witness:
int compressedPubKeySize = 33;
return SIG_SIZE + (pubKey != null ? pubKey.getPubKey().length : compressedPubKeySize);
} else {
throw new IllegalStateException("Unsupported script type");
}
}
/** @deprecated use {@link ScriptPattern#isP2SH(Script)} */
@Deprecated
public boolean isPayToScriptHash() {
return ScriptPattern.isP2SH(this);
}
/** @deprecated use {@link ScriptPattern#isSentToMultisig(Script)} */
@Deprecated
public boolean isSentToMultiSig() {
return ScriptPattern.isSentToMultisig(this);
}
private static boolean equalsRange(byte[] a, int start, byte[] b) {
if (start + b.length > a.length)
return false;
for (int i = 0; i < b.length; i++)
if (a[i + start] != b[i])
return false;
return true;
}
/**
* Returns the script bytes of inputScript with all instances of the specified script object removed
*/
public static byte[] removeAllInstancesOf(byte[] inputScript, byte[] chunkToRemove) {
// We usually don't end up removing anything
UnsafeByteArrayOutputStream bos = new UnsafeByteArrayOutputStream(inputScript.length);
int cursor = 0;
while (cursor < inputScript.length) {
boolean skip = equalsRange(inputScript, cursor, chunkToRemove);
int opcode = inputScript[cursor++] & 0xFF;
int additionalBytes = 0;
if (opcode >= 0 && opcode < OP_PUSHDATA1) {
additionalBytes = opcode;
} else if (opcode == OP_PUSHDATA1) {
additionalBytes = (0xFF & inputScript[cursor]) + 1;
} else if (opcode == OP_PUSHDATA2) {
additionalBytes = Utils.readUint16(inputScript, cursor) + 2;
} else if (opcode == OP_PUSHDATA4) {
additionalBytes = (int) Utils.readUint32(inputScript, cursor) + 4;
}
if (!skip) {
try {
bos.write(opcode);
bos.write(Arrays.copyOfRange(inputScript, cursor, cursor + additionalBytes));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
cursor += additionalBytes;
}
return bos.toByteArray();
}
/**
* Returns the script bytes of inputScript with all instances of the given op code removed
*/
public static byte[] removeAllInstancesOfOp(byte[] inputScript, int opCode) {
return removeAllInstancesOf(inputScript, new byte[] {(byte)opCode});
}
////////////////////// Script verification and helpers ////////////////////////////////
private static boolean castToBool(byte[] data) {
for (int i = 0; i < data.length; i++)
{
// "Can be negative zero" - Bitcoin Core (see OpenSSL's BN_bn2mpi)
if (data[i] != 0)
return !(i == data.length - 1 && (data[i] & 0xFF) == 0x80);
}
return false;
}
/**
* Cast a script chunk to a BigInteger.
*
* @see #castToBigInteger(byte[], int, boolean) for values with different maximum
* sizes.
* @throws ScriptException if the chunk is longer than 4 bytes.
*/
private static BigInteger castToBigInteger(byte[] chunk, final boolean requireMinimal) throws ScriptException {
return castToBigInteger(chunk, 4, requireMinimal);
}
/**
* Cast a script chunk to a BigInteger. Normally you would want
* {@link #castToBigInteger(byte[], boolean)} instead, this is only for cases where
* the normal maximum length does not apply (i.e. CHECKLOCKTIMEVERIFY, CHECKSEQUENCEVERIFY).
*
* @param maxLength the maximum length in bytes.
* @param requireMinimal check if the number is encoded with the minimum possible number of bytes
* @throws ScriptException if the chunk is longer than the specified maximum.
*/
/* package private */ static BigInteger castToBigInteger(final byte[] chunk, final int maxLength, final boolean requireMinimal) throws ScriptException {
if (chunk.length > maxLength)
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "Script attempted to use an integer larger than " + maxLength + " bytes");
if (requireMinimal && chunk.length > 0) {
// Check that the number is encoded with the minimum possible
// number of bytes.
//
// If the most-significant-byte - excluding the sign bit - is zero
// then we're not minimal. Note how this test also rejects the
// negative-zero encoding, 0x80.
if ((chunk[chunk.length - 1] & 0x7f) == 0) {
// One exception: if there's more than one byte and the most
// significant bit of the second-most-significant-byte is set
// it would conflict with the sign bit. An example of this case
// is +-255, which encode to 0xff00 and 0xff80 respectively.
// (big-endian).
if (chunk.length <= 1 || (chunk[chunk.length - 2] & 0x80) == 0) {
throw new ScriptException(ScriptError.SCRIPT_ERR_UNKNOWN_ERROR, "non-minimally encoded script number");
}
}
}
return Utils.decodeMPI(Utils.reverseBytes(chunk), false);
}
/** @deprecated use {@link ScriptPattern#isOpReturn(Script)} */
@Deprecated
public boolean isOpReturn() {
return ScriptPattern.isOpReturn(this);
}
// Utility that doesn't copy for internal use
private byte[] getQuickProgram() {
if (program != null)
return program;
return getProgram();
}
/**
* Get the {@link Script.ScriptType}.
* @return The script type, or null if the script is of unknown type
*/
public @Nullable ScriptType getScriptType() {
if (ScriptPattern.isP2PKH(this))
return ScriptType.P2PKH;
if (ScriptPattern.isP2PK(this))
return ScriptType.P2PK;
if (ScriptPattern.isP2SH(this))
return ScriptType.P2SH;
if (ScriptPattern.isP2WPKH(this))
return ScriptType.P2WPKH;
if (ScriptPattern.isP2WSH(this))
return ScriptType.P2WSH;
return null;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
return Arrays.equals(getQuickProgram(), ((Script)o).getQuickProgram());
}
@Override
public int hashCode() {
return Arrays.hashCode(getQuickProgram());
}
}