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",
|
"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"]
|
||||||
]
|
]
|
||||||
|
|
|
@ -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"]
|
||||||
]
|
]
|
||||||
|
|
|
@ -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":
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue