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",
"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"]
]

View file

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

View file

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

View file

@ -127,10 +127,19 @@ var (
// flag is set and the script contains invalid pubkeys.
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
// is set and the script contains push operations that do not use
// the minimal opcode required.
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 (
@ -222,6 +231,7 @@ type Script struct {
strictMultiSig bool // verify multisig stack item is zero length
discourageUpgradableNops bool // NOP1 to NOP10 are reserved for future soft-fork upgrades
verifyStrictEncoding bool // verify strict encoding of signatures
verifyCleanStack bool // verify stack is clean after script evaluation
verifyDERSignatures bool // verify signatures compily with the DER
savedFirstStack [][]byte // stack from first script for bip16 scripts
}
@ -624,6 +634,12 @@ const (
// executed.
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
// to compily with the DER format.
ScriptVerifyDERSignatures
@ -655,7 +671,8 @@ const (
ScriptVerifyStrictEncoding |
ScriptVerifyMinimalData |
ScriptStrictMultiSig |
ScriptDiscourageUpgradableNops
ScriptDiscourageUpgradableNops |
ScriptVerifyCleanStack
)
// 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.
bip16 := flags&ScriptBip16 == ScriptBip16
if bip16 && isScriptHash(m.scripts[1]) {
if flags&ScriptBip16 == ScriptBip16 && isScriptHash(m.scripts[1]) {
// if we are pay to scripthash then we only accept input
// scripts that push data
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.astack.verifyMinimalData = true
}
if flags&ScriptVerifyCleanStack == ScriptVerifyCleanStack {
if flags&ScriptBip16 != ScriptBip16 {
return nil, ErrInvalidFlags
}
m.verifyCleanStack = true
}
m.tx = *tx
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
// successful, leaving a a true boolean on the stack. An error otherwise,
// 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
// then we have run out of scripts to run.
if s.scriptidx < len(s.scripts) {
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
}
v, err := s.dstack.PopBool()
if err == nil && v == false {
if err != nil {
return err
}
if v == false {
// log interesting data.
log.Tracef("%v", newLogClosure(func() string {
dis0, _ := s.DisasmScript(0)
@ -779,9 +807,9 @@ func (s *Script) CheckErrorCondition() (err error) {
return fmt.Sprintf("scripts failed: script0: %s\n"+
"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
@ -827,7 +855,7 @@ func (s *Script) Step() (done bool, err error) {
s.scriptidx++
// We check script ran ok, if so then we pull
// the script out of the first stack and executre that.
err := s.CheckErrorCondition()
err := s.CheckErrorCondition(false)
if err != nil {
return false, err
}
@ -880,10 +908,12 @@ func (s *Script) validPC() error {
// DisasmScript returns the disassembly string for the script at offset
// ``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) {
return "", ErrStackInvalidIndex
}
var disstr string
for i := range s.scripts[idx] {
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
// 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()
if err != nil {
return "", err

View file

@ -2559,7 +2559,7 @@ func TestCheckErrorCondition(t *testing.T) {
return
}
err = engine.CheckErrorCondition()
err = engine.CheckErrorCondition(false)
if err != txscript.ErrStackScriptUnfinished {
t.Errorf("got unexepected error %v on %dth iteration",
err, i)
@ -2576,7 +2576,7 @@ func TestCheckErrorCondition(t *testing.T) {
return
}
err = engine.CheckErrorCondition()
err = engine.CheckErrorCondition(false)
if err != nil {
t.Errorf("unexpected error %v on final check", err)
}
@ -4816,3 +4816,53 @@ func TestIsPushOnlyScript(t *testing.T) {
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)
}
}
}