Initial implementation.

This commit is contained in:
Dave Collins 2013-06-12 16:35:27 -05:00
parent 63af0dbca9
commit 1197770159
15 changed files with 8475 additions and 1 deletions

13
LICENSE Normal file
View 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.

View File

@ -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
View 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
View 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
View 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
View 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
View 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
View 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)
}

1603
opcode.go Normal file

File diff suppressed because it is too large Load Diff

3104
opcode_test.go Normal file

File diff suppressed because it is too large Load Diff

697
script.go Normal file
View 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

File diff suppressed because it is too large Load Diff

350
stack.go Normal file
View 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
View 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
View 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)