github.com/deroproject/derosuite@v2.1.6-1.0.20200307070847-0f2e589c7a2b+incompatible/blockchain/transaction_verify.go (about)

     1  // Copyright 2017-2018 DERO Project. All rights reserved.
     2  // Use of this source code in any form is governed by RESEARCH license.
     3  // license can be found in the LICENSE file.
     4  // GPG: 0F39 E425 8C65 3947 702A  8234 08B2 0360 A03A 9DE8
     5  //
     6  //
     7  // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
     8  // EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
     9  // MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
    10  // THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
    11  // SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
    12  // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
    13  // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
    14  // STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
    15  // THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
    16  
    17  package blockchain
    18  
    19  //import "fmt"
    20  import "time"
    21  
    22  /*import "bytes"
    23  import "encoding/binary"
    24  
    25  import "github.com/romana/rlog"
    26  
    27  */
    28  
    29  import "sync"
    30  import "runtime/debug"
    31  
    32  import "github.com/romana/rlog"
    33  import log "github.com/sirupsen/logrus"
    34  
    35  import "github.com/deroproject/derosuite/config"
    36  import "github.com/deroproject/derosuite/block"
    37  import "github.com/deroproject/derosuite/crypto"
    38  import "github.com/deroproject/derosuite/storage"
    39  import "github.com/deroproject/derosuite/crypto/ringct"
    40  import "github.com/deroproject/derosuite/transaction"
    41  
    42  //import "github.com/deroproject/derosuite/emission"
    43  
    44  // caches x of transactions validity
    45  // it is always atomic
    46  // the cache is not txhash -> validity mapping
    47  // instead it is txhash+expanded ringmembers
    48  // if the entry exist, the tx is valid
    49  // it stores special hash and first seen time
    50  // this can only be used on expanded transactions
    51  var transaction_valid_cache sync.Map
    52  
    53  // this go routine continuously scans and cleans up the cache for expired entries
    54  func clean_up_valid_cache() {
    55  
    56  	for {
    57  		time.Sleep(3600 * time.Second)
    58  		current_time := time.Now()
    59  
    60  		// track propagation upto 10 minutes
    61  		transaction_valid_cache.Range(func(k, value interface{}) bool {
    62  			first_seen := value.(time.Time)
    63  			if current_time.Sub(first_seen).Round(time.Second).Seconds() > 3600 {
    64  				transaction_valid_cache.Delete(k)
    65  			}
    66  			return true
    67  		})
    68  
    69  	}
    70  }
    71  
    72  /* Coinbase transactions need to verify the amount of coins
    73   * */
    74  func (chain *Blockchain) Verify_Transaction_Coinbase(dbtx storage.DBTX, cbl *block.Complete_Block, minertx *transaction.Transaction) (result bool) {
    75  
    76  	if !minertx.IsCoinbase() { // transaction is not coinbase, return failed
    77  		return false
    78  	}
    79  
    80  	// coinbase transactions only have 1 vin, 1 vout
    81  	if len(minertx.Vin) != 1 {
    82  		return false
    83  	}
    84  
    85  	if len(minertx.Vout) != 1 {
    86  		return false
    87  	}
    88  
    89  	public_key := minertx.Vout[0].Target.(transaction.Txout_to_key).Key
    90  
    91  	if !public_key.Public_Key_Valid() { // if public_key is not valid ( not a point on the curve reject the TX)
    92  		logger.WithFields(log.Fields{"txid": minertx.GetHash()}).Warnf("TX public is INVALID %s ", public_key)
    93  		return false
    94  
    95  	}
    96  
    97  	// check whether the height mentioned in tx.Vin is equal to block height
    98  	// this does NOT hold for genesis block so test it differently
    99  
   100  	expected_height := chain.Calculate_Height_At_Tips(dbtx, cbl.Bl.Tips)
   101  
   102  	if minertx.Vin[0].(transaction.Txin_gen).Height != uint64(expected_height) {
   103  		logger.Warnf("Rejected  Block due to invalid Height  actual %d   expected %d", minertx.Vin[0].(transaction.Txin_gen).Height, expected_height)
   104  		return false
   105  
   106  	}
   107  
   108  	return true
   109  }
   110  
   111  // all non miner tx must be non-coinbase tx
   112  // each check is placed in a separate  block of code, to avoid ambigous code or faulty checks
   113  // all check are placed and not within individual functions ( so as we cannot skip a check )
   114  // This function verifies tx fully, means all checks,
   115  // if the transaction has passed the check it can be added to mempool, relayed or added to blockchain
   116  // the transaction has already been deserialized thats it
   117  //
   118  func (chain *Blockchain) Verify_Transaction_NonCoinbase(dbtx storage.DBTX, hf_version int64, tx *transaction.Transaction) (result bool) {
   119  	result = false
   120  
   121  	var tx_hash crypto.Hash
   122  	var tx_serialized []byte // serialized tx
   123  	defer func() {           // safety so if anything wrong happens, verification fails
   124  		if r := recover(); r != nil {
   125  			logger.WithFields(log.Fields{"txid": tx_hash}).Warnf("Recovered while Verifying transaction, failed verification, Stack trace below")
   126  			logger.Warnf("Stack trace  \n%s", debug.Stack())
   127  			result = false
   128  		}
   129  	}()
   130  
   131  	tx_hash = tx.GetHash()
   132  
   133  	if tx.Version != 2 {
   134  		return false
   135  	}
   136  
   137  	// make sure atleast 1 vin and 1 vout are there
   138  	if len(tx.Vin) < 1 || len(tx.Vout) < 1 {
   139  		logger.WithFields(log.Fields{"txid": tx_hash}).Warnf("Incoming TX does NOT have atleast 1 vin and 1 vout")
   140  		return false
   141  	}
   142  
   143  	// this means some other checks have failed somewhere else
   144  	if tx.IsCoinbase() { // transaction coinbase must never come here
   145  		logger.WithFields(log.Fields{"txid": tx_hash}).Warnf("Coinbase tx in non coinbase path, Please investigate")
   146  		return false
   147  	}
   148  
   149  	// Vin can be only specific type rest all make the fail case
   150  	for i := 0; i < len(tx.Vin); i++ {
   151  		switch tx.Vin[i].(type) {
   152  		case transaction.Txin_gen:
   153  			return false // this is for coinbase so fail it
   154  		case transaction.Txin_to_key: // pass
   155  		default:
   156  			return false
   157  		}
   158  	}
   159  
   160  	if hf_version >= 2 {
   161  
   162  		/*  // if ever a need comes ups
   163  		if len(tx.Vin) >= config.MAX_VIN{
   164  			rlog.Warnf("Tx %s has more Vins than allowed limit 299 actual %d",tx_hash,len(tx.Vin))
   165  			return
   166  		}*/
   167  
   168  		if len(tx.Vout) >= config.MAX_VOUT {
   169  			rlog.Warnf("Tx %s has more Vouts than allowed limit 7 actual %d", tx_hash, len(tx.Vout))
   170  			return
   171  		}
   172  	}
   173  
   174  	// Vout can be only specific type rest all make th fail case
   175  	for i := 0; i < len(tx.Vout); i++ {
   176  		switch tx.Vout[i].Target.(type) {
   177  		case transaction.Txout_to_key: // pass
   178  
   179  			public_key := tx.Vout[i].Target.(transaction.Txout_to_key).Key
   180  
   181  			if !public_key.Public_Key_Valid() { // if public_key is not valid ( not a point on the curve reject the TX)
   182  				logger.WithFields(log.Fields{"txid": tx_hash}).Warnf("TX public is INVALID %s ", public_key)
   183  				return false
   184  
   185  			}
   186  		default:
   187  			return false
   188  		}
   189  	}
   190  
   191  	// Vout should have amount 0
   192  	for i := 0; i < len(tx.Vout); i++ {
   193  		if tx.Vout[i].Amount != 0 {
   194  			logger.WithFields(log.Fields{"txid": tx_hash, "Amount": tx.Vout[i].Amount}).Warnf("Amount must be zero in ringCT world")
   195  			return false
   196  		}
   197  	}
   198  
   199  	// check the mixin , it should be atleast 4 and should be same through out the tx ( all other inputs)
   200  	// someone did send a mixin of 3 in 12006 block height
   201  	// atlantis has minimum mixin of 5
   202  	if hf_version >= 2 {
   203  		mixin := len(tx.Vin[0].(transaction.Txin_to_key).Key_offsets)
   204  
   205  		if mixin < config.MIN_MIXIN {
   206  			logger.WithFields(log.Fields{"txid": tx_hash, "Mixin": mixin}).Warnf("Mixin cannot be more than %d.", config.MIN_MIXIN)
   207  			return false
   208  		}
   209  		if mixin >= config.MAX_MIXIN {
   210  			logger.WithFields(log.Fields{"txid": tx_hash, "Mixin": mixin}).Warnf("Mixin cannot be more than %d.", config.MAX_MIXIN)
   211  			return false
   212  		}
   213  
   214  		for i := 0; i < len(tx.Vin); i++ {
   215  			if mixin != len(tx.Vin[i].(transaction.Txin_to_key).Key_offsets) {
   216  				logger.WithFields(log.Fields{"txid": tx_hash, "Mixin": mixin}).Warnf("Mixin must be same for entire TX in ringCT world")
   217  				return false
   218  			}
   219  		}
   220  	}
   221  
   222  	// duplicate ringmembers are not allowed, check them here
   223  	// just in case protect ourselves as much as we can
   224  	for i := 0; i < len(tx.Vin); i++ {
   225  		ring_members := map[uint64]bool{} // create a separate map for each input
   226  		ring_member := uint64(0)
   227  		for j := 0; j < len(tx.Vin[i].(transaction.Txin_to_key).Key_offsets); j++ {
   228  			ring_member += tx.Vin[i].(transaction.Txin_to_key).Key_offsets[j]
   229  			if _, ok := ring_members[ring_member]; ok {
   230  				logger.WithFields(log.Fields{"txid": tx_hash, "input_index": i}).Warnf("Duplicate ring member within the TX")
   231  				return false
   232  			}
   233  			ring_members[ring_member] = true // add member to ring member
   234  		}
   235  
   236  		//	rlog.Debugf("Ring members for %d %+v", i, ring_members )
   237  	}
   238  
   239  	// check whether the key image is duplicate within the inputs
   240  	// NOTE: a block wide key_image duplication is done during block testing but we are still keeping it
   241  	{
   242  		kimages := map[crypto.Hash]bool{}
   243  		for i := 0; i < len(tx.Vin); i++ {
   244  			if _, ok := kimages[tx.Vin[i].(transaction.Txin_to_key).K_image]; ok {
   245  				logger.WithFields(log.Fields{
   246  					"txid":   tx_hash,
   247  					"kimage": tx.Vin[i].(transaction.Txin_to_key).K_image,
   248  				}).Warnf("TX using duplicate inputs within the TX")
   249  				return false
   250  			}
   251  			kimages[tx.Vin[i].(transaction.Txin_to_key).K_image] = true // add element to map for next check
   252  		}
   253  	}
   254  
   255  	// check whether the key image is low order attack, if yes reject it right now
   256  	for i := 0; i < len(tx.Vin); i++ {
   257  		k_image := crypto.Key(tx.Vin[i].(transaction.Txin_to_key).K_image)
   258  		curve_order := crypto.CurveOrder()
   259  		mult_result := crypto.ScalarMultKey(&k_image, &curve_order)
   260  		if *mult_result != crypto.Identity {
   261  			logger.WithFields(log.Fields{
   262  				"txid":        tx_hash,
   263  				"kimage":      tx.Vin[i].(transaction.Txin_to_key).K_image,
   264  				"curve_order": curve_order,
   265  				"mult_result": *mult_result,
   266  				"identity":    crypto.Identity,
   267  			}).Warnf("TX contains a low order key image attack, but we are already safeguarded")
   268  			return false
   269  		}
   270  	}
   271  
   272  	// disallow old transactions with borrowmean signatures
   273  	if hf_version >= 2 {
   274  		switch tx.RctSignature.Get_Sig_Type() {
   275  		case ringct.RCTTypeSimple, ringct.RCTTypeFull:
   276  			return false
   277  		}
   278  	}
   279  
   280  	// check whether the TX contains a signature or NOT
   281  	switch tx.RctSignature.Get_Sig_Type() {
   282  	case ringct.RCTTypeSimpleBulletproof, ringct.RCTTypeSimple, ringct.RCTTypeFull: // default case, pass through
   283  	default:
   284  		logger.WithFields(log.Fields{"txid": tx_hash}).Warnf("TX does NOT contain a ringct signature. It is NOT possible")
   285  		return false
   286  	}
   287  
   288  	// check tx size for validity
   289  	if hf_version >= 2 {
   290  		tx_serialized = tx.Serialize()
   291  		if len(tx_serialized) >= config.CRYPTONOTE_MAX_TX_SIZE {
   292  			rlog.Warnf("tx %s rejected Size(%d) is more than allowed(%d)", tx_hash, len(tx.Serialize()), config.CRYPTONOTE_MAX_TX_SIZE)
   293  			return false
   294  		}
   295  	}
   296  
   297  	// expand the signature first
   298  	// whether the inputs are mature and can be used at time is verified while expanding the inputs
   299  
   300  	//rlog.Debugf("txverify tx %s hf_version %d", tx_hash, hf_version )
   301  	if !chain.Expand_Transaction_v2(dbtx, hf_version, tx) {
   302  		rlog.Warnf("TX %s inputs could not be expanded or inputs are NOT mature", tx_hash)
   303  		return false
   304  	}
   305  
   306  	//logger.Infof("Expanded tx %+v", tx.RctSignature)
   307  
   308  	// create a temporary hash out of expanded transaction
   309  	// this feature is very critical and helps the daemon by spreading out the compute load
   310  	// over the entire time between 2 blocks
   311  	// this tremendously helps in block propagation times
   312  	// and make them easy to process just like like small 50 KB blocks
   313  
   314  	// each ring member if 64 bytes
   315  	tmp_buffer := make([]byte, 0, len(tx.Vin)*32+len(tx.Vin)*len(tx.Vin[0].(transaction.Txin_to_key).Key_offsets)*64)
   316  
   317  	// build the buffer for special hash
   318  	// DO NOT skip anything, use full serialized tx, it is used while building keccak hash
   319  	// use everything from tx expansion etc
   320  	for i := 0; i < len(tx.Vin); i++ { // append all mlsag sigs
   321  		tmp_buffer = append(tmp_buffer, tx.RctSignature.MlsagSigs[i].II[0][:]...)
   322  	}
   323  	for i := 0; i < len(tx.RctSignature.MixRing); i++ {
   324  		for j := 0; j < len(tx.RctSignature.MixRing[i]); j++ {
   325  			tmp_buffer = append(tmp_buffer, tx.RctSignature.MixRing[i][j].Destination[:]...)
   326  			tmp_buffer = append(tmp_buffer, tx.RctSignature.MixRing[i][j].Mask[:]...)
   327  		}
   328  	}
   329  
   330  	// 1 less allocation this way
   331  	special_hash := crypto.Keccak256(tx_serialized, tmp_buffer)
   332  
   333  	if _, ok := transaction_valid_cache.Load(special_hash); ok {
   334  		//logger.Infof("Found in cache %s ",tx_hash)
   335  		return true
   336  	} else {
   337  		//logger.Infof("TX not found in cache %s len %d ",tx_hash, len(tmp_buffer))
   338  	}
   339  
   340  	// check the ring signature
   341  	if !tx.RctSignature.Verify() {
   342  
   343  		//logger.Infof("tx expanded %+v\n", tx.RctSignature.MixRing)
   344  		logger.WithFields(log.Fields{"txid": tx_hash}).Warnf("TX RCT Signature failed")
   345  		return false
   346  
   347  	}
   348  
   349  	// signature got verified, cache it
   350  	transaction_valid_cache.Store(special_hash, time.Now())
   351  	//logger.Infof("TX validity marked in cache %s ",tx_hash)
   352  
   353  	//logger.WithFields(log.Fields{"txid": tx_hash}).Debugf("TX successfully verified")
   354  
   355  	return true
   356  }
   357  
   358  // double spend check is separate from the core checks ( due to softforks )
   359  func (chain *Blockchain) Verify_Transaction_NonCoinbase_DoubleSpend_Check(dbtx storage.DBTX, tx *transaction.Transaction) (result bool) {
   360  	result = false
   361  
   362  	var tx_hash crypto.Hash
   363  	defer func() { // safety so if anything wrong happens, verification fails
   364  		if r := recover(); r != nil {
   365  			logger.WithFields(log.Fields{"txid": tx_hash}).Warnf("Recovered while Verifying transaction, failed verification, Stack trace below")
   366  			logger.Warnf("Stack trace  \n%s", debug.Stack())
   367  			result = false
   368  		}
   369  	}()
   370  
   371  	tx_hash = tx.GetHash()
   372  
   373  	// a similiar block level check is done for double spending attacks within the block itself
   374  	// check whether the key image is already used or spent earlier ( in blockchain )
   375  	for i := 0; i < len(tx.Vin); i++ {
   376  		k_image := crypto.Key(tx.Vin[i].(transaction.Txin_to_key).K_image)
   377  		if spent_height, ok := chain.Read_KeyImage_Status(dbtx, crypto.Hash(k_image)); ok {
   378  			_ = spent_height
   379  			//rlog.Warnf("Key image is already spent at height %d, attempt to double spend KI %s TXID %s", spent_height,k_image,tx_hash)
   380  			return false
   381  		}
   382  	}
   383  	return true
   384  
   385  }
   386  
   387  // verify all non coinbase tx, single threaded for double spending on current active chain
   388  func (chain *Blockchain) Verify_Block_DoubleSpending(dbtx storage.DBTX, cbl *block.Complete_Block) (result bool) {
   389  	for i := 0; i < len(cbl.Txs); i++ {
   390  		if !chain.Verify_Transaction_NonCoinbase_DoubleSpend_Check(dbtx, cbl.Txs[i]) {
   391  			return false
   392  		}
   393  	}
   394  
   395  	return true
   396  }