diff --git a/script.go b/script.go index 16acdb9b..bd3b90cb 100644 --- a/script.go +++ b/script.go @@ -308,6 +308,41 @@ func IsPushOnlyScript(script []byte) bool { return isPushOnly(pops) } +// HasCanonicalPushes returns whether or not the passed script only contains +// canonical data pushes. A canonical data push one where the fewest number of +// bytes possible to encode the size of the data being pushed is used. This +// includes using the small integer opcodes for single byte data that can be +// represented directly. +func HasCanonicalPushes(script []byte) bool { + pops, err := parseScript(script) + if err != nil { + return false + } + + for _, pop := range pops { + opcode := pop.opcode.value + data := pop.data + dataLen := len(pop.data) + if opcode > OP_16 { + continue + } + if opcode < OP_PUSHDATA1 && opcode > OP_0 && (dataLen == 1 && data[0] <= 16) { + return false + } + if opcode == OP_PUSHDATA1 && dataLen < OP_PUSHDATA1 { + return false + } + if opcode == OP_PUSHDATA2 && dataLen <= 0xff { + return false + } + if opcode == OP_PUSHDATA4 && dataLen <= 0xffff { + return false + } + } + + return true +} + // GetScriptClass returns the class of the script passed. If the script does not // parse then NonStandardTy will be returned. func GetScriptClass(script []byte) ScriptClass { diff --git a/script_test.go b/script_test.go index 14061dd5..3f193016 100644 --- a/script_test.go +++ b/script_test.go @@ -15,6 +15,31 @@ import ( "testing" ) +func TestStandardPushes(t *testing.T) { + for i := 0; i < 1000; i++ { + builder := btcscript.NewScriptBuilder() + builder.PushInt64(int64(i)) + if result := btcscript.IsPushOnlyScript(builder.Script()); !result { + t.Errorf("StandardPushesTests IsPushOnlyScript test #%d failed: %x\n", i, builder.Script()) + } + if result := btcscript.HasCanonicalPushes(builder.Script()); !result { + t.Errorf("StandardPushesTests HasCanonicalPushes test #%d failed: %x\n", i, builder.Script()) + continue + } + } + for i := 0; i < 1000; i++ { + builder := btcscript.NewScriptBuilder() + builder.PushData(bytes.Repeat([]byte{0x49}, i)) + if result := btcscript.IsPushOnlyScript(builder.Script()); !result { + t.Errorf("StandardPushesTests IsPushOnlyScript test #%d failed: %x\n", i, builder.Script()) + } + if result := btcscript.HasCanonicalPushes(builder.Script()); !result { + t.Errorf("StandardPushesTests HasCanonicalPushes test #%d failed: %x\n", i, builder.Script()) + continue + } + } +} + type txTest struct { name string tx *btcwire.MsgTx