mirror of
https://github.com/btcsuite/btcd.git
synced 2025-01-19 05:33:36 +01:00
Initial implementation.
This commit is contained in:
parent
63af0dbca9
commit
1197770159
13
LICENSE
Normal file
13
LICENSE
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
|
||||||
|
Permission to use, copy, modify, and distribute this software for any
|
||||||
|
purpose with or without fee is hereby granted, provided that the above
|
||||||
|
copyright notice and this permission notice appear in all copies.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||||
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||||
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||||
|
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||||
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||||
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||||
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
73
README.md
73
README.md
@ -1,4 +1,75 @@
|
|||||||
btcscript
|
btcscript
|
||||||
=========
|
=========
|
||||||
|
|
||||||
Package btcscript implements the bitcoin transaction scripts.
|
Package btcscript implements the bitcoin transaction scripts. There is a test
|
||||||
|
suite which is aiming to reach 100% code coverage. See
|
||||||
|
`test_coverage.txt` for the current coverage (using gocov). On a
|
||||||
|
UNIX-like OS, the script `cov_report.sh` can be used to generate the
|
||||||
|
report. Package btcscript is licensed under the liberal ISC license.
|
||||||
|
|
||||||
|
This package is one of the core packages from btcd, an alternative full-node
|
||||||
|
implementation of bitcoin which is under active development by Conformal.
|
||||||
|
Although it was primarily written for btcd, this package has intentionally been
|
||||||
|
designed so it can be used as a standalone package for any projects needing to
|
||||||
|
use or validate bitcoin transaction scripts.
|
||||||
|
|
||||||
|
## Bitcoin Scripts
|
||||||
|
|
||||||
|
Bitcoin provides a stack-based, FORTH-like langauge for the scripts in
|
||||||
|
the bitcoin transactions. This language is not turing complete
|
||||||
|
although it is still fairly powerful. A description of the language
|
||||||
|
can be found at https://en.bitcoin.it/wiki/Script
|
||||||
|
|
||||||
|
## Sample Use
|
||||||
|
|
||||||
|
```Go
|
||||||
|
pkscript := txS.TxOut[origintxidx].PkScript
|
||||||
|
engine, err := btcscript.NewScript(sigScript, pkscript, txInIdx,
|
||||||
|
txValidator, pver,
|
||||||
|
timestamp.After(btcscript.Bip16Activation))
|
||||||
|
err = engine.Execute()
|
||||||
|
```
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
Full `go doc` style documentation for the project can be viewed online without
|
||||||
|
installing this package by using the GoDoc site
|
||||||
|
[here](http://godoc.org/github.com/conformal/btcscript).
|
||||||
|
|
||||||
|
You can also view the documentation locally once the package is installed with
|
||||||
|
the `godoc` tool by running `godoc -http=":6060"` and pointing your browser to
|
||||||
|
http://localhost:6060/pkg/github.com/conformal/btcscript
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
$ go get github.com/conformal/btcscript
|
||||||
|
```
|
||||||
|
|
||||||
|
## TODO
|
||||||
|
|
||||||
|
- Increase test coverage to 100%
|
||||||
|
|
||||||
|
## GPG Verification Key
|
||||||
|
|
||||||
|
All official release tags are signed by Conformal so users can ensure the code
|
||||||
|
has not been tampered with and is coming from Conformal. To verify the
|
||||||
|
signature perform the following:
|
||||||
|
|
||||||
|
- Download the public key from the Conformal website at
|
||||||
|
https://opensource.conformal.com/GIT-GPG-KEY-conformal.txt
|
||||||
|
|
||||||
|
- Import the public key into your GPG keyring:
|
||||||
|
```bash
|
||||||
|
gpg --import GIT-GPG-KEY-conformal.txt
|
||||||
|
```
|
||||||
|
|
||||||
|
- Verify the release tag with the following command where `TAG_NAME` is a
|
||||||
|
placeholder for the specific tag:
|
||||||
|
```bash
|
||||||
|
git tag -v TAG_NAME
|
||||||
|
```
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Package btcscript is licensed under the liberal ISC License.
|
||||||
|
190
address.go
Normal file
190
address.go
Normal file
@ -0,0 +1,190 @@
|
|||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcscript
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/conformal/btcutil"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScriptType is an enum type that represents the type of a script. It is
|
||||||
|
// returned from ScriptToAddress as part of the metadata about the script.
|
||||||
|
// It implements the Stringer interface for nice printing.
|
||||||
|
type ScriptType int
|
||||||
|
|
||||||
|
// String Converts the enumeration to a nice string value instead of a number.
|
||||||
|
func (t ScriptType) String() string {
|
||||||
|
if int(t) > len(scriptTypeToName) || int(t) < 0 {
|
||||||
|
return "Invalid"
|
||||||
|
}
|
||||||
|
return scriptTypeToName[t]
|
||||||
|
}
|
||||||
|
|
||||||
|
// Constant types representing known types of script found in the wild
|
||||||
|
const (
|
||||||
|
ScriptUnknown ScriptType = iota
|
||||||
|
ScriptAddr
|
||||||
|
ScriptPubKey
|
||||||
|
ScriptStrange
|
||||||
|
ScriptGeneration
|
||||||
|
)
|
||||||
|
|
||||||
|
var scriptTypeToName = []string{
|
||||||
|
ScriptUnknown: "Unknown",
|
||||||
|
ScriptAddr: "Addr",
|
||||||
|
ScriptPubKey: "Pubkey",
|
||||||
|
ScriptStrange: "Strange",
|
||||||
|
ScriptGeneration: "Generation", // ScriptToAddress does not recieve enough information to identify Generation scripts.
|
||||||
|
}
|
||||||
|
|
||||||
|
type pkformat struct {
|
||||||
|
addrtype ScriptType
|
||||||
|
parsetype int
|
||||||
|
length int
|
||||||
|
databytes []pkbytes
|
||||||
|
allowmore bool
|
||||||
|
}
|
||||||
|
|
||||||
|
type pkbytes struct {
|
||||||
|
off int
|
||||||
|
val byte
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
scrPayAddr = iota
|
||||||
|
scrCollectAddr
|
||||||
|
scrCollectAddrComp
|
||||||
|
scrGeneratePubkeyAddr
|
||||||
|
scrPubkeyAddr
|
||||||
|
scrPubkeyAddrComp
|
||||||
|
scrNoAddr
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScriptToAddress extracts a payment address and the type out of a PkScript
|
||||||
|
func ScriptToAddress(script []byte) (ScriptType, string, error) {
|
||||||
|
// Currently this only understands one form of PkScript
|
||||||
|
validformats := []pkformat{
|
||||||
|
{ScriptAddr, scrPayAddr, 25, []pkbytes{{0, OP_DUP}, {1, OP_HASH160}, {2, OP_DATA_20}, {23, OP_EQUALVERIFY}, {24, OP_CHECKSIG}}, true},
|
||||||
|
{ScriptAddr, scrCollectAddr, 142, []pkbytes{{0, OP_DATA_75}, {76, OP_DATA_65}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddr, 141, []pkbytes{{0, OP_DATA_74}, {75, OP_DATA_65}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddr, 140, []pkbytes{{0, OP_DATA_73}, {74, OP_DATA_65}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddr, 139, []pkbytes{{0, OP_DATA_72}, {73, OP_DATA_65}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddr, 138, []pkbytes{{0, OP_DATA_71}, {72, OP_DATA_65}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddr, 137, []pkbytes{{0, OP_DATA_70}, {71, OP_DATA_65}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddr, 136, []pkbytes{{0, OP_DATA_69}, {70, OP_DATA_65}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddrComp, 110, []pkbytes{{0, OP_DATA_75}, {76, OP_DATA_33}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddrComp, 109, []pkbytes{{0, OP_DATA_74}, {75, OP_DATA_33}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddrComp, 108, []pkbytes{{0, OP_DATA_73}, {74, OP_DATA_33}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddrComp, 107, []pkbytes{{0, OP_DATA_72}, {73, OP_DATA_33}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddrComp, 106, []pkbytes{{0, OP_DATA_71}, {72, OP_DATA_33}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddrComp, 105, []pkbytes{{0, OP_DATA_70}, {71, OP_DATA_33}}, false},
|
||||||
|
{ScriptAddr, scrCollectAddrComp, 104, []pkbytes{{0, OP_DATA_69}, {70, OP_DATA_33}}, false},
|
||||||
|
{ScriptPubKey, scrGeneratePubkeyAddr, 74, []pkbytes{{0, OP_DATA_73}}, false},
|
||||||
|
{ScriptPubKey, scrGeneratePubkeyAddr, 73, []pkbytes{{0, OP_DATA_72}}, false},
|
||||||
|
{ScriptPubKey, scrGeneratePubkeyAddr, 72, []pkbytes{{0, OP_DATA_71}}, false},
|
||||||
|
{ScriptPubKey, scrGeneratePubkeyAddr, 71, []pkbytes{{0, OP_DATA_70}}, false},
|
||||||
|
{ScriptPubKey, scrGeneratePubkeyAddr, 70, []pkbytes{{0, OP_DATA_69}}, false},
|
||||||
|
{ScriptPubKey, scrPubkeyAddr, 67, []pkbytes{{0, OP_DATA_65}, {66, OP_CHECKSIG}}, true},
|
||||||
|
{ScriptPubKey, scrPubkeyAddrComp, 35, []pkbytes{{0, OP_DATA_33}, {34, OP_CHECKSIG}}, true},
|
||||||
|
{ScriptStrange, scrNoAddr, 33, []pkbytes{{0, OP_DATA_32}}, false},
|
||||||
|
{ScriptStrange, scrNoAddr, 33, []pkbytes{{0, OP_HASH160}, {1, OP_DATA_20}, {22, OP_EQUAL}}, false},
|
||||||
|
}
|
||||||
|
|
||||||
|
var format pkformat
|
||||||
|
var success bool
|
||||||
|
for _, format = range validformats {
|
||||||
|
if format.length != len(script) {
|
||||||
|
if len(script) < format.length {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !format.allowmore {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
success = true
|
||||||
|
for _, pkbyte := range format.databytes {
|
||||||
|
if pkbyte.off >= len(script) {
|
||||||
|
success = false
|
||||||
|
break
|
||||||
|
}
|
||||||
|
if script[pkbyte.off] != pkbyte.val {
|
||||||
|
log.Tracef("off at byte %v %v %v", pkbyte.off, script[pkbyte.off], pkbyte.val)
|
||||||
|
success = false
|
||||||
|
break
|
||||||
|
} else {
|
||||||
|
log.Tracef("match at byte %v: ok", pkbyte.off)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if success == true {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if success == false && len(script) > 1 {
|
||||||
|
// check for a few special case
|
||||||
|
if script[len(script)-1] == OP_CHECK_MULTISIG {
|
||||||
|
// Multisig ScriptPubKey
|
||||||
|
return ScriptStrange, "Unknown", nil
|
||||||
|
}
|
||||||
|
if script[0] == OP_0 && (len(script) <= 75 && byte(len(script)) == script[1]+2) {
|
||||||
|
// Multisig ScriptSig
|
||||||
|
return ScriptStrange, "Unknown", nil
|
||||||
|
}
|
||||||
|
if script[0] == OP_HASH160 && len(script) == 23 && script[22] == OP_EQUAL {
|
||||||
|
// Multisig ScriptSig
|
||||||
|
return ScriptStrange, "Unknown", nil
|
||||||
|
}
|
||||||
|
if script[0] == OP_DATA_36 && len(script) == 37 {
|
||||||
|
// Multisig ScriptSig
|
||||||
|
return ScriptStrange, "Unknown", nil
|
||||||
|
}
|
||||||
|
|
||||||
|
return ScriptUnknown, "Unknown", StackErrUnknownAddress
|
||||||
|
}
|
||||||
|
|
||||||
|
var atype byte
|
||||||
|
var abuf []byte
|
||||||
|
var addr string
|
||||||
|
switch format.parsetype {
|
||||||
|
case scrPayAddr:
|
||||||
|
atype = 0x00
|
||||||
|
abuf = script[3:23]
|
||||||
|
case scrCollectAddr:
|
||||||
|
// script is replaced with the md160 of the pubkey
|
||||||
|
slen := len(script)
|
||||||
|
pubkey := script[slen-65:]
|
||||||
|
abuf = calcHash160(pubkey)
|
||||||
|
case scrCollectAddrComp:
|
||||||
|
// script is replaced with the md160 of the pubkey
|
||||||
|
slen := len(script)
|
||||||
|
pubkey := script[slen-33:]
|
||||||
|
abuf = calcHash160(pubkey)
|
||||||
|
case scrGeneratePubkeyAddr:
|
||||||
|
atype = 0x00
|
||||||
|
addr = "Unknown"
|
||||||
|
case scrNoAddr:
|
||||||
|
addr = "Unknown"
|
||||||
|
case scrPubkeyAddr:
|
||||||
|
atype = 0x00
|
||||||
|
pubkey := script[1:66]
|
||||||
|
abuf = calcHash160(pubkey)
|
||||||
|
case scrPubkeyAddrComp:
|
||||||
|
atype = 0x00
|
||||||
|
pubkey := script[1:34]
|
||||||
|
abuf = calcHash160(pubkey)
|
||||||
|
default:
|
||||||
|
log.Warnf("parsetype is %v", format.parsetype)
|
||||||
|
}
|
||||||
|
|
||||||
|
if abuf != nil {
|
||||||
|
addrbytes := append([]byte{atype}, abuf[:]...)
|
||||||
|
|
||||||
|
cksum := btcwire.DoubleSha256(addrbytes)
|
||||||
|
addrbytes = append(addrbytes, cksum[:4]...)
|
||||||
|
addr = btcutil.Base58Encode(addrbytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
return format.addrtype, addr, nil
|
||||||
|
}
|
245
address_test.go
Normal file
245
address_test.go
Normal file
@ -0,0 +1,245 @@
|
|||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
package btcscript_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/conformal/btcscript"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type addressTest struct {
|
||||||
|
script []byte
|
||||||
|
address string
|
||||||
|
shouldFail error
|
||||||
|
class btcscript.ScriptType
|
||||||
|
}
|
||||||
|
|
||||||
|
var addressTests = []addressTest{
|
||||||
|
{script: []byte{btcscript.OP_DATA_65,
|
||||||
|
0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a,
|
||||||
|
0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc,
|
||||||
|
0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48,
|
||||||
|
0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a,
|
||||||
|
0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc,
|
||||||
|
0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b,
|
||||||
|
0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f,
|
||||||
|
0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12,
|
||||||
|
0xa3, btcscript.OP_CHECKSIG},
|
||||||
|
address: "12cbQLTFMXRnSzktFkuoG3eHoMeFtpTu3S",
|
||||||
|
class: btcscript.ScriptPubKey,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_DATA_65,
|
||||||
|
0x04, 0x11, 0xdb, 0x93, 0xe1, 0xdc, 0xdb, 0x8a,
|
||||||
|
0x01, 0x6b, 0x49, 0x84, 0x0f, 0x8c, 0x53, 0xbc,
|
||||||
|
0x1e, 0xb6, 0x8a, 0x38, 0x2e, 0x97, 0xb1, 0x48,
|
||||||
|
0x2e, 0xca, 0xd7, 0xb1, 0x48, 0xa6, 0x90, 0x9a,
|
||||||
|
0x5c, 0xb2, 0xe0, 0xea, 0xdd, 0xfb, 0x84, 0xcc,
|
||||||
|
0xf9, 0x74, 0x44, 0x64, 0xf8, 0x2e, 0x16, 0x0b,
|
||||||
|
0xfa, 0x9b, 0x8b, 0x64, 0xf9, 0xd4, 0xc0, 0x3f,
|
||||||
|
0x99, 0x9b, 0x86, 0x43, 0xf6, 0x56, 0xb4, 0x12,
|
||||||
|
0xa3},
|
||||||
|
shouldFail: btcscript.StackErrUnknownAddress,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_DATA_71,
|
||||||
|
0x30, 0x44, 0x02, 0x20, 0x4e, 0x45, 0xe1, 0x69,
|
||||||
|
0x32, 0xb8, 0xaf, 0x51, 0x49, 0x61, 0xa1, 0xd3,
|
||||||
|
0xa1, 0xa2, 0x5f, 0xdf, 0x3f, 0x4f, 0x77, 0x32,
|
||||||
|
0xe9, 0xd6, 0x24, 0xc6, 0xc6, 0x15, 0x48, 0xab,
|
||||||
|
0x5f, 0xb8, 0xcd, 0x41, 0x02, 0x20, 0x18, 0x15,
|
||||||
|
0x22, 0xec, 0x8e, 0xca, 0x07, 0xde, 0x48, 0x60,
|
||||||
|
0xa4, 0xac, 0xdd, 0x12, 0x90, 0x9d, 0x83, 0x1c,
|
||||||
|
0xc5, 0x6c, 0xbb, 0xac, 0x46, 0x22, 0x08, 0x22,
|
||||||
|
0x21, 0xa8, 0x76, 0x8d, 0x1d, 0x09, 0x01},
|
||||||
|
address: "Unknown",
|
||||||
|
class: btcscript.ScriptPubKey,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_DUP, btcscript.OP_HASH160,
|
||||||
|
btcscript.OP_DATA_20,
|
||||||
|
0xad, 0x06, 0xdd, 0x6d, 0xde, 0xe5, 0x5c, 0xbc,
|
||||||
|
0xa9, 0xa9, 0xe3, 0x71, 0x3b, 0xd7, 0x58, 0x75,
|
||||||
|
0x09, 0xa3, 0x05, 0x64,
|
||||||
|
btcscript.OP_EQUALVERIFY, btcscript.OP_CHECKSIG,
|
||||||
|
},
|
||||||
|
address: "1Gmt8AzabtngttF3PcZzLR1p7uCMaHNuGY",
|
||||||
|
class: btcscript.ScriptAddr,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_DATA_73,
|
||||||
|
0x30, 0x46, 0x02, 0x21, 0x00, 0xdd, 0xc6, 0x97,
|
||||||
|
0x38, 0xbf, 0x23, 0x36, 0x31, 0x8e, 0x4e, 0x04,
|
||||||
|
0x1a, 0x5a, 0x77, 0xf3, 0x05, 0xda, 0x87, 0x42,
|
||||||
|
0x8a, 0xb1, 0x60, 0x6f, 0x02, 0x32, 0x60, 0x01,
|
||||||
|
0x78, 0x54, 0x35, 0x0d, 0xdc, 0x02, 0x21, 0x00,
|
||||||
|
0x81, 0x7a, 0xf0, 0x9d, 0x2e, 0xec, 0x36, 0x86,
|
||||||
|
0x2d, 0x16, 0x00, 0x98, 0x52, 0xb7, 0xe3, 0xa0,
|
||||||
|
0xf6, 0xdd, 0x76, 0x59, 0x82, 0x90, 0xb7, 0x83,
|
||||||
|
0x4e, 0x14, 0x53, 0x66, 0x03, 0x67, 0xe0, 0x7a,
|
||||||
|
0x01,
|
||||||
|
btcscript.OP_DATA_65,
|
||||||
|
0x04, 0xcd, 0x42, 0x40, 0xc1, 0x98, 0xe1, 0x25,
|
||||||
|
0x23, 0xb6, 0xf9, 0xcb, 0x9f, 0x5b, 0xed, 0x06,
|
||||||
|
0xde, 0x1b, 0xa3, 0x7e, 0x96, 0xa1, 0xbb, 0xd1,
|
||||||
|
0x37, 0x45, 0xfc, 0xf9, 0xd1, 0x1c, 0x25, 0xb1,
|
||||||
|
0xdf, 0xf9, 0xa5, 0x19, 0x67, 0x5d, 0x19, 0x88,
|
||||||
|
0x04, 0xba, 0x99, 0x62, 0xd3, 0xec, 0xa2, 0xd5,
|
||||||
|
0x93, 0x7d, 0x58, 0xe5, 0xa7, 0x5a, 0x71, 0x04,
|
||||||
|
0x2d, 0x40, 0x38, 0x8a, 0x4d, 0x30, 0x7f, 0x88,
|
||||||
|
0x7d},
|
||||||
|
address: "16tRBxwU7t5hEHaPLqiE35gS3jaGBppraH",
|
||||||
|
class: btcscript.ScriptAddr,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_DATA_73,
|
||||||
|
0x30, 0x46, 0x02, 0x21, 0x00, 0xac, 0x7e, 0x4e,
|
||||||
|
0x27, 0xf2, 0xb1, 0x1c, 0xb8, 0x6f, 0xb5, 0xaa,
|
||||||
|
0x87, 0x2a, 0xb9, 0xd3, 0x2c, 0xdc, 0x08, 0x33,
|
||||||
|
0x80, 0x73, 0x3e, 0x3e, 0x98, 0x47, 0xff, 0x77,
|
||||||
|
0xa0, 0x69, 0xcd, 0xdf, 0xab, 0x02, 0x21, 0x00,
|
||||||
|
0xc0, 0x4c, 0x3e, 0x6f, 0xfe, 0x88, 0xa1, 0x5b,
|
||||||
|
0xc5, 0x07, 0xb8, 0xe5, 0x71, 0xaa, 0x35, 0x92,
|
||||||
|
0x8a, 0xcf, 0xe1, 0x5a, 0x4a, 0x23, 0x20, 0x1b,
|
||||||
|
0x08, 0xfe, 0x3c, 0x7b, 0x3c, 0x97, 0xc8, 0x8f,
|
||||||
|
0x01,
|
||||||
|
btcscript.OP_DATA_33,
|
||||||
|
0x02, 0x40, 0x05, 0xc9, 0x45, 0xd8, 0x6a, 0xc6,
|
||||||
|
0xb0, 0x1f, 0xb0, 0x42, 0x58, 0x34, 0x5a, 0xbe,
|
||||||
|
0xa7, 0xa8, 0x45, 0xbd, 0x25, 0x68, 0x9e, 0xdb,
|
||||||
|
0x72, 0x3d, 0x5a, 0xd4, 0x06, 0x8d, 0xdd, 0x30,
|
||||||
|
0x36,
|
||||||
|
},
|
||||||
|
address: "1272555ceTPn2WepjzVgFESWdfNQjqdjgp",
|
||||||
|
class: btcscript.ScriptAddr,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_DATA_32,
|
||||||
|
0x30, 0x46, 0x02, 0x21, 0x00, 0xac, 0x7e, 0x4e,
|
||||||
|
0x27, 0xf2, 0xb1, 0x1c, 0xb8, 0x6f, 0xb5, 0xaa,
|
||||||
|
0x87, 0x2a, 0xb9, 0xd3, 0x2c, 0xdc, 0x08, 0x33,
|
||||||
|
0x80, 0x73, 0x3e, 0x3e, 0x98, 0x47, 0xff, 0x77,
|
||||||
|
},
|
||||||
|
address: "Unknown",
|
||||||
|
class: btcscript.ScriptStrange,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_DATA_33,
|
||||||
|
0x02, 0x40, 0x05, 0xc9, 0x45, 0xd8, 0x6a, 0xc6,
|
||||||
|
0xb0, 0x1f, 0xb0, 0x42, 0x58, 0x34, 0x5a, 0xbe,
|
||||||
|
0xa7, 0xa8, 0x45, 0xbd, 0x25, 0x68, 0x9e, 0xdb,
|
||||||
|
0x72, 0x3d, 0x5a, 0xd4, 0x06, 0x8d, 0xdd, 0x30,
|
||||||
|
0x36,
|
||||||
|
btcscript.OP_CHECKSIG,
|
||||||
|
},
|
||||||
|
address: "1272555ceTPn2WepjzVgFESWdfNQjqdjgp",
|
||||||
|
class: btcscript.ScriptPubKey,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_DATA_33,
|
||||||
|
0x02, 0x40, 0x05, 0xc9, 0x45, 0xd8, 0x6a, 0xc6,
|
||||||
|
0xb0, 0x1f, 0xb0, 0x42, 0x58, 0x34, 0x5a, 0xbe,
|
||||||
|
0xa7, 0xa8, 0x45, 0xbd, 0x25, 0x68, 0x9e, 0xdb,
|
||||||
|
0x72, 0x3d, 0x5a, 0xd4, 0x06, 0x8d, 0xdd, 0x30,
|
||||||
|
0x36,
|
||||||
|
btcscript.OP_CHECK_MULTISIG, // note this isn't a real tx
|
||||||
|
},
|
||||||
|
address: "Unknown",
|
||||||
|
class: btcscript.ScriptStrange,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_0, btcscript.OP_DATA_33,
|
||||||
|
0x02, 0x40, 0x05, 0xc9, 0x45, 0xd8, 0x6a, 0xc6,
|
||||||
|
0xb0, 0x1f, 0xb0, 0x42, 0x58, 0x34, 0x5a, 0xbe,
|
||||||
|
0xa7, 0xa8, 0x45, 0xbd, 0x25, 0x68, 0x9e, 0xdb,
|
||||||
|
0x72, 0x3d, 0x5a, 0xd4, 0x06, 0x8d, 0xdd, 0x30,
|
||||||
|
0x36, // note this isn't a real tx
|
||||||
|
},
|
||||||
|
address: "Unknown",
|
||||||
|
class: btcscript.ScriptStrange,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_HASH160, btcscript.OP_DATA_20,
|
||||||
|
0x02, 0x40, 0x05, 0xc9, 0x45, 0xd8, 0x6a, 0xc6,
|
||||||
|
0xb0, 0x1f, 0xb0, 0x42, 0x58, 0x34, 0x5a, 0xbe,
|
||||||
|
0xa7, 0xa8, 0x45, 0xbd,
|
||||||
|
btcscript.OP_EQUAL, // note this isn't a real tx
|
||||||
|
},
|
||||||
|
address: "Unknown",
|
||||||
|
class: btcscript.ScriptStrange,
|
||||||
|
},
|
||||||
|
{script: []byte{btcscript.OP_DATA_36,
|
||||||
|
0x02, 0x40, 0x05, 0xc9, 0x45, 0xd8, 0x6a, 0xc6,
|
||||||
|
0xb0, 0x1f, 0xb0, 0x42, 0x58, 0x34, 0x5a, 0xbe,
|
||||||
|
0xb0, 0x1f, 0xb0, 0x42, 0x58, 0x34, 0x5a, 0xbe,
|
||||||
|
0xb0, 0x1f, 0xb0, 0x42, 0x58, 0x34, 0x5a, 0xbe,
|
||||||
|
0xa7, 0xa8, 0x45, 0xbd,
|
||||||
|
// note this isn't a real tx
|
||||||
|
},
|
||||||
|
address: "Unknown",
|
||||||
|
class: btcscript.ScriptStrange,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddresses(t *testing.T) {
|
||||||
|
for i, s := range addressTests {
|
||||||
|
class, address, err := btcscript.ScriptToAddress(s.script)
|
||||||
|
if s.shouldFail != nil {
|
||||||
|
if err != s.shouldFail {
|
||||||
|
t.Errorf("Address test %v failed is err [%v] should be [%v]", i, err, s.shouldFail)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("Address test %v failed err %v", i, err)
|
||||||
|
} else {
|
||||||
|
if s.address != address {
|
||||||
|
t.Errorf("Address test %v mismatch is [%v] want [%v]", i, address, s.address)
|
||||||
|
}
|
||||||
|
if s.class != class {
|
||||||
|
t.Errorf("Address test %v class mismatch is [%v] want [%v]", i, class, s.class)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type stringifyTest struct {
|
||||||
|
name string
|
||||||
|
scripttype btcscript.ScriptType
|
||||||
|
stringed string
|
||||||
|
}
|
||||||
|
|
||||||
|
var stringifyTests = []stringifyTest{
|
||||||
|
stringifyTest{
|
||||||
|
name: "unknown",
|
||||||
|
scripttype: btcscript.ScriptUnknown,
|
||||||
|
stringed: "Unknown",
|
||||||
|
},
|
||||||
|
stringifyTest{
|
||||||
|
name: "addr",
|
||||||
|
scripttype: btcscript.ScriptAddr,
|
||||||
|
stringed: "Addr",
|
||||||
|
},
|
||||||
|
stringifyTest{
|
||||||
|
name: "pubkey",
|
||||||
|
scripttype: btcscript.ScriptPubKey,
|
||||||
|
stringed: "Pubkey",
|
||||||
|
},
|
||||||
|
stringifyTest{
|
||||||
|
name: "strange",
|
||||||
|
scripttype: btcscript.ScriptStrange,
|
||||||
|
stringed: "Strange",
|
||||||
|
},
|
||||||
|
stringifyTest{
|
||||||
|
name: "generation",
|
||||||
|
scripttype: btcscript.ScriptGeneration,
|
||||||
|
stringed: "Generation",
|
||||||
|
},
|
||||||
|
stringifyTest{
|
||||||
|
name: "not in enum",
|
||||||
|
scripttype: btcscript.ScriptType(255),
|
||||||
|
stringed: "Invalid",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStringify(t *testing.T) {
|
||||||
|
for _, test := range stringifyTests {
|
||||||
|
typeString := test.scripttype.String()
|
||||||
|
if typeString != test.stringed {
|
||||||
|
t.Errorf("%s: got \"%s\" expected \"%s\"", test.name,
|
||||||
|
typeString, test.stringed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
17
cov_report.sh
Normal file
17
cov_report.sh
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
# This script uses gocov to generate a test coverage report.
|
||||||
|
# The gocov tool my be obtained with the following command:
|
||||||
|
# go get github.com/axw/gocov/gocov
|
||||||
|
#
|
||||||
|
# It will be installed to $GOPATH/bin, so ensure that location is in your $PATH.
|
||||||
|
|
||||||
|
# Check for gocov.
|
||||||
|
type gocov >/dev/null 2>&1
|
||||||
|
if [ $? -ne 0 ]; then
|
||||||
|
echo >&2 "This script requires the gocov tool."
|
||||||
|
echo >&2 "You may obtain it with the following command:"
|
||||||
|
echo >&2 "go get github.com/axw/gocov/gocov"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
gocov test | gocov report
|
59
doc.go
Normal file
59
doc.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
/*
|
||||||
|
Package btcscript implements bitcoin transaction scripts.
|
||||||
|
|
||||||
|
A complete description of the script language used by bitcoin can be found at
|
||||||
|
https://en.bitcoin.it/wiki/Script. The following only serves as a quick
|
||||||
|
overview to provide information on how to use the package.
|
||||||
|
|
||||||
|
This package provides data structures and functions to parse and execute
|
||||||
|
bitcoin transaction scripts.
|
||||||
|
|
||||||
|
Script Overview
|
||||||
|
|
||||||
|
Bitcoin transaction scripts are written in a stack-base, FORTH-like language.
|
||||||
|
|
||||||
|
The bitcoin script language consists of a number of opcodes which fall into
|
||||||
|
several categories such pushing and popping data to and from the stack,
|
||||||
|
performing basic and bitwise arithmetic, conditional branching, comparing
|
||||||
|
hashes, and checking cryptographic signatures. Scripts are processed from left
|
||||||
|
to right and intentionally do not provide loops.
|
||||||
|
|
||||||
|
The vast majority of Bitcoin scripts at the time of this writing are of several
|
||||||
|
standard forms which consist of a spender providing a public key and a signature
|
||||||
|
which proves the spender owns the associated private key. This information
|
||||||
|
is used to prove the the spender is authorized to perform the transaction.
|
||||||
|
|
||||||
|
One benefit of using a scripting language is added flexibility in specifying
|
||||||
|
what conditions must be met in order to spend bitcoins.
|
||||||
|
|
||||||
|
Usage
|
||||||
|
|
||||||
|
The usage of this package consists of creating a new script engine for a pair
|
||||||
|
of transaction inputs and outputs and using the engine to execute the scripts.
|
||||||
|
|
||||||
|
The following function is an example of how to create and execute a script
|
||||||
|
engine to validate a transaction.
|
||||||
|
|
||||||
|
// ValidateTx validates the txIdx'th input of tx. The output transaction
|
||||||
|
// corresponding to the this input is the txInIdx'th output of txIn. The
|
||||||
|
// block timestamp of tx is timestamp and the protocol version involved
|
||||||
|
// is pver.
|
||||||
|
func ValidateTx(tx *btcwire.MsgTx, txIdx int, txIn *btcwire.MsgTx, txInIdx int, pver int, timestamp time.Time) {
|
||||||
|
pkScript := txIn.TxOut[txInIdx].PkScript
|
||||||
|
sigScript := tx.txIn[TxIdx]
|
||||||
|
engine, err := btcscript.NewScript(sigScript, pkScript, txInIdx,
|
||||||
|
tx, pver, timestamp.After(btcscript.Bip16Activation))
|
||||||
|
return engine.Execute()
|
||||||
|
}
|
||||||
|
|
||||||
|
Errors
|
||||||
|
|
||||||
|
Errors returned by this package are of the form btcscript.StackErrX where X
|
||||||
|
indicates the specific error. See Variables in the package documentation for a
|
||||||
|
full list.
|
||||||
|
*/
|
||||||
|
package btcscript
|
44
internal_test.go
Normal file
44
internal_test.go
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcscript
|
||||||
|
|
||||||
|
// this file is present to export some internal interfaces so that we can
|
||||||
|
// test them reliably.
|
||||||
|
|
||||||
|
func TstRemoveOpcode(pkscript []byte, opcode byte) ([]byte, error) {
|
||||||
|
pops, err := parseScript(pkscript)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pops = removeOpcode(pops, opcode)
|
||||||
|
return unparseScript(pops), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func TstRemoveOpcodeByData(pkscript []byte, data []byte) ([]byte, error) {
|
||||||
|
pops, err := parseScript(pkscript)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
pops = removeOpcodeByData(pops, data)
|
||||||
|
return unparseScript(pops), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
type TstScriptType scriptType
|
||||||
|
|
||||||
|
const (
|
||||||
|
TstPubKeyTy TstScriptType = TstScriptType(pubKeyTy)
|
||||||
|
TstPubKeyHashTy = TstScriptType(pubKeyHashTy)
|
||||||
|
TstScriptHashTy = TstScriptType(scriptHashTy)
|
||||||
|
TstMultiSigTy = TstScriptType(multiSigTy)
|
||||||
|
TstNonStandardTy = TstScriptType(nonStandardTy)
|
||||||
|
)
|
||||||
|
|
||||||
|
func TstTypeOfScript(script []byte) TstScriptType {
|
||||||
|
pops, err := parseScript(script)
|
||||||
|
if err != nil {
|
||||||
|
return TstNonStandardTy
|
||||||
|
}
|
||||||
|
return TstScriptType(typeOfScript(pops))
|
||||||
|
}
|
65
log.go
Normal file
65
log.go
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcscript
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"github.com/conformal/seelog"
|
||||||
|
"io"
|
||||||
|
)
|
||||||
|
|
||||||
|
// log is a logger that is initialized with no output filters. This
|
||||||
|
// means the package will not perform any logging by default until the caller
|
||||||
|
// requests it.
|
||||||
|
var log seelog.LoggerInterface
|
||||||
|
|
||||||
|
// The default amount of logging is none.
|
||||||
|
func init() {
|
||||||
|
DisableLog()
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisableLog disables all library log output. Logging output is disabled
|
||||||
|
// by default until either UseLogger or SetLogWriter are called.
|
||||||
|
func DisableLog() {
|
||||||
|
log = seelog.Disabled
|
||||||
|
}
|
||||||
|
|
||||||
|
// UseLogger uses a specified Logger to output package logging info.
|
||||||
|
// This should be used in preference to SetLogWriter if the caller is also
|
||||||
|
// using seelog.
|
||||||
|
func UseLogger(logger seelog.LoggerInterface) {
|
||||||
|
log = logger
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetLogWriter uses a specified io.Writer to output package logging info.
|
||||||
|
// This allows a caller to direct package logging output without needing a
|
||||||
|
// dependency on seelog. If the caller is also using seelog, UseLogger should
|
||||||
|
// be used instead.
|
||||||
|
func SetLogWriter(w io.Writer) error {
|
||||||
|
if w == nil {
|
||||||
|
return errors.New("nil writer")
|
||||||
|
}
|
||||||
|
|
||||||
|
l, err := seelog.LoggerFromWriterWithMinLevel(w, seelog.TraceLvl)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
UseLogger(l)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// LogClosure is a closure that can be printed with %v to be used to
|
||||||
|
// generate expensive-to-create data for a detailed log level and avoid doing
|
||||||
|
// the work if the data isn't printed.
|
||||||
|
type logClosure func() string
|
||||||
|
|
||||||
|
func (c logClosure) String() string {
|
||||||
|
return c()
|
||||||
|
}
|
||||||
|
|
||||||
|
func newLogClosure(c func() string) logClosure {
|
||||||
|
return logClosure(c)
|
||||||
|
}
|
3104
opcode_test.go
Normal file
3104
opcode_test.go
Normal file
File diff suppressed because it is too large
Load Diff
697
script.go
Normal file
697
script.go
Normal file
@ -0,0 +1,697 @@
|
|||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcscript
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/conformal/btcwire"
|
||||||
|
"github.com/davecgh/go-spew/spew"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
|
// StackErrShortScript is returned if the script has an opcode that is too long
|
||||||
|
// for the length of the script.
|
||||||
|
var StackErrShortScript = errors.New("execute past end of script")
|
||||||
|
|
||||||
|
// StackErrUnderflow is returned if an opcode requires more items on the stack
|
||||||
|
// than is present.
|
||||||
|
var StackErrUnderflow = errors.New("stack underflow")
|
||||||
|
|
||||||
|
// StackErrInvalidArgs is returned if the argument for an opcode is out of
|
||||||
|
// acceptable range.
|
||||||
|
var StackErrInvalidArgs = errors.New("invalid argument")
|
||||||
|
|
||||||
|
// StackErrOpDisabled is returned when a disabled opcode is encountered in the
|
||||||
|
// script.
|
||||||
|
var StackErrOpDisabled = errors.New("Disabled Opcode")
|
||||||
|
|
||||||
|
// StackErrVerifyFailed is returned when one of the OP_VERIFY or OP_*VERIFY
|
||||||
|
// instructions is executed and the conditions fails.
|
||||||
|
var StackErrVerifyFailed = errors.New("Verify failed")
|
||||||
|
|
||||||
|
// StackErrNumberTooBig is returned when the argument for an opcode that should
|
||||||
|
// be an offset is obviously far too large.
|
||||||
|
var StackErrNumberTooBig = errors.New("number too big")
|
||||||
|
|
||||||
|
// StackErrInvalidOpcode is returned when an opcode marked as invalid or a
|
||||||
|
// completely undefined opcode is encountered.
|
||||||
|
var StackErrInvalidOpcode = errors.New("Invalid Opcode")
|
||||||
|
|
||||||
|
// StackErrReservedOpcode is returned when an opcode marked as reserved is
|
||||||
|
// encountered.
|
||||||
|
var StackErrReservedOpcode = errors.New("Reserved Opcode")
|
||||||
|
|
||||||
|
// StackErrEarlyReturn is returned when OP_RETURN is exectured in the script.
|
||||||
|
var StackErrEarlyReturn = errors.New("Script returned early")
|
||||||
|
|
||||||
|
// StackErrNoIf is returned if an OP_ELSE or OP_ENDIF is encountered without
|
||||||
|
// first having an OP_IF or OP_NOTIF in the script.
|
||||||
|
var StackErrNoIf = errors.New("OP_ELSE or OP_ENDIF with no matching OP_IF")
|
||||||
|
|
||||||
|
// StackErrMissingEndif is returned if the end of a script is reached without
|
||||||
|
// and OP_ENDIF to correspond to a conditional expression.
|
||||||
|
var StackErrMissingEndif = fmt.Errorf("execute fail, in conditional execution")
|
||||||
|
|
||||||
|
// StackErrTooManyPubkeys is returned if an OP_CHECKMULTISIG is encountered
|
||||||
|
// with more than MaxPubKeysPerMultiSig pubkeys present.
|
||||||
|
var StackErrTooManyPubkeys = errors.New("Invalid pubkey count in OP_CHECKMULTISIG")
|
||||||
|
|
||||||
|
// StackErrTooManyOperations is returned if a script has more then
|
||||||
|
// MaxOpsPerScript opcodes that do not push data.
|
||||||
|
var StackErrTooManyOperations = errors.New("Too many operations in script")
|
||||||
|
|
||||||
|
// StackErrElementTooBig is returned if the size of an element to be pushed to
|
||||||
|
// the stack is over MaxScriptElementSize.
|
||||||
|
var StackErrElementTooBig = errors.New("Element in script too large")
|
||||||
|
|
||||||
|
// StackErrUnknownAddress is returned when ScriptToAddress does not recognise
|
||||||
|
// the pattern of the script and thus can not find the address for payment.
|
||||||
|
var StackErrUnknownAddress = fmt.Errorf("non-recognised address")
|
||||||
|
|
||||||
|
// StackErrScriptFailed is returned when at the end of a script the boolean
|
||||||
|
// on top of the stack is false signifying that the script has failed.
|
||||||
|
var StackErrScriptFailed = fmt.Errorf("execute fail, fail on stack")
|
||||||
|
|
||||||
|
// Bip16Activation is the timestamp where BIP0016 is valid to use in the
|
||||||
|
// blockchain. To be used to determine if BIP0016 should be called for or not.
|
||||||
|
// This timestamp corresponds to Sun Apr 1 00:00:00 UTC 2012.
|
||||||
|
var Bip16Activation = time.Unix(1333238400, 0)
|
||||||
|
|
||||||
|
// Hash type bits from the end of a signature.
|
||||||
|
const (
|
||||||
|
SigHashOld = 0x0
|
||||||
|
SigHashAll = 0x1
|
||||||
|
SigHashNone = 0x2
|
||||||
|
SigHashSingle = 0x3
|
||||||
|
SigHashAnyOneCanPay = 0x80
|
||||||
|
)
|
||||||
|
|
||||||
|
// These are the constants specified for maximums in individual scripts.
|
||||||
|
const (
|
||||||
|
MaxOpsPerScript = 201 // Max number of non-push operations.
|
||||||
|
MaxPubKeysPerMultiSig = 20 // Multisig can't have more sigs than this.
|
||||||
|
MaxScriptElementSize = 520 // Max bytes pushable to the stack.
|
||||||
|
)
|
||||||
|
|
||||||
|
// ScriptType is an enumeration for the list of standard types of script.
|
||||||
|
type scriptType byte
|
||||||
|
|
||||||
|
// Types of script payment known about in the blockchain.
|
||||||
|
const (
|
||||||
|
pubKeyTy scriptType = iota // Pay pubkey.
|
||||||
|
pubKeyHashTy // Pay pubkey hash.
|
||||||
|
scriptHashTy // Pay to script hash.
|
||||||
|
multiSigTy // Multi signature.
|
||||||
|
nonStandardTy // None of the above.
|
||||||
|
)
|
||||||
|
|
||||||
|
// Script is the virtual machine that executes btcscripts.
|
||||||
|
type Script struct {
|
||||||
|
scripts [][]parsedOpcode
|
||||||
|
scriptidx int
|
||||||
|
scriptoff int
|
||||||
|
lastcodesep int
|
||||||
|
dstack Stack // data stack
|
||||||
|
astack Stack // alt stack
|
||||||
|
tx btcwire.MsgTx
|
||||||
|
txidx int
|
||||||
|
pver uint32
|
||||||
|
condStack []int
|
||||||
|
numOps int
|
||||||
|
bip16 bool // treat execution as pay-to-script-hash
|
||||||
|
savedFirstStack [][]byte // stack from first script for bip16 scripts
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPubkey returns true if the script passed is a pubkey transaction, false
|
||||||
|
// otherwise.
|
||||||
|
func isPubkey(pops []parsedOpcode) bool {
|
||||||
|
return len(pops) == 2 &&
|
||||||
|
pops[0].opcode.value > OP_FALSE &&
|
||||||
|
pops[0].opcode.value <= OP_DATA_75 &&
|
||||||
|
pops[1].opcode.value == OP_CHECKSIG
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPubkeyHash returns true if the script passed is a pubkey hash transaction,
|
||||||
|
// false otherwise.
|
||||||
|
func isPubkeyHash(pops []parsedOpcode) bool {
|
||||||
|
return len(pops) == 5 &&
|
||||||
|
pops[0].opcode.value == OP_DUP &&
|
||||||
|
pops[1].opcode.value == OP_HASH160 &&
|
||||||
|
pops[2].opcode.value == OP_DATA_20 &&
|
||||||
|
pops[3].opcode.value == OP_EQUALVERIFY &&
|
||||||
|
pops[4].opcode.value == OP_CHECKSIG
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// isScriptHash returns true if the script passed is a pay-to-script-hash (P2SH)
|
||||||
|
// transction, false otherwise.
|
||||||
|
func isScriptHash(pops []parsedOpcode) bool {
|
||||||
|
return len(pops) == 3 &&
|
||||||
|
pops[0].opcode.value == OP_HASH160 &&
|
||||||
|
pops[1].opcode.value == OP_DATA_20 &&
|
||||||
|
pops[2].opcode.value == OP_EQUAL
|
||||||
|
}
|
||||||
|
|
||||||
|
// isMultiSig returns true if the passed script is a multisig transaction, false
|
||||||
|
// otherwise.
|
||||||
|
func isMultiSig(pops []parsedOpcode) bool {
|
||||||
|
l := len(pops)
|
||||||
|
// absolute minimum is 1 pubkey so
|
||||||
|
// OP_1-16, pubkey, OP_1, OP_CHECK_MULTISIG
|
||||||
|
if l < 4 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pops[0].opcode.value < OP_1 ||
|
||||||
|
pops[0].opcode.value > OP_16 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pops[l-2].opcode.value < OP_1 ||
|
||||||
|
pops[l-2].opcode.value > OP_16 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if pops[l-1].opcode.value != OP_CHECK_MULTISIG {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
for _, pop := range pops[1 : l-2] {
|
||||||
|
// valid pubkeys are either 65 or 33 bytes
|
||||||
|
if len(pop.data) != 33 &&
|
||||||
|
len(pop.data) != 65 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// isPushOnly returns true if the script only pushes data, false otherwise.
|
||||||
|
func isPushOnly(pops []parsedOpcode) bool {
|
||||||
|
// technically we cheat here, we don't look at opcodes
|
||||||
|
for _, pop := range pops {
|
||||||
|
// all opcodes up to OP_16 are data instructions.
|
||||||
|
if pop.opcode.value < OP_FALSE ||
|
||||||
|
pop.opcode.value > OP_16 {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// scriptType returns the type of the script being inspected from the known
|
||||||
|
// standard types.
|
||||||
|
func typeOfScript(pops []parsedOpcode) scriptType {
|
||||||
|
// XXX dubious optimisation: order these in order of popularity in the
|
||||||
|
// blockchain
|
||||||
|
if isPubkey(pops) {
|
||||||
|
return pubKeyTy
|
||||||
|
} else if isPubkeyHash(pops) {
|
||||||
|
return pubKeyHashTy
|
||||||
|
} else if isScriptHash(pops) {
|
||||||
|
return scriptHashTy
|
||||||
|
} else if isMultiSig(pops) {
|
||||||
|
return multiSigTy
|
||||||
|
}
|
||||||
|
return nonStandardTy
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// parseScript preparses the script in bytes into a list of parsedOpcodes while
|
||||||
|
// applying a number of sanity checks.
|
||||||
|
func parseScript(script []byte) ([]parsedOpcode, error) {
|
||||||
|
retScript := []parsedOpcode{}
|
||||||
|
for i := 0; i < len(script); {
|
||||||
|
instr := script[i]
|
||||||
|
op, ok := opcodemap[instr]
|
||||||
|
if !ok {
|
||||||
|
return nil, StackErrInvalidOpcode
|
||||||
|
}
|
||||||
|
pop := parsedOpcode{opcode: op}
|
||||||
|
// parse data out of instruction.
|
||||||
|
switch {
|
||||||
|
case op.length == 1:
|
||||||
|
// no data, done here
|
||||||
|
i++
|
||||||
|
case op.length > 1:
|
||||||
|
if len(script[i:]) < op.length {
|
||||||
|
return nil, StackErrShortScript
|
||||||
|
}
|
||||||
|
// slice out the data.
|
||||||
|
pop.data = script[i+1 : i+op.length]
|
||||||
|
i += op.length
|
||||||
|
case op.length < 0:
|
||||||
|
var err error
|
||||||
|
var l uint
|
||||||
|
off := i + 1
|
||||||
|
switch op.length {
|
||||||
|
case -1:
|
||||||
|
l, err = scriptUInt8(script[off:])
|
||||||
|
case -2:
|
||||||
|
l, err = scriptUInt16(script[off:])
|
||||||
|
case -4:
|
||||||
|
l, err = scriptUInt32(script[off:])
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid opcode length %d", op.length)
|
||||||
|
}
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
off = i + 1 - op.length // beginning of data
|
||||||
|
if int(l) > len(script[off:]) {
|
||||||
|
return nil, StackErrShortScript
|
||||||
|
}
|
||||||
|
if l > MaxScriptElementSize {
|
||||||
|
return nil, StackErrElementTooBig
|
||||||
|
}
|
||||||
|
pop.data = script[off : off+int(l)]
|
||||||
|
i += 1 - op.length + int(l)
|
||||||
|
}
|
||||||
|
retScript = append(retScript, pop)
|
||||||
|
}
|
||||||
|
return retScript, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// unparseScript reversed the action of parseScript and returns the
|
||||||
|
// parsedOpcodes as a list of bytes
|
||||||
|
func unparseScript(pops []parsedOpcode) []byte {
|
||||||
|
script := []byte{}
|
||||||
|
for _, pop := range pops {
|
||||||
|
script = append(script, pop.bytes()...)
|
||||||
|
}
|
||||||
|
return script
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewScript returns a new script engine for the provided tx and input idx with
|
||||||
|
// a signature script scriptSig and a pubkeyscript scriptPubKey. If bip16 is
|
||||||
|
// true then it will be treated as if the bip16 threshhold has passed and thus
|
||||||
|
// pay-to-script hash transactions will be fully validated.
|
||||||
|
func NewScript(scriptSig []byte, scriptPubKey []byte, txidx int, tx *btcwire.MsgTx, pver uint32, bip16 bool) (*Script, error) {
|
||||||
|
var m Script
|
||||||
|
scripts := [][]byte{scriptSig, scriptPubKey}
|
||||||
|
m.scripts = make([][]parsedOpcode, len(scripts))
|
||||||
|
for i, scr := range scripts {
|
||||||
|
var err error
|
||||||
|
m.scripts[i], err = parseScript(scr)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the first scripts(s) are empty, must set the PC to the next script.
|
||||||
|
if len(scr) == 0 {
|
||||||
|
// yes this could end up setting to an invalid intial PC if all scripts were empty
|
||||||
|
m.scriptidx = i + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if bip16 && isScriptHash(m.scripts[1]) {
|
||||||
|
// if we are pay to scripthash then we only accept input
|
||||||
|
// scripts that push data
|
||||||
|
if !isPushOnly(m.scripts[0]) {
|
||||||
|
return nil, errors.New("pay to script hash with non " +
|
||||||
|
"pushonly input")
|
||||||
|
}
|
||||||
|
m.bip16 = true
|
||||||
|
}
|
||||||
|
|
||||||
|
for i, _ := range tx.TxIn {
|
||||||
|
tx.TxIn[i].SignatureScript = []byte{}
|
||||||
|
}
|
||||||
|
m.tx = *tx
|
||||||
|
m.txidx = txidx
|
||||||
|
m.pver = pver
|
||||||
|
m.condStack = []int{OpCondTrue}
|
||||||
|
|
||||||
|
return &m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Execute will execturte all script in the script engine and return either nil
|
||||||
|
// for successful validation or an error if one occurred.
|
||||||
|
func (s *Script) Execute() (err error) {
|
||||||
|
done := false
|
||||||
|
for done != true {
|
||||||
|
log.Tracef("%v", newLogClosure(func() string {
|
||||||
|
dis, err := s.DisasmPC()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Sprintf("stepping (%v)", err)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("stepping %v", dis)
|
||||||
|
}))
|
||||||
|
|
||||||
|
done, err = s.Step()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log.Tracef("%v", newLogClosure(func() string {
|
||||||
|
var dstr, astr string
|
||||||
|
|
||||||
|
// if we're tracing, dump the stacks.
|
||||||
|
if s.dstack.Depth() != 0 {
|
||||||
|
dstr = "Stack\n" + spew.Sdump(s.dstack)
|
||||||
|
}
|
||||||
|
if s.astack.Depth() != 0 {
|
||||||
|
astr = "AltStack\n" + spew.Sdump(s.astack)
|
||||||
|
}
|
||||||
|
|
||||||
|
return dstr + astr
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
if s.dstack.Depth() < 1 {
|
||||||
|
return fmt.Errorf("stack empty at end of execution")
|
||||||
|
}
|
||||||
|
v, err := s.dstack.PopBool()
|
||||||
|
if err == nil && v == false {
|
||||||
|
// log interesting data.
|
||||||
|
log.Tracef("%v", func() string {
|
||||||
|
dis0, _ := s.DisasmScript(0)
|
||||||
|
dis1, _ := s.DisasmScript(1)
|
||||||
|
return fmt.Sprintf("script0: %s\n script1: %s",
|
||||||
|
dis0, dis1)
|
||||||
|
})
|
||||||
|
err = StackErrScriptFailed
|
||||||
|
}
|
||||||
|
if err == nil && len(s.condStack) != 1 {
|
||||||
|
// conditional execution stack context left active
|
||||||
|
err = StackErrMissingEndif
|
||||||
|
}
|
||||||
|
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step will execute the next instruction and move the program counter to the
|
||||||
|
// next opcode in the script, or the next script if the curent has ended. Step
|
||||||
|
// will return true in the case that the last opcode was successfully executed.
|
||||||
|
// if an error is returned then the result of calling Step or any other method
|
||||||
|
// is undefined.
|
||||||
|
func (m *Script) Step() (done bool, err error) {
|
||||||
|
// verify that it is pointing to a valid script address
|
||||||
|
err = m.validPC()
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
opcode := m.scripts[m.scriptidx][m.scriptoff]
|
||||||
|
|
||||||
|
executeInstr := true
|
||||||
|
if m.condStack[0] != OpCondTrue {
|
||||||
|
// some opcodes still 'activate' if on the non-executing side
|
||||||
|
// of conditional execution
|
||||||
|
if opcode.conditional() {
|
||||||
|
executeInstr = true
|
||||||
|
} else {
|
||||||
|
executeInstr = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if executeInstr {
|
||||||
|
err = opcode.exec(m)
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// prepare for next instruction
|
||||||
|
m.scriptoff++
|
||||||
|
if m.scriptoff >= len(m.scripts[m.scriptidx]) {
|
||||||
|
// should only be == from checks before
|
||||||
|
m.scriptoff = 0
|
||||||
|
if m.scriptidx == 0 && m.bip16 {
|
||||||
|
m.savedFirstStack = m.GetStack()
|
||||||
|
} else if m.scriptidx == 1 && m.bip16 {
|
||||||
|
// Check for successful completion of script
|
||||||
|
v, err := m.dstack.PopBool()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
if v == false {
|
||||||
|
return false, StackErrScriptFailed
|
||||||
|
}
|
||||||
|
// check that first element on stack is a bool
|
||||||
|
script := m.savedFirstStack[len(m.savedFirstStack)-1]
|
||||||
|
pops, err := parseScript(script)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
m.scripts = append(m.scripts, pops)
|
||||||
|
// Set stack to be the stack from first script
|
||||||
|
// minus the script itself
|
||||||
|
m.SetStack(m.savedFirstStack[:len(m.savedFirstStack)-1])
|
||||||
|
}
|
||||||
|
m.scriptidx++
|
||||||
|
// there are zero length scripts in the wild
|
||||||
|
if m.scriptidx < len(m.scripts) && m.scriptoff >= len(m.scripts[m.scriptidx]) {
|
||||||
|
m.scriptidx++
|
||||||
|
}
|
||||||
|
m.lastcodesep = 0
|
||||||
|
if m.scriptidx >= len(m.scripts) {
|
||||||
|
done = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// curPC returns either the current script and offset, or an error if the
|
||||||
|
// position isn't valid.
|
||||||
|
func (m *Script) curPC() (script int, off int, err error) {
|
||||||
|
err = m.validPC()
|
||||||
|
if err != nil {
|
||||||
|
return 0, 0, err
|
||||||
|
}
|
||||||
|
return m.scriptidx, m.scriptoff, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// validPC returns an error if the current script position is valid for
|
||||||
|
// execution, nil otherwise.
|
||||||
|
func (m *Script) validPC() error {
|
||||||
|
if m.scriptidx >= len(m.scripts) {
|
||||||
|
return fmt.Errorf("Past input scripts %v:%v %v:xxxx", m.scriptidx, m.scriptoff, len(m.scripts))
|
||||||
|
}
|
||||||
|
if m.scriptoff >= len(m.scripts[m.scriptidx]) {
|
||||||
|
return fmt.Errorf("Past input scripts %v:%v %v:%04d", m.scriptidx, m.scriptoff, m.scriptidx, len(m.scripts[m.scriptidx]))
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisasmScript returns the disassembly string for the script at offset
|
||||||
|
// ``idx''. Where 0 is the scriptSig and 1 is the scriptPubKey.
|
||||||
|
func (m *Script) DisasmScript(idx int) (disstr string, err error) {
|
||||||
|
if idx >= len(m.scripts) {
|
||||||
|
return "", fmt.Errorf("Invalid script index")
|
||||||
|
}
|
||||||
|
for i := range m.scripts[idx] {
|
||||||
|
disstr = disstr + m.disasm(idx, i) + "\n"
|
||||||
|
}
|
||||||
|
return disstr, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisasmPC returns the string for the disassembly of the opcode that will be
|
||||||
|
// next to execute when Step() is called.
|
||||||
|
func (m *Script) DisasmPC() (disstr string, err error) {
|
||||||
|
scriptidx, scriptoff, err := m.curPC()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return m.disasm(scriptidx, scriptoff), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// disasm is a helper member to produce the output for DisasmPC and
|
||||||
|
// DisasmScript. It produces the opcode prefixed by the program counter at the
|
||||||
|
// provided position in the script. it does no error checking and leaves that
|
||||||
|
// to the caller to provide a valid offse.
|
||||||
|
func (m *Script) disasm(scriptidx int, scriptoff int) string {
|
||||||
|
return fmt.Sprintf("%02x:%04x: %s", scriptidx, scriptoff,
|
||||||
|
m.scripts[scriptidx][scriptoff].print(false))
|
||||||
|
}
|
||||||
|
|
||||||
|
// subScript will return the script since the last OP_CODESEPARATOR
|
||||||
|
func (s *Script) subScript() []parsedOpcode {
|
||||||
|
return s.scripts[s.scriptidx][s.lastcodesep:]
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeOpcode will remove any opcode matching ``opcode'' from the opcode
|
||||||
|
// stream in pkscript
|
||||||
|
func removeOpcode(pkscript []parsedOpcode, opcode byte) []parsedOpcode {
|
||||||
|
retScript := []parsedOpcode{}
|
||||||
|
for _, pop := range pkscript {
|
||||||
|
if pop.opcode.value != opcode {
|
||||||
|
retScript = append(retScript, pop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retScript
|
||||||
|
}
|
||||||
|
|
||||||
|
// removeOpcodeByData will return the pkscript minus any opcodes that would
|
||||||
|
// push the data in ``data'' to the stack.
|
||||||
|
func removeOpcodeByData(pkscript []parsedOpcode, data []byte) []parsedOpcode {
|
||||||
|
retScript := []parsedOpcode{}
|
||||||
|
for _, pop := range pkscript {
|
||||||
|
if !bytes.Equal(pop.data, data) {
|
||||||
|
retScript = append(retScript, pop)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return retScript
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// DisasmString formats a disassembled script for one line printing.
|
||||||
|
func DisasmString(buf []byte) (string, error) {
|
||||||
|
disbuf := ""
|
||||||
|
opcodes, err := parseScript(buf)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
for _, pop := range opcodes {
|
||||||
|
disbuf += pop.print(true) + " "
|
||||||
|
}
|
||||||
|
if disbuf != "" {
|
||||||
|
disbuf = disbuf[:len(disbuf)-1]
|
||||||
|
}
|
||||||
|
return disbuf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// calcScriptHash will, given the a script and hashtype for the current
|
||||||
|
// scriptmachine, calculate the doubleSha256 hash of the transaction and
|
||||||
|
// script to be used for signature signing and verification.
|
||||||
|
func (s *Script) calcScriptHash(script []parsedOpcode, hashType byte) []byte {
|
||||||
|
|
||||||
|
// remove all instances of OP_CODESEPARATOR still left in the script
|
||||||
|
script = removeOpcode(script, OP_CODESEPARATOR)
|
||||||
|
|
||||||
|
// Make a deep copy of the transaction, zeroing out the script
|
||||||
|
// for all inputs that are not currently being processed.
|
||||||
|
txCopy := s.tx.Copy()
|
||||||
|
txidx := s.txidx
|
||||||
|
for i := range txCopy.TxIn {
|
||||||
|
var txIn btcwire.TxIn
|
||||||
|
txIn = *txCopy.TxIn[i]
|
||||||
|
txCopy.TxIn[i] = &txIn
|
||||||
|
if i == txidx {
|
||||||
|
txCopy.TxIn[txidx].SignatureScript =
|
||||||
|
unparseScript(script)
|
||||||
|
} else {
|
||||||
|
txCopy.TxIn[i].SignatureScript = []byte{}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Default behaviour has all outputs set up.
|
||||||
|
for i := range txCopy.TxOut {
|
||||||
|
var txOut btcwire.TxOut
|
||||||
|
txOut = *txCopy.TxOut[i]
|
||||||
|
txCopy.TxOut[i] = &txOut
|
||||||
|
}
|
||||||
|
|
||||||
|
switch hashType & 31 {
|
||||||
|
case SigHashNone:
|
||||||
|
txCopy.TxOut = txCopy.TxOut[0:0] // empty slice
|
||||||
|
for i := range txCopy.TxIn {
|
||||||
|
if i != txidx {
|
||||||
|
txCopy.TxIn[i].Sequence = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case SigHashSingle:
|
||||||
|
// resize output array to up to and including current output
|
||||||
|
txCopy.TxOut = txCopy.TxOut[:txidx+1]
|
||||||
|
// all but current output get zeroed out
|
||||||
|
for i := 0; i < txidx; i++ {
|
||||||
|
txCopy.TxOut[i].Value = -1
|
||||||
|
txCopy.TxOut[i].PkScript = []byte{}
|
||||||
|
}
|
||||||
|
// Sequence on all other inputs is 0, too.
|
||||||
|
for i := range txCopy.TxIn {
|
||||||
|
if i != txidx {
|
||||||
|
txCopy.TxIn[i].Sequence = 0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
// XXX bitcoind treats undefined hashtypes like normal
|
||||||
|
// SigHashAll for purposes of hash generation.
|
||||||
|
fallthrough
|
||||||
|
case SigHashOld:
|
||||||
|
fallthrough
|
||||||
|
case SigHashAll:
|
||||||
|
// nothing special here
|
||||||
|
}
|
||||||
|
if hashType&SigHashAnyOneCanPay != 0 {
|
||||||
|
txCopy.TxIn = txCopy.TxIn[s.txidx : s.txidx+1]
|
||||||
|
txidx = 0
|
||||||
|
}
|
||||||
|
|
||||||
|
var wbuf bytes.Buffer
|
||||||
|
txCopy.BtcEncode(&wbuf, s.pver)
|
||||||
|
// Append LE 4 bytes hash type
|
||||||
|
binary.Write(&wbuf, binary.LittleEndian, uint32(hashType))
|
||||||
|
|
||||||
|
return btcwire.DoubleSha256(wbuf.Bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
// scriptUInt8 return the number stored in the first byte of a slice.
|
||||||
|
func scriptUInt8(script []byte) (uint, error) {
|
||||||
|
if len(script) <= 1 {
|
||||||
|
return 0, StackErrShortScript
|
||||||
|
}
|
||||||
|
return uint(script[0]), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scriptUInt16 returns the number stored in the next 2 bytes of a slice.
|
||||||
|
func scriptUInt16(script []byte) (uint, error) {
|
||||||
|
if len(script) <= 2 {
|
||||||
|
return 0, StackErrShortScript
|
||||||
|
}
|
||||||
|
// Yes this is little endian
|
||||||
|
return ((uint(script[1]) << 8) | uint(script[0])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// scriptUInt32 returns the number stored in the first 4 bytes of a slice.
|
||||||
|
func scriptUInt32(script []byte) (uint, error) {
|
||||||
|
if len(script) <= 4 {
|
||||||
|
return 0, StackErrShortScript
|
||||||
|
}
|
||||||
|
// Yes this is little endian
|
||||||
|
return ((uint(script[3]) << 24) | (uint(script[2]) << 16) |
|
||||||
|
(uint(script[1]) << 8) | uint(script[0])), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// getStack returns the contents of stack as a byte array bottom up
|
||||||
|
func getStack(stack *Stack) [][]byte {
|
||||||
|
array := make([][]byte, stack.Depth())
|
||||||
|
for i := range array {
|
||||||
|
// PeekByteArry can't fail due to overflow, already checked
|
||||||
|
array[len(array)-i-1], _ =
|
||||||
|
stack.PeekByteArray(i)
|
||||||
|
}
|
||||||
|
return array
|
||||||
|
}
|
||||||
|
|
||||||
|
// setStack sets the stack to the contents of the array where the last item in
|
||||||
|
// the array is the top item in the stack.
|
||||||
|
func setStack(stack *Stack, data [][]byte) {
|
||||||
|
// This can not error. Only errors are for invalid arguments.
|
||||||
|
_ = stack.DropN(stack.Depth())
|
||||||
|
|
||||||
|
for i := range data {
|
||||||
|
stack.PushByteArray(data[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetStack returns the contents of the primary stack as an array. where the
|
||||||
|
// last item in the array is the top of the stack.
|
||||||
|
func (s *Script) GetStack() [][]byte {
|
||||||
|
return getStack(&s.dstack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetStack sets the contents of the primary stack to the contents of the
|
||||||
|
// provided array where the last item in the array will be the top of the stack.
|
||||||
|
func (s *Script) SetStack(data [][]byte) {
|
||||||
|
setStack(&s.dstack, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAltStack returns the contents of the primary stack as an array. where the
|
||||||
|
// last item in the array is the top of the stack.
|
||||||
|
func (s *Script) GetAltStack() [][]byte {
|
||||||
|
return getStack(&s.astack)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAltStack sets the contents of the primary stack to the contents of the
|
||||||
|
// provided array where the last item in the array will be the top of the stack.
|
||||||
|
func (s *Script) SetAltStack(data [][]byte) {
|
||||||
|
setStack(&s.astack, data)
|
||||||
|
}
|
1060
script_test.go
Normal file
1060
script_test.go
Normal file
File diff suppressed because it is too large
Load Diff
350
stack.go
Normal file
350
stack.go
Normal file
@ -0,0 +1,350 @@
|
|||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcscript
|
||||||
|
|
||||||
|
import (
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// asInt converts a byte array to a bignum by treating it as a little endian
|
||||||
|
// number with sign bit.
|
||||||
|
func asInt(v []byte) *big.Int {
|
||||||
|
if len(v) == 0 {
|
||||||
|
return big.NewInt(0)
|
||||||
|
}
|
||||||
|
negative := false
|
||||||
|
msb := v[len(v)-1]
|
||||||
|
if msb&0x80 == 0x80 {
|
||||||
|
negative = true
|
||||||
|
// remove sign bit
|
||||||
|
v[len(v)-1] &= 0x7f
|
||||||
|
}
|
||||||
|
// trim leading 0 bytes
|
||||||
|
for ; msb == 0; msb = v[len(v)-1] {
|
||||||
|
v = v[:len(v)-1]
|
||||||
|
if len(v) == 0 {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// reverse bytes with a copy since stack is immutable.
|
||||||
|
intArray := make([]byte, len(v))
|
||||||
|
for i := range v {
|
||||||
|
intArray[len(v)-i-1] = v[i]
|
||||||
|
}
|
||||||
|
|
||||||
|
num := new(big.Int).SetBytes(intArray)
|
||||||
|
if negative {
|
||||||
|
num = num.Neg(num)
|
||||||
|
}
|
||||||
|
return num
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromInt provies a Big.Int in little endian format with the high bit of the
|
||||||
|
// msb donating sign.
|
||||||
|
func fromInt(v *big.Int) []byte {
|
||||||
|
negative := false
|
||||||
|
if v.Sign() == -1 {
|
||||||
|
negative = true
|
||||||
|
}
|
||||||
|
// Int.Bytes() trims leading zeros for us, so we don't have to.
|
||||||
|
b := v.Bytes()
|
||||||
|
if len(b) == 0 {
|
||||||
|
// negative 0
|
||||||
|
if negative {
|
||||||
|
return []byte{0x80}
|
||||||
|
}
|
||||||
|
// normal 0
|
||||||
|
return []byte{}
|
||||||
|
}
|
||||||
|
arr := make([]byte, len(b))
|
||||||
|
for i := range b {
|
||||||
|
arr[len(b)-i-1] = b[i]
|
||||||
|
}
|
||||||
|
// if would otherwise be negative, add a zero byte
|
||||||
|
if arr[len(arr)-1]&0x80 == 0x80 {
|
||||||
|
arr = append(arr, 0)
|
||||||
|
}
|
||||||
|
if negative {
|
||||||
|
arr[len(arr)-1] |= 0x80
|
||||||
|
}
|
||||||
|
return arr
|
||||||
|
}
|
||||||
|
|
||||||
|
// asBool gets the boolean value of the byte array.
|
||||||
|
func asBool(t []byte) bool {
|
||||||
|
for i := range t {
|
||||||
|
if t[i] != 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// fromBool converts a boolean into the appropriate byte array.
|
||||||
|
func fromBool(v bool) []byte {
|
||||||
|
if v {
|
||||||
|
return []byte{1}
|
||||||
|
}
|
||||||
|
return []byte{0}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stack represents a stack of immutable objects to be used with bitcoin scripts
|
||||||
|
// Objects may be shared, therefore in usage if a value is to be changed it
|
||||||
|
// *must* be deep-copied first to avoid changing other values on the stack.
|
||||||
|
type Stack struct {
|
||||||
|
stk [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushByteArray adds the given back array to the top of the stack.
|
||||||
|
func (s *Stack) PushByteArray(so []byte) {
|
||||||
|
s.stk = append(s.stk, so)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushInt converts the provided bignum to a suitable byte array then pushes
|
||||||
|
// it onto the top of the stack.
|
||||||
|
func (s *Stack) PushInt(val *big.Int) {
|
||||||
|
s.PushByteArray(fromInt(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PushBool converts the provided boolean to a suitable byte array then pushes
|
||||||
|
// it onto the top of the stack.
|
||||||
|
func (s *Stack) PushBool(val bool) {
|
||||||
|
s.PushByteArray(fromBool(val))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopByteArray pops the value off the top of the stack and returns it.
|
||||||
|
func (s *Stack) PopByteArray() ([]byte, error) {
|
||||||
|
return s.nipN(0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopInt pops the value off the top of the stack, converts it into a bignum and
|
||||||
|
// returns it.
|
||||||
|
func (s *Stack) PopInt() (*big.Int, error) {
|
||||||
|
so, err := s.PopByteArray()
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return asInt(so), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PopBool pops the value off the top of the stack, converts it into a bool and
|
||||||
|
// returns it.
|
||||||
|
func (s *Stack) PopBool() (bool, error) {
|
||||||
|
so, err := s.PopByteArray()
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return asBool(so), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekByteArray returns the nth item on the stack without removing it.
|
||||||
|
func (s *Stack) PeekByteArray(idx int) (so []byte, err error) {
|
||||||
|
sz := len(s.stk)
|
||||||
|
if idx < 0 || idx >= sz {
|
||||||
|
return nil, StackErrUnderflow
|
||||||
|
}
|
||||||
|
return s.stk[sz-idx-1], nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekInt returns the nth item on the stack as a bignum without removing it.
|
||||||
|
func (s *Stack) PeekInt(idx int) (i *big.Int, err error) {
|
||||||
|
so, err := s.PeekByteArray(idx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return asInt(so), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PeekBool returns the nth item on the stack as a bool without removing it.
|
||||||
|
func (s *Stack) PeekBool(idx int) (i bool, err error) {
|
||||||
|
so, err := s.PeekByteArray(idx)
|
||||||
|
if err != nil {
|
||||||
|
return false, err
|
||||||
|
}
|
||||||
|
return asBool(so), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// nipN is an internal function that removes the nth item on the stack and
|
||||||
|
// returns it.
|
||||||
|
func (s *Stack) nipN(idx int) (so []byte, err error) {
|
||||||
|
sz := len(s.stk)
|
||||||
|
if idx < 0 || idx > sz-1 {
|
||||||
|
err = StackErrUnderflow
|
||||||
|
return
|
||||||
|
}
|
||||||
|
so = s.stk[sz-idx-1]
|
||||||
|
if idx == 0 {
|
||||||
|
s.stk = s.stk[:sz-1]
|
||||||
|
} else if idx == sz-1 {
|
||||||
|
s.stk = s.stk[1:]
|
||||||
|
} else {
|
||||||
|
s1 := s.stk[sz-idx : sz]
|
||||||
|
s.stk = s.stk[:sz-idx-1]
|
||||||
|
s.stk = append(s.stk, s1...)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// NipN removes the Nth object on the stack
|
||||||
|
func (s *Stack) NipN(idx int) error {
|
||||||
|
_, err := s.nipN(idx)
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tuck copies the item at the top of the stack and inserts it before the 2nd
|
||||||
|
// to top item. e.g.: 2,1 -> 2,1,2
|
||||||
|
func (s *Stack) Tuck() error {
|
||||||
|
so2, err := s.PopByteArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
so1, err := s.PopByteArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.PushByteArray(so2) // stack 2
|
||||||
|
s.PushByteArray(so1) // stack 1,2
|
||||||
|
s.PushByteArray(so2) // stack 2,1,2
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Depth returns the number of items on the stack.
|
||||||
|
func (s *Stack) Depth() (sz int) {
|
||||||
|
sz = len(s.stk)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// DropN removes the top N items from the stack.
|
||||||
|
// e.g.
|
||||||
|
// DropN(1): 1,2,3 -> 1,2
|
||||||
|
// DropN(2): 1,2,3 -> 1
|
||||||
|
func (s *Stack) DropN(n int) error {
|
||||||
|
if n < 1 {
|
||||||
|
return StackErrInvalidArgs
|
||||||
|
}
|
||||||
|
for ; n > 0; n-- {
|
||||||
|
_, err := s.PopByteArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// DupN duplicates the top N items on the stack.
|
||||||
|
// e.g.
|
||||||
|
// DupN(1): 1,2,3 -> 1,2,3,3
|
||||||
|
// DupN(2): 1,2,3 -> 1,2,3,2,3
|
||||||
|
func (s *Stack) DupN(n int) error {
|
||||||
|
if n < 1 {
|
||||||
|
return StackErrInvalidArgs
|
||||||
|
}
|
||||||
|
// Iteratively duplicate the value n-1 down the stack n times.
|
||||||
|
// this leaves us with an in-order duplicate of the top N items on the
|
||||||
|
// stack.
|
||||||
|
for i := n; i > 0; i-- {
|
||||||
|
so, err := s.PeekByteArray(n - 1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.PushByteArray(so)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RotN rotates the top 3N items on the stack to the left
|
||||||
|
// e.g.
|
||||||
|
// RotN(1): 1,2,3 -> 2,3,1
|
||||||
|
func (s *Stack) RotN(n int) error {
|
||||||
|
if n < 1 {
|
||||||
|
return StackErrInvalidArgs
|
||||||
|
}
|
||||||
|
entry := 3*n - 1
|
||||||
|
// Nip the 3n-1th item from the stack to the top n times to rotate
|
||||||
|
// them up to the head of the stack.
|
||||||
|
for i := n; i > 0; i-- {
|
||||||
|
so, err := s.nipN(entry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.PushByteArray(so)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SwapN swaps the top N items on the stack with those below them.
|
||||||
|
// E.g.:
|
||||||
|
// SwapN(1): 1,2 -> 2,1
|
||||||
|
// SwapN(2): 1,2,3,4 -> 3,4,1,2
|
||||||
|
func (s *Stack) SwapN(n int) error {
|
||||||
|
if n < 1 {
|
||||||
|
return StackErrInvalidArgs
|
||||||
|
}
|
||||||
|
entry := 2*n - 1
|
||||||
|
for i := n; i > 0; i-- {
|
||||||
|
// swap 2n-1th entry to topj
|
||||||
|
so, err := s.nipN(entry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.PushByteArray(so)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// OverN copies N items N spaces back to the top of the stack.
|
||||||
|
// e.g.:
|
||||||
|
// OverN(1): 1,2 -> 1,2,1
|
||||||
|
// OverN(2): 1,2,3,4 -> 1,2,3,4,1,2
|
||||||
|
func (s *Stack) OverN(n int) error {
|
||||||
|
if n < 1 {
|
||||||
|
return StackErrInvalidArgs
|
||||||
|
}
|
||||||
|
// Copy 2n-1th entry to top of the stack
|
||||||
|
entry := 2*n - 1
|
||||||
|
for ; n > 0; n-- {
|
||||||
|
so, err := s.PeekByteArray(entry)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
s.PushByteArray(so)
|
||||||
|
// 4,1,2,3,4, now code original 3rd entry to top.
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// PickN copies the item N items back in the stack to the top.
|
||||||
|
// e.g.:
|
||||||
|
// PickN(1): 1,2,3 -> 1,2,3,2
|
||||||
|
// PickN(2): 1,2,3 -> 1,2,3,1
|
||||||
|
func (s *Stack) PickN(n int) error {
|
||||||
|
so, err := s.PeekByteArray(n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.PushByteArray(so)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RollN moves the item N items back in the stack to the top.
|
||||||
|
// e.g.:
|
||||||
|
// RollN(1): 1,2,3 -> 1,3,2
|
||||||
|
// RollN(2): 1,2,3 -> 2,3,1
|
||||||
|
func (s *Stack) RollN(n int) error {
|
||||||
|
so, err := s.nipN(n)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
s.PushByteArray(so)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
818
stack_test.go
Normal file
818
stack_test.go
Normal file
@ -0,0 +1,818 @@
|
|||||||
|
// Copyright (c) 2013 Conformal Systems LLC.
|
||||||
|
// Use of this source code is governed by an ISC
|
||||||
|
// license that can be found in the LICENSE file.
|
||||||
|
|
||||||
|
package btcscript_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"errors"
|
||||||
|
"github.com/conformal/btcscript"
|
||||||
|
"math/big"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
type stackTest struct {
|
||||||
|
name string
|
||||||
|
before [][]byte
|
||||||
|
operation func(*btcscript.Stack) error
|
||||||
|
expectedReturn error
|
||||||
|
after [][]byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var stackTests = []stackTest{
|
||||||
|
{
|
||||||
|
"noop",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"peek underflow (byte)",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
_, err := stack.PeekByteArray(5)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"peek underflow (int)",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
_, err := stack.PeekInt(5)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"peek underflow (bool)",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
_, err := stack.PeekBool(5)
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pop",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
val, err := stack.PopByteArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(val, []byte{5}) {
|
||||||
|
return errors.New("not equal!")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pop",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
val, err := stack.PopByteArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if !bytes.Equal(val, []byte{5}) {
|
||||||
|
return errors.New("not equal!")
|
||||||
|
}
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pop everything",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
_, err := stack.PopByteArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pop underflow",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
for i := 0; i < 6; i++ {
|
||||||
|
_, err := stack.PopByteArray()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pop bool",
|
||||||
|
[][]byte{{0}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
val, err := stack.PopBool()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != false {
|
||||||
|
return errors.New("unexpected value")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pop bool",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
val, err := stack.PopBool()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if val != true {
|
||||||
|
return errors.New("unexpected value")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pop bool",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
_, err := stack.PopBool()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
// XXX test popInt -> byte format matters here.
|
||||||
|
{
|
||||||
|
"dup",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
err := stack.DupN(1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dup2",
|
||||||
|
[][]byte{{1}, {2}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
err := stack.DupN(2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {1}, {2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dup3",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
err := stack.DupN(3)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {3}, {1}, {2}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dup0",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
err := stack.DupN(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
btcscript.StackErrInvalidArgs,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dup-1",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
err := stack.DupN(-1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
btcscript.StackErrInvalidArgs,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dup too much",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
err := stack.DupN(2)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"dup-1",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
err := stack.DupN(-1)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
btcscript.StackErrInvalidArgs,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PushBool true",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
stack.PushBool(true)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PushBool false",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
stack.PushBool(false)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{0}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PushBool PopBool",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
stack.PushBool(true)
|
||||||
|
val, err := stack.PopBool()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val != true {
|
||||||
|
return errors.New("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PushBool PopBool 2",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
stack.PushBool(false)
|
||||||
|
val, err := stack.PopBool()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val != false {
|
||||||
|
return errors.New("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PushInt PopBool",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
stack.PushInt(big.NewInt(1))
|
||||||
|
val, err := stack.PopBool()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val != true {
|
||||||
|
return errors.New("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PushInt PopBool 2",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
stack.PushInt(big.NewInt(0))
|
||||||
|
val, err := stack.PopBool()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val != false {
|
||||||
|
return errors.New("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"PushInt PopBool 2",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
stack.PushInt(big.NewInt(0))
|
||||||
|
val, err := stack.PopBool()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val != false {
|
||||||
|
return errors.New("unexpected value")
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nip top",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.NipN(0)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nip middle",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.NipN(1)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nip low",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.NipN(2)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{2}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nip too much",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
// bite off more than we can chew
|
||||||
|
return stack.NipN(3)
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{{2}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Nip too much",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
// bite off more than we can chew
|
||||||
|
return stack.NipN(3)
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{{2}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"keep on tucking",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.Tuck()
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {3}, {2}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"a little tucked up",
|
||||||
|
[][]byte{{1}}, // too few arguments for tuck
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.Tuck()
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"all tucked up",
|
||||||
|
[][]byte{}, // too few arguments for tuck
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.Tuck()
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drop 1",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.DropN(1)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drop 2",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.DropN(2)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drop 3",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.DropN(3)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drop 4",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.DropN(4)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drop 4/5",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.DropN(5)
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"drop invalid",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.DropN(0)
|
||||||
|
},
|
||||||
|
btcscript.StackErrInvalidArgs,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Rot1",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.RotN(1)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {3}, {4}, {2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Rot2",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {5}, {6}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.RotN(2)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{3}, {4}, {5}, {6}, {1}, {2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Rot too little",
|
||||||
|
[][]byte{{1}, {2}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.RotN(1)
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Rot0",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.RotN(0)
|
||||||
|
},
|
||||||
|
btcscript.StackErrInvalidArgs,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Swap1",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.SwapN(1)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {4}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Swap2",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.SwapN(2)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{3}, {4}, {1}, {2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Swap too little",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.SwapN(1)
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Swap0",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.SwapN(0)
|
||||||
|
},
|
||||||
|
btcscript.StackErrInvalidArgs,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Over1",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.OverN(1)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Over2",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.OverN(2)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {1}, {2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Over too little",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.OverN(1)
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Over0",
|
||||||
|
[][]byte{{1}, {2}, {3}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.OverN(0)
|
||||||
|
},
|
||||||
|
btcscript.StackErrInvalidArgs,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Pick1",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.PickN(1)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Pick2",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.PickN(2)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}, {2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Pick too little",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.PickN(1)
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Roll1",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.RollN(1)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {2}, {4}, {3}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Roll2",
|
||||||
|
[][]byte{{1}, {2}, {3}, {4}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.RollN(2)
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}, {3}, {4}, {2}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Roll too little",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
return stack.RollN(1)
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Peek bool",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
// Peek bool is otherwise pretty well tested, just check
|
||||||
|
// it works.
|
||||||
|
val, err := stack.PeekBool(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val != true {
|
||||||
|
return errors.New("invalid result")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Peek bool 2",
|
||||||
|
[][]byte{{0}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
// Peek bool is otherwise pretty well tested, just check
|
||||||
|
// it works.
|
||||||
|
val, err := stack.PeekBool(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val != false {
|
||||||
|
return errors.New("invalid result")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{0}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Peek int",
|
||||||
|
[][]byte{{1}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
// Peek int is otherwise pretty well tested, just check
|
||||||
|
// it works.
|
||||||
|
val, err := stack.PeekInt(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
return errors.New("invalid result")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{1}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"Peek int 2",
|
||||||
|
[][]byte{{0}},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
// Peek int is otherwise pretty well tested, just check
|
||||||
|
// it works.
|
||||||
|
val, err := stack.PeekInt(0)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val.Cmp(big.NewInt(0)) != 0 {
|
||||||
|
return errors.New("invalid result")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{{0}},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pop int",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
stack.PushInt(big.NewInt(1))
|
||||||
|
// Peek int is otherwise pretty well tested, just check
|
||||||
|
// it works.
|
||||||
|
val, err := stack.PopInt()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if val.Cmp(big.NewInt(1)) != 0 {
|
||||||
|
return errors.New("invalid result")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
},
|
||||||
|
nil,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"pop empty",
|
||||||
|
[][]byte{},
|
||||||
|
func(stack *btcscript.Stack) error {
|
||||||
|
// Peek int is otherwise pretty well tested, just check
|
||||||
|
// it works.
|
||||||
|
_, err := stack.PopInt()
|
||||||
|
return err
|
||||||
|
},
|
||||||
|
btcscript.StackErrUnderflow,
|
||||||
|
[][]byte{},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
func doTest(t *testing.T, test stackTest) {
|
||||||
|
stack := btcscript.Stack{}
|
||||||
|
|
||||||
|
for i := range test.before {
|
||||||
|
stack.PushByteArray(test.before[i])
|
||||||
|
}
|
||||||
|
err := test.operation(&stack)
|
||||||
|
if err != test.expectedReturn {
|
||||||
|
t.Errorf("%s: operation return not what expected: %v vs %v",
|
||||||
|
test.name, err, test.expectedReturn)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if len(test.after) != stack.Depth() {
|
||||||
|
t.Errorf("%s: stack depth doesn't match expected: %v vs %v",
|
||||||
|
test.name, len(test.after), stack.Depth())
|
||||||
|
}
|
||||||
|
for i := range test.after {
|
||||||
|
val, err := stack.PeekByteArray(stack.Depth() - i - 1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: can't peek %dth stack entry: %v",
|
||||||
|
test.name, i, err)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
if !bytes.Equal(val, test.after[i]) {
|
||||||
|
t.Errorf("%s: %dth stack entry doesn't match "+
|
||||||
|
"expected: %v vs %v", test.name, i, val,
|
||||||
|
test.after[i])
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestStack(t *testing.T) {
|
||||||
|
for i := range stackTests {
|
||||||
|
doTest(t, stackTests[i])
|
||||||
|
}
|
||||||
|
}
|
138
test_coverage.txt
Normal file
138
test_coverage.txt
Normal file
@ -0,0 +1,138 @@
|
|||||||
|
|
||||||
|
github.com/conformal/btcscript/stack.go asInt 100.00% (18/18)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeWithin 100.00% (13/13)
|
||||||
|
github.com/conformal/btcscript/opcode.go parsedOpcode.print 100.00% (12/12)
|
||||||
|
github.com/conformal/btcscript/opcode.go parsedOpcode.bytes 100.00% (12/12)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.nipN 100.00% (12/12)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeIf 100.00% (11/11)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeNotIf 100.00% (11/11)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeNumEqual 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeBoolOr 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.Tuck 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeBoolAnd 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeMin 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeGreaterThanOrEqual 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeLessThanOrEqual 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeGreaterThan 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeLessThan 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeNumNotEqual 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeMax 100.00% (10/10)
|
||||||
|
github.com/conformal/btcscript/script.go DisasmString 100.00% (9/9)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.RotN 100.00% (9/9)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.SwapN 100.00% (9/9)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.OverN 100.00% (9/9)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeAdd 100.00% (8/8)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.DupN 100.00% (8/8)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeSub 100.00% (8/8)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeEqual 100.00% (8/8)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeRoll 100.00% (7/7)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.DropN 100.00% (7/7)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodePick 100.00% (7/7)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeNot 100.00% (7/7)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode0NotEqual 100.00% (7/7)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeIfDup 100.00% (6/6)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeVerify 100.00% (6/6)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeElse 100.00% (6/6)
|
||||||
|
github.com/conformal/btcscript/opcode.go parsedOpcode.conditional 100.00% (6/6)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode1Add 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.RollN 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeRipemd160 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeHash256 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PickN 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeSize 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode1Sub 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeNegate 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeAbs 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/script.go removeOpcodeByData 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/script.go removeOpcode 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeSha256 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeHash160 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeToAltStack 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeFromAltStack 100.00% (5/5)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PopBool 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeCheckMultiSigVerify 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PeekInt 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/script.go unparseScript 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeEqualVerify 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PeekByteArray 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PeekBool 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeNumEqualVerify 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/stack.go asBool 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeEndif 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PopInt 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/script.go getStack 100.00% (4/4)
|
||||||
|
github.com/conformal/btcscript/stack.go fromBool 100.00% (3/3)
|
||||||
|
github.com/conformal/btcscript/script.go scriptUInt8 100.00% (3/3)
|
||||||
|
github.com/conformal/btcscript/script.go scriptUInt32 100.00% (3/3)
|
||||||
|
github.com/conformal/btcscript/address.go ScriptType.String 100.00% (3/3)
|
||||||
|
github.com/conformal/btcscript/script.go setStack 100.00% (3/3)
|
||||||
|
github.com/conformal/btcscript/script.go scriptUInt16 100.00% (3/3)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeFalse 100.00% (2/2)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodePushData 100.00% (2/2)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode1Negate 100.00% (2/2)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeN 100.00% (2/2)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeDepth 100.00% (2/2)
|
||||||
|
github.com/conformal/btcscript/opcode.go calcHash 100.00% (2/2)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeCodeSeparator 100.00% (2/2)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.Depth 100.00% (2/2)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.NipN 100.00% (2/2)
|
||||||
|
github.com/conformal/btcscript/script.go Script.GetAltStack 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PushBool 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode2Drop 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeReturn 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeNop 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeInvalid 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeReserved 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/script.go Script.subScript 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeDisabled 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode2Swap 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go init 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/script.go isPubkey 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/script.go isPubkeyHash 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/script.go isScriptHash 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/log.go newLogClosure 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/script.go Script.disasm 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/log.go UseLogger 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/log.go DisableLog 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/log.go init 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/script.go Script.GetStack 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/script.go Script.SetStack 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go calcHash160 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeTuck 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PushByteArray 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PushInt 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeSwap 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/stack.go Stack.PopByteArray 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeRot 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeOver 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeNip 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeDup 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeDrop 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode2Rot 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode2Over 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode3Dup 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcode2Dup 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/script.go Script.SetAltStack 100.00% (1/1)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeCheckMultiSig 96.43% (54/56)
|
||||||
|
github.com/conformal/btcscript/script.go NewScript 95.24% (20/21)
|
||||||
|
github.com/conformal/btcscript/address.go ScriptToAddress 94.92% (56/59)
|
||||||
|
github.com/conformal/btcscript/script.go parseScript 93.75% (30/32)
|
||||||
|
github.com/conformal/btcscript/stack.go fromInt 87.50% (14/16)
|
||||||
|
github.com/conformal/btcscript/script.go Script.Step 86.11% (31/36)
|
||||||
|
github.com/conformal/btcscript/script.go typeOfScript 83.33% (5/6)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeCheckSig 80.77% (21/26)
|
||||||
|
github.com/conformal/btcscript/opcode.go parsedOpcode.exec 80.00% (4/5)
|
||||||
|
github.com/conformal/btcscript/script.go Script.DisasmScript 80.00% (4/5)
|
||||||
|
github.com/conformal/btcscript/script.go Script.DisasmPC 75.00% (3/4)
|
||||||
|
github.com/conformal/btcscript/script.go Script.curPC 75.00% (3/4)
|
||||||
|
github.com/conformal/btcscript/script.go isPushOnly 75.00% (3/4)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeCheckSigVerify 75.00% (3/4)
|
||||||
|
github.com/conformal/btcscript/script.go Script.calcScriptHash 71.43% (25/35)
|
||||||
|
github.com/conformal/btcscript/script.go isMultiSig 61.54% (8/13)
|
||||||
|
github.com/conformal/btcscript/opcode.go opcodeSha1 60.00% (3/5)
|
||||||
|
github.com/conformal/btcscript/script.go Script.validPC 60.00% (3/5)
|
||||||
|
github.com/conformal/btcscript/script.go Script.Execute 55.17% (16/29)
|
||||||
|
github.com/conformal/btcscript/log.go SetLogWriter 0.00% (0/7)
|
||||||
|
github.com/conformal/btcscript/log.go logClosure.String 0.00% (0/1)
|
||||||
|
github.com/conformal/btcscript ------------------------- 92.51% (828/895)
|
||||||
|
|
Loading…
Reference in New Issue
Block a user