Add flag bitmask to NewScript for canonical sig checks.

This removes the bip16 bool from NewScript and adds it to flags (with
the constant ScriptBip16), and also adds a new flag,
ScriptCanonicalSignatures, which will call btcec.ParseDERSignature
parsing a signature during an Execute.  This is needed to emulate
bitcoind behavior, as bitcoind performs canonical signature validation
(DER format) in some places (like mempool acceptance) but not others
(like block validation).

Updated tests and test coverage file to reflect changes.
This commit is contained in:
David Hill 2013-10-24 10:13:58 -04:00 committed by Josh Rickmar
parent 40c75b27ac
commit 8eead5217d
5 changed files with 286 additions and 25 deletions

View File

@ -1682,7 +1682,12 @@ func opcodeCheckSig(op *parsedOpcode, s *Script) error {
return err
}
signature, err := btcec.ParseSignature(sigStr, btcec.S256())
var signature *btcec.Signature
if s.der {
signature, err = btcec.ParseDERSignature(sigStr, btcec.S256())
} else {
signature, err = btcec.ParseSignature(sigStr, btcec.S256())
}
if err != nil {
log.Warnf("can't parse signature from string: %v", err)
return err
@ -1761,10 +1766,17 @@ func opcodeCheckMultiSig(op *parsedOpcode, s *Script) error {
return err
}
// skip off the last byte for hashtype
signatures[i], err =
btcec.ParseSignature(
sigStrings[i][:len(sigStrings[i])-1],
btcec.S256())
if s.der {
signatures[i], err =
btcec.ParseDERSignature(
sigStrings[i][:len(sigStrings[i])-1],
btcec.S256())
} else {
signatures[i], err =
btcec.ParseSignature(
sigStrings[i][:len(sigStrings[i])-1],
btcec.S256())
}
if err != nil {
return err
}

View File

@ -19,6 +19,7 @@ import (
// All run on a fake tx with a single in, single out.
type opcodeTest struct {
script []byte
canonical bool
shouldPass bool
shouldFail error
}
@ -384,6 +385,18 @@ var opcodeTests = []opcodeTest{
0x8a, 0x06, 0x26, 0xf1, 0xba, 0xde, 0xd5, 0xc7, 0x2a, 0x70,
0x4f, 0x7e, 0x6c, 0xd8, 0x4c,
btcscript.OP_1, btcscript.OP_CHECK_MULTISIG},
canonical: false,
shouldPass: false},
{script: []byte{btcscript.OP_1, btcscript.OP_1, btcscript.OP_DATA_65,
0x04, 0xae, 0x1a, 0x62, 0xfe, 0x09, 0xc5, 0xf5, 0x1b, 0x13,
0x90, 0x5f, 0x07, 0xf0, 0x6b, 0x99, 0xa2, 0xf7, 0x15, 0x9b,
0x22, 0x25, 0xf3, 0x74, 0xcd, 0x37, 0x8d, 0x71, 0x30, 0x2f,
0xa2, 0x84, 0x14, 0xe7, 0xaa, 0xb3, 0x73, 0x97, 0xf5, 0x54,
0xa7, 0xdf, 0x5f, 0x14, 0x2c, 0x21, 0xc1, 0xb7, 0x30, 0x3b,
0x8a, 0x06, 0x26, 0xf1, 0xba, 0xde, 0xd5, 0xc7, 0x2a, 0x70,
0x4f, 0x7e, 0x6c, 0xd8, 0x4c,
btcscript.OP_1, btcscript.OP_CHECK_MULTISIG},
canonical: true,
shouldPass: false},
/* up here because no defined error case. */
{script: []byte{btcscript.OP_1, btcscript.OP_1, btcscript.OP_DATA_65,
@ -468,7 +481,7 @@ var opcodeTests = []opcodeTest{
{script: []byte{252}, shouldPass: false},
}
func testScript(t *testing.T, script []byte) (err error) {
func testScript(t *testing.T, script []byte, canonical bool) (err error) {
// mock up fake tx.
tx := &btcwire.MsgTx{
Version: 1,
@ -493,8 +506,13 @@ func testScript(t *testing.T, script []byte) (err error) {
tx.TxOut[0].PkScript = script
var flags btcscript.ScriptFlags
if canonical {
flags = btcscript.ScriptCanonicalSignatures
}
engine, err := btcscript.NewScript(tx.TxIn[0].SignatureScript,
tx.TxOut[0].PkScript, 0, tx, false)
tx.TxOut[0].PkScript, 0, tx, flags)
if err != nil {
return err
}
@ -514,7 +532,7 @@ func TestScripts(t *testing.T) {
for i := range opcodeTests {
shouldPass := opcodeTests[i].shouldPass
shouldFail := opcodeTests[i].shouldFail
err := testScript(t, opcodeTests[i].script)
err := testScript(t, opcodeTests[i].script, opcodeTests[i].canonical)
if shouldFail != nil {
if err == nil {
t.Errorf("test %d passed should fail with %v", i, err)
@ -4279,7 +4297,7 @@ func testOpcode(t *testing.T, test *detailedTest) {
tx.TxOut[0].PkScript = test.script
engine, err := btcscript.NewScript(tx.TxIn[0].SignatureScript,
tx.TxOut[0].PkScript, 0, tx, false)
tx.TxOut[0].PkScript, 0, tx, 0)
if err != nil {
if err != test.expectedReturn {
t.Errorf("Error return not expected %s: %v %v",

View File

@ -159,6 +159,7 @@ type Script struct {
condStack []int
numOps int
bip16 bool // treat execution as pay-to-script-hash
der bool // enforce DER encoding
savedFirstStack [][]byte // stack from first script for bip16 scripts
}
@ -361,11 +362,35 @@ func unparseScript(pops []parsedOpcode) ([]byte, error) {
return script, nil
}
// ScriptFlags is a bitmask defining additional operations or
// tests that will be done when executing a Script.
type ScriptFlags uint32
const (
// ScriptBip16 defines whether the bip16 threshhold has passed and thus
// pay-to-script hash transactions will be fully validated.
ScriptBip16 ScriptFlags = 1 << iota
// ScriptCanonicalSignatures defines whether additional canonical
// signature checks are performed when parsing a signature.
//
// Canonical (DER) signatures are not required in the tx rules for
// block acceptance, but are checked in recent versions of bitcoind
// when accepting transactions to the mempool. Non-canonical (valid
// BER but not valid DER) transactions can potentially be changed
// before mined into a block, either by adding extra padding or
// flipping the sign of the R or S value in the signature, creating a
// transaction that still validates and spends the inputs, but is not
// recognized by creator of the transaction. Performing a canonical
// check enforces script signatures use a unique DER format.
ScriptCanonicalSignatures
)
// 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, bip16 bool) (*Script, error) {
func NewScript(scriptSig []byte, scriptPubKey []byte, txidx int, tx *btcwire.MsgTx, flags ScriptFlags) (*Script, error) {
var m Script
scripts := [][]byte{scriptSig, scriptPubKey}
m.scripts = make([][]parsedOpcode, len(scripts))
@ -385,6 +410,8 @@ func NewScript(scriptSig []byte, scriptPubKey []byte, txidx int, tx *btcwire.Msg
}
}
// Parse flags.
bip16 := flags&ScriptBip16 == ScriptBip16
if bip16 && isScriptHash(m.scripts[1]) {
// if we are pay to scripthash then we only accept input
// scripts that push data
@ -393,6 +420,9 @@ func NewScript(scriptSig []byte, scriptPubKey []byte, txidx int, tx *btcwire.Msg
}
m.bip16 = true
}
if flags&ScriptCanonicalSignatures == ScriptCanonicalSignatures {
m.der = true
}
m.tx = *tx
m.txidx = txidx
@ -964,19 +994,39 @@ func signatureScriptCustomReader(reader io.Reader, tx *btcwire.MsgTx, idx int,
//
// 0x30 <length> 0x02 <length r> r 0x02 <length s> s
func sigDER(r, s *big.Int) []byte {
// In DER format, a leading 0x00 octet must be prepended to
// the byte slice so it cannot be interpreted as a negative
// big-endian number. This is only done if the sign bit on
// the first byte is set.
var rb, sb []byte
if r.Bytes()[0]&0x80 != 0 {
paddedBytes := make([]byte, len(r.Bytes())+1)
copy(paddedBytes[1:], r.Bytes())
rb = paddedBytes
} else {
rb = r.Bytes()
}
if s.Bytes()[0]&0x80 != 0 {
paddedBytes := make([]byte, len(s.Bytes())+1)
copy(paddedBytes[1:], s.Bytes())
sb = paddedBytes
} else {
sb = s.Bytes()
}
// total length of returned signature is 1 byte for each magic and
// length (6 total), plus lengths of r and s
length := 6 + len(r.Bytes()) + len(s.Bytes())
length := 6 + len(rb) + len(sb)
b := make([]byte, length, length)
b[0] = 0x30
b[1] = byte(length - 2)
b[2] = 0x02
b[3] = byte(len(r.Bytes()))
offset := copy(b[4:], r.Bytes()) + 4
b[3] = byte(len(rb))
offset := copy(b[4:], rb) + 4
b[offset] = 0x02
b[offset+1] = byte(len(s.Bytes()))
copy(b[offset+2:], s.Bytes())
b[offset+1] = byte(len(sb))
copy(b[offset+2:], sb)
return b
}

View File

@ -20,6 +20,7 @@ type txTest struct {
pkScript []byte // output script of previous tx
idx int // tx idx to be run.
bip16 bool // is bip16 active?
canonicalSigs bool // should signatures be validated as canonical?
parseErr error // failure of NewScript
err error // Failure of Executre
shouldFail bool // Execute should fail with nonspecified error.
@ -566,6 +567,178 @@ var txTests = []txTest{
SigOps: 1,
},
},
txTest{
name: "Non-canonical signature: R value negative",
tx: &btcwire.MsgTx{
Version: 1,
TxIn: []*btcwire.TxIn{
&btcwire.TxIn{
PreviousOutpoint: btcwire.OutPoint{
Hash: btcwire.ShaHash([32]byte{
0xfe, 0x15, 0x62, 0xc4,
0x8b, 0x3a, 0xa6, 0x37,
0x3f, 0x42, 0xe9, 0x61,
0x51, 0x89, 0xcf, 0x73,
0x32, 0xd7, 0x33, 0x5c,
0xbe, 0xa7, 0x80, 0xbe,
0x69, 0x6a, 0xc6, 0xc6,
0x50, 0xfd, 0xda, 0x4a,
}),
Index: 1,
},
SignatureScript: []byte{
btcscript.OP_DATA_71,
0x30, 0x44, 0x02, 0x20, 0xa0,
0x42, 0xde, 0xe5, 0x52, 0x6b,
0xf2, 0x29, 0x4d, 0x3f, 0x3e,
0xb9, 0x5a, 0xa7, 0x73, 0x19,
0xd3, 0xff, 0x56, 0x7b, 0xcf,
0x36, 0x46, 0x07, 0x0c, 0x81,
0x12, 0x33, 0x01, 0xca, 0xce,
0xa9, 0x02, 0x20, 0xea, 0x48,
0xae, 0x58, 0xf5, 0x54, 0x10,
0x96, 0x3f, 0xa7, 0x03, 0xdb,
0x56, 0xf0, 0xba, 0xb2, 0x70,
0xb1, 0x08, 0x22, 0xc5, 0x1c,
0x68, 0x02, 0x6a, 0x97, 0x5c,
0x7d, 0xae, 0x11, 0x2e, 0x4f,
0x01,
btcscript.OP_DATA_65,
0x04, 0x49, 0x45, 0x33, 0x18,
0xbd, 0x5e, 0xcf, 0xea, 0x5f,
0x86, 0x32, 0x8c, 0x6d, 0x8e,
0xd4, 0x12, 0xb4, 0xde, 0x2c,
0xab, 0xd7, 0xb8, 0x56, 0x51,
0x2f, 0x8c, 0xb7, 0xfd, 0x25,
0xf6, 0x03, 0xb0, 0x55, 0xc5,
0xdf, 0xe6, 0x22, 0x4b, 0xc4,
0xfd, 0xbb, 0x6a, 0x7a, 0xa0,
0x58, 0xd7, 0x5d, 0xad, 0x92,
0x99, 0x45, 0x4f, 0x62, 0x1a,
0x95, 0xb4, 0xb0, 0x21, 0x0e,
0xc4, 0x09, 0x2b, 0xe5, 0x27,
},
Sequence: 4294967295,
},
&btcwire.TxIn{
PreviousOutpoint: btcwire.OutPoint{
Hash: btcwire.ShaHash([32]byte{
0x2a, 0xc7, 0xee, 0xf8,
0xa9, 0x62, 0x2d, 0xda,
0xec, 0x18, 0x3b, 0xba,
0xa9, 0x92, 0xb0, 0x7a,
0x70, 0x3b, 0x48, 0x86,
0x27, 0x9c, 0x46, 0xac,
0x25, 0xeb, 0x91, 0xec,
0x4c, 0x86, 0xd2, 0x9c,
}),
Index: 1,
},
SignatureScript: []byte{
btcscript.OP_DATA_71,
0x30, 0x44, 0x02, 0x20, 0xc3,
0x02, 0x3b, 0xed, 0x85, 0x0d,
0x94, 0x27, 0x8e, 0x06, 0xd2,
0x37, 0x92, 0x21, 0x55, 0x28,
0xdd, 0xdb, 0x63, 0xa4, 0xb6,
0x88, 0x33, 0x92, 0x06, 0xdd,
0xf9, 0xee, 0x72, 0x97, 0xa3,
0x08, 0x02, 0x20, 0x25, 0x00,
0x42, 0x8b, 0x26, 0x36, 0x45,
0x54, 0xcb, 0x11, 0xd3, 0x3e,
0x85, 0x35, 0x23, 0x49, 0x65,
0x82, 0x8e, 0x33, 0x6e, 0x1a,
0x4a, 0x72, 0x73, 0xeb, 0x5b,
0x8d, 0x1d, 0xd7, 0x02, 0xcc,
0x01,
btcscript.OP_DATA_65,
0x04, 0x49, 0x5c, 0x8f, 0x66,
0x90, 0x0d, 0xb7, 0x62, 0x69,
0x0b, 0x54, 0x49, 0xa1, 0xf4,
0xe7, 0xc2, 0xed, 0x1f, 0x4b,
0x34, 0x70, 0xfd, 0x42, 0x79,
0x68, 0xa1, 0x31, 0x76, 0x0c,
0x25, 0xf9, 0x12, 0x63, 0xad,
0x51, 0x73, 0x8e, 0x19, 0xb6,
0x07, 0xf5, 0xcf, 0x5f, 0x94,
0x27, 0x4a, 0x8b, 0xbc, 0x74,
0xba, 0x4b, 0x56, 0x0c, 0xe0,
0xb3, 0x08, 0x8f, 0x7f, 0x5c,
0x5f, 0xcf, 0xd6, 0xa0, 0x4b,
},
Sequence: 4294967295,
},
},
TxOut: []*btcwire.TxOut{
&btcwire.TxOut{
Value: 630320000,
PkScript: []byte{
btcscript.OP_DUP,
btcscript.OP_HASH160,
btcscript.OP_DATA_20,
0x43, 0xdc, 0x32, 0x1b, 0x66,
0x00, 0x51, 0x1f, 0xe0, 0xa9,
0x6a, 0x97, 0xc2, 0x59, 0x3a,
0x90, 0x54, 0x29, 0x74, 0xd6,
btcscript.OP_EQUALVERIFY,
btcscript.OP_CHECKSIG,
},
},
&btcwire.TxOut{
Value: 100000181,
PkScript: []byte{
btcscript.OP_DUP,
btcscript.OP_HASH160,
btcscript.OP_DATA_20,
0xa4, 0x05, 0xea, 0x18, 0x09,
0x14, 0xa9, 0x11, 0xd0, 0xb8,
0x07, 0x99, 0x19, 0x2b, 0x0b,
0x84, 0xae, 0x80, 0x1e, 0xbd,
btcscript.OP_EQUALVERIFY,
btcscript.OP_CHECKSIG,
},
},
&btcwire.TxOut{
Value: 596516343,
PkScript: []byte{
btcscript.OP_DUP,
btcscript.OP_HASH160,
btcscript.OP_DATA_20,
0x24, 0x56, 0x76, 0x45, 0x4f,
0x6f, 0xff, 0x28, 0x88, 0x39,
0x47, 0xea, 0x70, 0x23, 0x86,
0x9b, 0x8a, 0x71, 0xa3, 0x05,
btcscript.OP_EQUALVERIFY,
btcscript.OP_CHECKSIG,
},
},
},
LockTime: 0,
},
// Test input 0
pkScript: []byte{
btcscript.OP_DUP,
btcscript.OP_HASH160,
btcscript.OP_DATA_20,
0xfd, 0xf6, 0xea, 0xe7, 0x10,
0xa0, 0xc4, 0x49, 0x7a, 0x8d,
0x0f, 0xd2, 0x9a, 0xf6, 0x6b,
0xac, 0x94, 0xaf, 0x6c, 0x98,
btcscript.OP_EQUALVERIFY,
btcscript.OP_CHECKSIG,
},
idx: 0,
canonicalSigs: true,
shouldFail: true,
nSigOps: 1,
scriptInfo: btcscript.ScriptInfo{
PkScriptClass: btcscript.PubKeyHashTy,
NumInputs: 2,
ExpectedInputs: 2,
SigOps: 1,
},
},
// tx 51bf528ecf3c161e7c021224197dbe84f9a8564212f6207baa014c01a1668e1e
// first instance of an AnyoneCanPay signature in the blockchain
txTest{
@ -1265,9 +1438,16 @@ var txTests = []txTest{
// nothing in the blockchain that we have yet seen uses them, making it hard
// to confirm we implemented the spec correctly.
func testTx(t *testing.T, test txTest) {
var flags btcscript.ScriptFlags
if test.bip16 {
flags |= btcscript.ScriptBip16
}
if test.canonicalSigs {
flags |= btcscript.ScriptCanonicalSignatures
}
engine, err := btcscript.NewScript(
test.tx.TxIn[test.idx].SignatureScript, test.pkScript,
test.idx, test.tx, test.bip16)
test.idx, test.tx, flags)
if err != nil {
if err != test.parseErr {
t.Errorf("Failed to parse %s: got \"%v\" expected "+
@ -1983,7 +2163,7 @@ func TestBadPC(t *testing.T) {
for _, test := range pcTests {
engine, err := btcscript.NewScript(tx.TxIn[0].SignatureScript,
pkScript, 0, tx, false)
pkScript, 0, tx, 0)
if err != nil {
t.Errorf("Failed to create script: %v", err)
}
@ -2053,7 +2233,7 @@ func TestCheckErrorCondition(t *testing.T) {
}
engine, err := btcscript.NewScript(tx.TxIn[0].SignatureScript, pkScript,
0, tx, false)
0, tx, 0)
if err != nil {
t.Errorf("failed to create script: %v", err)
}
@ -2460,10 +2640,11 @@ nexttest:
}
// Validate tx input scripts
scriptFlags := btcscript.ScriptBip16 | btcscript.ScriptCanonicalSignatures
for j, txin := range tx.TxIn {
engine, err := btcscript.NewScript(txin.SignatureScript,
SigScriptTests[i].inputs[j].txout.PkScript,
j, tx, true)
j, tx, scriptFlags)
if err != nil {
t.Errorf("cannot create script vm for test %v: %v",
SigScriptTests[i].name, err)

View File

@ -4,17 +4,17 @@ github.com/conformal/btcscript/script.go Script.Step 100.00% (37/37)
github.com/conformal/btcscript/script.go parseScriptTemplate 100.00% (30/30)
github.com/conformal/btcscript/script.go CalcScriptInfo 100.00% (25/25)
github.com/conformal/btcscript/opcode.go parsedOpcode.bytes 100.00% (23/23)
github.com/conformal/btcscript/script.go sigDER 100.00% (22/22)
github.com/conformal/btcscript/script.go NewScript 100.00% (21/21)
github.com/conformal/btcscript/stack.go asInt 100.00% (21/21)
github.com/conformal/btcscript/script.go NewScript 100.00% (18/18)
github.com/conformal/btcscript/script.go signatureScriptCustomReader 100.00% (15/15)
github.com/conformal/btcscript/stack.go fromInt 100.00% (14/14)
github.com/conformal/btcscript/stack.go Stack.nipN 100.00% (14/14)
github.com/conformal/btcscript/script.go GetPreciseSigOpCount 100.00% (13/13)
github.com/conformal/btcscript/opcode.go opcodeWithin 100.00% (13/13)
github.com/conformal/btcscript/script.go isMultiSig 100.00% (13/13)
github.com/conformal/btcscript/script.go GetPreciseSigOpCount 100.00% (13/13)
github.com/conformal/btcscript/opcode.go parsedOpcode.print 100.00% (12/12)
github.com/conformal/btcscript/opcode.go opcodeNotIf 100.00% (11/11)
github.com/conformal/btcscript/script.go sigDER 100.00% (11/11)
github.com/conformal/btcscript/opcode.go opcodeIf 100.00% (11/11)
github.com/conformal/btcscript/opcode.go opcodeMax 100.00% (10/10)
github.com/conformal/btcscript/script.go getSigOpCount 100.00% (10/10)
@ -140,8 +140,8 @@ github.com/conformal/btcscript/script.go Script.SetStack 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 opcodeRot 100.00% (1/1)
github.com/conformal/btcscript/opcode.go opcodeCheckMultiSig 98.21% (55/56)
github.com/conformal/btcscript/opcode.go opcodeCheckSig 96.15% (25/26)
github.com/conformal/btcscript/opcode.go opcodeCheckMultiSig 98.28% (57/58)
github.com/conformal/btcscript/opcode.go opcodeCheckSig 96.55% (28/29)
github.com/conformal/btcscript/script.go calcScriptHash 82.05% (32/39)
github.com/conformal/btcscript/script.go Script.CheckErrorCondition 78.57% (11/14)
github.com/conformal/btcscript/opcode.go opcodeCheckSigVerify 75.00% (3/4)
@ -149,5 +149,5 @@ github.com/conformal/btcscript/script.go Script.Execute 44.44% (8/18)
github.com/conformal/btcscript/log.go SetLogWriter 0.00% (0/7)
github.com/conformal/btcscript/script.go IsPushOnlyScript 0.00% (0/4)
github.com/conformal/btcscript/log.go logClosure.String 0.00% (0/1)
github.com/conformal/btcscript --------------------------- 96.52% (970/1005)
github.com/conformal/btcscript --------------------------- 96.58% (989/1024)