Skip to content

Commit

Permalink
txscript: Add new flag ScriptVerifyMinimalData
Browse files Browse the repository at this point in the history
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
  • Loading branch information
dajohi committed Feb 24, 2015
1 parent 63c1172 commit 5a4312d
Show file tree
Hide file tree
Showing 6 changed files with 242 additions and 2 deletions.
96 changes: 96 additions & 0 deletions txscript/data/script_invalid.json
Original file line number Diff line number Diff line change
Expand Up @@ -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."],
Expand Down
45 changes: 45 additions & 0 deletions txscript/data/script_valid.json
Original file line number Diff line number Diff line change
Expand Up @@ -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", ""],
Expand Down
6 changes: 5 additions & 1 deletion txscript/internal_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
45 changes: 45 additions & 0 deletions txscript/opcode.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}

Expand Down
13 changes: 13 additions & 0 deletions txscript/script.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 (
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
39 changes: 38 additions & 1 deletion txscript/stack.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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)
}

Expand Down Expand Up @@ -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)
}

Expand Down

0 comments on commit 5a4312d

Please sign in to comment.