/** * 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); }