Skip to content

Commit

Permalink
Implement standard checks for BIP62 shortest possible data push rules…
Browse files Browse the repository at this point in the history
…. Also fix ScriptBuilder so it doesn't build longer than necessary data pushes any more.
  • Loading branch information
schildbach committed May 27, 2014
1 parent 7b24a72 commit ccc3dbd
Show file tree
Hide file tree
Showing 4 changed files with 120 additions and 3 deletions.
13 changes: 11 additions & 2 deletions core/src/main/java/com/google/bitcoin/script/ScriptBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -48,10 +48,19 @@ public ScriptBuilder op(int opcode) {
}

public ScriptBuilder data(byte[] data) {
// implements BIP62
byte[] copy = Arrays.copyOf(data, data.length);
int opcode;
if (data.length < OP_PUSHDATA1) {
opcode = data.length; // OP_0 in case of empty vector
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) {
Expand Down
36 changes: 36 additions & 0 deletions core/src/main/java/com/google/bitcoin/script/ScriptChunk.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@
import javax.annotation.Nullable;

import static com.google.bitcoin.core.Utils.bytesToHexString;
import static com.google.bitcoin.script.ScriptOpCodes.OP_0;
import static com.google.bitcoin.script.ScriptOpCodes.OP_1;
import static com.google.bitcoin.script.ScriptOpCodes.OP_16;
import static com.google.bitcoin.script.ScriptOpCodes.OP_1NEGATE;
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;
Expand Down Expand Up @@ -64,11 +68,43 @@ public boolean isOpCode() {
return opcode > OP_PUSHDATA4;
}

/**
* Returns true if this chunk is pushdata content, including the single-byte pushdatas.
*/
public boolean isPushData() {
return opcode <= OP_16;
}

public int getStartLocationInProgram() {
checkState(startLocationInProgram >= 0);
return startLocationInProgram;
}

/**
* Called on a pushdata chunk, returns true if it uses the smallest possible way (according to BIP62) to push the data.
*/
public boolean isShortestPossiblePushData() {
checkState(isPushData());
if (data.length == 0)
return opcode == OP_0;
if (data.length == 1) {
byte b = data[0];
if (b >= 0x01 && b <= 0x10)
return opcode == OP_1 + b - 1;
if (b == 0x81)
return opcode == OP_1NEGATE;
}
if (data.length < OP_PUSHDATA1)
return opcode == data.length;
if (data.length < 256)
return opcode == OP_PUSHDATA1;
if (data.length < 65536)
return opcode == OP_PUSHDATA2;

// can never be used, but implemented for completeness
return opcode == OP_PUSHDATA4;
}

public void write(OutputStream stream) throws IOException {
if (isOpCode()) {
checkState(data == null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,8 +20,11 @@
import com.google.bitcoin.core.NetworkParameters;
import com.google.bitcoin.core.Transaction;
import com.google.bitcoin.core.TransactionConfidence;
import com.google.bitcoin.core.TransactionInput;
import com.google.bitcoin.core.TransactionOutput;
import com.google.bitcoin.core.Wallet;
import com.google.bitcoin.script.ScriptChunk;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

Expand Down Expand Up @@ -104,7 +107,8 @@ private Result analyzeIsFinal() {
public enum RuleViolation {
NONE,
VERSION,
DUST
DUST,
SHORTEST_POSSIBLE_PUSHDATA
}

/**
Expand All @@ -127,6 +131,25 @@ public static RuleViolation isStandard(Transaction tx) {
log.warn("TX considered non-standard due to output {} being dusty", i);
return RuleViolation.DUST;
}
for (ScriptChunk chunk : output.getScriptPubKey().getChunks()) {
if (chunk.isPushData() && !chunk.isShortestPossiblePushData()) {
log.warn("TX considered non-standard due to output {} having a longer than necessary data push: {}",
i, chunk);
return RuleViolation.SHORTEST_POSSIBLE_PUSHDATA;
}
}
}

final List<TransactionInput> inputs = tx.getInputs();
for (int i = 0; i < inputs.size(); i++) {
TransactionInput input = inputs.get(i);
for (ScriptChunk chunk : input.getScriptSig().getChunks()) {
if (chunk.data != null && chunk.isShortestPossiblePushData()) {
log.warn("TX considered non-standard due to input {} having a longer than necessary data push: {}",
i, chunk);
return RuleViolation.SHORTEST_POSSIBLE_PUSHDATA;
}
}
}

return RuleViolation.NONE;
Expand Down
49 changes: 49 additions & 0 deletions core/src/test/java/com/google/bitcoin/script/ScriptChunkTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
/**
* 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.
* 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 com.google.bitcoin.script;

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 org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;

import org.junit.Test;

public class ScriptChunkTest {

@Test
public void testShortestPossibleDataPush() {
assertTrue("empty push", new ScriptBuilder().data(new byte[0]).build().getChunks().get(0)
.isShortestPossiblePushData());

for (byte i = -1; i < 127; i++)
assertTrue("push of single byte " + i, new ScriptBuilder().data(new byte[] { i }).build().getChunks()
.get(0).isShortestPossiblePushData());

for (int len = 2; len < Script.MAX_SCRIPT_ELEMENT_SIZE; len++)
assertTrue("push of " + len + " bytes", new ScriptBuilder().data(new byte[len]).build().getChunks().get(0)
.isShortestPossiblePushData());

// non-standard chunks
for (byte i = 1; i <= 16; i++)
assertFalse("push of smallnum " + i, new ScriptChunk(1, new byte[] { i }).isShortestPossiblePushData());
assertFalse("push of 75 bytes", new ScriptChunk(OP_PUSHDATA1, new byte[75]).isShortestPossiblePushData());
assertFalse("push of 255 bytes", new ScriptChunk(OP_PUSHDATA2, new byte[255]).isShortestPossiblePushData());
assertFalse("push of 65535 bytes", new ScriptChunk(OP_PUSHDATA4, new byte[65535]).isShortestPossiblePushData());
}
}

0 comments on commit ccc3dbd

Please sign in to comment.