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:
parent
d3aebcaed3
commit
9523345814
5 changed files with 114 additions and 14 deletions
|
@ -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"]
|
||||
]
|
||||
|
|
|
@ -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"]
|
||||
]
|
||||
|
|
|
@ -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":
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue