/**
  * Verify the transaction structure as follows
  *
  * <ul>
  *   <li>A transaction must have at least one input and one output
  *   <li>A transaction output may not specify a negative number of coins
  *   <li>The sum of all of the output amounts must not exceed 21,000,000 BTC
  *   <li>A non-coinbase transaction may not contain any unconnected inputs
  *   <li>A connected output may not be used by more than one input
  *   <li>The input script must contain only push-data operations
  * </ul>
  *
  * @param canonical TRUE to enforce canonical transactions
  * @throws VerificationException Script verification failed
  */
 public void verify(boolean canonical) throws VerificationException {
   try {
     // Must have at least one input and one output
     if (txInputs.isEmpty() || txOutputs.isEmpty())
       throw new VerificationException(
           "Transaction does not have at least 1 input and 1 output",
           RejectMessage.REJECT_INVALID,
           txHash);
     // No output value may be negative
     // Sum of all output values must not exceed MAX_MONEY
     BigInteger outTotal = BigInteger.ZERO;
     for (TransactionOutput txOut : txOutputs) {
       BigInteger outValue = txOut.getValue();
       if (outValue.signum() < 0)
         throw new VerificationException(
             "Transaction output value is negative", RejectMessage.REJECT_INVALID, txHash);
       outTotal = outTotal.add(outValue);
       if (outTotal.compareTo(NetParams.MAX_MONEY) > 0)
         throw new VerificationException(
             "Total transaction output amount exceeds maximum",
             RejectMessage.REJECT_INVALID,
             txHash);
       byte[] scriptBytes = txOut.getScriptBytes();
     }
     if (!coinBase) {
       // All inputs must have connected outputs
       // No outpoint may be used more than once
       // Input scripts must consist of only push-data operations
       List<OutPoint> outPoints = new ArrayList<>(txInputs.size());
       for (TransactionInput txIn : txInputs) {
         OutPoint outPoint = txIn.getOutPoint();
         if (outPoint.getHash().equals(Sha256Hash.ZERO_HASH) || outPoint.getIndex() < 0)
           throw new VerificationException(
               "Non-coinbase transaction contains unconnected inputs",
               RejectMessage.REJECT_INVALID,
               txHash);
         if (outPoints.contains(outPoint))
           throw new VerificationException(
               "Connected output used in multiple inputs", RejectMessage.REJECT_INVALID, txHash);
         outPoints.add(outPoint);
         if (canonical) {
           if (!Script.checkInputScript(txIn.getScriptBytes()))
             throw new VerificationException(
                 "Input script must contain only canonical push-data operations",
                 RejectMessage.REJECT_NONSTANDARD,
                 txHash);
         }
       }
     }
   } catch (EOFException exc) {
     throw new VerificationException(
         "End-of-data while processing script", RejectMessage.REJECT_MALFORMED, txHash);
   }
 }
 /**
  * Creates a new transaction from the serialized data in the byte stream
  *
  * @param inBuffer Serialized buffer
  * @throws EOFException Byte stream is too short
  * @throws VerificationException Verification error
  */
 public Transaction(SerializedBuffer inBuffer) throws EOFException, VerificationException {
   //
   // Mark our current position within the input stream
   //
   int segmentStart = inBuffer.getSegmentStart();
   inBuffer.setSegmentStart();
   //
   // Get the transaction version
   //
   txVersion = inBuffer.getInt();
   //
   // Get the transaction inputs
   //
   int inCount = inBuffer.getVarInt();
   if (inCount < 0) throw new VerificationException("Transaction input count is negative");
   txInputs = new ArrayList<>(Math.max(inCount, 1));
   for (int i = 0; i < inCount; i++) txInputs.add(new TransactionInput(this, i, inBuffer));
   //
   // A coinbase transaction has a single unconnected input with a transaction hash of zero
   // and an output index of -1
   //
   if (txInputs.size() == 1) {
     OutPoint outPoint = txInputs.get(0).getOutPoint();
     coinBase = (outPoint.getHash().equals(Sha256Hash.ZERO_HASH) && outPoint.getIndex() == -1);
   } else {
     coinBase = false;
   }
   //
   // Get the transaction outputs
   //
   int outCount = inBuffer.getVarInt();
   if (outCount < 0) throw new EOFException("Transaction output count is negative");
   txOutputs = new ArrayList<>(Math.max(outCount, 1));
   for (int i = 0; i < outCount; i++) txOutputs.add(new TransactionOutput(i, inBuffer));
   //
   // Get the transaction lock time
   //
   txLockTime = inBuffer.getUnsignedInt();
   //
   // Save a copy of the serialized transaction
   //
   txData = inBuffer.getSegmentBytes();
   //
   // Calculate the transaction hash using the serialized data
   //
   txHash = new Sha256Hash(Utils.reverseBytes(Utils.doubleDigest(txData)));
   //
   // Calculate the normalized transaction ID
   //
   List<byte[]> bufferList = new ArrayList<>(txInputs.size() + txOutputs.size());
   txInputs
       .stream()
       .forEach(
           (txInput) -> {
             bufferList.add(txInput.getOutPoint().getBytes());
           });
   txOutputs
       .stream()
       .forEach(
           (txOutput) -> {
             bufferList.add(txOutput.getBytes());
           });
   normID = new Sha256Hash(Utils.reverseBytes(Utils.doubleDigest(bufferList)));
   //
   // Restore the previous segment (if any)
   //
   inBuffer.setSegmentStart(segmentStart);
 }