Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Allow any sequence of pushdata in OP_RETURN #2309

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
44 changes: 35 additions & 9 deletions txscript/standard.go
Original file line number Diff line number Diff line change
Expand Up @@ -504,25 +504,51 @@ func isNullDataScript(scriptVersion uint16, script []byte) bool {
// A null script is of the form:
// OP_RETURN <optional data>
//
// Thus, it can either be a single OP_RETURN or an OP_RETURN followed by a
// data push up to MaxDataCarrierSize bytes.
// The entire scriptPubKey, including OP_RETURN and the payload, must not
// exceed 83 bytes by default.

// 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.
const MaxScriptPubKeySize = 83

// Check if the script size exceeds the maximum allowed size.
if len(script) > MaxScriptPubKeySize {
return false
}

// The script must start with OP_RETURN.
if len(script) < 1 || script[0] != OP_RETURN {
return false
}

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

// OP_RETURN followed by data push up to MaxDataCarrierSize bytes.
// Tokenize and validate the script after OP_RETURN.
tokenizer := MakeScriptTokenizer(scriptVersion, script[1:])
return tokenizer.Next() && tokenizer.Done() &&
(IsSmallInt(tokenizer.Opcode()) || tokenizer.Opcode() <= OP_PUSHDATA4) &&
len(tokenizer.Data()) <= MaxDataCarrierSize
for tokenizer.Next() {
opcode := tokenizer.Opcode()

// Allow small integer opcodes (OP_1 to OP_16).
if IsSmallInt(opcode) {
continue
}

// Allow valid data pushes.
if opcode <= OP_PUSHDATA4 {
// Ensure each data push is valid and within limits.
if len(tokenizer.Data()) > MaxDataCarrierSize {
return false
}
continue
}

// Any other opcode is invalid for a null data script.
return false
}

// Ensure the tokenizer successfully parsed the entire script.
return tokenizer.Done()
}

// scriptType returns the type of the script being inspected from the known
Expand Down
29 changes: 25 additions & 4 deletions txscript/standard_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -1032,11 +1032,10 @@ var scriptClassTests = []struct {
class: NonStandardTy,
},
{
// Almost nulldata, but add an additional opcode after the data
// to make it nonstandard.
name: "almost nulldata",
// nulldata, with combinations of op_code and pushdata
name: "nulldata with multiple pushes",
script: "RETURN 4 TRUE",
class: NonStandardTy,
class: NullDataTy,
},

// The next few are almost multisig (it is the more complex script type)
Expand Down Expand Up @@ -1277,6 +1276,28 @@ func TestNullDataScript(t *testing.T) {
}
}

func TestIsNullDataScript(t *testing.T) {
tests := []struct {
name string
data []byte
expectedIsNullData bool
}{{
name: "multiple pushes",
data: hexToBytes("6a5d0f160100e6a233fc078088a5a9a30700"),
expectedIsNullData: true,
},
}
for i, test := range tests {
isNullData := isNullDataScript(0, test.data)
if isNullData != test.expectedIsNullData {
t.Errorf("IsNullDataScript: #%d (%s) wrong result -- "+
"got: %v, want: %v", i, test.name, isNullData,
test.expectedIsNullData)
continue
}
}
}

// TestNewScriptClass tests whether NewScriptClass returns a valid ScriptClass.
func TestNewScriptClass(t *testing.T) {
tests := []struct {
Expand Down