diff --git a/opcode.go b/opcode.go index 65012698..21a4ee18 100644 --- a/opcode.go +++ b/opcode.go @@ -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 } diff --git a/opcode_test.go b/opcode_test.go index 151b7464..d757463e 100644 --- a/opcode_test.go +++ b/opcode_test.go @@ -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", diff --git a/script.go b/script.go index 10871793..9aa814bb 100644 --- a/script.go +++ b/script.go @@ -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 0x02 r 0x02 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 } diff --git a/script_test.go b/script_test.go index 0caee16b..3962650b 100644 --- a/script_test.go +++ b/script_test.go @@ -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) diff --git a/test_coverage.txt b/test_coverage.txt index 0e9376e9..0ac26ed4 100644 --- a/test_coverage.txt +++ b/test_coverage.txt @@ -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)