From 127d3bed6660b2c103ec3c7fc0b2e49ed3192ee7 Mon Sep 17 00:00:00 2001 From: Olaoluwa Osuntokun Date: Mon, 10 Feb 2025 18:34:03 -0800 Subject: [PATCH] input: add new ScriptIsOpReturn helper func --- input/fuzz_script_is_op_return_test.go | 51 ++++++++++++++++++++++++++ input/script_utils.go | 33 +++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 input/fuzz_script_is_op_return_test.go diff --git a/input/fuzz_script_is_op_return_test.go b/input/fuzz_script_is_op_return_test.go new file mode 100644 index 000000000..148d7b02e --- /dev/null +++ b/input/fuzz_script_is_op_return_test.go @@ -0,0 +1,51 @@ +package input + +import ( + "testing" + + "github.com/btcsuite/btcd/chaincfg" + "github.com/btcsuite/btcd/txscript" + "github.com/stretchr/testify/require" +) + +// FuzzScriptIsOpReturn fuzzes the ScriptIsOpReturn function. +func FuzzScriptIsOpReturn(f *testing.F) { + // Seed the corpus with some representative inputs. + f.Add([]byte{}) + f.Add([]byte{txscript.OP_RETURN}) + + // Use our canonical ScriptBuilder to produce a valid OP_RETURN script. + builder := txscript.NewScriptBuilder() + builder.AddOp(txscript.OP_RETURN) + builder.AddData([]byte("valid data")) + script, err := builder.Script() + require.NoError(f, err) + f.Add(script) + + // An example of a script that does not start with OP_RETURN. + f.Add([]byte{txscript.OP_DUP, txscript.OP_RETURN}) + + f.Fuzz(func(t *testing.T, pkScript []byte) { + result := ScriptIsOpReturn(pkScript) + + var expected bool + if len(script) == 0 || script[0] != txscript.OP_RETURN { + expected = false + } else { + scriptClass, _, _, err := txscript.ExtractPkScriptAddrs( + pkScript, &chaincfg.MainNetParams, + ) + if err != nil { + t.Fatalf("unable to extract pk script "+ + "addresses: %v", err) + } + + expected = scriptClass == txscript.NullDataTy + } + + if result != expected { + t.Fatalf("for script %x, expected ScriptIsOpReturn=%v;"+ + " got %v", script, expected, result) + } + }) +} diff --git a/input/script_utils.go b/input/script_utils.go index 90e368dd1..0b38fad35 100644 --- a/input/script_utils.go +++ b/input/script_utils.go @@ -3239,3 +3239,36 @@ func ComputeCommitmentPoint(commitSecret []byte) *btcec.PublicKey { _, pubKey := btcec.PrivKeyFromBytes(commitSecret) return pubKey } + +// ScriptIsOpReturn returns true if the passed script is an OP_RETURN script. +// +// Lifted from the txscript package: +// https://github.com/btcsuite/btcd/blob/cc26860b40265e1332cca8748c5dbaf3c81cc094/txscript/standard.go#L493-L526. +// +//nolint:ll +func ScriptIsOpReturn(script []byte) bool { + // A null script is of the form: + // OP_RETURN + // + // Thus, it can either be a single OP_RETURN or an OP_RETURN followed by + // a data push up to MaxDataCarrierSize bytes. + + // 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] != txscript.OP_RETURN { + return false + } + + // Single OP_RETURN. + if len(script) == 1 { + return true + } + + // OP_RETURN followed by data push up to MaxDataCarrierSize bytes. + tokenizer := txscript.MakeScriptTokenizer(0, script[1:]) + + return tokenizer.Next() && tokenizer.Done() && + (txscript.IsSmallInt(tokenizer.Opcode()) || + tokenizer.Opcode() <= txscript.OP_PUSHDATA4) && + len(tokenizer.Data()) <= txscript.MaxDataCarrierSize +}