mirror of
https://github.com/Qortal/altcoinj.git
synced 2025-02-07 14:54:15 +00:00
Keep opcode in ScriptChunk representation of scripts. The goal is to know how data was pushed and be able to apply malleability rules. All unit-tests pass.
This commit is contained in:
parent
b47995ed97
commit
c236ae418f
@ -1,6 +1,7 @@
|
||||
/**
|
||||
* Copyright 2011 Google Inc.
|
||||
* Copyright 2012 Matt Corallo.
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -108,12 +109,12 @@ public class Script {
|
||||
StringBuilder buf = new StringBuilder();
|
||||
for (ScriptChunk chunk : chunks) {
|
||||
if (chunk.isOpCode()) {
|
||||
buf.append(getOpCodeName(chunk.data[0]));
|
||||
buf.append(getOpCodeName(chunk.opcode));
|
||||
buf.append(" ");
|
||||
} else {
|
||||
// Data chunk
|
||||
buf.append("[");
|
||||
buf.append(bytesToHexString(chunk.data));
|
||||
buf.append(chunk.data != null ? bytesToHexString(chunk.data) : "null");
|
||||
buf.append("] ");
|
||||
}
|
||||
}
|
||||
@ -192,13 +193,13 @@ public class Script {
|
||||
|
||||
ScriptChunk chunk;
|
||||
if (dataToRead == -1) {
|
||||
chunk = new ScriptChunk(true, new byte[]{(byte) opcode}, startLocationInProgram);
|
||||
chunk = new ScriptChunk(opcode, null, startLocationInProgram);
|
||||
} else {
|
||||
if (dataToRead > bis.available())
|
||||
throw new ScriptException("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(false, data, startLocationInProgram);
|
||||
chunk = new ScriptChunk(opcode, data, startLocationInProgram);
|
||||
}
|
||||
// Save some memory by eliminating redundant copies of the same chunk objects. INTERN_TABLE can be null
|
||||
// here because this method is called whilst setting it up.
|
||||
@ -272,12 +273,16 @@ public class Script {
|
||||
if (chunks.size() != 2) {
|
||||
throw new ScriptException("Script not of right size, expecting 2 but got " + chunks.size());
|
||||
}
|
||||
if (chunks.get(0).data.length > 2 && chunks.get(1).data.length > 2) {
|
||||
final ScriptChunk chunk0 = chunks.get(0);
|
||||
final byte[] chunk0data = chunk0.data;
|
||||
final ScriptChunk chunk1 = chunks.get(1);
|
||||
final byte[] chunk1data = chunk1.data;
|
||||
if (chunk0data != null && chunk0data.length > 2 && chunk1data != null && chunk1data.length > 2) {
|
||||
// If we have two large constants assume the input to a pay-to-address output.
|
||||
return chunks.get(1).data;
|
||||
} else if (chunks.get(1).data.length == 1 && chunks.get(1).equalsOpCode(OP_CHECKSIG) && chunks.get(0).data.length > 2) {
|
||||
return chunk1data;
|
||||
} else if (chunk1.equalsOpCode(OP_CHECKSIG) && chunk0data != null && chunk0data.length > 2) {
|
||||
// A large constant followed by an OP_CHECKSIG is the key.
|
||||
return chunks.get(0).data;
|
||||
return chunk0data;
|
||||
} else {
|
||||
throw new ScriptException("Script did not match expected form: " + toString());
|
||||
}
|
||||
@ -381,8 +386,7 @@ public class Script {
|
||||
int lastOpCode = OP_INVALIDOPCODE;
|
||||
for (ScriptChunk chunk : chunks) {
|
||||
if (chunk.isOpCode()) {
|
||||
int opcode = 0xFF & chunk.data[0];
|
||||
switch (opcode) {
|
||||
switch (chunk.opcode) {
|
||||
case OP_CHECKSIG:
|
||||
case OP_CHECKSIGVERIFY:
|
||||
sigOps++;
|
||||
@ -397,19 +401,12 @@ public class Script {
|
||||
default:
|
||||
break;
|
||||
}
|
||||
lastOpCode = opcode;
|
||||
lastOpCode = chunk.opcode;
|
||||
}
|
||||
}
|
||||
return sigOps;
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts an opcode to its int representation (including OP_1NEGATE and OP_0/OP_FALSE)
|
||||
* @throws IllegalArgumentException If the opcode is not an OP_N opcode
|
||||
*/
|
||||
public static int decodeFromOpN(byte opcode) throws IllegalArgumentException {
|
||||
return decodeFromOpN((int)opcode);
|
||||
}
|
||||
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");
|
||||
if (opcode == OP_0)
|
||||
@ -499,13 +496,13 @@ public class Script {
|
||||
// Second to last chunk must be an OP_N opcode and there should be that many data chunks (keys).
|
||||
ScriptChunk m = chunks.get(chunks.size() - 2);
|
||||
if (!m.isOpCode()) return false;
|
||||
int numKeys = decodeFromOpN(m.data[0]);
|
||||
int numKeys = decodeFromOpN(m.opcode);
|
||||
if (numKeys < 1 || chunks.size() != 3 + numKeys) return false;
|
||||
for (int i = 1; i < chunks.size() - 2; i++) {
|
||||
if (chunks.get(i).isOpCode()) return false;
|
||||
}
|
||||
// First chunk must be an OP_N opcode too.
|
||||
if (decodeFromOpN(chunks.get(0).data[0]) < 1) return false;
|
||||
if (decodeFromOpN(chunks.get(0).opcode) < 1) return false;
|
||||
} catch (IllegalStateException e) {
|
||||
return false; // Not an OP_N opcode.
|
||||
}
|
||||
@ -605,7 +602,7 @@ public class Script {
|
||||
|
||||
stack.add(chunk.data);
|
||||
} else {
|
||||
int opcode = 0xFF & chunk.data[0];
|
||||
int opcode = chunk.opcode;
|
||||
if (opcode > OP_16) {
|
||||
opCount++;
|
||||
if (opCount > 201)
|
||||
@ -1254,7 +1251,7 @@ public class Script {
|
||||
// TODO: Check if we can take out enforceP2SH if there's a checkpoint at the enforcement block.
|
||||
if (enforceP2SH && scriptPubKey.isPayToScriptHash()) {
|
||||
for (ScriptChunk chunk : chunks)
|
||||
if (chunk.isOpCode() && (chunk.data[0] & 0xff) > OP_16)
|
||||
if (chunk.isOpCode() && chunk.opcode > OP_16)
|
||||
throw new ScriptException("Attempted to spend a P2SH scriptPubKey with a script that contained script ops");
|
||||
|
||||
byte[] scriptPubKeyBytes = p2shStack.pollLast();
|
||||
|
@ -42,20 +42,31 @@ public class ScriptBuilder {
|
||||
}
|
||||
|
||||
public ScriptBuilder op(int opcode) {
|
||||
chunks.add(new ScriptChunk(true, new byte[]{(byte)opcode}));
|
||||
checkArgument(opcode > OP_PUSHDATA4);
|
||||
chunks.add(new ScriptChunk(opcode, null));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScriptBuilder data(byte[] data) {
|
||||
byte[] copy = Arrays.copyOf(data, data.length);
|
||||
chunks.add(new ScriptChunk(false, copy));
|
||||
int opcode;
|
||||
if (data.length < OP_PUSHDATA1) {
|
||||
opcode = data.length; // OP_0 in case of empty vector
|
||||
} else if (data.length < 256) {
|
||||
opcode = OP_PUSHDATA1;
|
||||
} else if (data.length < 65536) {
|
||||
opcode = OP_PUSHDATA2;
|
||||
} else {
|
||||
throw new RuntimeException("Unimplemented");
|
||||
}
|
||||
chunks.add(new ScriptChunk(opcode, copy));
|
||||
return this;
|
||||
}
|
||||
|
||||
public ScriptBuilder smallNum(int num) {
|
||||
checkArgument(num >= 0, "Cannot encode negative numbers with smallNum");
|
||||
checkArgument(num <= 16, "Cannot encode numbers larger than 16 with smallNum");
|
||||
chunks.add(new ScriptChunk(true, new byte[]{(byte)Script.encodeToOpN(num)}));
|
||||
chunks.add(new ScriptChunk(Script.encodeToOpN(num), null));
|
||||
return this;
|
||||
}
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
/*
|
||||
* Copyright 2013 Google Inc.
|
||||
* Copyright 2014 Andreas Schildbach
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -22,38 +23,42 @@ import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import static com.google.bitcoin.script.ScriptOpCodes.OP_PUSHDATA1;
|
||||
import static com.google.bitcoin.script.ScriptOpCodes.OP_PUSHDATA2;
|
||||
import static com.google.bitcoin.script.ScriptOpCodes.OP_PUSHDATA4;
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
/**
|
||||
* An element that is either an opcode or a raw byte array (signature, pubkey, etc).
|
||||
*/
|
||||
public class ScriptChunk {
|
||||
private boolean isOpCode;
|
||||
public final int opcode;
|
||||
@Nullable
|
||||
public final byte[] data;
|
||||
private int startLocationInProgram;
|
||||
|
||||
public ScriptChunk(boolean isOpCode, byte[] data) {
|
||||
this(isOpCode, data, -1);
|
||||
public ScriptChunk(int opcode, byte[] data) {
|
||||
this(opcode, data, -1);
|
||||
}
|
||||
|
||||
public ScriptChunk(boolean isOpCode, byte[] data, int startLocationInProgram) {
|
||||
this.isOpCode = isOpCode;
|
||||
public ScriptChunk(int opcode, byte[] data, int startLocationInProgram) {
|
||||
this.opcode = opcode;
|
||||
this.data = data;
|
||||
this.startLocationInProgram = startLocationInProgram;
|
||||
}
|
||||
|
||||
public boolean equalsOpCode(int opCode) {
|
||||
return isOpCode && data.length == 1 && (0xFF & data[0]) == opCode;
|
||||
public boolean equalsOpCode(int opcode) {
|
||||
return opcode == this.opcode;
|
||||
}
|
||||
|
||||
/**
|
||||
* If this chunk is a single byte of non-pushdata content (could be OP_RESERVED or some invalid Opcode)
|
||||
*/
|
||||
public boolean isOpCode() {
|
||||
return isOpCode;
|
||||
return opcode > OP_PUSHDATA4;
|
||||
}
|
||||
|
||||
public int getStartLocationInProgram() {
|
||||
@ -62,25 +67,33 @@ public class ScriptChunk {
|
||||
}
|
||||
|
||||
public void write(OutputStream stream) throws IOException {
|
||||
if (isOpCode) {
|
||||
checkState(data.length == 1);
|
||||
stream.write(data);
|
||||
} else {
|
||||
checkState(data.length <= Script.MAX_SCRIPT_ELEMENT_SIZE);
|
||||
if (data.length < OP_PUSHDATA1) {
|
||||
stream.write(data.length);
|
||||
} else if (data.length <= 0xFF) {
|
||||
if (isOpCode()) {
|
||||
checkState(data == null);
|
||||
stream.write(opcode);
|
||||
} else if (data != null) {
|
||||
checkNotNull(data);
|
||||
if (opcode < OP_PUSHDATA1) {
|
||||
checkState(data.length == opcode);
|
||||
stream.write(opcode);
|
||||
} else if (opcode == OP_PUSHDATA1) {
|
||||
checkState(data.length <= 0xFF);
|
||||
stream.write(OP_PUSHDATA1);
|
||||
stream.write(data.length);
|
||||
} else if (data.length <= 0xFFFF) {
|
||||
} else if (opcode == OP_PUSHDATA2) {
|
||||
checkState(data.length <= 0xFFFF);
|
||||
stream.write(OP_PUSHDATA2);
|
||||
stream.write(0xFF & data.length);
|
||||
stream.write(0xFF & (data.length >> 8));
|
||||
} else {
|
||||
} else if (opcode == OP_PUSHDATA4) {
|
||||
checkState(data.length <= Script.MAX_SCRIPT_ELEMENT_SIZE);
|
||||
stream.write(OP_PUSHDATA4);
|
||||
Utils.uint32ToByteStreamLE(data.length, stream);
|
||||
} else {
|
||||
throw new RuntimeException("Unimplemented");
|
||||
}
|
||||
stream.write(data);
|
||||
} else {
|
||||
stream.write(opcode); // smallNum
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +104,7 @@ public class ScriptChunk {
|
||||
|
||||
ScriptChunk chunk = (ScriptChunk) o;
|
||||
|
||||
if (isOpCode != chunk.isOpCode) return false;
|
||||
if (opcode != chunk.opcode) return false;
|
||||
if (startLocationInProgram != chunk.startLocationInProgram) return false;
|
||||
if (!Arrays.equals(data, chunk.data)) return false;
|
||||
|
||||
@ -100,7 +113,7 @@ public class ScriptChunk {
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
int result = (isOpCode ? 1 : 0);
|
||||
int result = opcode;
|
||||
result = 31 * result + (data != null ? Arrays.hashCode(data) : 0);
|
||||
result = 31 * result + startLocationInProgram;
|
||||
return result;
|
||||
|
@ -385,9 +385,7 @@ public class ScriptOpCodes {
|
||||
/**
|
||||
* Converts the given OpCode into a string (eg "0", "PUSHDATA", or "NON_OP(10)")
|
||||
*/
|
||||
public static String getOpCodeName(byte opCode) {
|
||||
int opcode = opCode & 0xff;
|
||||
|
||||
public static String getOpCodeName(int opcode) {
|
||||
if (opCodeMap.containsKey(opcode))
|
||||
return opCodeMap.get(opcode);
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user