From 5a4312d9ca2787f8f55734a4b7cfd91821c5a893 Mon Sep 17 00:00:00 2001 From: David Hill Date: Thu, 12 Feb 2015 12:27:27 -0500 Subject: [PATCH] txscript: Add new flag ScriptVerifyMinimalData The ScriptVerifyMinimalData enforces that all push operations use the minimal data push required. This is part of BIP0062. This commit mimics Bitcoin Core commit 698c6abb25c1fbbc7fa4ba46b60e9f17d97332ef --- txscript/data/script_invalid.json | 96 +++++++++++++++++++++++++++++++ txscript/data/script_valid.json | 45 +++++++++++++++ txscript/internal_test.go | 6 +- txscript/opcode.go | 45 +++++++++++++++ txscript/script.go | 13 +++++ txscript/stack.go | 39 ++++++++++++- 6 files changed, 242 insertions(+), 2 deletions(-) diff --git a/txscript/data/script_invalid.json b/txscript/data/script_invalid.json index 32c05b8b..f1c17d4d 100644 --- a/txscript/data/script_invalid.json +++ b/txscript/data/script_invalid.json @@ -403,6 +403,102 @@ ["0 0x01 VER", "HASH160 0x14 0x0f4d7845db968f2a81b530b6f3c1d6246d4c7e01 EQUAL", "P2SH,STRICTENC", "OP_VER in P2SH should fail"], ["0x00", "'00' EQUAL", "P2SH,STRICTENC", "Basic OP_0 execution"], + +["MINIMALDATA enforcement for PUSHDATAs"], + +["0x4c 0x00", "DROP 1", "MINIMALDATA", "Empty vector minimally represented by OP_0"], +["0x01 0x81", "DROP 1", "MINIMALDATA", "-1 minimally represented by OP_1NEGATE"], +["0x01 0x01", "DROP 1", "MINIMALDATA", "1 to 16 minimally represented by OP_1 to OP_16"], +["0x01 0x02", "DROP 1", "MINIMALDATA"], +["0x01 0x03", "DROP 1", "MINIMALDATA"], +["0x01 0x04", "DROP 1", "MINIMALDATA"], +["0x01 0x05", "DROP 1", "MINIMALDATA"], +["0x01 0x06", "DROP 1", "MINIMALDATA"], +["0x01 0x07", "DROP 1", "MINIMALDATA"], +["0x01 0x08", "DROP 1", "MINIMALDATA"], +["0x01 0x09", "DROP 1", "MINIMALDATA"], +["0x01 0x0a", "DROP 1", "MINIMALDATA"], +["0x01 0x0b", "DROP 1", "MINIMALDATA"], +["0x01 0x0c", "DROP 1", "MINIMALDATA"], +["0x01 0x0d", "DROP 1", "MINIMALDATA"], +["0x01 0x0e", "DROP 1", "MINIMALDATA"], +["0x01 0x0f", "DROP 1", "MINIMALDATA"], +["0x01 0x10", "DROP 1", "MINIMALDATA"], + +["0x4c 0x48 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", "DROP 1", "MINIMALDATA", + "PUSHDATA1 of 72 bytes minimally represented by direct push"], + +["0x4d 0xFF00 0x111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", "DROP 1", "MINIMALDATA", + "PUSHDATA2 of 255 bytes minimally represented by PUSHDATA1"], + +["0x4f 0x00100000 0x11111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111111", "DROP 1", "MINIMALDATA", + "PUSHDATA4 of 256 bytes minimally represented by PUSHDATA2"], + + +["MINIMALDATA enforcement for numeric arguments"], + +["0x01 0x00", "NOT DROP 1", "MINIMALDATA", "numequals 0"], +["0x02 0x0000", "NOT DROP 1", "MINIMALDATA", "numequals 0"], +["0x01 0x80", "NOT DROP 1", "MINIMALDATA", "0x80 (negative zero) numequals 0"], +["0x02 0x0080", "NOT DROP 1", "MINIMALDATA", "numequals 0"], +["0x02 0x0500", "NOT DROP 1", "MINIMALDATA", "numequals 5"], +["0x03 0x050000", "NOT DROP 1", "MINIMALDATA", "numequals 5"], +["0x02 0x0580", "NOT DROP 1", "MINIMALDATA", "numequals -5"], +["0x03 0x050080", "NOT DROP 1", "MINIMALDATA", "numequals -5"], +["0x03 0xff7f80", "NOT DROP 1", "MINIMALDATA", "Minimal encoding is 0xffff"], +["0x03 0xff7f00", "NOT DROP 1", "MINIMALDATA", "Minimal encoding is 0xff7f"], +["0x04 0xffff7f80", "NOT DROP 1", "MINIMALDATA", "Minimal encoding is 0xffffff"], +["0x04 0xffff7f00", "NOT DROP 1", "MINIMALDATA", "Minimal encoding is 0xffff7f"], + +["Test every numeric-accepting opcode for correct handling of the numeric minimal encoding rule"], + +["1 0x02 0x0000", "PICK DROP", "MINIMALDATA"], +["1 0x02 0x0000", "ROLL DROP 1", "MINIMALDATA"], +["0x02 0x0000", "1ADD DROP 1", "MINIMALDATA"], +["0x02 0x0000", "1SUB DROP 1", "MINIMALDATA"], +["0x02 0x0000", "NEGATE DROP 1", "MINIMALDATA"], +["0x02 0x0000", "ABS DROP 1", "MINIMALDATA"], +["0x02 0x0000", "NOT DROP 1", "MINIMALDATA"], +["0x02 0x0000", "0NOTEQUAL DROP 1", "MINIMALDATA"], + +["0 0x02 0x0000", "ADD DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "ADD DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "SUB DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "SUB DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "BOOLAND DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "BOOLAND DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "BOOLOR DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "BOOLOR DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "NUMEQUAL DROP 1", "MINIMALDATA"], +["0x02 0x0000 1", "NUMEQUAL DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "NUMEQUALVERIFY 1", "MINIMALDATA"], +["0x02 0x0000 0", "NUMEQUALVERIFY 1", "MINIMALDATA"], +["0 0x02 0x0000", "NUMNOTEQUAL DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "NUMNOTEQUAL DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "LESSTHAN DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "LESSTHAN DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "GREATERTHAN DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "GREATERTHAN DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "LESSTHANOREQUAL DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "LESSTHANOREQUAL DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "GREATERTHANOREQUAL DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "GREATERTHANOREQUAL DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "MIN DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "MIN DROP 1", "MINIMALDATA"], +["0 0x02 0x0000", "MAX DROP 1", "MINIMALDATA"], +["0x02 0x0000 0", "MAX DROP 1", "MINIMALDATA"], + +["0x02 0x0000 0 0", "WITHIN DROP 1", "MINIMALDATA"], +["0 0x02 0x0000 0", "WITHIN DROP 1", "MINIMALDATA"], +["0 0 0x02 0x0000", "WITHIN DROP 1", "MINIMALDATA"], + +["0 0 0x02 0x0000", "CHECKMULTISIG DROP 1", "MINIMALDATA"], +["0 0x02 0x0000 0", "CHECKMULTISIG DROP 1", "MINIMALDATA"], +["0 0x02 0x0000 0 1", "CHECKMULTISIG DROP 1", "MINIMALDATA"], +["0 0 0x02 0x0000", "CHECKMULTISIGVERIFY 1", "MINIMALDATA"], +["0 0x02 0x0000 0", "CHECKMULTISIGVERIFY 1", "MINIMALDATA"], + + ["Order of CHECKMULTISIG evaluation tests, inverted by swapping the order of"], ["pubkeys/signatures so they fail due to the STRICTENC rules on validly encoded"], ["signatures and pubkeys."], diff --git a/txscript/data/script_valid.json b/txscript/data/script_valid.json index 6fdc2c9a..1ee4b690 100644 --- a/txscript/data/script_valid.json +++ b/txscript/data/script_valid.json @@ -569,6 +569,51 @@ ["0x04 0xffff7f80", "0x03 0xffffff NUMEQUAL", "", ""], ["0x04 0xffff7f00", "0x03 0xffff7f NUMEQUAL", "", ""], +["Unevaluated non-minimal pushes are ignored"], + +["0 IF 0x4c 0x00 ENDIF 1", "", "MINIMALDATA", "non-minimal PUSHDATA1 ignored"], +["0 IF 0x4d 0x0000 ENDIF 1", "", "MINIMALDATA", "non-minimal PUSHDATA2 ignored"], +["0 IF 0x4c 0x00000000 ENDIF 1", "", "MINIMALDATA", "non-minimal PUSHDATA4 ignored"], +["0 IF 0x01 0x81 ENDIF 1", "", "MINIMALDATA", "1NEGATE equiv"], +["0 IF 0x01 0x01 ENDIF 1", "", "MINIMALDATA", "OP_1 equiv"], +["0 IF 0x01 0x02 ENDIF 1", "", "MINIMALDATA", "OP_2 equiv"], +["0 IF 0x01 0x03 ENDIF 1", "", "MINIMALDATA", "OP_3 equiv"], +["0 IF 0x01 0x04 ENDIF 1", "", "MINIMALDATA", "OP_4 equiv"], +["0 IF 0x01 0x05 ENDIF 1", "", "MINIMALDATA", "OP_5 equiv"], +["0 IF 0x01 0x06 ENDIF 1", "", "MINIMALDATA", "OP_6 equiv"], +["0 IF 0x01 0x07 ENDIF 1", "", "MINIMALDATA", "OP_7 equiv"], +["0 IF 0x01 0x08 ENDIF 1", "", "MINIMALDATA", "OP_8 equiv"], +["0 IF 0x01 0x09 ENDIF 1", "", "MINIMALDATA", "OP_9 equiv"], +["0 IF 0x01 0x0a ENDIF 1", "", "MINIMALDATA", "OP_10 equiv"], +["0 IF 0x01 0x0b ENDIF 1", "", "MINIMALDATA", "OP_11 equiv"], +["0 IF 0x01 0x0c ENDIF 1", "", "MINIMALDATA", "OP_12 equiv"], +["0 IF 0x01 0x0d ENDIF 1", "", "MINIMALDATA", "OP_13 equiv"], +["0 IF 0x01 0x0e ENDIF 1", "", "MINIMALDATA", "OP_14 equiv"], +["0 IF 0x01 0x0f ENDIF 1", "", "MINIMALDATA", "OP_15 equiv"], +["0 IF 0x01 0x10 ENDIF 1", "", "MINIMALDATA", "OP_16 equiv"], + +["Numeric minimaldata rules are only applied when a stack item is numerically evaluated; the push itself is allowed"], + +["0x01 0x00", "1", "MINIMALDATA"], +["0x01 0x80", "1", "MINIMALDATA"], +["0x02 0x0180", "1", "MINIMALDATA"], +["0x02 0x0100", "1", "MINIMALDATA"], +["0x02 0x0200", "1", "MINIMALDATA"], +["0x02 0x0300", "1", "MINIMALDATA"], +["0x02 0x0400", "1", "MINIMALDATA"], +["0x02 0x0500", "1", "MINIMALDATA"], +["0x02 0x0600", "1", "MINIMALDATA"], +["0x02 0x0700", "1", "MINIMALDATA"], +["0x02 0x0800", "1", "MINIMALDATA"], +["0x02 0x0900", "1", "MINIMALDATA"], +["0x02 0x0a00", "1", "MINIMALDATA"], +["0x02 0x0b00", "1", "MINIMALDATA"], +["0x02 0x0c00", "1", "MINIMALDATA"], +["0x02 0x0d00", "1", "MINIMALDATA"], +["0x02 0x0e00", "1", "MINIMALDATA"], +["0x02 0x0f00", "1", "MINIMALDATA"], +["0x02 0x1000", "1", "MINIMALDATA"], + ["Valid version of the 'Test every numeric-accepting opcode for correct handling of the numeric minimal encoding rule' script_invalid test"], ["1 0x02 0x0000", "PICK DROP", ""], diff --git a/txscript/internal_test.go b/txscript/internal_test.go index d8c79044..2d73a50a 100644 --- a/txscript/internal_test.go +++ b/txscript/internal_test.go @@ -4563,11 +4563,15 @@ func parseScriptFlags(flagStr string) (ScriptFlags, error) { sFlags := strings.Split(flagStr, ",") for _, flag := range sFlags { switch flag { + case "": + // Nothing. case "DERSIG": flags |= ScriptVerifyDERSignatures case "DISCOURAGE_UPGRADABLE_NOPS": flags |= ScriptDiscourageUpgradableNops - case "", "NONE": + case "MINIMALDATA": + flags |= ScriptVerifyMinimalData + case "NONE": // Nothing. case "NULLDUMMY": flags |= ScriptStrictMultiSig diff --git a/txscript/opcode.go b/txscript/opcode.go index 141f807b..a701dc46 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -933,6 +933,41 @@ func (pop *parsedOpcode) conditional() bool { } } +// checkMinimalDataPush returns whether or not the current data +// push uses the correct opcode. +func (pop *parsedOpcode) checkMinimalDataPush() error { + data := pop.data + dataLen := len(data) + opcode := pop.opcode.value + + if dataLen == 0 && opcode != OP_0 { + return ErrStackMinimalData + } else if dataLen == 1 && data[0] >= 1 && data[0] <= 16 { + if opcode != OP_1+data[0]-1 { + // Should have used OP_1 .. OP_16 + return ErrStackMinimalData + } + } else if dataLen == 1 && data[0] == 0x81 { + if opcode != OP_1NEGATE { + return ErrStackMinimalData + } + } else if dataLen <= 75 { + if int(opcode) != dataLen { + // Should have used a direct push + return ErrStackMinimalData + } + } else if dataLen <= 255 { + if opcode != OP_PUSHDATA1 { + return ErrStackMinimalData + } + } else if dataLen <= 65535 { + if opcode != OP_PUSHDATA2 { + return ErrStackMinimalData + } + } + return nil +} + // exec peforms execution on the opcode. It takes into account whether or not // it is hidden by conditionals, but some rules still must be tested in this // case. @@ -963,6 +998,16 @@ func (pop *parsedOpcode) exec(s *Script) error { if s.condStack[0] != OpCondTrue && !pop.conditional() { return nil } + + // Ensure all executed data push opcodes use the minimal encoding when + // the minimal data verification is set. + if s.dstack.verifyMinimalData && s.condStack[0] == OpCondTrue && + pop.opcode.value >= 0 && pop.opcode.value <= OP_PUSHDATA4 { + if err := pop.checkMinimalDataPush(); err != nil { + return err + } + } + return pop.opcode.opfunc(pop, s) } diff --git a/txscript/script.go b/txscript/script.go index 8470b866..70483a0a 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -126,6 +126,11 @@ var ( // ErrStackInvalidPubKey is returned when the ScriptVerifyScriptEncoding // flag is set and the script contains invalid pubkeys. ErrStackInvalidPubKey = errors.New("invalid strict pubkey") + + // ErrStackMinimalData is returned when the ScriptVerifyMinimalData flag + // is set and the script contains push operations that do not use + // the minimal opcode required. + ErrStackMinimalData = errors.New("non-minimally encoded script number") ) const ( @@ -638,6 +643,10 @@ const ( // to compily with the DER format. ScriptVerifyDERSignatures + // ScriptVerifyMinimalData defines that signatures must use the smallest + // push operator. This is both rules 3 and 4 of BIP0062. + ScriptVerifyMinimalData + // ScriptVerifySigPushOnly defines that signature scripts must contain // only pushed data. This is rule 2 of BIP0062. ScriptVerifySigPushOnly @@ -700,6 +709,10 @@ func NewScript(scriptSig []byte, scriptPubKey []byte, txidx int, tx *wire.MsgTx, if flags&ScriptVerifyDERSignatures == ScriptVerifyDERSignatures { m.verifyDERSignatures = true } + if flags&ScriptVerifyMinimalData == ScriptVerifyMinimalData { + m.dstack.verifyMinimalData = true + m.astack.verifyMinimalData = true + } m.tx = *tx m.txidx = txidx diff --git a/txscript/stack.go b/txscript/stack.go index c2fdba4a..6e096c56 100644 --- a/txscript/stack.go +++ b/txscript/stack.go @@ -100,7 +100,34 @@ func fromBool(v bool) []byte { // Objects may be shared, therefore in usage if a value is to be changed it // *must* be deep-copied first to avoid changing other values on the stack. type Stack struct { - stk [][]byte + stk [][]byte + verifyMinimalData bool +} + +// checkMinimalData returns whether or not the passed byte array adheres to +// the minimal encoding requirements, if enabled. +func (s *Stack) checkMinimalData(so []byte) error { + if !s.verifyMinimalData || len(so) == 0 { + return nil + } + + // Check that the number is encoded with the minimum possible + // number of bytes. + // + // If the most-significant-byte - excluding the sign bit - is zero + // then we're not minimal. Note how this test also rejects the + // negative-zero encoding, 0x80. + if so[len(so)-1]&0x7f == 0 { + // One exception: if there's more than one byte and the most + // significant bit of the second-most-significant-byte is set + // it would conflict with the sign bit. An example of this case + // is +-255, which encode to 0xff00 and 0xff80 respectively. + // (big-endian). + if len(so) == 1 || so[len(so)-2]&0x80 == 0 { + return ErrStackMinimalData + } + } + return nil } // PushByteArray adds the given back array to the top of the stack. @@ -132,6 +159,11 @@ func (s *Stack) PopInt() (*big.Int, error) { if err != nil { return nil, err } + + if err := s.checkMinimalData(so); err != nil { + return nil, err + } + return asInt(so) } @@ -160,6 +192,11 @@ func (s *Stack) PeekInt(idx int) (i *big.Int, err error) { if err != nil { return nil, err } + + if err := s.checkMinimalData(so); err != nil { + return nil, err + } + return asInt(so) }