/**
  * Serialize the transaction
  *
  * @param outBuffer Output buffer
  * @return Output buffer
  */
 @Override
 public final SerializedBuffer getBytes(SerializedBuffer outBuffer) {
   if (txData != null) {
     outBuffer.putBytes(txData);
   } else {
     outBuffer
         .putInt(txVersion)
         .putVarInt(txInputs.size())
         .putBytes(txInputs)
         .putVarInt(txOutputs.size())
         .putBytes(txOutputs)
         .putUnsignedInt(txLockTime);
   }
   return outBuffer;
 }
 /**
  * Build a 'getblocks' message
  *
  * @param peer Destination peer
  * @param blockList Block hash list
  * @param stopBlock Stop block hash (Sha256Hash.ZERO_HASH to get all blocks)
  * @return 'getblocks' message
  */
 public static Message buildGetBlocksMessage(
     Peer peer, List<Sha256Hash> blockList, Sha256Hash stopBlock) {
   //
   // Build the message payload
   //
   // The protocol version will be set to the lesser of our version and the peer version
   //
   SerializedBuffer msgBuffer = new SerializedBuffer(blockList.size() * 32 + 40);
   msgBuffer
       .putInt(Math.min(peer.getVersion(), NetParams.PROTOCOL_VERSION))
       .putVarInt(blockList.size());
   for (Sha256Hash hash : blockList) {
     msgBuffer.putBytes(Helper.reverseBytes(hash.getBytes()));
   }
   msgBuffer.putBytes(Helper.reverseBytes(stopBlock.getBytes()));
   //
   // Build the message
   //
   ByteBuffer buffer = MessageHeader.buildMessage("getblocks", msgBuffer);
   return new Message(buffer, peer, MessageHeader.MessageCommand.GETBLOCKS);
 }
 /**
  * Process the 'getblocks' message and return an 'inv' message
  *
  * @param msg Message
  * @param inBuffer Input buffer
  * @param msgListener Message listener
  * @throws EOFException End-of-data processing stream
  * @throws VerificationException Message verification failed
  */
 public static void processGetBlocksMessage(
     Message msg, SerializedBuffer inBuffer, MessageListener msgListener)
     throws EOFException, VerificationException {
   //
   // Process the message
   //
   int version = inBuffer.getInt();
   if (version < NetParams.MIN_PROTOCOL_VERSION)
     throw new VerificationException(
         String.format("Protocol version %d is not supported", version));
   int count = inBuffer.getVarInt();
   if (count < 0 || count > 500)
     throw new VerificationException("More than 500 locator entries in 'getblocks' message");
   List<Sha256Hash> blockList = new ArrayList<>(count);
   for (int i = 0; i < count; i++)
     blockList.add(new Sha256Hash(Helper.reverseBytes(inBuffer.getBytes(32))));
   Sha256Hash stopBlock = new Sha256Hash(Helper.reverseBytes(inBuffer.getBytes(32)));
   //
   // Notify the message listener
   //
   msgListener.processGetBlocks(msg, version, blockList, stopBlock);
 }
 /**
  * Creates a new transaction using the provided inputs
  *
  * @param inputs List of signed inputs
  * @param outputs List of outputs
  * @throws ECException Unable to sign transaction
  * @throws ScriptException Script processing error
  * @throws VerificationException Transaction verification failure
  */
 public Transaction(List<SignedInput> inputs, List<TransactionOutput> outputs)
     throws ECException, ScriptException, VerificationException {
   SerializedBuffer outBuffer = new SerializedBuffer(1024);
   txVersion = 1;
   txOutputs = outputs;
   txLockTime = 0;
   coinBase = false;
   //
   // Create the transaction inputs
   //
   txInputs = new ArrayList<>(inputs.size());
   for (int i = 0; i < inputs.size(); i++)
     txInputs.add(new TransactionInput(this, i, inputs.get(i).getOutPoint()));
   //
   // Now sign each input and create the input scripts
   //
   for (int i = 0; i < inputs.size(); i++) {
     SignedInput input = inputs.get(i);
     ECKey key = input.getKey();
     byte[] contents;
     //
     // Serialize the transaction for signing using the SIGHASH_ALL hash type
     //
     outBuffer.rewind();
     serializeForSignature(i, ScriptOpCodes.SIGHASH_ALL, input.getScriptBytes(), outBuffer);
     outBuffer.putInt(ScriptOpCodes.SIGHASH_ALL);
     contents = outBuffer.toByteArray();
     //
     // Create the DER-encoded signature
     //
     ECDSASignature sig = key.createSignature(contents);
     byte[] encodedSig = sig.encodeToDER();
     //
     // Create the input script using the SIGHASH_ALL hash type
     //   <sig> <pubKey>
     //
     byte[] pubKey = key.getPubKey();
     byte[] scriptBytes = new byte[1 + encodedSig.length + 1 + 1 + pubKey.length];
     scriptBytes[0] = (byte) (encodedSig.length + 1);
     System.arraycopy(encodedSig, 0, scriptBytes, 1, encodedSig.length);
     int offset = encodedSig.length + 1;
     scriptBytes[offset++] = (byte) ScriptOpCodes.SIGHASH_ALL;
     scriptBytes[offset++] = (byte) pubKey.length;
     System.arraycopy(pubKey, 0, scriptBytes, offset, pubKey.length);
     txInputs.get(i).setScriptBytes(scriptBytes);
   }
   //
   // Serialize the entire transaction
   //
   outBuffer.rewind();
   getBytes(outBuffer);
   txData = outBuffer.toByteArray();
   //
   // 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)));
 }
 /**
  * Serializes the transaction for use in a signature
  *
  * @param index Current transaction index
  * @param sigHashType Signature hash type
  * @param subScriptBytes Replacement script for the current input
  * @param outBuffer Output buffer
  * @throws ScriptException Transaction index out-of-range
  */
 public final void serializeForSignature(
     int index, int sigHashType, byte[] subScriptBytes, SerializedBuffer outBuffer)
     throws ScriptException {
   int hashType;
   boolean anyoneCanPay;
   //
   // The transaction input must be within range
   //
   if (index < 0 || index >= txInputs.size())
     throw new ScriptException("Transaction input index is not valid");
   //
   // Check for a valid hash type
   //
   // Note that SIGHASH_ANYONE_CAN_PAY is or'ed with one of the other hash types.  So we need
   // to remove it when checking for a valid signature.
   //
   // SIGHASH_ALL:    This is the default. It indicates that everything about the transaction is
   // signed
   //                 except for the input scripts. Signing the input scripts as well would
   // obviously make
   //                 it impossible to construct a transaction.
   // SIGHASH_NONE:   The outputs are not signed and can be anything. This mode allows others to
   // update
   //                 the transaction by changing their inputs sequence numbers.  This means that
   // all
   //                 input sequence numbers are set to 0 except for the current input.
   // SIGHASH_SINGLE: Outputs up to and including the current input index number are included.
   // Outputs
   //                 before the current index have a -1 value and an empty script.  All input
   // sequence
   //                 numbers are set to 0 except for the current input.
   //
   // The SIGHASH_ANYONE_CAN_PAY modifier can be combined with the above three modes. When set,
   // only that
   // input is signed and the other inputs can be anything.
   //
   // In all cases, the script for the current input is replaced with the script from the connected
   // output.  All other input scripts are set to an empty script.
   //
   // The reference client accepts an invalid hash types and treats it as SIGHASH_ALL.  So we need
   // to
   // do the same.
   //
   anyoneCanPay = ((sigHashType & ScriptOpCodes.SIGHASH_ANYONE_CAN_PAY) != 0);
   hashType = sigHashType & (255 - ScriptOpCodes.SIGHASH_ANYONE_CAN_PAY);
   if (hashType != ScriptOpCodes.SIGHASH_ALL
       && hashType != ScriptOpCodes.SIGHASH_NONE
       && hashType != ScriptOpCodes.SIGHASH_SINGLE) hashType = ScriptOpCodes.SIGHASH_ALL;
   //
   // Serialize the version
   //
   outBuffer.putInt(txVersion);
   //
   // Serialize the inputs
   //
   // For SIGHASH_ANYONE_CAN_PAY, only the current input is included in the signature.
   // Otherwise, all inputs are included.
   //
   List<TransactionInput> sigInputs;
   if (anyoneCanPay) {
     sigInputs = new ArrayList<>(1);
     sigInputs.add(txInputs.get(index));
   } else {
     sigInputs = txInputs;
   }
   outBuffer.putVarInt(sigInputs.size());
   byte[] emptyScriptBytes = new byte[0];
   for (TransactionInput txInput : sigInputs)
     txInput.serializeForSignature(
         index,
         hashType,
         (txInput.getIndex() == index ? subScriptBytes : emptyScriptBytes),
         outBuffer);
   //
   // Serialize the outputs
   //
   if (hashType == ScriptOpCodes.SIGHASH_NONE) {
     //
     // There are no outputs for SIGHASH_NONE
     //
     outBuffer.putVarInt(0);
   } else if (hashType == ScriptOpCodes.SIGHASH_SINGLE) {
     //
     // The output list is resized to the input index+1
     //
     if (txOutputs.size() <= index)
       throw new ScriptException("Input index out-of-range for SIGHASH_SINGLE");
     outBuffer.putVarInt(index + 1);
     for (TransactionOutput txOutput : txOutputs) {
       if (txOutput.getIndex() > index) break;
       txOutput.serializeForSignature(index, hashType, outBuffer);
     }
   } else {
     //
     // All outputs are serialized for SIGHASH_ALL
     //
     outBuffer.putVarInt(txOutputs.size());
     for (TransactionOutput txOutput : txOutputs)
       txOutput.serializeForSignature(index, hashType, outBuffer);
   }
   //
   // Serialize the lock time
   //
   outBuffer.putUnsignedInt(txLockTime);
 }
 /**
  * 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);
 }