Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 22 additions & 9 deletions txscript/standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,17 @@ import (

const (
// MaxDataCarrierSize is the maximum number of bytes allowed in pushed
// data to be considered a nulldata transaction
// data to be considered a nulldata transaction when using the
// NullDataScript function. This is kept at 80 for backwards compatibility.
MaxDataCarrierSize = 80

// MaxNullDataScriptSize is the maximum total size in bytes of a nulldata
// script (including OP_RETURN and all push opcodes) to be considered
// standard. This was increased from 83 bytes to 10000 bytes to match
// Bitcoin Core's relaxed policy as of v0.12.0, which removed the single
// push restriction and enforces limits on total script size instead.
MaxNullDataScriptSize = 10000

// StandardVerifyFlags are the script flags which are used when
// executing transaction scripts to enforce additional checks which
// are required for the script to be considered standard. These checks
Expand Down Expand Up @@ -502,27 +510,32 @@ func isNullDataScript(scriptVersion uint16, script []byte) bool {
}

// A null script is of the form:
// OP_RETURN <optional data>
// OP_RETURN <optional data pushes>
//
// Thus, it can either be a single OP_RETURN or an OP_RETURN followed by a
// data push up to MaxDataCarrierSize bytes.
// It can be a single OP_RETURN or an OP_RETURN followed by any number of
// data pushes. The total script size must not exceed MaxNullDataScriptSize.
// This matches Bitcoin Core's behavior as of v0.12.0 which removed the
// single push restriction.

// The script can't possibly be a null data script if it doesn't start
// with OP_RETURN. Fail fast to avoid more work below.
if len(script) < 1 || script[0] != OP_RETURN {
return false
}

// Enforce the total script size limit.
if len(script) > MaxNullDataScriptSize {
return false
}

// Single OP_RETURN.
if len(script) == 1 {
return true
}

// OP_RETURN followed by data push up to MaxDataCarrierSize bytes.
tokenizer := MakeScriptTokenizer(scriptVersion, script[1:])
return tokenizer.Next() && tokenizer.Done() &&
(IsSmallInt(tokenizer.Opcode()) || tokenizer.Opcode() <= OP_PUSHDATA4) &&
len(tokenizer.Data()) <= MaxDataCarrierSize
// OP_RETURN followed by push-only data. All opcodes after OP_RETURN
// must be data pushes (OP_0 through OP_16 and direct/PUSHDATA pushes).
return IsPushOnlyScript(script[1:])
}

// scriptType returns the type of the script being inspected from the known
Expand Down
65 changes: 58 additions & 7 deletions txscript/standard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,30 @@ func mustParseShortForm(script string) []byte {
return s
}

// buildLargeNullDataScript creates a null data script with the specified
// number of data bytes. Uses PUSHDATA2 for larger data sizes.
func buildLargeNullDataScript(dataSize int) string {
// For PUSHDATA2, the format is:
// OP_RETURN OP_PUSHDATA2 <2-byte little-endian length> <data>
// Total overhead: 1 (OP_RETURN) + 1 (OP_PUSHDATA2) + 2 (length) = 4 bytes
// But we're using hex representation in the test format.

// Build the data bytes (all zeros for simplicity).
data := make([]byte, dataSize)

// Build the script manually:
// OP_RETURN (0x6a) + OP_PUSHDATA2 (0x4d) + length (2 bytes LE) + data
script := make([]byte, 1+1+2+dataSize)
script[0] = OP_RETURN
script[1] = OP_PUSHDATA2
script[2] = byte(dataSize & 0xff)
script[3] = byte((dataSize >> 8) & 0xff)
copy(script[4:], data)

// Convert to hex string for the test framework
return hex.EncodeToString(script)
}

// newAddressPubKey returns a new btcutil.AddressPubKey from the provided
// serialized public key. It panics if an error occurs. This is only used in
// the tests as a helper since the only way it can fail is if there is an error
Expand Down Expand Up @@ -1022,20 +1046,47 @@ var scriptClassTests = []struct {
class: NullDataTy,
},
{
// Nulldata with more than max allowed data to be considered
// standard (so therefore nonstandard)
name: "nulldata exceed max standard push",
// Nulldata with 81 bytes of data (now allowed since the limit
// is on total script size, not individual push size).
name: "nulldata 81-byte push",
script: "RETURN PUSHDATA1 0x51 0x046708afdb0fe5548271967f1a67" +
"130b7105cd6a828e03909a67962e0ea1f61deb649f6bc3f4cef3" +
"046708afdb0fe5548271967f1a67130b7105cd6a828e03909a67" +
"962e0ea1f61deb649f6bc3f4cef308",
class: NonStandardTy,
class: NullDataTy,
},
{
// Almost nulldata, but add an additional opcode after the data
// to make it nonstandard.
name: "almost nulldata",
// Nulldata with multiple data pushes (now allowed as of Bitcoin
// Core v0.12.0 which removed the single push restriction).
name: "nulldata multiple pushes",
script: "RETURN 4 TRUE",
class: NullDataTy,
},
{
// Nulldata with many data pushes using various push opcodes.
name: "nulldata many pushes",
script: "RETURN 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 " +
"DATA_8 0x0102030405060708",
class: NullDataTy,
},
{
// Nulldata with non-push opcode after OP_RETURN (nonstandard).
name: "nulldata non-push opcode",
script: "RETURN DATA_4 0x01020304 DUP",
class: NonStandardTy,
},
{
// Nulldata with large data push (9996 bytes, within 10000 byte limit).
// Total: 1 (OP_RETURN) + 1 (OP_PUSHDATA2) + 2 (length) + 9996 (data) = 10000
name: "nulldata large within limit",
script: buildLargeNullDataScript(9996),
class: NullDataTy,
},
{
// Nulldata exceeding the 10000 byte script size limit.
// Total: 1 (OP_RETURN) + 1 (OP_PUSHDATA2) + 2 (length) + 9997 (data) = 10001
name: "nulldata exceeds script size limit",
script: buildLargeNullDataScript(9997),
class: NonStandardTy,
},

Expand Down