diff --git a/txscript/data/script_invalid.json b/txscript/data/script_invalid.json index 91e4696c..99d65551 100644 --- a/txscript/data/script_invalid.json +++ b/txscript/data/script_invalid.json @@ -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"] ] diff --git a/txscript/data/script_valid.json b/txscript/data/script_valid.json index 9b3d2f33..34e2c8d6 100644 --- a/txscript/data/script_valid.json +++ b/txscript/data/script_valid.json @@ -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"] ] diff --git a/txscript/internal_test.go b/txscript/internal_test.go index 8f47cbe1..e307313f 100644 --- a/txscript/internal_test.go +++ b/txscript/internal_test.go @@ -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": diff --git a/txscript/script.go b/txscript/script.go index 530a6ef6..ba270372 100644 --- a/txscript/script.go +++ b/txscript/script.go @@ -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 diff --git a/txscript/script_test.go b/txscript/script_test.go index 904fa793..0777879b 100644 --- a/txscript/script_test.go +++ b/txscript/script_test.go @@ -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) + } + } +}