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