txscript: Add new flag ScriptVerifyCleanStack

The ScriptVerifyCleanStack flag requires that only a single
stack element remains after evaluation and that when interpreted
as a bool, it must be true.  This is BIP0062, rule 6.

This mimics Bitcoin Core commit b6e03cc59208305681745ad06f2056ffe6690597
This commit is contained in:
David Hill 2015-02-25 15:04:37 -05:00
parent d3aebcaed3
commit 9523345814
5 changed files with 114 additions and 14 deletions

View file

@ -779,6 +779,18 @@
"P2SH,STRICTENC", "P2SH,STRICTENC",
"2-of-3 with one valid and one invalid signature due to parse error, nSigs > validSigs" "2-of-3 with one valid and one invalid signature due to parse error, nSigs > validSigs"
], ],
[
"11 0x47 0x3044022053205076a7bb13d2db3162a2d97d8197631f829b065948b7019b15482af819a902204328dcc02c994ca086b1226d0d5f1674d23cfae0d846143df812b81cab3391e801",
"0x41 0x0479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8 CHECKSIG",
"CLEANSTACK,P2SH",
"P2PK with unnecessary input"
],
[
"11 0x47 0x304402202f7505132be14872581f35d74b759212d9da40482653f1ffa3116c3294a4a51702206adbf347a2240ca41c66522b1a22a41693610b76a8e7770645dc721d1635854f01 0x43 0x410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac",
"HASH160 0x14 0x31edc23bdafda4639e669f89ad6b2318dd79d032 EQUAL",
"CLEANSTACK,P2SH",
"P2SH with unnecessary input"
],
["The End"] ["The End"]
] ]

View file

@ -897,6 +897,12 @@
"P2SH", "P2SH",
"P2SH with unnecessary input but no CLEANSTACK" "P2SH with unnecessary input but no CLEANSTACK"
], ],
[
"0x47 0x304402202f7505132be14872581f35d74b759212d9da40482653f1ffa3116c3294a4a51702206adbf347a2240ca41c66522b1a22a41693610b76a8e7770645dc721d1635854f01 0x43 0x410479be667ef9dcbbac55a06295ce870b07029bfcdb2dce28d959f2815b16f81798483ada7726a3c4655da4fbfc0e1108a8fd17b448a68554199c47d08ffb10d4b8ac",
"HASH160 0x14 0x31edc23bdafda4639e669f89ad6b2318dd79d032 EQUAL",
"CLEANSTACK,P2SH",
"P2SH with CLEANSTACK"
],
["The End"] ["The End"]
] ]

View file

@ -4573,6 +4573,8 @@ func parseScriptFlags(flagStr string) (ScriptFlags, error) {
switch flag { switch flag {
case "": case "":
// Nothing. // Nothing.
case "CLEANSTACK":
flags |= ScriptVerifyCleanStack
case "DERSIG": case "DERSIG":
flags |= ScriptVerifyDERSignatures flags |= ScriptVerifyDERSignatures
case "DISCOURAGE_UPGRADABLE_NOPS": case "DISCOURAGE_UPGRADABLE_NOPS":

View file

@ -127,10 +127,19 @@ var (
// flag is set and the script contains invalid pubkeys. // flag is set and the script contains invalid pubkeys.
ErrStackInvalidPubKey = errors.New("invalid strict pubkey") ErrStackInvalidPubKey = errors.New("invalid strict pubkey")
// ErrStackCleanStack is returned when the ScriptVerifyCleanStack flag
// is set and after evalution the stack does not contain only one element,
// which also must be true if interpreted as a boolean.
ErrStackCleanStack = errors.New("stack is not clean")
// ErrStackMinimalData is returned when the ScriptVerifyMinimalData flag // ErrStackMinimalData is returned when the ScriptVerifyMinimalData flag
// is set and the script contains push operations that do not use // is set and the script contains push operations that do not use
// the minimal opcode required. // the minimal opcode required.
ErrStackMinimalData = errors.New("non-minimally encoded script number") ErrStackMinimalData = errors.New("non-minimally encoded script number")
// ErrInvalidFlags is returned when the passed flags to NewScript contain
// an invalid combination.
ErrInvalidFlags = errors.New("invalid flags combination")
) )
const ( const (
@ -222,6 +231,7 @@ type Script struct {
strictMultiSig bool // verify multisig stack item is zero length strictMultiSig bool // verify multisig stack item is zero length
discourageUpgradableNops bool // NOP1 to NOP10 are reserved for future soft-fork upgrades discourageUpgradableNops bool // NOP1 to NOP10 are reserved for future soft-fork upgrades
verifyStrictEncoding bool // verify strict encoding of signatures verifyStrictEncoding bool // verify strict encoding of signatures
verifyCleanStack bool // verify stack is clean after script evaluation
verifyDERSignatures bool // verify signatures compily with the DER verifyDERSignatures bool // verify signatures compily with the DER
savedFirstStack [][]byte // stack from first script for bip16 scripts savedFirstStack [][]byte // stack from first script for bip16 scripts
} }
@ -624,6 +634,12 @@ const (
// executed. // executed.
ScriptDiscourageUpgradableNops ScriptDiscourageUpgradableNops
// ScriptVerifyCleanStack defines that the stack must contain only
// one stack element after evaluation and that the element must be
// true if interpreted as a boolean. This is rule 6 of BIP0062.
// This flag should never be used without the ScriptBip16 flag.
ScriptVerifyCleanStack
// ScriptVerifyDERSignatures defines that signatures are required // ScriptVerifyDERSignatures defines that signatures are required
// to compily with the DER format. // to compily with the DER format.
ScriptVerifyDERSignatures ScriptVerifyDERSignatures
@ -655,7 +671,8 @@ const (
ScriptVerifyStrictEncoding | ScriptVerifyStrictEncoding |
ScriptVerifyMinimalData | ScriptVerifyMinimalData |
ScriptStrictMultiSig | ScriptStrictMultiSig |
ScriptDiscourageUpgradableNops ScriptDiscourageUpgradableNops |
ScriptVerifyCleanStack
) )
// NewScript returns a new script engine for the provided tx and input idx with // NewScript returns a new script engine for the provided tx and input idx with
@ -690,8 +707,7 @@ func NewScript(scriptSig []byte, scriptPubKey []byte, txidx int, tx *wire.MsgTx,
} }
// Parse flags. // Parse flags.
bip16 := flags&ScriptBip16 == ScriptBip16 if flags&ScriptBip16 == ScriptBip16 && isScriptHash(m.scripts[1]) {
if bip16 && isScriptHash(m.scripts[1]) {
// if we are pay to scripthash then we only accept input // if we are pay to scripthash then we only accept input
// scripts that push data // scripts that push data
if !isPushOnly(m.scripts[0]) { if !isPushOnly(m.scripts[0]) {
@ -715,6 +731,12 @@ func NewScript(scriptSig []byte, scriptPubKey []byte, txidx int, tx *wire.MsgTx,
m.dstack.verifyMinimalData = true m.dstack.verifyMinimalData = true
m.astack.verifyMinimalData = true m.astack.verifyMinimalData = true
} }
if flags&ScriptVerifyCleanStack == ScriptVerifyCleanStack {
if flags&ScriptBip16 != ScriptBip16 {
return nil, ErrInvalidFlags
}
m.verifyCleanStack = true
}
m.tx = *tx m.tx = *tx
m.txidx = txidx m.txidx = txidx
@ -755,23 +777,29 @@ func (s *Script) Execute() (err error) {
})) }))
} }
return s.CheckErrorCondition() return s.CheckErrorCondition(true)
} }
// CheckErrorCondition returns nil if the running script has ended and was // CheckErrorCondition returns nil if the running script has ended and was
// successful, leaving a a true boolean on the stack. An error otherwise, // successful, leaving a a true boolean on the stack. An error otherwise,
// including if the script has not finished. // including if the script has not finished.
func (s *Script) CheckErrorCondition() (err error) { func (s *Script) CheckErrorCondition(finalScript bool) error {
// Check we are actually done. if pc is past the end of script array // Check we are actually done. if pc is past the end of script array
// then we have run out of scripts to run. // then we have run out of scripts to run.
if s.scriptidx < len(s.scripts) { if s.scriptidx < len(s.scripts) {
return ErrStackScriptUnfinished return ErrStackScriptUnfinished
} }
if s.dstack.Depth() < 1 { if finalScript && s.verifyCleanStack && s.dstack.Depth() != 1 {
return ErrStackCleanStack
} else if s.dstack.Depth() < 1 {
return ErrStackEmptyStack return ErrStackEmptyStack
} }
v, err := s.dstack.PopBool() v, err := s.dstack.PopBool()
if err == nil && v == false { if err != nil {
return err
}
if v == false {
// log interesting data. // log interesting data.
log.Tracef("%v", newLogClosure(func() string { log.Tracef("%v", newLogClosure(func() string {
dis0, _ := s.DisasmScript(0) dis0, _ := s.DisasmScript(0)
@ -779,9 +807,9 @@ func (s *Script) CheckErrorCondition() (err error) {
return fmt.Sprintf("scripts failed: script0: %s\n"+ return fmt.Sprintf("scripts failed: script0: %s\n"+
"script1: %s", dis0, dis1) "script1: %s", dis0, dis1)
})) }))
err = ErrStackScriptFailed return ErrStackScriptFailed
} }
return err return nil
} }
// Step will execute the next instruction and move the program counter to the // Step will execute the next instruction and move the program counter to the
@ -827,7 +855,7 @@ func (s *Script) Step() (done bool, err error) {
s.scriptidx++ s.scriptidx++
// We check script ran ok, if so then we pull // We check script ran ok, if so then we pull
// the script out of the first stack and executre that. // the script out of the first stack and executre that.
err := s.CheckErrorCondition() err := s.CheckErrorCondition(false)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -880,10 +908,12 @@ func (s *Script) validPC() error {
// DisasmScript returns the disassembly string for the script at offset // DisasmScript returns the disassembly string for the script at offset
// ``idx''. Where 0 is the scriptSig and 1 is the scriptPubKey. // ``idx''. Where 0 is the scriptSig and 1 is the scriptPubKey.
func (s *Script) DisasmScript(idx int) (disstr string, err error) { func (s *Script) DisasmScript(idx int) (string, error) {
if idx >= len(s.scripts) { if idx >= len(s.scripts) {
return "", ErrStackInvalidIndex return "", ErrStackInvalidIndex
} }
var disstr string
for i := range s.scripts[idx] { for i := range s.scripts[idx] {
disstr = disstr + s.disasm(idx, i) + "\n" disstr = disstr + s.disasm(idx, i) + "\n"
} }
@ -892,7 +922,7 @@ func (s *Script) DisasmScript(idx int) (disstr string, err error) {
// DisasmPC returns the string for the disassembly of the opcode that will be // DisasmPC returns the string for the disassembly of the opcode that will be
// next to execute when Step() is called. // next to execute when Step() is called.
func (s *Script) DisasmPC() (disstr string, err error) { func (s *Script) DisasmPC() (string, error) {
scriptidx, scriptoff, err := s.curPC() scriptidx, scriptoff, err := s.curPC()
if err != nil { if err != nil {
return "", err return "", err

View file

@ -2559,7 +2559,7 @@ func TestCheckErrorCondition(t *testing.T) {
return return
} }
err = engine.CheckErrorCondition() err = engine.CheckErrorCondition(false)
if err != txscript.ErrStackScriptUnfinished { if err != txscript.ErrStackScriptUnfinished {
t.Errorf("got unexepected error %v on %dth iteration", t.Errorf("got unexepected error %v on %dth iteration",
err, i) err, i)
@ -2576,7 +2576,7 @@ func TestCheckErrorCondition(t *testing.T) {
return return
} }
err = engine.CheckErrorCondition() err = engine.CheckErrorCondition(false)
if err != nil { if err != nil {
t.Errorf("unexpected error %v on final check", err) t.Errorf("unexpected error %v on final check", err)
} }
@ -4816,3 +4816,53 @@ func TestIsPushOnlyScript(t *testing.T) {
test.expected) test.expected)
} }
} }
func TestInvalidFlagCombinations(t *testing.T) {
t.Parallel()
tests := []txscript.ScriptFlags{
txscript.ScriptVerifyCleanStack,
}
// tx with almost empty scripts.
tx := &wire.MsgTx{
Version: 1,
TxIn: []*wire.TxIn{
{
PreviousOutPoint: wire.OutPoint{
Hash: wire.ShaHash([32]byte{
0xc9, 0x97, 0xa5, 0xe5,
0x6e, 0x10, 0x41, 0x02,
0xfa, 0x20, 0x9c, 0x6a,
0x85, 0x2d, 0xd9, 0x06,
0x60, 0xa2, 0x0b, 0x2d,
0x9c, 0x35, 0x24, 0x23,
0xed, 0xce, 0x25, 0x85,
0x7f, 0xcd, 0x37, 0x04,
}),
Index: 0,
},
SignatureScript: []uint8{txscript.OP_NOP},
Sequence: 4294967295,
},
},
TxOut: []*wire.TxOut{
{
Value: 1000000000,
PkScript: []byte{},
},
},
LockTime: 0,
}
pkScript := []byte{txscript.OP_NOP}
for i, test := range tests {
_, err := txscript.NewScript(tx.TxIn[0].SignatureScript,
pkScript, 0, tx, test)
if err != txscript.ErrInvalidFlags {
t.Fatalf("TestInvalidFlagCombinations #%d unexpected "+
"error: %v", i, err)
}
}
}