From 58741b0fc5f0b3368224f44da0a6e088c80905c1 Mon Sep 17 00:00:00 2001 From: Chris Pacia Date: Thu, 5 May 2022 05:37:14 -0400 Subject: [PATCH] May 2022 Hardfork (#499) * Add Script64BitIntegers script flag This commit adds a flag to the script engine to use 64 bit integers and updates the ScriptNum class to optionally support 64 bit integers. This is the first part of the May 2022 hardfork. * Add ScriptNumber 64 bit tests * Create native introspection opcodes * Check for script flag in new opcodes * Update script_tests.json * Add opcode tests * Add fork activation * Add activation tests * Create testnet4 params * Bump version * Copy opcode slices before returning * Add op_mul and fix overflow handling * Disable op_mul before fork * update scriptnum and makeScriptNum documentation for 64-bit cases (#501) * Fix typos Co-authored-by: emergent_reasons <34025062+emergent-reasons@users.noreply.github.com> --- bchec/genprecomps.go | 1 + bchec/gensecp256k1.go | 1 + bchrpc/proxy/gw_tools.go | 1 + blockchain/fullblocks_test.go | 107 ++++ blockchain/fullblocktests/generate.go | 195 ++++++ blockchain/scriptval.go | 23 +- blockchain/thresholdstate.go | 8 +- blockchain/validate.go | 8 + chaincfg/genesis.go | 28 + chaincfg/params.go | 118 +++- config.go | 5 + database/ffldb/disk.go | 1 + database/ffldb/disk_darwin.go | 1 + database/ffldb/disk_dragonfly.go | 1 + database/ffldb/disk_freebsd.go | 1 + database/ffldb/disk_linux.go | 1 + database/ffldb/disk_windows.go | 1 + integration/bip0009_test.go | 1 + integration/csv_fork_test.go | 1 + integration/rpcserver_test.go | 1 + integration/rpctest/rpc_harness_test.go | 1 + limits/limits_unix.go | 1 + params.go | 9 + signalsigterm.go | 1 + txscript/data/script_tests.json | 80 ++- txscript/engine.go | 19 +- txscript/engine_test.go | 12 +- txscript/error.go | 4 + txscript/error_test.go | 1 + txscript/example_test.go | 2 +- txscript/hashcache.go | 36 ++ txscript/opcode.go | 458 ++++++++++++-- txscript/opcode_test.go | 760 +++++++++++++++++++++++- txscript/reference_test.go | 6 +- txscript/scriptnum.go | 81 ++- txscript/scriptnum_test.go | 221 +++++-- txscript/sign_test.go | 4 +- txscript/stack.go | 9 +- txscript/stack_test.go | 4 +- txscript/standard.go | 4 +- version/version.go | 4 +- wire/protocol.go | 3 + 42 files changed, 2036 insertions(+), 188 deletions(-) diff --git a/bchec/genprecomps.go b/bchec/genprecomps.go index 86806e571..70c5f72ca 100644 --- a/bchec/genprecomps.go +++ b/bchec/genprecomps.go @@ -5,6 +5,7 @@ // This file is ignored during the regular build due to the following build tag. // It is called by go generate and used to automatically generate pre-computed // tables used to accelerate operations. +//go:build ignore // +build ignore package main diff --git a/bchec/gensecp256k1.go b/bchec/gensecp256k1.go index 2131c7e69..e8496d1de 100644 --- a/bchec/gensecp256k1.go +++ b/bchec/gensecp256k1.go @@ -4,6 +4,7 @@ // This file is ignored during the regular build due to the following build tag. // This build tag is set during go generate. +//go:build gensecp256k1 // +build gensecp256k1 package bchec diff --git a/bchrpc/proxy/gw_tools.go b/bchrpc/proxy/gw_tools.go index 90d1d30da..8b166ac12 100644 --- a/bchrpc/proxy/gw_tools.go +++ b/bchrpc/proxy/gw_tools.go @@ -1,3 +1,4 @@ +//go:build tools // +build tools package main diff --git a/blockchain/fullblocks_test.go b/blockchain/fullblocks_test.go index 257c4b935..2a137fa56 100644 --- a/blockchain/fullblocks_test.go +++ b/blockchain/fullblocks_test.go @@ -714,3 +714,110 @@ func TestPhononActivation(t *testing.T) { } } } + +// TestCosmicInflationActivation tests that 64 bit integers and native introspection +// opcodes activate correctly. +func TestCosmicInflationActivation(t *testing.T) { + tests, err := fullblocktests.GenerateCosmicInflationBlocks() + if err != nil { + t.Fatalf("failed to generate tests: %v", err) + } + + // Create a new database and chain instance to run tests against. + params := &chaincfg.RegressionNetParams + params.CosmicInflationActivationTime = 0 + params.PhononForkHeight = 0 + params.GravitonForkHeight = 0 + params.MagneticAnonomalyForkHeight = 0 + params.UahfForkHeight = 0 + chain, teardownFunc, err := chainSetup("fullblocktest", + params, 32000000) + if err != nil { + t.Errorf("Failed to setup chain instance: %v", err) + return + } + defer teardownFunc() + + // testAcceptedBlock attempts to process the block in the provided test + // instance and ensures that it was accepted according to the flags + // specified in the test. + testAcceptedBlock := func(item fullblocktests.AcceptedBlock) { + blockHeight := item.Height + block := bchutil.NewBlock(item.Block) + block.SetHeight(blockHeight) + t.Logf("Testing block %s (hash %s, height %d)", + item.Name, block.Hash(), blockHeight) + + isMainChain, isOrphan, err := chain.ProcessBlock(block, + blockchain.BFNone) + if err != nil { + t.Fatalf("block %q (hash %s, height %d) should "+ + "have been accepted: %v", item.Name, + block.Hash(), blockHeight, err) + } + + // Ensure the main chain and orphan flags match the values + // specified in the test. + if isMainChain != item.IsMainChain { + t.Fatalf("block %q (hash %s, height %d) unexpected main "+ + "chain flag -- got %v, want %v", item.Name, + block.Hash(), blockHeight, isMainChain, + item.IsMainChain) + } + if isOrphan != item.IsOrphan { + t.Fatalf("block %q (hash %s, height %d) unexpected "+ + "orphan flag -- got %v, want %v", item.Name, + block.Hash(), blockHeight, isOrphan, + item.IsOrphan) + } + } + + // testRejectedBlock attempts to process the block in the provided test + // instance and ensures that it was rejected with the reject code + // specified in the test. + testRejectedBlock := func(item fullblocktests.RejectedBlock) { + blockHeight := item.Height + block := bchutil.NewBlock(item.Block) + block.SetHeight(blockHeight) + t.Logf("Testing block %s (hash %s, height %d)", + item.Name, block.Hash(), blockHeight) + + _, _, err := chain.ProcessBlock(block, blockchain.BFNone) + if err == nil { + t.Fatalf("block %q (hash %s, height %d) should not "+ + "have been accepted", item.Name, block.Hash(), + blockHeight) + } + + // Ensure the error code is of the expected type and the reject + // code matches the value specified in the test instance. + rerr, ok := err.(blockchain.RuleError) + if !ok { + t.Fatalf("block %q (hash %s, height %d) returned "+ + "unexpected error type -- got %T, want "+ + "blockchain.RuleError", item.Name, block.Hash(), + blockHeight, err) + } + if rerr.ErrorCode != item.RejectCode { + t.Fatalf("block %q (hash %s, height %d) does not have "+ + "expected reject code -- got %v, want %v", + item.Name, block.Hash(), blockHeight, + rerr.ErrorCode, item.RejectCode) + } + } + + for testNum, test := range tests { + for itemNum, item := range test { + switch item := item.(type) { + case fullblocktests.AcceptedBlock: + testAcceptedBlock(item) + case fullblocktests.RejectedBlock: + testRejectedBlock(item) + default: + t.Fatalf("test #%d, item #%d is not one of "+ + "the supported test instance types -- "+ + "got type: %T", testNum, itemNum, item) + } + } + } +} diff --git a/blockchain/fullblocktests/generate.go b/blockchain/fullblocktests/generate.go index 9a7bc9456..74702ad46 100644 --- a/blockchain/fullblocktests/generate.go +++ b/blockchain/fullblocktests/generate.go @@ -2852,3 +2852,198 @@ func GeneratePhononBlocks() (tests [][]TestInstance, err error) { }) return tests, nil } + +// GenerateCosmicInflationBlocks generates a test chain containing several +// 64 bit integer and native introspection transactions per block. +func GenerateCosmicInflationBlocks() (tests [][]TestInstance, err error) { + // In order to simplify the generation code which really should never + // fail unless the test code itself is broken, panics are used + // internally. This deferred func ensures any panics don't escape the + // generator by replacing the named error return with the underlying + // panic error. + defer func() { + if r := recover(); r != nil { + tests = nil + + switch rt := r.(type) { + case string: + err = errors.New(rt) + case error: + err = rt + default: + err = errors.New("Unknown panic") + } + } + }() + + // Create a test generator instance initialized with the genesis block + // as the tip. + g, err := makeTestGenerator(regressionNetParams) + if err != nil { + return nil, err + } + + acceptBlock := func(blockName string, block *wire.MsgBlock, isMainChain, isOrphan bool) TestInstance { + blockHeight := g.blockHeights[blockName] + return AcceptedBlock{blockName, block, blockHeight, isMainChain, + isOrphan} + } + + coinbaseMaturity := g.params.CoinbaseMaturity + var testInstances []TestInstance + for i := uint16(0); i < coinbaseMaturity; i++ { + blockName := fmt.Sprintf("bm%d", i) + g.nextBlock(blockName, nil) + g.saveTipCoinbaseOut() + testInstances = append(testInstances, acceptBlock(g.tipName, + g.tip, true, false)) + } + tests = append(tests, testInstances) + + // Collect spendable outputs. This simplifies the code below. + var outs []*spendableOut + for i := uint16(0); i < coinbaseMaturity; i++ { + op := g.oldestCoinbaseOut() + outs = append(outs, &op) + } + + add64BitInteger := func(block *wire.MsgBlock) { + // unsortedTxs is a slice of bchutil.Tx's that we will sort later using CTOR. + var unsortedTxs []*bchutil.Tx + + // Let's first add any txs (excluding the coinbase) into unsortedTxs + for _, tx := range block.Transactions[1:] { + unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx)) + } + + // Create one tx paying a p2sh output + tx1 := createSpendTx(outs[0], 0) + builder := txscript.NewScriptBuilder(). + AddOp(txscript.OP_1). + AddOp(txscript.OP_ADD). + AddInt64(1152921504606846976). + AddOp(txscript.OP_EQUAL) + redeemScript, err := builder.Script() + if err != nil { + panic(err) + } + + addr, err := bchutil.NewAddressScriptHash(redeemScript, &chaincfg.RegressionNetParams) + if err != nil { + panic(err) + } + script, err := txscript.PayToAddrScript(addr) + if err != nil { + panic(err) + } + tx1.TxOut[0].PkScript = script + + block.AddTransaction(tx1) + unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx1)) + + // Spend from tx1 + so1 := &spendableOut{ + amount: bchutil.Amount(tx1.TxOut[0].Value), + prevOut: wire.OutPoint{ + Hash: tx1.TxHash(), + Index: 0, + }, + } + + tx2 := createSpendTx(so1, 0) + scriptSig, err := txscript.NewScriptBuilder(). + AddInt64(1152921504606846975). + AddData(redeemScript).Script() + if err != nil { + panic(err) + } + tx2.TxIn[0].SignatureScript = scriptSig + block.AddTransaction(tx2) + unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx2)) + + // Sort the unsortedTxs slice + sort.Sort(mining.TxSorter(unsortedTxs)) + // Set block.Transactions to only the coinbase + block.Transactions = block.Transactions[:1] + + // Add each tx from our (now sorted) slice + for _, tx := range unsortedTxs { + block.Transactions = append(block.Transactions, tx.MsgTx()) + } + } + + addIntrospection := func(block *wire.MsgBlock) { + // unsortedTxs is a slice of bchutil.Tx's that we will sort later using CTOR. + var unsortedTxs []*bchutil.Tx + + // Let's first add any txs (excluding the coinbase) into unsortedTxs + for _, tx := range block.Transactions[1:] { + unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx)) + } + + // Create one tx paying a p2sh output + tx1 := createSpendTx(outs[1], 0) + builder := txscript.NewScriptBuilder(). + AddData(make([]byte, 20)). + AddOp(txscript.OP_DROP). + AddOp(txscript.OP_ACTIVEBYTECODE). + AddOp(txscript.OP_EQUAL) + redeemScript, err := builder.Script() + if err != nil { + panic(err) + } + + addr, err := bchutil.NewAddressScriptHash(redeemScript, &chaincfg.RegressionNetParams) + if err != nil { + panic(err) + } + script, err := txscript.PayToAddrScript(addr) + if err != nil { + panic(err) + } + tx1.TxOut[0].PkScript = script + + block.AddTransaction(tx1) + unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx1)) + + // Spend from tx1 + so1 := &spendableOut{ + amount: bchutil.Amount(tx1.TxOut[0].Value), + prevOut: wire.OutPoint{ + Hash: tx1.TxHash(), + Index: 0, + }, + } + + tx2 := createSpendTx(so1, 0) + scriptSig, err := txscript.NewScriptBuilder(). + AddData(redeemScript). + AddData(redeemScript).Script() + if err != nil { + panic(err) + } + tx2.TxIn[0].SignatureScript = scriptSig + block.AddTransaction(tx2) + unsortedTxs = append(unsortedTxs, bchutil.NewTx(tx2)) + + // Sort the unsortedTxs slice + sort.Sort(mining.TxSorter(unsortedTxs)) + // Set block.Transactions to only the coinbase + block.Transactions = block.Transactions[:1] + + // Add each tx from our (now sorted) slice + for _, tx := range unsortedTxs { + block.Transactions = append(block.Transactions, tx.MsgTx()) + } + } + + g.nextBlock("b0", nil, add64BitInteger) + tests = append(tests, []TestInstance{ + acceptBlock(g.tipName, g.tip, true, false), + }) + g.nextBlock("b1", nil, addIntrospection) + tests = append(tests, []TestInstance{ + acceptBlock(g.tipName, g.tip, true, false), + }) + return tests, nil +} diff --git a/blockchain/scriptval.go b/blockchain/scriptval.go index 79846aba3..5bd252d10 100644 --- a/blockchain/scriptval.go +++ b/blockchain/scriptval.go @@ -77,9 +77,30 @@ out: sigScript := txIn.SignatureScript pkScript := utxo.PkScript() inputAmount := utxo.Amount() + + utxoEntryCache := txscript.NewUtxoCache() + for i, in := range txVI.tx.MsgTx().TxIn { + if i == txVI.txInIndex { + utxoEntryCache.AddEntry(i, *wire.NewTxOut(utxo.amount, utxo.pkScript)) + continue + } + u := v.utxoView.LookupEntry(in.PreviousOutPoint) + if u == nil { + str := fmt.Sprintf("unable to find unspent "+ + "output %v referenced from "+ + "transaction %s:%d", + in.PreviousOutPoint, txVI.tx.Hash(), + i) + err := ruleError(ErrMissingTxOut, str) + v.sendResult(err) + break out + } + utxoEntryCache.AddEntry(i, *wire.NewTxOut(u.amount, u.pkScript)) + } + vm, err := txscript.NewEngine(pkScript, txVI.tx.MsgTx(), txVI.txInIndex, v.flags, v.sigCache, txVI.sigHashes, - inputAmount) + utxoEntryCache, inputAmount) if err != nil { str := fmt.Sprintf("failed to parse input "+ "%s:%d which references output %v - "+ diff --git a/blockchain/thresholdstate.go b/blockchain/thresholdstate.go index a27d299ac..c95b764b1 100644 --- a/blockchain/thresholdstate.go +++ b/blockchain/thresholdstate.go @@ -6,7 +6,7 @@ package blockchain import ( "fmt" - + "github.com/gcash/bchd/chaincfg" "github.com/gcash/bchd/chaincfg/chainhash" ) @@ -297,6 +297,12 @@ func (b *BlockChain) IsDeploymentActive(deploymentID uint32) (bool, error) { // // This function MUST be called with the chain state lock held (for writes). func (b *BlockChain) deploymentState(prevNode *blockNode, deploymentID uint32) (ThresholdState, error) { + if deploymentID == chaincfg.DeploymentCSV && b.chainParams.CSVHeight > 0 { + if prevNode.height+1 >= b.chainParams.CSVHeight { + return ThresholdActive, nil + } + } + if deploymentID > uint32(len(b.chainParams.Deployments)) { return ThresholdFailed, DeploymentError(deploymentID) } diff --git a/blockchain/validate.go b/blockchain/validate.go index 78bae84f5..56ea9be2a 100644 --- a/blockchain/validate.go +++ b/blockchain/validate.go @@ -997,6 +997,9 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *bchutil.Block, vi // OP_REVERSEBYTES. phononActive := node.height > b.chainParams.PhononForkHeight + // If CosmicInflation is active we enforce 64BitIntegers and NativeIntrospection + cosmicInflationActive := node.parent.CalcPastMedianTime().Unix() >= int64(b.chainParams.CosmicInflationActivationTime) + // BIP0030 added a rule to prevent blocks which contain duplicate // transactions that 'overwrite' older transactions which are not fully // spent. See the documentation for checkBIP0030 for more details. @@ -1091,6 +1094,11 @@ func (b *BlockChain) checkConnectBlock(node *blockNode, block *bchutil.Block, vi scriptFlags |= txscript.ScriptReportSigChecks | txscript.ScriptVerifyReverseBytes } + // If CosmicInflation hardfork is active enforce 64BitIntegers and NativeIntrospection + if cosmicInflationActive { + scriptFlags |= txscript.ScriptVerify64BitIntegers | txscript.ScriptVerifyNativeIntrospection + } + // Perform several checks on the inputs for each transaction. Also // accumulate the total fees. This could technically be combined with // the loop above instead of running another loop over the transactions, diff --git a/chaincfg/genesis.go b/chaincfg/genesis.go index 5a5d41cff..7710148ec 100644 --- a/chaincfg/genesis.go +++ b/chaincfg/genesis.go @@ -143,6 +143,34 @@ var testNet3GenesisBlock = wire.MsgBlock{ Transactions: []*wire.MsgTx{&genesisCoinbaseTx}, } +// testNet4GenesisHash is the hash of the first block in the block chain for the +// test network (version 4). +var testNet4GenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy. + 0x7b, 0x9f, 0xfd, 0x44, 0xdd, 0x73, 0xc0, 0x5f, + 0x2a, 0x15, 0xd3, 0x74, 0x74, 0x79, 0xcc, 0x18, + 0x17, 0x75, 0x26, 0xce, 0x68, 0x86, 0x78, 0x9a, + 0xc4, 0x10, 0xd4, 0x1d, 0x00, 0x00, 0x00, 0x00, +}) + +// testNet4GenesisMerkleRoot is the hash of the first transaction in the genesis +// block for the test network (version 4). It is the same as the merkle root +// for the main network. +var testNet4GenesisMerkleRoot = genesisMerkleRoot + +// testNet4GenesisBlock defines the genesis block of the block chain which +// serves as the public transaction ledger for the test network (version 4). +var testNet4GenesisBlock = wire.MsgBlock{ + Header: wire.BlockHeader{ + Version: 1, + PrevBlock: chainhash.Hash{}, // 0000000000000000000000000000000000000000000000000000000000000000 + MerkleRoot: testNet4GenesisMerkleRoot, // 4a5e1e4baab89f3a32518a88c31bc87f618f76673e2cc77ab2127b7afdeda33b + Timestamp: time.Unix(1597811185, 0), + Bits: 0x1d00ffff, // 486604799 [00000000ffff0000000000000000000000000000000000000000000000000000] + Nonce: 114152193, + }, + Transactions: []*wire.MsgTx{&genesisCoinbaseTx}, +} + // simNetGenesisHash is the hash of the first block in the block chain for the // simulation test network. var simNetGenesisHash = chainhash.Hash([chainhash.HashSize]byte{ // Make go vet happy. diff --git a/chaincfg/params.go b/chaincfg/params.go index a9cf2847a..94f1e788e 100644 --- a/chaincfg/params.go +++ b/chaincfg/params.go @@ -138,15 +138,21 @@ type Params struct { BIP0065Height int32 BIP0066Height int32 + // Only testnet4 uses CSV activation by height. All the others use the + // deployment schedule. If this value is set to anything other than zero + // then it will activate at this height. + CSVHeight int32 + // The following are the heights at which the Bitcoin Cash specific forks // became active. - UahfForkHeight int32 // August 1, 2017 hardfork - DaaForkHeight int32 // November 13, 2017 hardfork - MagneticAnonomalyForkHeight int32 // November 15, 2018 hardfork - GreatWallForkHeight int32 // May 15, 2019 hardfork - GravitonForkHeight int32 // Nov 15, 2019 hardfork - PhononForkHeight int32 // May 15, 2020 hardfork - AxionActivationHeight int32 // Nov 15, 2020 hardfork + UahfForkHeight int32 // August 1, 2017 hardfork + DaaForkHeight int32 // November 13, 2017 hardfork + MagneticAnonomalyForkHeight int32 // November 15, 2018 hardfork + GreatWallForkHeight int32 // May 15, 2019 hardfork + GravitonForkHeight int32 // Nov 15, 2019 hardfork + PhononForkHeight int32 // May 15, 2020 hardfork + AxionActivationHeight int32 // Nov 15, 2020 hardfork + CosmicInflationActivationTime uint64 // May 15, 2022 hardfork // CoinbaseMaturity is the number of blocks required before newly mined // coins (coinbase transactions) can be spent. @@ -280,6 +286,8 @@ var MainNetParams = Params{ PhononForkHeight: 635258, // 000000000000000003302c47d01e78f1c86aa3b0e96b066761a5059bc8f5781a AxionActivationHeight: 661647, // 00000000000000000083ed4b7a780d59e3983513215518ad75654bb02deee62f + CosmicInflationActivationTime: 1652616000, + CoinbaseMaturity: 100, SubsidyReductionInterval: 210000, TargetTimespan: time.Hour * 24 * 14, // 14 days @@ -445,6 +453,8 @@ var RegressionNetParams = Params{ PhononForkHeight: 1000, AxionActivationHeight: 0, // Always active on regtest + CosmicInflationActivationTime: 1652616000, + SubsidyReductionInterval: 150, TargetTimespan: time.Hour * 24 * 14, // 14 days TargetTimePerBlock: time.Minute * 10, // 10 minutes @@ -535,6 +545,8 @@ var TestNet3Params = Params{ PhononForkHeight: 1378460, // 0000000070f33c64cb94629680fbc57d17bea354a73e693affcb366d023db324 AxionActivationHeight: 1421481, // 00000000062c7f32591d883c99fc89ebe74a83287c0f2b7ffeef72e62217d40b + CosmicInflationActivationTime: 1652616000, + CoinbaseMaturity: 100, SubsidyReductionInterval: 210000, TargetTimespan: time.Hour * 24 * 14, // 14 days @@ -611,6 +623,97 @@ var TestNet3Params = Params{ SlpAddressPrefix: "slptest", } +// TestNet4Params defines the network parameters for the test Bitcoin network +// (version 4). Not to be confused with the regression test network, this +// network is sometimes simply called "testnet". +var TestNet4Params = Params{ + Name: "testnet4", + Net: wire.TestNet4, + DefaultPort: "28333", + DNSSeeds: []DNSSeed{ + {"testnet4.imaginary.cash", true}, + }, + + // Chain parameters + GenesisBlock: &testNet4GenesisBlock, + GenesisHash: &testNet4GenesisHash, + PowLimit: testNet3PowLimit, + PowLimitBits: 0x1d00ffff, + BIP0034Height: 2, + BIP0065Height: 3, + BIP0066Height: 4, + CSVHeight: 5, + + UahfForkHeight: 5, + DaaForkHeight: 3000, + MagneticAnonomalyForkHeight: 3999, + GreatWallForkHeight: 0, + GravitonForkHeight: 4999, + PhononForkHeight: 0, + AxionActivationHeight: 16844, + + CosmicInflationActivationTime: 1637694000, + + CoinbaseMaturity: 100, + SubsidyReductionInterval: 210000, + TargetTimespan: time.Hour * 24 * 14, // 14 days + TargetTimePerBlock: time.Minute * 10, // 10 minutes + RetargetAdjustmentFactor: 4, // 25% less, 400% more + ReduceMinDifficulty: true, + NoDifficultyAdjustment: false, + MinDiffReductionTime: time.Minute * 20, // TargetTimePerBlock * 2 + AsertDifficultyHalflife: 3600, // 1 hour + AsertDifficultyAnchorHeight: 16844, + AsertDifficultyAnchorParentTimestamp: 1605451779, + AsertDifficultyAnchorBits: 0x1d00ffff, + GenerateSupported: false, + + // Checkpoints ordered from oldest to newest. + Checkpoints: []Checkpoint{}, + + // Consensus rule change deployments. + // + // The miner confirmation window is defined as: + // target proof of work timespan / target proof of work spacing + RuleChangeActivationThreshold: 1512, // 75% of MinerConfirmationWindow + MinerConfirmationWindow: 2016, + Deployments: [DefinedDeployments]ConsensusDeployment{ + DeploymentTestDummy: { + BitNumber: 28, + StartTime: 1199145601, // January 1, 2008 UTC + ExpireTime: 1230767999, // December 31, 2008 UTC + }, + DeploymentCSV: { + BitNumber: 0, + StartTime: 1456790400, // March 1st, 2016 + ExpireTime: 1493596800, // May 1st, 2017 + }, + }, + + // Mempool parameters + RelayNonStdTxs: false, + + // The prefix for the cashaddress + CashAddressPrefix: "bchtest", // always bchtest for testnet + + // Address encoding magics + LegacyPubKeyHashAddrID: 0x6f, // starts with m or n + LegacyScriptHashAddrID: 0xc4, // starts with 2 + PrivateKeyID: 0xef, // starts with 9 (uncompressed) or c (compressed) + + // BIP32 hierarchical deterministic extended key magics + HDPrivateKeyID: [4]byte{0x04, 0x35, 0x83, 0x94}, // starts with tprv + HDPublicKeyID: [4]byte{0x04, 0x35, 0x87, 0xcf}, // starts with tpub + + // BIP44 coin type used in the hierarchical deterministic path for + // address generation. + HDCoinType: 1, // all coins use 1 + + // slp indexer parameters + SlpIndexStartHeight: 0, + SlpAddressPrefix: "slptest", +} + // SimNetParams defines the network parameters for the simulation test Bitcoin // network. This network is similar to the normal test network except it is // intended for private use within a group of individuals doing simulation @@ -637,6 +740,7 @@ var SimNetParams = Params{ MagneticAnonomalyForkHeight: 3000, GreatWallForkHeight: 0, AxionActivationHeight: 4000, + CosmicInflationActivationTime: 0, CoinbaseMaturity: 100, SubsidyReductionInterval: 210000, TargetTimespan: time.Hour * 24 * 14, // 14 days diff --git a/config.go b/config.go index 514c34a59..6f178e7e3 100644 --- a/config.go +++ b/config.go @@ -155,6 +155,7 @@ type config struct { NoOnion bool `long:"noonion" description:"Disable connecting to tor hidden services"` TorIsolation bool `long:"torisolation" description:"Enable Tor stream isolation by randomizing user credentials for each connection."` TestNet3 bool `long:"testnet" description:"Use the test network"` + TestNet4 bool `long:"testnet4" description:"Use the test 4 network"` RegressionTest bool `long:"regtest" description:"Use the regression test network"` RegressionTestAnyHost bool `long:"regtestanyhost" description:"In regression test mode, allow connections from any host, not just localhost"` RegressionTestNoReset bool `long:"regtestnoreset" description:"In regression test mode, don't reset the network db on node restart"` @@ -590,6 +591,10 @@ func loadConfig() (*config, []string, error) { numNets++ activeNetParams = &testNet3Params } + if cfg.TestNet4 { + numNets++ + activeNetParams = &testNet4Params + } if cfg.RegressionTest { numNets++ activeNetParams = ®ressionNetParams diff --git a/database/ffldb/disk.go b/database/ffldb/disk.go index 256750365..ff6b84ea6 100644 --- a/database/ffldb/disk.go +++ b/database/ffldb/disk.go @@ -1,3 +1,4 @@ +//go:build solaris || plan9 || netbsd || openbsd // +build solaris plan9 netbsd openbsd // Copyright (c) 2013-2018 The btcsuite developers diff --git a/database/ffldb/disk_darwin.go b/database/ffldb/disk_darwin.go index 225054fdd..3c1a967a8 100644 --- a/database/ffldb/disk_darwin.go +++ b/database/ffldb/disk_darwin.go @@ -1,3 +1,4 @@ +//go:build darwin // +build darwin // Copyright (c) 2013-2018 The btcsuite developers diff --git a/database/ffldb/disk_dragonfly.go b/database/ffldb/disk_dragonfly.go index 5591571d6..a29f9fef4 100644 --- a/database/ffldb/disk_dragonfly.go +++ b/database/ffldb/disk_dragonfly.go @@ -1,3 +1,4 @@ +//go:build dragonfly // +build dragonfly // Copyright (c) 2013-2018 The btcsuite developers diff --git a/database/ffldb/disk_freebsd.go b/database/ffldb/disk_freebsd.go index be65b0fa6..e2f27a282 100644 --- a/database/ffldb/disk_freebsd.go +++ b/database/ffldb/disk_freebsd.go @@ -1,3 +1,4 @@ +//go:build freebsd // +build freebsd // Copyright (c) 2013-2018 The btcsuite developers diff --git a/database/ffldb/disk_linux.go b/database/ffldb/disk_linux.go index 35f87f938..7ccf6265b 100644 --- a/database/ffldb/disk_linux.go +++ b/database/ffldb/disk_linux.go @@ -1,3 +1,4 @@ +//go:build linux // +build linux // Copyright (c) 2013-2018 The btcsuite developers diff --git a/database/ffldb/disk_windows.go b/database/ffldb/disk_windows.go index e31e8ddbc..e616719b3 100644 --- a/database/ffldb/disk_windows.go +++ b/database/ffldb/disk_windows.go @@ -1,3 +1,4 @@ +//go:build windows // +build windows // Copyright (c) 2013-2018 The btcsuite developers diff --git a/integration/bip0009_test.go b/integration/bip0009_test.go index 1f9efdd25..e03c64a9a 100644 --- a/integration/bip0009_test.go +++ b/integration/bip0009_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // This file is ignored during the regular tests due to the following build tag. +//go:build rpctest // +build rpctest package integration diff --git a/integration/csv_fork_test.go b/integration/csv_fork_test.go index 78100669f..c59231c0c 100644 --- a/integration/csv_fork_test.go +++ b/integration/csv_fork_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // This file is ignored during the regular tests due to the following build tag. +//go:build rpctest // +build rpctest package integration diff --git a/integration/rpcserver_test.go b/integration/rpcserver_test.go index 49ffd407f..2ad22b3c6 100644 --- a/integration/rpcserver_test.go +++ b/integration/rpcserver_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // This file is ignored during the regular tests due to the following build tag. +//go:build rpctest // +build rpctest package integration diff --git a/integration/rpctest/rpc_harness_test.go b/integration/rpctest/rpc_harness_test.go index 40909ef8d..98d31ebd1 100644 --- a/integration/rpctest/rpc_harness_test.go +++ b/integration/rpctest/rpc_harness_test.go @@ -3,6 +3,7 @@ // license that can be found in the LICENSE file. // This file is ignored during the regular tests due to the following build tag. +//go:build rpctest // +build rpctest package rpctest diff --git a/limits/limits_unix.go b/limits/limits_unix.go index 270fa7830..80ae52d67 100644 --- a/limits/limits_unix.go +++ b/limits/limits_unix.go @@ -2,6 +2,7 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. +//go:build !windows && !plan9 // +build !windows,!plan9 package limits diff --git a/params.go b/params.go index 6f01107bc..b60748b31 100644 --- a/params.go +++ b/params.go @@ -52,6 +52,15 @@ var testNet3Params = params{ gRRPPort: "18335", } +// testNet4Params contains parameters specific to the test network (version 4) +// (wire.TestNet4). NOTE: The RPC port is intentionally different than the +// reference implementation - see the mainNetParams comment for details. +var testNet4Params = params{ + Params: &chaincfg.TestNet4Params, + rpcPort: "18334", + gRRPPort: "18335", +} + // simNetParams contains parameters specific to the simulation test network // (wire.SimNet). var simNetParams = params{ diff --git a/signalsigterm.go b/signalsigterm.go index 831655010..63bdb9c01 100644 --- a/signalsigterm.go +++ b/signalsigterm.go @@ -2,6 +2,7 @@ // Use of this source code is governed by an ISC // license that can be found in the LICENSE file. +//go:build darwin || dragonfly || freebsd || linux || netbsd || openbsd || solaris // +build darwin dragonfly freebsd linux netbsd openbsd solaris package main diff --git a/txscript/data/script_tests.json b/txscript/data/script_tests.json index ce810ca33..6d69bc625 100644 --- a/txscript/data/script_tests.json +++ b/txscript/data/script_tests.json @@ -269,24 +269,24 @@ ["0", "IF CHECKDATASIG ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF CHECKDATASIGVERIFY ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "opcodes >= FIRST_UNDEFINED_OP_VALUE invalid if executed"], + ["0", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "available, unassigned codepoints, invalid if executed"], ["0", "IF 0xbe ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xbf ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc0 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc1 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc2 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc3 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc4 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc5 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc6 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc7 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc8 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xc9 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xca ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xcb ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xcc ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xcd ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], - ["0", "IF 0xce ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_INPUTINDEX ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "opcodes for native introspection (not activated here)"], + ["0", "IF OP_ACTIVEBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_TXVERSION ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_TXINPUTCOUNT ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_TXOUTPUTCOUNT ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_TXLOCKTIME ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_UTXOVALUE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_UTXOBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_OUTPOINTTXHASH ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_OUTPOINTINDEX ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_INPUTBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_INPUTSEQUENCENUMBER ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_OUTPUTVALUE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_OUTPUTBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF 0xce ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "opcodes >= FIRST_UNDEFINED_OP_VALUE invalid if executed"], ["0", "IF 0xcf ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xd0 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], ["0", "IF 0xd1 ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], @@ -1252,24 +1252,38 @@ "P2SH,DISCOURAGE_UPGRADABLE_NOPS", "DISCOURAGE_UPGRADABLE_NOPS", "Discouraged NOP10 in redeemScript"], ["0x50","1", "P2SH,STRICTENC", "BAD_OPCODE", "opcode 0x50 is reserved"], - ["1", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE", "opcodes >= FIRST_UNDEFINED_OP_VALUE invalid if executed"], - ["1", "IF 0xbe ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], + ["1", "IF 0xbd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE", "available, unassigned codepoints, invalid if executed"], + ["0", "IF OP_INPUTINDEX ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "opcodes for native introspection (not activated here)"], + ["0", "IF OP_ACTIVEBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_TXVERSION ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_TXINPUTCOUNT ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_TXOUTPUTCOUNT ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_TXLOCKTIME ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_UTXOVALUE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_UTXOBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_OUTPOINTTXHASH ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_OUTPOINTINDEX ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_INPUTBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_INPUTSEQUENCENUMBER ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_OUTPUTVALUE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF OP_OUTPUTBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "OK"], + ["0", "IF 0xbe ELSE 1 ENDIF", "P2SH,STRICTENC", "OK", "available, unassigned codepoints, invalid if executed"], ["1", "IF 0xbf ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc0 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc1 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc2 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc3 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc4 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc5 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc6 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc7 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc8 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xc9 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xca ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xcb ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xcc ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xcd ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], - ["1", "IF 0xce ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], + ["1", "IF OP_INPUTINDEX ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE", "opcodes for native introspection (not activated here)"], + ["1", "IF OP_ACTIVEBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_TXVERSION ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_TXINPUTCOUNT ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_TXOUTPUTCOUNT ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_TXLOCKTIME ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_UTXOVALUE ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_UTXOBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_OUTPOINTTXHASH ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_OUTPOINTINDEX ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_INPUTBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_INPUTSEQUENCENUMBER ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_OUTPUTVALUE ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF OP_OUTPUTBYTECODE ELSE 1 ENDIF", "P2SH,STRICTENC", "DISABLED_OPCODE"], + ["1", "IF 0xce ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE", "opcodes >= FIRST_UNDEFINED_OP_VALUE invalid if executed"], ["1", "IF 0xcf ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xd0 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], ["1", "IF 0xd1 ELSE 1 ENDIF", "P2SH,STRICTENC", "BAD_OPCODE"], diff --git a/txscript/engine.go b/txscript/engine.go index ff6da282a..a1e5256ef 100644 --- a/txscript/engine.go +++ b/txscript/engine.go @@ -120,6 +120,13 @@ const ( // ScriptVerifyReverseBytes enables the use of OP_REVERSEBYTES in the // script. ScriptVerifyReverseBytes + + // ScriptVerify64BitIntegers enables the use of 64 bit ScriptNums + ScriptVerify64BitIntegers + + // ScriptVerifyNativeIntrospection enables the suite of native introspection + // opcodes. + ScriptVerifyNativeIntrospection ) // HasFlag returns whether the ScriptFlags has the passed flag set. @@ -154,6 +161,7 @@ type Engine struct { flags ScriptFlags sigCache *SigCache hashCache *TxSigHashes + utxoCache *UtxoCache bip16 bool // treat execution as pay-to-script-hash savedFirstStack [][]byte // stack from first script for bip16 scripts inputAmount int64 @@ -869,7 +877,7 @@ func (vm *Engine) Clone() *Engine { // transaction, and input index. The flags modify the behavior of the script // engine according to the description provided by each flag. func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags, - sigCache *SigCache, hashCache *TxSigHashes, inputAmount int64) (*Engine, error) { + sigCache *SigCache, hashCache *TxSigHashes, utxoCache *UtxoCache, inputAmount int64) (*Engine, error) { // The provided transaction input index must refer to a valid input. if txIdx < 0 || txIdx >= len(tx.TxIn) { @@ -897,7 +905,7 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags // it possible to have a situation where P2SH would not be a soft fork // when it should be. vm := Engine{flags: flags, sigCache: sigCache, hashCache: hashCache, - inputAmount: inputAmount} + utxoCache: utxoCache, inputAmount: inputAmount} if vm.hasFlag(ScriptVerifyCleanStack) && (!vm.hasFlag(ScriptBip16)) { return nil, scriptError(ErrInvalidFlags, "invalid flags combination") @@ -948,6 +956,13 @@ func NewEngine(scriptPubKey []byte, tx *wire.MsgTx, txIdx int, flags ScriptFlags vm.dstack.verifyMinimalData = true vm.astack.verifyMinimalData = true } + if vm.hasFlag(ScriptVerify64BitIntegers) { + vm.dstack.defaultScriptNumLen = defaultBigScriptNumLen + vm.astack.defaultScriptNumLen = defaultBigScriptNumLen + } else { + vm.dstack.defaultScriptNumLen = defaultSmallScriptNumLen + vm.astack.defaultScriptNumLen = defaultSmallScriptNumLen + } vm.tx = *tx vm.txIdx = txIdx diff --git a/txscript/engine_test.go b/txscript/engine_test.go index d4c18ea0b..2678ecc3d 100644 --- a/txscript/engine_test.go +++ b/txscript/engine_test.go @@ -57,7 +57,7 @@ func TestBadPC(t *testing.T) { pkScript := mustParseShortForm("NOP") for _, test := range tests { - vm, err := NewEngine(pkScript, tx, 0, 0, nil, nil, -1) + vm, err := NewEngine(pkScript, tx, 0, 0, nil, nil, nil, -1) if err != nil { t.Errorf("Failed to create script: %v", err) } @@ -114,7 +114,7 @@ func TestCheckErrorCondition(t *testing.T) { pkScript := mustParseShortForm("NOP NOP NOP NOP NOP NOP NOP NOP NOP" + " NOP TRUE") - vm, err := NewEngine(pkScript, tx, 0, 0, nil, nil, 0) + vm, err := NewEngine(pkScript, tx, 0, 0, nil, nil, nil, 0) if err != nil { t.Errorf("failed to create script: %v", err) } @@ -190,7 +190,7 @@ func TestInvalidFlagCombinations(t *testing.T) { pkScript := []byte{OP_NOP} for i, test := range tests { - _, err := NewEngine(pkScript, tx, 0, test, nil, nil, -1) + _, err := NewEngine(pkScript, tx, 0, test, nil, nil, nil, -1) if !IsErrorCode(err, ErrInvalidFlags) { t.Fatalf("TestInvalidFlagCombinations #%d unexpected "+ "error: %v", i, err) @@ -729,7 +729,7 @@ func TestSegwitExemption(t *testing.T) { } // This should fail the clean stack rule. - vm, err := NewEngine(pkScript, tx, 0, ScriptVerifyCleanStack|ScriptBip16, nil, nil, 0) + vm, err := NewEngine(pkScript, tx, 0, ScriptVerifyCleanStack|ScriptBip16, nil, nil, nil, 0) if err != nil { t.Errorf("failed to create script: %v", err) } @@ -739,7 +739,7 @@ func TestSegwitExemption(t *testing.T) { } // We add the segwit exemption flag and now the same input should pass. - vm, err = NewEngine(pkScript, tx, 0, ScriptVerifyCleanStack|ScriptBip16|ScriptVerifyAllowSegwitRecovery, nil, nil, 0) + vm, err = NewEngine(pkScript, tx, 0, ScriptVerifyCleanStack|ScriptBip16|ScriptVerifyAllowSegwitRecovery, nil, nil, nil, 0) if err != nil { t.Errorf("failed to create script: %v", err) } @@ -849,7 +849,7 @@ func TestScriptVerifyInputSigChecks(t *testing.T) { tx.TxIn[0].SignatureScript = sigBytes - vm, err := NewEngine(pkScript, tx, 0, StandardVerifyFlags, nil, nil, 0) + vm, err := NewEngine(pkScript, tx, 0, StandardVerifyFlags, nil, nil, nil, 0) if err != nil { t.Errorf("failed to create script: %v", err) } diff --git a/txscript/error.go b/txscript/error.go index 8739f9448..8e8dd9b13 100644 --- a/txscript/error.go +++ b/txscript/error.go @@ -324,6 +324,9 @@ const ( // allowed signature density. ErrInputSigChecks + // ErrIntegerOverflow is returned when a stack operation overflows an int64. + ErrIntegerOverflow + // numErrorCodes is the maximum error code number used in tests. This // entry MUST be the last entry in the enum. numErrorCodes @@ -393,6 +396,7 @@ var errorCodeStrings = map[ErrorCode]string{ ErrInvalidDummy: "ErrInvalidDummy", ErrInvalidBitCount: "ErrInvalidBitCount", ErrInputSigChecks: "ErrInputSigChecks", + ErrIntegerOverflow: "ErrIntegerOverflow", } // String returns the ErrorCode as a human-readable name. diff --git a/txscript/error_test.go b/txscript/error_test.go index 0669d05c8..26d6a28b1 100644 --- a/txscript/error_test.go +++ b/txscript/error_test.go @@ -78,6 +78,7 @@ func TestErrorCodeStringer(t *testing.T) { {ErrInvalidDummy, "ErrInvalidDummy"}, {ErrInvalidBitCount, "ErrInvalidBitCount"}, {ErrInputSigChecks, "ErrInputSigChecks"}, + {ErrIntegerOverflow, "ErrIntegerOverflow"}, {0xffff, "Unknown ErrorCode (65535)"}, } diff --git a/txscript/example_test.go b/txscript/example_test.go index 25bfdf763..02622fba8 100644 --- a/txscript/example_test.go +++ b/txscript/example_test.go @@ -168,7 +168,7 @@ func ExampleSignTxOutput() { txscript.ScriptVerifyBip143SigHash | txscript.ScriptVerifySchnorr vm, err := txscript.NewEngine(originTx.TxOut[0].PkScript, redeemTx, 0, - flags, nil, nil, -1) + flags, nil, nil, nil, -1) if err != nil { fmt.Println(err) return diff --git a/txscript/hashcache.go b/txscript/hashcache.go index 54d77df01..6d289451f 100644 --- a/txscript/hashcache.go +++ b/txscript/hashcache.go @@ -5,6 +5,7 @@ package txscript import ( + "errors" "sync" "github.com/gcash/bchd/chaincfg/chainhash" @@ -87,3 +88,38 @@ func (h *HashCache) PurgeSigHashes(txid *chainhash.Hash) { delete(h.sigHashes, *txid) h.Unlock() } + +// UtxoCache houses the utxos (scriptPubkey and value) for each input index +// in a single transaction. We use this class for the native introspection +// opcodes instead of the UtxoViewpoint class from the blockchain package to +// avoid circular imports. +type UtxoCache struct { + utxos map[int]wire.TxOut + + sync.RWMutex +} + +// NewUtxoCache returns a new instance of the UtxoCache. +func NewUtxoCache() *UtxoCache { + return &UtxoCache{ + utxos: make(map[int]wire.TxOut), + } +} + +// AddEntry adds a utxo entry for the given input index. +func (u *UtxoCache) AddEntry(i int, output wire.TxOut) { + u.Lock() + u.utxos[i] = output + u.Unlock() +} + +// GetEntry adds a utxo entry for the given input index. +func (u *UtxoCache) GetEntry(i int) (wire.TxOut, error) { + u.RLock() + utxo, ok := u.utxos[i] + if !ok { + return wire.TxOut{}, errors.New("not found") + } + u.RUnlock() + return utxo, nil +} diff --git a/txscript/opcode.go b/txscript/opcode.go index f5373b981..6daf31252 100644 --- a/txscript/opcode.go +++ b/txscript/opcode.go @@ -232,20 +232,20 @@ const ( OP_UNKNOWN189 = 0xbd // 189 OP_UNKNOWN190 = 0xbe // 190 OP_UNKNOWN191 = 0xbf // 191 - OP_UNKNOWN192 = 0xc0 // 192 - OP_UNKNOWN193 = 0xc1 // 193 - OP_UNKNOWN194 = 0xc2 // 194 - OP_UNKNOWN195 = 0xc3 // 195 - OP_UNKNOWN196 = 0xc4 // 196 - OP_UNKNOWN197 = 0xc5 // 197 - OP_UNKNOWN198 = 0xc6 // 198 - OP_UNKNOWN199 = 0xc7 // 199 - OP_UNKNOWN200 = 0xc8 // 200 - OP_UNKNOWN201 = 0xc9 // 201 - OP_UNKNOWN202 = 0xca // 202 - OP_UNKNOWN203 = 0xcb // 203 - OP_UNKNOWN204 = 0xcc // 204 - OP_UNKNOWN205 = 0xcd // 205 + OP_INPUTINDEX = 0xc0 // 192 + OP_ACTIVEBYTECODE = 0xc1 // 193 + OP_TXVERSION = 0xc2 // 194 + OP_TXINPUTCOUNT = 0xc3 // 195 + OP_TXOUTPUTCOUNT = 0xc4 // 196 + OP_TXLOCKTIME = 0xc5 // 197 + OP_UTXOVALUE = 0xc6 // 198 + OP_UTXOBYTECODE = 0xc7 // 199 + OP_OUTPOINTTXHASH = 0xc8 // 200 + OP_OUTPOINTINDEX = 0xc9 // 201 + OP_INPUTBYTECODE = 0xca // 202 + OP_INPUTSEQUENCENUMBER = 0xcb // 203 + OP_OUTPUTVALUE = 0xcc // 204 + OP_OUTPUTBYTECODE = 0xcd // 205 OP_UNKNOWN206 = 0xce // 206 OP_UNKNOWN207 = 0xcf // 207 OP_UNKNOWN208 = 0xd0 // 208 @@ -472,7 +472,7 @@ var opcodeArray = [256]opcode{ OP_0NOTEQUAL: {OP_0NOTEQUAL, "OP_0NOTEQUAL", 1, opcode0NotEqual}, OP_ADD: {OP_ADD, "OP_ADD", 1, opcodeAdd}, OP_SUB: {OP_SUB, "OP_SUB", 1, opcodeSub}, - OP_MUL: {OP_MUL, "OP_MUL", 1, opcodeDisabled}, + OP_MUL: {OP_MUL, "OP_MUL", 1, opcodeMul}, OP_DIV: {OP_DIV, "OP_DIV", 1, opcodeDiv}, OP_MOD: {OP_MOD, "OP_MOD", 1, opcodeMod}, OP_LSHIFT: {OP_LSHIFT, "OP_LSHIFT", 1, opcodeDisabled}, @@ -504,6 +504,22 @@ var opcodeArray = [256]opcode{ OP_CHECKDATASIG: {OP_CHECKDATASIG, "OP_CHECKDATASIG", 1, opcodeCheckDataSig}, OP_CHECKDATASIGVERIFY: {OP_CHECKDATASIGVERIFY, "OP_CHECKDATASIGVERIFY", 1, opcodeCheckDataSigVerify}, + // Native introspection opcodes. + OP_INPUTINDEX: {OP_INPUTINDEX, "OP_INPUTINDEX", 1, opcodeInputIndex}, + OP_ACTIVEBYTECODE: {OP_ACTIVEBYTECODE, "OP_ACTIVEBYTECODE", 1, opcodeActiveBytecode}, + OP_TXVERSION: {OP_TXVERSION, "OP_TXVERSION", 1, opcodeTxVersion}, + OP_TXINPUTCOUNT: {OP_TXINPUTCOUNT, "OP_TXINPUTCOUNT", 1, opcodeTxInputCount}, + OP_TXOUTPUTCOUNT: {OP_TXOUTPUTCOUNT, "OP_TXOUTPUTCOUNT", 1, opcodeTxOutputCount}, + OP_TXLOCKTIME: {OP_TXLOCKTIME, "OP_TXLOCKTIME", 1, opcodeTxLocktime}, + OP_UTXOVALUE: {OP_UTXOVALUE, "OP_UTXOVALUE", 1, opcodeUtxoValue}, + OP_UTXOBYTECODE: {OP_UTXOBYTECODE, "OP_UTXOBYTECODE", 1, opcodeUtxoByteCode}, + OP_OUTPOINTTXHASH: {OP_OUTPOINTTXHASH, "OP_OUTPOINTTXHASH", 1, opcodeOutpointTxHash}, + OP_OUTPOINTINDEX: {OP_OUTPOINTINDEX, "OP_OUTPOINTINDEX", 1, opcodeOutpointIndex}, + OP_INPUTBYTECODE: {OP_INPUTBYTECODE, "OP_INPUTBYTECODE", 1, opcodeInputBytecode}, + OP_INPUTSEQUENCENUMBER: {OP_INPUTSEQUENCENUMBER, "OP_INPUTSEQUENCENUMBER", 1, opcodeInputSequenceNumber}, + OP_OUTPUTVALUE: {OP_OUTPUTVALUE, "OP_OUTPUTVALUE", 1, opcodeOutputValue}, + OP_OUTPUTBYTECODE: {OP_OUTPUTBYTECODE, "OP_OUTPUTBYTECODE", 1, opcodeOutputBytecode}, + // Reserved opcodes. OP_NOP1: {OP_NOP1, "OP_NOP1", 1, opcodeNop}, OP_NOP4: {OP_NOP4, "OP_NOP4", 1, opcodeNop}, @@ -518,20 +534,6 @@ var opcodeArray = [256]opcode{ OP_UNKNOWN189: {OP_UNKNOWN189, "OP_UNKNOWN189", 1, opcodeInvalid}, OP_UNKNOWN190: {OP_UNKNOWN190, "OP_UNKNOWN190", 1, opcodeInvalid}, OP_UNKNOWN191: {OP_UNKNOWN191, "OP_UNKNOWN191", 1, opcodeInvalid}, - OP_UNKNOWN192: {OP_UNKNOWN192, "OP_UNKNOWN192", 1, opcodeInvalid}, - OP_UNKNOWN193: {OP_UNKNOWN193, "OP_UNKNOWN193", 1, opcodeInvalid}, - OP_UNKNOWN194: {OP_UNKNOWN194, "OP_UNKNOWN194", 1, opcodeInvalid}, - OP_UNKNOWN195: {OP_UNKNOWN195, "OP_UNKNOWN195", 1, opcodeInvalid}, - OP_UNKNOWN196: {OP_UNKNOWN196, "OP_UNKNOWN196", 1, opcodeInvalid}, - OP_UNKNOWN197: {OP_UNKNOWN197, "OP_UNKNOWN197", 1, opcodeInvalid}, - OP_UNKNOWN198: {OP_UNKNOWN198, "OP_UNKNOWN198", 1, opcodeInvalid}, - OP_UNKNOWN199: {OP_UNKNOWN199, "OP_UNKNOWN199", 1, opcodeInvalid}, - OP_UNKNOWN200: {OP_UNKNOWN200, "OP_UNKNOWN200", 1, opcodeInvalid}, - OP_UNKNOWN201: {OP_UNKNOWN201, "OP_UNKNOWN201", 1, opcodeInvalid}, - OP_UNKNOWN202: {OP_UNKNOWN202, "OP_UNKNOWN202", 1, opcodeInvalid}, - OP_UNKNOWN203: {OP_UNKNOWN203, "OP_UNKNOWN203", 1, opcodeInvalid}, - OP_UNKNOWN204: {OP_UNKNOWN204, "OP_UNKNOWN204", 1, opcodeInvalid}, - OP_UNKNOWN205: {OP_UNKNOWN205, "OP_UNKNOWN205", 1, opcodeInvalid}, OP_UNKNOWN206: {OP_UNKNOWN206, "OP_UNKNOWN206", 1, opcodeInvalid}, OP_UNKNOWN207: {OP_UNKNOWN207, "OP_UNKNOWN207", 1, opcodeInvalid}, OP_UNKNOWN208: {OP_UNKNOWN208, "OP_UNKNOWN208", 1, opcodeInvalid}, @@ -629,8 +631,6 @@ func (pop *parsedOpcode) isDisabled() bool { return true case OP_2DIV: return true - case OP_MUL: - return true case OP_LSHIFT: return true case OP_RSHIFT: @@ -1466,7 +1466,16 @@ func opcodeNum2bin(op *parsedOpcode, vm *Engine) error { return err } - size := int(n.Int32()) + var ( + defaultScriptNumLen = defaultSmallScriptNumLen + size int + ) + if vm.hasFlag(ScriptVerify64BitIntegers) { + size = int(n.Int64()) + defaultScriptNumLen = defaultBigScriptNumLen + } else { + size = int(n.Int32()) + } if size > MaxScriptElementSize { return scriptError(ErrNumberTooBig, @@ -1513,6 +1522,11 @@ func opcodeBin2num(op *parsedOpcode, vm *Engine) error { if err != nil { return err } + + defaultScriptNumLen := defaultSmallScriptNumLen + if vm.hasFlag(ScriptVerify64BitIntegers) { + defaultScriptNumLen = defaultBigScriptNumLen + } if len(n.Bytes()) > defaultScriptNumLen { return scriptError(ErrNumberTooBig, fmt.Sprintf("script numbers are limited to %d bytes", defaultScriptNumLen)) @@ -1784,8 +1798,15 @@ func opcodeAdd(op *parsedOpcode, vm *Engine) error { return err } - vm.dstack.PushInt(v0 + v1) - return nil + c := v0 + v1 + if c == minInt64 { + return scriptError(ErrIntegerOverflow, "integer overflow") + } + if (c > v0) == (v1 > 0) { + vm.dstack.PushInt(c) + return nil + } + return scriptError(ErrIntegerOverflow, "integer overflow") } // opcodeSub treats the top two items on the data stack as integers and replaces @@ -1804,8 +1825,51 @@ func opcodeSub(op *parsedOpcode, vm *Engine) error { return err } - vm.dstack.PushInt(v1 - v0) - return nil + c := v1 - v0 + if c == minInt64 { + return scriptError(ErrIntegerOverflow, "integer overflow") + } + if (c < v1) == (v0 > 0) { + vm.dstack.PushInt(c) + return nil + } + return scriptError(ErrIntegerOverflow, "integer overflow") +} + +// opcodeMul return the integer product of a and b. +// +// Stack transformation: a b OP_MUL -> out +func opcodeMul(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerify64BitIntegers) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + b, err := vm.dstack.PopInt() + if err != nil { + return err + } + + a, err := vm.dstack.PopInt() + if err != nil { + return err + } + + if a == 0 || b == 0 { + vm.dstack.PushInt(0) + return nil + } + c := a * b + if c == minInt64 { + return scriptError(ErrIntegerOverflow, "integer overflow") + } + if (c < 0) == ((a < 0) != (b < 0)) { + if c/b == a { + vm.dstack.PushInt(c) + return nil + } + } + return scriptError(ErrIntegerOverflow, "integer overflow") } // opcodeDiv return the integer quotient of a and b. If the result @@ -2447,7 +2511,6 @@ func opcodeCheckMultiSig(op *parsedOpcode, vm *Engine) error { str := fmt.Sprintf("number of signatures %d is negative", numSignatures) return scriptError(ErrInvalidSignatureCount, str) - } if numSignatures > numPubKeys { str := fmt.Sprintf("more signatures than pubkeys: %d > %d", @@ -2880,6 +2943,327 @@ func opcodeCheckDataSigVerify(op *parsedOpcode, vm *Engine) error { return err } +// opcodeInputIndex pushes the index of the input being evaluated to the +// stack as a Script Number. +func opcodeInputIndex(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + vm.dstack.PushInt(scriptNum(vm.txIdx)) + return nil +} + +// opcodeActiveBytecode pushes the bytecode currently being evaluated, beginning +// after the last executed OP_CODESEPARATOR, to the stack1. For Pay-to-Script-Hash +// (P2SH) evaluations, this is the redeem bytecode of the Unspent Transaction Output +// (UTXO) being spent; for all other evaluations, this is the locking bytecode of +// the UTXO being spent. +// +// This behavior matches the existing behavior of the BCH VM during P2SH evaluation – +// once the P2SH pattern is matched, the remaining stack is copied, and the VM begins +// evaluation again with the P2SH redeem bytecode set as the new active bytecode. +// (In the Satoshi implementation, this P2SH redeem bytecode is passed as a CScript +// to a new execution of EvalScript.) +func opcodeActiveBytecode(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + subScript := vm.subScript() + l := 0 + for _, parsedOpcode := range subScript { + l += parsedOpcode.opcode.length + } + script := make([]byte, 0, l) + for _, parsedOpcode := range subScript { + b, err := parsedOpcode.bytes() + if err != nil { + return err + } + script = append(script, b...) + } + if len(script) > MaxScriptElementSize { + str := fmt.Sprintf("size %d exceeds max allowed size %d", + len(script), MaxScriptElementSize) + return scriptError(ErrElementTooBig, str) + } + vm.dstack.PushByteArray(script) + return nil +} + +// opcodeTxVersion pushes the version of the current transaction to the stack as a +// Script Number. +func opcodeTxVersion(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + vm.dstack.PushInt(scriptNum(vm.tx.Version)) + return nil +} + +// opcodeTxInputCount pushes the count of inputs in the current transaction to the +// stack as a Script Number. +func opcodeTxInputCount(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + vm.dstack.PushInt(scriptNum(len(vm.tx.TxIn))) + return nil +} + +// opcodeTxOutputCount pushes the count of outputs in the current transaction to the +// stack as a Script Number. +func opcodeTxOutputCount(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + vm.dstack.PushInt(scriptNum(len(vm.tx.TxOut))) + return nil +} + +// opcodeTxLocktime pushes the locktime of the current transaction to the stack +// as a Script Number. +func opcodeTxLocktime(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + vm.dstack.PushInt(scriptNum(vm.tx.LockTime)) + return nil +} + +// opcodeUtxoValue pops the top item from the stack as an input index (Script Number) +// and pushes the value (in satoshis) of the Unspent Transaction Output (UTXO) spent by that +// input to the stack as a Script Number. +// +// Stack transformation: a OP_UTXOVALUE -> b +func opcodeUtxoValue(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + i, err := vm.dstack.PopInt() + if err != nil { + return err + } + utxo, err := vm.utxoCache.GetEntry(int(i.Int32())) + if err != nil { + return scriptError(ErrInvalidIndex, "index out of range") + } + vm.dstack.PushInt(scriptNum(utxo.Value)) + return nil +} + +// opcodeUtxoByteCode pops the top item from the stack as an input index (Script Number) +// and pushes the full locking bytecode of the Unspent Transaction Output (UTXO) spent +// by that input to the stack. +// +// Stack transformation: a OP_UTXOBYTECODE -> x +func opcodeUtxoByteCode(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + i, err := vm.dstack.PopInt() + if err != nil { + return err + } + utxo, err := vm.utxoCache.GetEntry(int(i.Int32())) + if err != nil { + return scriptError(ErrInvalidIndex, "index out of range") + } + if len(utxo.PkScript) > MaxScriptElementSize { + str := fmt.Sprintf("size %d exceeds max allowed size %d", + len(utxo.PkScript), MaxScriptElementSize) + return scriptError(ErrElementTooBig, str) + } + ret := make([]byte, len(utxo.PkScript)) + copy(ret, utxo.PkScript) + vm.dstack.PushByteArray(ret) + return nil +} + +// opcodeOutpointTxHash pop the top item from the stack as an input index (Script Number). +// From that input, push the outpoint transaction hash – the hash of the transaction which +// created the Unspent Transaction Output (UTXO) which is being spent – to the stack in +// OP_HASH256 byte order. +// +// This is the byte order produced/required by all BCH VM operations which employ SHA-256 +// (including OP_SHA256 and OP_HASH256), the byte order used for outpoint transaction hashes +// in the P2P transaction format, and the byte order produced by most SHA-256 libraries. +// For reference, the genesis block header in this byte order is little-endian – +// 6fe28c0ab6f1b372c1a6a246ae63f74f931e8365e15a089c68d6190000000000 – and can be produced +// by this script: <0x010000000000000000000000000000000000000000000000000000000000000000000000 +// 3ba3edfd7a7b12b27ac72c3e67768f617fc81bc3888a51323a9fb8aa4b1e5e4a29ab5f49ffff001d1dac2b7c> +// OP_HASH256. (Note, this is the opposite byte order as is commonly used in user interfaces +// like block explorers.) +// +// Stack transformation: a OP_OUTPOINTTXHASH -> x +func opcodeOutpointTxHash(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + i, err := vm.dstack.PopInt() + if err != nil { + return err + } + if i.Int32() >= int32(len(vm.tx.TxIn)) { + str := fmt.Sprintf("index %d out of range", + i.Int32()) + return scriptError(ErrInvalidIndex, str) + } + outpointHash := vm.tx.TxIn[i].PreviousOutPoint.Hash + ret := make([]byte, len(outpointHash)) + copy(ret, outpointHash[:]) + vm.dstack.PushByteArray(ret) + return nil +} + +// opcodeOutpointIndex pops the top item from the stack as an input index (Script Number). +// From that input, push the outpoint index – the index of the output in the transaction +// which created the Unspent Transaction Output (UTXO) which is being spent – to the +// stack as a Script Number. +// +// Stack transformation: a OP_OUTPOINTINDEX -> b +func opcodeOutpointIndex(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + i, err := vm.dstack.PopInt() + if err != nil { + return err + } + if i.Int32() >= int32(len(vm.tx.TxIn)) { + str := fmt.Sprintf("index %d out of range", + i.Int32()) + return scriptError(ErrInvalidIndex, str) + } + vm.dstack.PushInt(scriptNum(vm.tx.TxIn[i].PreviousOutPoint.Index)) + return nil +} + +// opcodeInputBytecode pops the top item from the stack as an input index (Script Number) +// and pushes the unlocking bytecode of the input at that index to the stack. +// +// Stack transformation: a OP_INPUTBYTECODE -> x +func opcodeInputBytecode(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + i, err := vm.dstack.PopInt() + if err != nil { + return err + } + if i.Int32() >= int32(len(vm.tx.TxIn)) { + str := fmt.Sprintf("index %d out of range", + i.Int32()) + return scriptError(ErrInvalidIndex, str) + } + if len(vm.tx.TxIn[i].SignatureScript) > MaxScriptElementSize { + str := fmt.Sprintf("size %d exceeds max allowed size %d", + len(vm.tx.TxIn[i].SignatureScript), MaxScriptElementSize) + return scriptError(ErrElementTooBig, str) + } + ret := make([]byte, len(vm.tx.TxIn[i].SignatureScript)) + copy(ret, vm.tx.TxIn[i].SignatureScript) + vm.dstack.PushByteArray(ret) + return nil +} + +// opcodeInputSequenceNumber pops the top item from the stack as an input index (Script Number) +// and pushes the sequence number of the input at that index to the stack as a Script Number. +// +// Stack transformation: a OP_INPUTSEQUENCENUMBER -> b +func opcodeInputSequenceNumber(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + i, err := vm.dstack.PopInt() + if err != nil { + return err + } + if i.Int32() >= int32(len(vm.tx.TxIn)) { + str := fmt.Sprintf("index %d out of range", + i.Int32()) + return scriptError(ErrInvalidIndex, str) + } + vm.dstack.PushInt(scriptNum(vm.tx.TxIn[i].Sequence)) + return nil +} + +// opcodeOutputValue pops the top item from the stack as an output index (Script Number) +// and pushes the value (in satoshis) of the output at that index to the stack as a Script Number. +// +// Stack transformation: a OP_OUTPUTVALUE -> b +func opcodeOutputValue(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + i, err := vm.dstack.PopInt() + if err != nil { + return err + } + if i.Int32() >= int32(len(vm.tx.TxOut)) { + str := fmt.Sprintf("index %d out of range", + i.Int32()) + return scriptError(ErrInvalidIndex, str) + } + vm.dstack.PushInt(scriptNum(vm.tx.TxOut[i].Value)) + return nil +} + +// opcodeOutputBytecode pops the top item from the stack as an output index (Script Number) +// and pushes the locking bytecode of the output at that index to the stack. +// +// Stack transformation: a OP_OUTPUTBYTECODE -> x +func opcodeOutputBytecode(op *parsedOpcode, vm *Engine) error { + if !vm.hasFlag(ScriptVerifyNativeIntrospection) { + str := fmt.Sprintf("attempt to execute disabled opcode %s", + op.opcode.name) + return scriptError(ErrDisabledOpcode, str) + } + i, err := vm.dstack.PopInt() + if err != nil { + return err + } + if i.Int32() >= int32(len(vm.tx.TxOut)) { + str := fmt.Sprintf("index %d out of range", + i.Int32()) + return scriptError(ErrInvalidIndex, str) + } + if len(vm.tx.TxOut[i].PkScript) > MaxScriptElementSize { + str := fmt.Sprintf("size %d exceeds max allowed size %d", + len(vm.tx.TxOut[i].PkScript), MaxScriptElementSize) + return scriptError(ErrElementTooBig, str) + } + ret := make([]byte, len(vm.tx.TxOut[i].PkScript)) + copy(ret, vm.tx.TxOut[i].PkScript) + vm.dstack.PushByteArray(ret) + return nil +} + // OpcodeByName is a map that can be used to lookup an opcode by its // human-readable name (OP_CHECKMULTISIG, OP_CHECKSIG, etc). var OpcodeByName = make(map[string]byte) diff --git a/txscript/opcode_test.go b/txscript/opcode_test.go index e197ad576..a79df2c2d 100644 --- a/txscript/opcode_test.go +++ b/txscript/opcode_test.go @@ -6,7 +6,11 @@ package txscript import ( "bytes" + "encoding/binary" + "encoding/hex" "fmt" + "github.com/gcash/bchd/chaincfg/chainhash" + "github.com/gcash/bchd/wire" "strconv" "strings" "testing" @@ -74,6 +78,13 @@ func TestOpcodeDisasm(t *testing.T) { 0xae: "OP_CHECKMULTISIG", 0xaf: "OP_CHECKMULTISIGVERIFY", 0xba: "OP_CHECKDATASIG", 0xbb: "OP_CHECKDATASIGVERIFY", 0xbc: "OP_REVERSEBYTES", 0xfa: "OP_SMALLINTEGER", + 0xc0: "OP_INPUTINDEX", 0xc1: "OP_ACTIVEBYTECODE", + 0xc2: "OP_TXVERSION", 0xc3: "OP_TXINPUTCOUNT", + 0xc4: "OP_TXOUTPUTCOUNT", 0xc5: "OP_TXLOCKTIME", + 0xc6: "OP_UTXOVALUE", 0xc7: "OP_UTXOBYTECODE", + 0xc8: "OP_OUTPOINTTXHASH", 0xc9: "OP_OUTPOINTINDEX", + 0xca: "OP_INPUTBYTECODE", 0xcb: "OP_INPUTSEQUENCENUMBER", + 0xcc: "OP_OUTPUTVALUE", 0xcd: "OP_OUTPUTBYTECODE", 0xfb: "OP_PUBKEYS", 0xfd: "OP_PUBKEYHASH", 0xfe: "OP_PUBKEY", 0xff: "OP_INVALIDOPCODE", } @@ -121,7 +132,7 @@ func TestOpcodeDisasm(t *testing.T) { } // OP_UNKNOWN#. - case opcodeVal >= 0xbd && opcodeVal <= 0xf9 || opcodeVal == 0xfc: + case (opcodeVal >= 0xbd && opcodeVal <= 0xbf) || (opcodeVal >= 0xce && opcodeVal <= 0xf9) || opcodeVal == 0xfc: expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) } @@ -187,7 +198,7 @@ func TestOpcodeDisasm(t *testing.T) { } // OP_UNKNOWN#. - case opcodeVal >= 0xbd && opcodeVal <= 0xf9 || opcodeVal == 0xfc: + case (opcodeVal >= 0xbd && opcodeVal <= 0xbf) || (opcodeVal >= 0xce && opcodeVal <= 0xf9) || opcodeVal == 0xfc: expectedStr = "OP_UNKNOWN" + strconv.Itoa(opcodeVal) } @@ -201,3 +212,748 @@ func TestOpcodeDisasm(t *testing.T) { } } } + +func TestNativeIntrospectionOpcodes(t *testing.T) { + newHashFromStr := func(hexStr string) chainhash.Hash { + hash, err := chainhash.NewHashFromStr(hexStr) + if err != nil { + panic(err) + } + return *hash + } + newScript := func(builder *ScriptBuilder) []byte { + script, err := builder.Script() + if err != nil { + panic(err) + } + return script + } + fromHex := func(s string) []byte { + b, err := hex.DecodeString(s) + if err != nil { + panic(err) + } + return b + } + + testVectors := [][]struct { + name string + scriptPubkey []byte + scriptSig []byte + index int + flags ScriptFlags + expectedError ErrorCode + }{ + // OP_INPUTINDEX (nullary) + { + { + name: "OP_INPUTINDEX 0", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_INPUTINDEX). + AddInt64(0). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_INPUTINDEX 1", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_INPUTINDEX). + AddInt64(1). + AddOp(OP_EQUAL)), + index: 1, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_INPUTINDEX Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_INPUTINDEX). + AddInt64(1). + AddOp(OP_EQUAL)), + index: 1, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_ACTIVEBYTECODE (nullary) + { + { + name: "OP_ACTIVEBYTECODE OP_9", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_ACTIVEBYTECODE). + AddOp(OP_9). + AddOp(OP_DROP). + AddOp(OP_EQUAL)), + scriptSig: fromHex("04c1597587"), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_ACTIVEBYTECODE OP_10", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_ACTIVEBYTECODE). + AddOp(OP_10). + AddOp(OP_DROP). + AddOp(OP_EQUAL)), + scriptSig: fromHex("04c15a7587"), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_ACTIVEBYTECODE OP_CODESEPERATOR first", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_10). + AddOp(OP_11). + AddInt64(7654321). + AddOp(OP_DROP). + AddOp(OP_DROP). + AddOp(OP_DROP). + AddOp(OP_CODESEPARATOR). + AddOp(OP_5). + AddOp(OP_DROP). + AddOp(OP_ACTIVEBYTECODE). + AddOp(OP_EQUAL)), + scriptSig: fromHex("045575c187"), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_ACTIVEBYTECODE OP_CODESEPERATOR second", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_10). + AddOp(OP_DROP). + AddOp(OP_ACTIVEBYTECODE). + AddOp(OP_EQUALVERIFY). + AddOp(OP_CODESEPARATOR). + AddOp(OP_1)), + scriptSig: fromHex("065a75c188ab51"), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_ACTIVEBYTECODE max", + scriptPubkey: newScript(NewScriptBuilder(). + AddData(make([]byte, MaxScriptElementSize-6)). + AddOp(OP_DROP). + AddOp(OP_ACTIVEBYTECODE). + AddOp(OP_EQUAL)), + scriptSig: fromHex("4d08024d02020000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075c187"), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_ACTIVEBYTECODE exceeds max", + scriptPubkey: newScript(NewScriptBuilder(). + AddData(make([]byte, MaxScriptElementSize-5)). + AddOp(OP_DROP). + AddOp(OP_ACTIVEBYTECODE). + AddOp(OP_EQUAL)), + scriptSig: fromHex("4d09024d0302000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000075c187"), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrElementTooBig, + }, + { + name: "OP_ACTIVEBYTECODE not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_10). + AddOp(OP_DROP). + AddOp(OP_ACTIVEBYTECODE). + AddOp(OP_EQUALVERIFY). + AddOp(OP_CODESEPARATOR). + AddOp(OP_1)), + scriptSig: fromHex("065a75c188ab51"), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_TXVERSION (nullary) + { + { + name: "OP_TXVERSION", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_TXVERSION). + AddInt64(1). + AddOp(OP_EQUAL)), + index: 1, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_TXVERSION Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_INPUTINDEX). + AddInt64(1). + AddOp(OP_EQUAL)), + index: 1, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_TXINPUTCOUNT (nullary) + { + { + name: "OP_TXINPUTCOUNT", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_TXINPUTCOUNT). + AddInt64(2). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_TXINPUTCOUNT Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_TXINPUTCOUNT). + AddInt64(1). + AddOp(OP_EQUAL)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_TXOUTPUTCOUNT (nullary) + { + { + name: "OP_TXOUTPUTCOUNT", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_TXOUTPUTCOUNT). + AddInt64(2). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_TXOUTPUTCOUNT Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_TXOUTPUTCOUNT). + AddInt64(1). + AddOp(OP_EQUAL)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_TXLOCKTIME (nullary) + { + { + name: "OP_TXLOCKTIME", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_TXLOCKTIME). + AddInt64(10000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_TXLOCKTIME Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_TXLOCKTIME). + AddInt64(1). + AddOp(OP_EQUAL)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_UTXOVALUE (unary) + { + { + name: "OP_UTXOVALUE first input", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(0). + AddOp(OP_UTXOVALUE). + AddInt64(2000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_UTXOVALUE second input", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_UTXOVALUE). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_UTXOVALUE out of range", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(2). + AddOp(OP_UTXOVALUE). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidIndex, + }, + { + name: "OP_UTXOVALUE missing arg", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_UTXOVALUE). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidStackOperation, + }, + { + name: "OP_UTXOVALUE Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_UTXOVALUE)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_UTXOBYTECODE (unary) + { + { + name: "OP_UTXOBYTECODE first input", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(0). + AddOp(OP_UTXOBYTECODE). + AddData(fromHex("0000000000000000")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_UTXOBYTECODE exceeds max size", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_UTXOBYTECODE). + AddData(fromHex("0000000000000001")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrElementTooBig, + }, + { + name: "OP_UTXOBYTECODE out of range", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(2). + AddOp(OP_UTXOBYTECODE). + AddData(fromHex("0000000000000001")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidIndex, + }, + { + name: "OP_UTXOBYTECODE missing arg", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_UTXOBYTECODE). + AddData(fromHex("0000000000000001")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidStackOperation, + }, + { + name: "OP_UTXOBYTECODE Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_UTXOBYTECODE)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_OUTPOINTTXHASH (unary) + { + { + name: "OP_OUTPOINTHASH first input", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(0). + AddOp(OP_OUTPOINTTXHASH). + AddData(fromHex("2f663b097fa3439dc2a637d350f410a969587750a99459104363526995ae89be")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_OUTPOINTTXHASH second input", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_OUTPOINTTXHASH). + AddData(fromHex("48b36698efedb8c0a26282e46441948cfb15fae9b78193d3ce4f092b00fcd508")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_OUTPOINTTXHASH out of range", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(2). + AddOp(OP_OUTPOINTTXHASH). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidIndex, + }, + { + name: "OP_OUTPOINTTXHASH missing arg", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_OUTPOINTTXHASH). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidStackOperation, + }, + { + name: "OP_OUTPOINTTXHASH Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_OUTPOINTTXHASH)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_OUTPOINTINDEX (unary) + { + { + name: "OP_OUTPOINTINDEX first input", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(0). + AddOp(OP_OUTPOINTINDEX). + AddInt64(5). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_OUTPOINTINDEX second input", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_OUTPOINTINDEX). + AddInt64(7). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_OUTPOINTINDEX out of range", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(2). + AddOp(OP_OUTPOINTINDEX). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidIndex, + }, + { + name: "OP_OUTPOINTINDEX missing arg", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_OUTPOINTINDEX). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidStackOperation, + }, + { + name: "OP_OUTPOINTINDEX Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_OUTPOINTINDEX)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_INPUTBYTECODE (unary) + { + { + name: "OP_INPUTBYTECODE first input", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_DROP). + AddInt64(0). + AddOp(OP_INPUTBYTECODE). + AddData(fromHex("0000000000000000")). + AddOp(OP_EQUAL)), + index: 0, + scriptSig: fromHex("0000000000000000"), + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_INPUTBYTECODE exceeds max size", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_DROP). + AddInt64(0). + AddOp(OP_INPUTBYTECODE). + AddData(fromHex("0000000000000001")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + scriptSig: make([]byte, MaxScriptElementSize+1), + expectedError: ErrElementTooBig, + }, + { + name: "OP_INPUTBYTECODE out of range", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(2). + AddOp(OP_INPUTBYTECODE). + AddData(fromHex("0000000000000001")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidIndex, + }, + { + name: "OP_INPUTBYTECODE missing arg", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_INPUTBYTECODE). + AddData(fromHex("0000000000000001")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidStackOperation, + }, + { + name: "OP_INPUTBYTECODE Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_INPUTBYTECODE)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_INPUTSEQUENCENUMBER (unary) + { + { + name: "OP_INPUTSEQUENCENUMBER first input", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(0). + AddOp(OP_INPUTSEQUENCENUMBER). + AddInt64(13). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_INPUTSEQUENCENUMBER second input", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_INPUTSEQUENCENUMBER). + AddInt64(22). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_INPUTSEQUENCENUMBER out of range", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(2). + AddOp(OP_INPUTSEQUENCENUMBER). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidIndex, + }, + { + name: "OP_INPUTSEQUENCENUMBER missing arg", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_INPUTSEQUENCENUMBER). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidStackOperation, + }, + { + name: "OP_INPUTSEQUENCENUMBER Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_INPUTSEQUENCENUMBER)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_OUTPUTVALUE (unary) + { + { + name: "OP_OUTPUTVALUE first output", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(0). + AddOp(OP_OUTPUTVALUE). + AddInt64(10000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_OUTPUTVALUE second output", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_OUTPUTVALUE). + AddInt64(20000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_OUTPUTVALUE out of range", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(2). + AddOp(OP_OUTPUTVALUE). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidIndex, + }, + { + name: "OP_OUTPUTVALUE missing arg", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_OUTPUTVALUE). + AddInt64(12000). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidStackOperation, + }, + { + name: "OP_OUTPUTVALUE Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_OUTPUTVALUE)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + // OP_OUTPUTBYTECODE (unary) + { + { + name: "OP_OUTPUTBYTECODE first output", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(0). + AddOp(OP_OUTPUTBYTECODE). + AddData(fromHex("76a914000000000000000000000000000000000000000088ac")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + }, + { + name: "OP_OUTPUTBYTECODE exceeds max size", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_OUTPUTBYTECODE). + AddData(fromHex("0000000000000001")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrElementTooBig, + }, + { + name: "OP_OUTPUTBYTECODE out of range", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(2). + AddOp(OP_OUTPUTBYTECODE). + AddData(fromHex("0000000000000001")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidIndex, + }, + { + name: "OP_OUTPUTBYTECODE missing arg", + scriptPubkey: newScript(NewScriptBuilder(). + AddOp(OP_OUTPUTBYTECODE). + AddData(fromHex("0000000000000001")). + AddOp(OP_EQUAL)), + index: 0, + flags: ScriptVerifyNativeIntrospection, + expectedError: ErrInvalidStackOperation, + }, + { + name: "OP_OUTPUTBYTECODE Not activated", + scriptPubkey: newScript(NewScriptBuilder(). + AddInt64(1). + AddOp(OP_OUTPUTBYTECODE)), + index: 0, + expectedError: ErrDisabledOpcode, + }, + }, + } + + tx := &wire.MsgTx{ + Version: 1, + TxIn: []*wire.TxIn{ + { + PreviousOutPoint: wire.OutPoint{ + Hash: newHashFromStr("be89ae9569526343105994a950775869a910f450d337a6c29d43a37f093b662f"), + Index: 5, + }, + SignatureScript: nil, + Sequence: 13, + }, + { + PreviousOutPoint: wire.OutPoint{ + Hash: newHashFromStr("08d5fc002b094fced39381b7e9fa15fb8c944164e48262a2c0b8edef9866b348"), + Index: 7, + }, + SignatureScript: nil, + Sequence: 22, + }, + }, + TxOut: []*wire.TxOut{ + { + Value: 10000, + PkScript: newScript(NewScriptBuilder(). + AddOp(OP_DUP). + AddOp(OP_HASH160). + addData(fromHex("0000000000000000000000000000000000000000")). + AddOp(OP_EQUALVERIFY). + AddOp(OP_CHECKSIG)), + }, + { + Value: 20000, + PkScript: newScript(NewScriptBuilder(). + AddOp(OP_HASH160). + addData(make([]byte, MaxScriptElementSize)). + AddOp(OP_EQUAL)), + }, + }, + LockTime: 10000, + } + + cache := NewUtxoCache() + for i := range tx.TxIn { + b := make([]byte, 8) + binary.BigEndian.PutUint64(b, uint64(i)) + if i == 1 { + b = make([]byte, MaxScriptElementSize+1) + } + cache.AddEntry(i, wire.TxOut{ + Value: int64(2000 + (i * 10000)), + PkScript: b, + }) + } + + for i, group := range testVectors { + for x, test := range group { + utxo, err := cache.GetEntry(test.index) + if err != nil { + t.Fatalf("Test %d - %s: Utxo not found", i*x, test.name) + } + + if test.scriptSig != nil { + tx.TxIn[test.index].SignatureScript = test.scriptSig + } + + vm, err := NewEngine(test.scriptPubkey, tx, test.index, test.flags, nil, nil, cache, utxo.Value) + if err == nil { + err = vm.Execute() + } + if err != nil { + if !IsErrorCode(err, test.expectedError) { + t.Errorf("Test %d - %s: Expected error %v got %v", i*x, test.name, test.expectedError, err) + } + } else { + if test.expectedError != ErrInternal { + t.Errorf("Test %d - %s: Expected error %v got nil", i*x, test.name, test.expectedError) + } + } + tx.TxIn[test.index].SignatureScript = nil + } + } +} diff --git a/txscript/reference_test.go b/txscript/reference_test.go index d0321b09d..31f69209d 100644 --- a/txscript/reference_test.go +++ b/txscript/reference_test.go @@ -430,7 +430,7 @@ func testScripts(t *testing.T, tests [][]interface{}, useSigCache bool) { tx := createSpendingTx(scriptSig, scriptPubKey, int64(inputAmt)) - vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache, nil, + vm, err := NewEngine(scriptPubKey, tx, 0, flags, sigCache, nil, nil, int64(inputAmt)) if err == nil { err = vm.Execute() @@ -638,7 +638,7 @@ testloop: // input fails the transaction has failed. (some of the // test txns have good inputs, too.. vm, err := NewEngine(prevOut.pkScript, tx.MsgTx(), k, - flags, nil, nil, prevOut.inputVal) + flags, nil, nil, nil, prevOut.inputVal) if err != nil { continue testloop } @@ -790,7 +790,7 @@ testloop: continue testloop } vm, err := NewEngine(prevOut.pkScript, tx.MsgTx(), k, - flags, nil, nil, prevOut.inputVal) + flags, nil, nil, nil, prevOut.inputVal) if err != nil { t.Errorf("test (%d:%v:%d) failed to create "+ "script: %v", i, test, k, err) diff --git a/txscript/scriptnum.go b/txscript/scriptnum.go index 16fab6ecf..4bdf61a67 100644 --- a/txscript/scriptnum.go +++ b/txscript/scriptnum.go @@ -12,9 +12,16 @@ const ( maxInt32 = 1<<31 - 1 minInt32 = -1 << 31 - // defaultScriptNumLen is the default number of bytes - // data being interpreted as an integer may be. - defaultScriptNumLen = 4 + maxInt64 = 1<<63 - 1 + minInt64 = -1 << 63 + + // defaultSmallScriptNumLen is the default number of bytes + // data being interpreted as a small integer may be. + defaultSmallScriptNumLen = 4 + + // defaultBigScriptNumLen is the default number of bytes + // data being interpreted as a big integer may be. + defaultBigScriptNumLen = 8 ) // scriptNum represents a numeric value used in the scripting engine with @@ -22,21 +29,33 @@ const ( // // All numbers are stored on the data and alternate stacks encoded as little // endian with a sign bit. All numeric opcodes such as OP_ADD, OP_SUB, -// and OP_MUL, are only allowed to operate on 4-byte integers in the range -// [-2^31 + 1, 2^31 - 1], however the results of numeric operations may overflow -// and remain valid so long as they are not used as inputs to other numeric -// operations or otherwise interpreted as an integer. +// and OP_MUL, are only allowed to operate on n-byte integers where n=4 bytes +// until the CosmicInflation upgrade, and n=8 bytes after the upgrade. That is, +// before CosmicInflation the range is [-2^31 + 1, 2^31 - 1], and after the +// range is [-2^63 + 1, 2^63 - 1]. +// +// However the results of numeric operations may overflow. Before the +// CosmicInflation upgrade, overflows remain valid so long as they are not used +// as inputs to other numeric operations or otherwise interpreted as an integer. +// After the CosmicInflation upgrade, overflows result in an error and +// termination of script execution. // -// For example, it is possible for OP_ADD to have 2^31 - 1 for its two operands -// resulting 2^32 - 2, which overflows, but is still pushed to the stack as the +// For example, before the CosmicInflation upgrade: +// It is possible for OP_ADD to have 2^31 - 1 for its two operands resulting +// in 2^32 - 2, which overflows, but is still pushed to the stack as the // result of the addition. That value can then be used as input to OP_VERIFY // which will succeed because the data is being interpreted as a boolean. // However, if that same value were to be used as input to another numeric // opcode, such as OP_SUB, it must fail. // +// In a similar example after the CosmicInflation upgrade: +// It is possible for OP_ADD to have 2^63 - 1 for its two operands resulting +// in 2^64 - 2, which overflows, and results in an error and termination of +// script execution. +// // This type handles the aforementioned requirements by storing all numeric -// operation results as an int64 to handle overflow and provides the Bytes -// method to get the serialized representation (including values that overflow). +// operation results as an int64 and provides the Bytes method to get the +//serialized representation (including values that overflow). // // Then, whenever data is interpreted as an integer, it is converted to this // type by using the makeScriptNum function which will return an error if the @@ -156,17 +175,34 @@ func (n scriptNum) Int32() int32 { return int32(n) } +// Int64 returns the script number clamped to a valid int64. That is to say +// when the script number is higher than the max allowed int64, the max int64 +// value is returned and vice versa for the minimum value. Note that this +// behavior is different from a simple int64 cast because that truncates +// and the consensus rules dictate numbers which are directly cast to ints +// provide this behavior. +// +// In practice, for most opcodes, the number should never be out of range since +// it will have been created with makeScriptNum using the defaultScriptLen +// value, which rejects them. In case something in the future ends up calling +// this function against the result of some arithmetic, which IS allowed to be +// out of range before being reinterpreted as an integer, this will provide the +// correct behavior. +func (n scriptNum) Int64() int64 { + if n > maxInt64 { + return maxInt64 + } + + if n < minInt64 { + return minInt64 + } + + return int64(n) +} + // makeScriptNum interprets the passed serialized bytes as an encoded integer // and returns the result as a script number. // -// Since the consensus rules dictate that serialized bytes interpreted as ints -// are only allowed to be in the range determined by a maximum number of bytes, -// on a per opcode basis, an error will be returned when the provided bytes -// would result in a number outside of that range. In particular, the range for -// the vast majority of opcodes dealing with numeric values are limited to 4 -// bytes and therefore will pass that value to this function resulting in an -// allowed range of [-2^31 + 1, 2^31 - 1]. -// // The requireMinimal flag causes an error to be returned if additional checks // on the encoding determine it is not represented with the smallest possible // number of bytes or is the negative 0 encoding, [0x80]. For example, consider @@ -176,10 +212,11 @@ func (n scriptNum) Int32() int32 { // // The scriptNumLen is the maximum number of bytes the encoded value can be // before an ErrStackNumberTooBig is returned. This effectively limits the -// range of allowed values. +// range of allowed values and is important, for example, where script +// arguments are allowed only in a limited range on a per opcode basis. +// // WARNING: Great care should be taken if passing a value larger than -// defaultScriptNumLen, which could lead to addition and multiplication -// overflows. +// defaultScriptNumLen, which could lead to undefined behavior. // // See the Bytes function documentation for example encodings. func makeScriptNum(v []byte, requireMinimal bool, scriptNumLen int) (scriptNum, error) { diff --git a/txscript/scriptnum_test.go b/txscript/scriptnum_test.go index ffa331682..94289ef9c 100644 --- a/txscript/scriptnum_test.go +++ b/txscript/scriptnum_test.go @@ -104,35 +104,68 @@ func TestMakeScriptNum(t *testing.T) { err error }{ // Minimal encoding must reject negative 0. - {hexToBytes("80"), 0, defaultScriptNumLen, true, errMinimalData}, + {hexToBytes("80"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // Minimally encoded valid values with minimal encoding flag. // Should not error and return expected integral number. - {nil, 0, defaultScriptNumLen, true, nil}, - {hexToBytes("01"), 1, defaultScriptNumLen, true, nil}, - {hexToBytes("81"), -1, defaultScriptNumLen, true, nil}, - {hexToBytes("7f"), 127, defaultScriptNumLen, true, nil}, - {hexToBytes("ff"), -127, defaultScriptNumLen, true, nil}, - {hexToBytes("8000"), 128, defaultScriptNumLen, true, nil}, - {hexToBytes("8080"), -128, defaultScriptNumLen, true, nil}, - {hexToBytes("8100"), 129, defaultScriptNumLen, true, nil}, - {hexToBytes("8180"), -129, defaultScriptNumLen, true, nil}, - {hexToBytes("0001"), 256, defaultScriptNumLen, true, nil}, - {hexToBytes("0081"), -256, defaultScriptNumLen, true, nil}, - {hexToBytes("ff7f"), 32767, defaultScriptNumLen, true, nil}, - {hexToBytes("ffff"), -32767, defaultScriptNumLen, true, nil}, - {hexToBytes("008000"), 32768, defaultScriptNumLen, true, nil}, - {hexToBytes("008080"), -32768, defaultScriptNumLen, true, nil}, - {hexToBytes("ffff00"), 65535, defaultScriptNumLen, true, nil}, - {hexToBytes("ffff80"), -65535, defaultScriptNumLen, true, nil}, - {hexToBytes("000008"), 524288, defaultScriptNumLen, true, nil}, - {hexToBytes("000088"), -524288, defaultScriptNumLen, true, nil}, - {hexToBytes("000070"), 7340032, defaultScriptNumLen, true, nil}, - {hexToBytes("0000f0"), -7340032, defaultScriptNumLen, true, nil}, - {hexToBytes("00008000"), 8388608, defaultScriptNumLen, true, nil}, - {hexToBytes("00008080"), -8388608, defaultScriptNumLen, true, nil}, - {hexToBytes("ffffff7f"), 2147483647, defaultScriptNumLen, true, nil}, - {hexToBytes("ffffffff"), -2147483647, defaultScriptNumLen, true, nil}, + {nil, 0, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("01"), 1, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("81"), -1, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("7f"), 127, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("ff"), -127, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("8000"), 128, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("8080"), -128, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("8100"), 129, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("8180"), -129, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("0001"), 256, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("0081"), -256, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("ff7f"), 32767, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("ffff"), -32767, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("008000"), 32768, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("008080"), -32768, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("ffff00"), 65535, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("ffff80"), -65535, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("000008"), 524288, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("000088"), -524288, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("000070"), 7340032, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("0000f0"), -7340032, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("00008000"), 8388608, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("00008080"), -8388608, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("ffffff7f"), 2147483647, defaultSmallScriptNumLen, true, nil}, + {hexToBytes("ffffffff"), -2147483647, defaultSmallScriptNumLen, true, nil}, + + {nil, 0, defaultBigScriptNumLen, true, nil}, + {hexToBytes("01"), 1, defaultBigScriptNumLen, true, nil}, + {hexToBytes("81"), -1, defaultBigScriptNumLen, true, nil}, + {hexToBytes("7f"), 127, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ff"), -127, defaultBigScriptNumLen, true, nil}, + {hexToBytes("8000"), 128, defaultBigScriptNumLen, true, nil}, + {hexToBytes("8080"), -128, defaultBigScriptNumLen, true, nil}, + {hexToBytes("8100"), 129, defaultBigScriptNumLen, true, nil}, + {hexToBytes("8180"), -129, defaultBigScriptNumLen, true, nil}, + {hexToBytes("0001"), 256, defaultBigScriptNumLen, true, nil}, + {hexToBytes("0081"), -256, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ff7f"), 32767, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ffff"), -32767, defaultBigScriptNumLen, true, nil}, + {hexToBytes("008000"), 32768, defaultBigScriptNumLen, true, nil}, + {hexToBytes("008080"), -32768, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ffff00"), 65535, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ffff80"), -65535, defaultBigScriptNumLen, true, nil}, + {hexToBytes("000008"), 524288, defaultBigScriptNumLen, true, nil}, + {hexToBytes("000088"), -524288, defaultBigScriptNumLen, true, nil}, + {hexToBytes("000070"), 7340032, defaultBigScriptNumLen, true, nil}, + {hexToBytes("0000f0"), -7340032, defaultBigScriptNumLen, true, nil}, + {hexToBytes("00008000"), 8388608, defaultBigScriptNumLen, true, nil}, + {hexToBytes("00008080"), -8388608, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ffffff7f"), 2147483647, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ffffffff"), -2147483647, defaultBigScriptNumLen, true, nil}, + {hexToBytes("0000000002"), 8589934592, defaultBigScriptNumLen, true, nil}, + {hexToBytes("0000000082"), -8589934592, defaultBigScriptNumLen, true, nil}, + {hexToBytes("0000000000000040"), 4611686018427387904, defaultBigScriptNumLen, true, nil}, + {hexToBytes("00000000000000c0"), -4611686018427387904, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ffffffffffffff7f"), 9223372036854775807, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ffffffffffffffff"), -9223372036854775807, defaultBigScriptNumLen, true, nil}, + {hexToBytes("ffffffff7f"), 549755813887, 5, true, nil}, {hexToBytes("ffffffffff"), -549755813887, 5, true, nil}, {hexToBytes("ffffffffffffff7f"), 9223372036854775807, 8, true, nil}, @@ -145,50 +178,53 @@ func TestMakeScriptNum(t *testing.T) { // Minimally encoded values that are out of range for data that // is interpreted as script numbers with the minimal encoding // flag set. Should error and return 0. - {hexToBytes("0000008000"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000008080"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000009000"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000009080"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000000001"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("0000000081"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffff00"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffff80"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffff7f"), 0, defaultScriptNumLen, true, errNumTooBig}, - {hexToBytes("ffffffffffffffff"), 0, defaultScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000008000"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000008080"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000009000"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000009080"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffff00"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffff80"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000000001"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("0000000081"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffff00"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffff80"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffffff00"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffffff80"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffffff7f"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffffffff"), 0, defaultSmallScriptNumLen, true, errNumTooBig}, + + {hexToBytes("ffffffffffffff7fff"), 0, defaultBigScriptNumLen, true, errNumTooBig}, + {hexToBytes("ffffffffffffffffff"), 0, defaultBigScriptNumLen, true, errNumTooBig}, // Non-minimally encoded, but otherwise valid values with // minimal encoding flag. Should error and return 0. - {hexToBytes("00"), 0, defaultScriptNumLen, true, errMinimalData}, // 0 - {hexToBytes("0100"), 0, defaultScriptNumLen, true, errMinimalData}, // 1 - {hexToBytes("7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 127 - {hexToBytes("800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 128 - {hexToBytes("810000"), 0, defaultScriptNumLen, true, errMinimalData}, // 129 - {hexToBytes("000100"), 0, defaultScriptNumLen, true, errMinimalData}, // 256 - {hexToBytes("ff7f00"), 0, defaultScriptNumLen, true, errMinimalData}, // 32767 - {hexToBytes("00800000"), 0, defaultScriptNumLen, true, errMinimalData}, // 32768 - {hexToBytes("ffff0000"), 0, defaultScriptNumLen, true, errMinimalData}, // 65535 - {hexToBytes("00000800"), 0, defaultScriptNumLen, true, errMinimalData}, // 524288 - {hexToBytes("00007000"), 0, defaultScriptNumLen, true, errMinimalData}, // 7340032 - {hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520 + {hexToBytes("00"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 0 + {hexToBytes("0100"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 1 + {hexToBytes("7f00"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 127 + {hexToBytes("800000"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 128 + {hexToBytes("810000"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 129 + {hexToBytes("000100"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 256 + {hexToBytes("ff7f00"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 32767 + {hexToBytes("00800000"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 32768 + {hexToBytes("ffff0000"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 65535 + {hexToBytes("00000800"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 524288 + {hexToBytes("00007000"), 0, defaultSmallScriptNumLen, true, errMinimalData}, // 7340032 + {hexToBytes("0009000100"), 0, 5, true, errMinimalData}, // 16779520 // Non-minimally encoded, but otherwise valid values without // minimal encoding flag. Should not error and return expected // integral number. - {hexToBytes("00"), 0, defaultScriptNumLen, false, nil}, - {hexToBytes("0100"), 1, defaultScriptNumLen, false, nil}, - {hexToBytes("7f00"), 127, defaultScriptNumLen, false, nil}, - {hexToBytes("800000"), 128, defaultScriptNumLen, false, nil}, - {hexToBytes("810000"), 129, defaultScriptNumLen, false, nil}, - {hexToBytes("000100"), 256, defaultScriptNumLen, false, nil}, - {hexToBytes("ff7f00"), 32767, defaultScriptNumLen, false, nil}, - {hexToBytes("00800000"), 32768, defaultScriptNumLen, false, nil}, - {hexToBytes("ffff0000"), 65535, defaultScriptNumLen, false, nil}, - {hexToBytes("00000800"), 524288, defaultScriptNumLen, false, nil}, - {hexToBytes("00007000"), 7340032, defaultScriptNumLen, false, nil}, + {hexToBytes("00"), 0, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("0100"), 1, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("7f00"), 127, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("800000"), 128, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("810000"), 129, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("000100"), 256, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("ff7f00"), 32767, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("00800000"), 32768, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("ffff0000"), 65535, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("00000800"), 524288, defaultSmallScriptNumLen, false, nil}, + {hexToBytes("00007000"), 7340032, defaultSmallScriptNumLen, false, nil}, {hexToBytes("0009000100"), 16779520, 5, false, nil}, } @@ -272,6 +308,65 @@ func TestScriptNumInt32(t *testing.T) { } } +// TestScriptNumInt64 ensures that the Int64 function on script number behaves +// as expected. +func TestScriptNumInt64(t *testing.T) { + t.Parallel() + + tests := []struct { + in scriptNum + want int64 + }{ + // Values inside the valid int64 range are just the values + // themselves cast to an int64. + {0, 0}, + {1, 1}, + {-1, -1}, + {127, 127}, + {-127, -127}, + {128, 128}, + {-128, -128}, + {129, 129}, + {-129, -129}, + {256, 256}, + {-256, -256}, + {32767, 32767}, + {-32767, -32767}, + {32768, 32768}, + {-32768, -32768}, + {65535, 65535}, + {-65535, -65535}, + {524288, 524288}, + {-524288, -524288}, + {7340032, 7340032}, + {-7340032, -7340032}, + {8388608, 8388608}, + {-8388608, -8388608}, + {2147483647, 2147483647}, + {-2147483647, -2147483647}, + {-2147483648, -2147483648}, + {2147483648, 2147483648}, + {-2147483649, -2147483649}, + {1152921504606846975, 1152921504606846975}, + {-1152921504606846975, -1152921504606846975}, + {2305843009213693951, 2305843009213693951}, + {-2305843009213693951, -2305843009213693951}, + {4611686018427387903, 4611686018427387903}, + {-4611686018427387903, -4611686018427387903}, + {9223372036854775807, 9223372036854775807}, + {-9223372036854775808, -9223372036854775808}, + } + + for _, test := range tests { + got := test.in.Int64() + if got != test.want { + t.Errorf("Int64: did not get expected value for %d - "+ + "got %d, want %d", test.in, got, test.want) + continue + } + } +} + func Test_minimallyEncode(t *testing.T) { tests := []struct { data []byte diff --git a/txscript/sign_test.go b/txscript/sign_test.go index e0184cd72..9991eb220 100644 --- a/txscript/sign_test.go +++ b/txscript/sign_test.go @@ -58,7 +58,7 @@ func mkGetScript(scripts map[string][]byte) ScriptDB { func checkScripts(msg string, tx *wire.MsgTx, idx int, inputAmt int64, sigScript, pkScript []byte) error { tx.TxIn[idx].SignatureScript = sigScript vm, err := NewEngine(pkScript, tx, idx, - ScriptBip16|ScriptVerifyDERSignatures|ScriptVerifyBip143SigHash|ScriptVerifySchnorr, nil, nil, inputAmt) + ScriptBip16|ScriptVerifyDERSignatures|ScriptVerifyBip143SigHash|ScriptVerifySchnorr, nil, nil, nil, inputAmt) if err != nil { return fmt.Errorf("failed to make script engine for %s: %v", msg, err) @@ -1702,7 +1702,7 @@ nexttest: scriptFlags := ScriptBip16 | ScriptVerifyDERSignatures | ScriptVerifyBip143SigHash | ScriptVerifySchnorr for j := range tx.TxIn { vm, err := NewEngine(sigScriptTests[i]. - inputs[j].txout.PkScript, tx, j, scriptFlags, nil, nil, 0) + inputs[j].txout.PkScript, tx, j, scriptFlags, nil, nil, nil, 0) if err != nil { t.Errorf("cannot create script vm for test %v: %v", sigScriptTests[i].name, err) diff --git a/txscript/stack.go b/txscript/stack.go index 7d6557bba..080183fa5 100644 --- a/txscript/stack.go +++ b/txscript/stack.go @@ -36,8 +36,9 @@ func fromBool(v bool) []byte { // changed it *must* be deep-copied first to avoid changing other values on the // stack. type stack struct { - stk [][]byte - verifyMinimalData bool + stk [][]byte + verifyMinimalData bool + defaultScriptNumLen int } // Depth returns the number of items on the stack. @@ -86,7 +87,7 @@ func (s *stack) PopInt() (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen) + return makeScriptNum(so, s.verifyMinimalData, s.defaultScriptNumLen) } // PopBool pops the value off the top of the stack, converts it into a bool, and @@ -126,7 +127,7 @@ func (s *stack) PeekInt(idx int32) (scriptNum, error) { return 0, err } - return makeScriptNum(so, s.verifyMinimalData, defaultScriptNumLen) + return makeScriptNum(so, s.verifyMinimalData, s.defaultScriptNumLen) } // PeekBool returns the Nth item on the stack as a bool without removing it. diff --git a/txscript/stack_test.go b/txscript/stack_test.go index 6ae3a889d..d0e2bdab6 100644 --- a/txscript/stack_test.go +++ b/txscript/stack_test.go @@ -905,7 +905,9 @@ func TestStack(t *testing.T) { for _, test := range tests { // Setup the initial stack state and perform the test operation. - s := stack{} + s := stack{ + defaultScriptNumLen: defaultSmallScriptNumLen, + } for i := range test.before { s.PushByteArray(test.before[i]) } diff --git a/txscript/standard.go b/txscript/standard.go index 42174ae1f..d6b9e03b7 100644 --- a/txscript/standard.go +++ b/txscript/standard.go @@ -45,7 +45,9 @@ const ( ScriptVerifySchnorrMultisig | ScriptVerifyReverseBytes | ScriptReportSigChecks | - ScriptVerifyInputSigChecks + ScriptVerifyInputSigChecks | + ScriptVerify64BitIntegers | + ScriptVerifyNativeIntrospection ) // ScriptClass is an enumeration for the list of standard types of script. diff --git a/version/version.go b/version/version.go index d6e9a9e85..0f91a5a2f 100644 --- a/version/version.go +++ b/version/version.go @@ -17,8 +17,8 @@ const semanticAlphabet = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqr // versioning 2.0.0 spec (http://semver.org/). const ( AppMajor uint = 0 - AppMinor uint = 18 - AppPatch uint = 1 + AppMinor uint = 19 + AppPatch uint = 0 // AppPreRelease MUST only contain characters from semanticAlphabet // per the semantic versioning spec. diff --git a/wire/protocol.go b/wire/protocol.go index 0b5eba682..a5455563f 100644 --- a/wire/protocol.go +++ b/wire/protocol.go @@ -185,6 +185,9 @@ const ( // TestNet3 represents the test network (version 3). TestNet3 BitcoinNet = 0xf4f3e5f4 + // TestNet4 represents the test network (version 4). + TestNet4 BitcoinNet = 0xafdab7e2 + // SimNet represents the simulation test network. SimNet BitcoinNet = 0x12141c16 )