/** * Calculates a PMT given the list of leaf hashes and which leaves need to be included. The relevant interior hashes * are calculated and a new PMT returned. */ public static PartialMerkleTree buildFromLeaves(NetworkParameters params, byte[] includeBits, List<Sha256Hash> allLeafHashes) { // Calculate height of the tree. int height = 0; while (getTreeWidth(allLeafHashes.size(), height) > 1) height++; List<Boolean> bitList = new ArrayList<>(); List<Sha256Hash> hashes = new ArrayList<>(); traverseAndBuild(height, 0, allLeafHashes, includeBits, bitList, hashes); byte[] bits = new byte[(int)Math.ceil(bitList.size() / 8.0)]; for (int i = 0; i < bitList.size(); i++) if (bitList.get(i)) Utils.setBitLE(bits, i); return new PartialMerkleTree(params, bits, hashes, allLeafHashes.size()); }
private static Sha256Hash calcHash(int height, int pos, List<Sha256Hash> hashes) { if (height == 0) { // Hash at height 0 is just the regular tx hash itself. return hashes.get(pos); } int h = height - 1; int p = pos * 2; Sha256Hash left = calcHash(h, p, hashes); // Calculate right hash if not beyond the end of the array - copy left hash otherwise. Sha256Hash right; if (p + 1 < getTreeWidth(hashes.size(), h)) { right = calcHash(h, p + 1, hashes); } else { right = left; } return combineLeftRight(left.getBytes(), right.getBytes()); }
@Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; FilteredBlock other = (FilteredBlock) o; return associatedTransactions.equals(other.associatedTransactions) && header.equals(other.header) && merkleTree.equals(other.merkleTree); }
private static void traverseAndBuild(int height, int pos, List<Sha256Hash> allLeafHashes, byte[] includeBits, List<Boolean> matchedChildBits, List<Sha256Hash> resultHashes) { boolean parentOfMatch = false; // Is this node a parent of at least one matched hash? for (int p = pos << height; p < (pos+1) << height && p < allLeafHashes.size(); p++) { if (Utils.checkBitLE(includeBits, p)) { parentOfMatch = true; break; } } // Store as a flag bit. matchedChildBits.add(parentOfMatch); if (height == 0 || !parentOfMatch) { // If at height 0, or nothing interesting below, store hash and stop. resultHashes.add(calcHash(height, pos, allLeafHashes)); } else { // Otherwise descend into the subtrees. int h = height - 1; int p = pos * 2; traverseAndBuild(h, p, allLeafHashes, includeBits, matchedChildBits, resultHashes); if (p + 1 < getTreeWidth(allLeafHashes.size(), h)) traverseAndBuild(h, p + 1, allLeafHashes, includeBits, matchedChildBits, resultHashes); } }
@Override protected void parse() throws ProtocolException { byte[] headerBytes = new byte[Block.HEADER_SIZE]; System.arraycopy(payload, 0, headerBytes, 0, Block.HEADER_SIZE); header = params.getDefaultSerializer().makeBlock(headerBytes); merkleTree = new PartialMerkleTree(params, payload, Block.HEADER_SIZE); length = Block.HEADER_SIZE + merkleTree.getMessageSize(); }
private Sha256Hash recursiveExtractHashes(int height, int pos, ValuesUsed used, List<Sha256Hash> matchedHashes) throws VerificationException { if (used.bitsUsed >= matchedChildBits.length*8) { // overflowed the bits array - failure throw new VerificationException("PartialMerkleTree overflowed its bits array"); } boolean parentOfMatch = checkBitLE(matchedChildBits, used.bitsUsed++); if (height == 0 || !parentOfMatch) { // if at height 0, or nothing interesting below, use stored hash and do not descend if (used.hashesUsed >= hashes.size()) { // overflowed the hash array - failure throw new VerificationException("PartialMerkleTree overflowed its hash array"); } Sha256Hash hash = hashes.get(used.hashesUsed++); if (height == 0 && parentOfMatch) // in case of height 0, we have a matched txid matchedHashes.add(hash); return hash; } else { // otherwise, descend into the subtrees to extract matched txids and hashes byte[] left = recursiveExtractHashes(height - 1, pos * 2, used, matchedHashes).getBytes(), right; if (pos * 2 + 1 < getTreeWidth(transactionCount, height-1)) { right = recursiveExtractHashes(height - 1, pos * 2 + 1, used, matchedHashes).getBytes(); if (Arrays.equals(right, left)) throw new VerificationException("Invalid merkle tree with duplicated left/right branches"); } else { right = left; } // and combine them before returning return combineLeftRight(left, right); } }
while (getTreeWidth(transactionCount, height) > 1) height++; Sha256Hash merkleRoot = recursiveExtractHashes(height, 0, used, matchedHashesOut);
@Test(expected = VerificationException.class) public void merkleTreeMalleability() throws Exception { List<Sha256Hash> hashes = Lists.newArrayList(); for (byte i = 1; i <= 10; i++) hashes.add(numAsHash(i)); hashes.add(numAsHash(9)); hashes.add(numAsHash(10)); byte[] includeBits = new byte[2]; Utils.setBitLE(includeBits, 9); Utils.setBitLE(includeBits, 10); PartialMerkleTree pmt = PartialMerkleTree.buildFromLeaves(PARAMS, includeBits, hashes); List<Sha256Hash> matchedHashes = Lists.newArrayList(); pmt.getTxnHashAndMerkleRoot(matchedHashes); }
/** * Gets a list of leaf hashes which are contained in the partial merkle tree in this filtered block * * @throws ProtocolException If the partial merkle block is invalid or the merkle root of the partial merkle block doesnt match the block header */ public AbstractMap.SimpleEntry<List<Sha256Hash>, List<Integer>> getTransactionHashesAndIndexes() throws VerificationException { if (cachedTransactionHashes != null && cachedTransactionPositions != null) return new AbstractMap.SimpleEntry<List<Sha256Hash>, List<Integer>>(Collections.unmodifiableList(cachedTransactionHashes), Collections.unmodifiableList(cachedTransactionPositions)); List<Sha256Hash> hashesMatched = new LinkedList<>(); List<Integer> positions = new LinkedList<>(); if (header.getMerkleRoot().equals(merkleTree.getTxnHashAndMerkleRoot(hashesMatched, positions))) { cachedTransactionHashes = hashesMatched; cachedTransactionPositions = positions; return new AbstractMap.SimpleEntry<>(Collections.unmodifiableList(cachedTransactionHashes), Collections.unmodifiableList(cachedTransactionPositions)); } else throw new VerificationException("Merkle root of block header does not match merkle root of partial merkle tree."); }
/** * Creates a new FilteredBlock from the given Block, using this filter to select transactions. Matches can cause the * filter to be updated with the matched element, this ensures that when a filter is applied to a block, spends of * matched transactions are also matched. However it means this filter can be mutated by the operation. The returned * filtered block already has the matched transactions associated with it. */ public synchronized FilteredBlock applyAndUpdate(Block block) { List<Transaction> txns = block.getTransactions(); List<Sha256Hash> txHashes = new ArrayList<>(txns.size()); List<Transaction> matched = Lists.newArrayList(); byte[] bits = new byte[(int) Math.ceil(txns.size() / 8.0)]; for (int i = 0; i < txns.size(); i++) { Transaction tx = txns.get(i); txHashes.add(tx.getHash()); if (applyAndUpdate(tx)) { Utils.setBitLE(bits, i); matched.add(tx); } } PartialMerkleTree pmt = PartialMerkleTree.buildFromLeaves(block.getParams(), bits, txHashes); FilteredBlock filteredBlock = new FilteredBlock(block.getParams(), block.cloneAsHeader(), pmt); for (Transaction transaction : matched) filteredBlock.provideTransaction(transaction); return filteredBlock; }
@Override public void bitcoinSerializeToStream(OutputStream stream) throws IOException { if (header.transactions == null) header.bitcoinSerializeToStream(stream); else header.cloneAsHeader().bitcoinSerializeToStream(stream); merkleTree.bitcoinSerializeToStream(stream); }
/** Number of transactions in this block, before it was filtered */ public int getTransactionCount() { return merkleTree.getTransactionCount(); }
@Override public FilteredBlock makeFilteredBlock(byte[] payloadBytes) throws ProtocolException { long blockVersion = Utils.readUint32(payloadBytes, 0); int headerSize = Block.HEADER_SIZE; byte[] headerBytes = new byte[Block.HEADER_SIZE + 1]; System.arraycopy(payloadBytes, 0, headerBytes, 0, headerSize); headerBytes[80] = 0; // Need to provide 0 transactions so the block header can be constructed if (this.getParameters() instanceof AuxPoWNetworkParameters) { final AuxPoWNetworkParameters auxPoWParams = (AuxPoWNetworkParameters) this.getParameters(); if (auxPoWParams.isAuxPoWBlockVersion(blockVersion)) { final AltcoinBlock header = (AltcoinBlock) makeBlock(headerBytes, 0, Message.UNKNOWN_LENGTH); final AuxPoW auxpow = new AuxPoW(this.getParameters(), payloadBytes, Block.HEADER_SIZE, null, this); header.setAuxPoW(auxpow); int pmtOffset = headerSize + auxpow.getMessageSize(); int pmtLength = payloadBytes.length - pmtOffset; byte[] pmtBytes = new byte[pmtLength]; System.arraycopy(payloadBytes, pmtOffset, pmtBytes, 0, pmtLength); PartialMerkleTree pmt = new PartialMerkleTree(this.getParameters(), pmtBytes, 0); return new FilteredBlock(this.getParameters(), header, pmt); } } // We are either not in AuxPoW mode, or the block is not an AuxPoW block. return super.makeFilteredBlock(payloadBytes); } }
private static void traverseAndBuild(int height, int pos, List<Sha256Hash> allLeafHashes, byte[] includeBits, List<Boolean> matchedChildBits, List<Sha256Hash> resultHashes) { boolean parentOfMatch = false; // Is this node a parent of at least one matched hash? for (int p = pos << height; p < (pos+1) << height && p < allLeafHashes.size(); p++) { if (Utils.checkBitLE(includeBits, p)) { parentOfMatch = true; break; } } // Store as a flag bit. matchedChildBits.add(parentOfMatch); if (height == 0 || !parentOfMatch) { // If at height 0, or nothing interesting below, store hash and stop. resultHashes.add(calcHash(height, pos, allLeafHashes)); } else { // Otherwise descend into the subtrees. int h = height - 1; int p = pos * 2; traverseAndBuild(h, p, allLeafHashes, includeBits, matchedChildBits, resultHashes); if (p + 1 < getTreeWidth(allLeafHashes.size(), h)) traverseAndBuild(h, p + 1, allLeafHashes, includeBits, matchedChildBits, resultHashes); } }
private Sha256Hash recursiveExtractHashes(int height, int pos, ValuesUsed used, List<Sha256Hash> matchedHashes) throws VerificationException { if (used.bitsUsed >= matchedChildBits.length*8) { // overflowed the bits array - failure throw new VerificationException("PartialMerkleTree overflowed its bits array"); } boolean parentOfMatch = checkBitLE(matchedChildBits, used.bitsUsed++); if (height == 0 || !parentOfMatch) { // if at height 0, or nothing interesting below, use stored hash and do not descend if (used.hashesUsed >= hashes.size()) { // overflowed the hash array - failure throw new VerificationException("PartialMerkleTree overflowed its hash array"); } Sha256Hash hash = hashes.get(used.hashesUsed++); if (height == 0 && parentOfMatch) // in case of height 0, we have a matched txid matchedHashes.add(hash); return hash; } else { // otherwise, descend into the subtrees to extract matched txids and hashes byte[] left = recursiveExtractHashes(height - 1, pos * 2, used, matchedHashes).getBytes(), right; if (pos * 2 + 1 < getTreeWidth(transactionCount, height-1)) { right = recursiveExtractHashes(height - 1, pos * 2 + 1, used, matchedHashes).getBytes(); if (Arrays.equals(right, left)) throw new VerificationException("Invalid merkle tree with duplicated left/right branches"); } else { right = left; } // and combine them before returning return combineLeftRight(left, right); } }
@Override protected void parse() throws ProtocolException { byte[] headerBytes = new byte[Block.HEADER_SIZE]; System.arraycopy(payload, 0, headerBytes, 0, Block.HEADER_SIZE); header = params.getDefaultSerializer().makeBlock(headerBytes); merkleTree = new PartialMerkleTree(params, payload, Block.HEADER_SIZE); length = Block.HEADER_SIZE + merkleTree.getMessageSize(); }
while (getTreeWidth(transactionCount, height) > 1) height++; Sha256Hash merkleRoot = recursiveExtractHashes(height, 0, used, matchedHashesOut);
/** * Gets a list of leaf hashes which are contained in the partial merkle tree in this filtered block * * @throws ProtocolException If the partial merkle block is invalid or the merkle root of the partial merkle block doesnt match the block header */ public List<Sha256Hash> getTransactionHashes() throws VerificationException { if (cachedTransactionHashes != null) return Collections.unmodifiableList(cachedTransactionHashes); List<Sha256Hash> hashesMatched = new LinkedList<>(); if (header.getMerkleRoot().equals(merkleTree.getTxnHashAndMerkleRoot(hashesMatched))) { cachedTransactionHashes = hashesMatched; return Collections.unmodifiableList(cachedTransactionHashes); } else throw new VerificationException("Merkle root of block header does not match merkle root of partial merkle tree."); }
/** * Creates a new FilteredBlock from the given Block, using this filter to select transactions. Matches can cause the * filter to be updated with the matched element, this ensures that when a filter is applied to a block, spends of * matched transactions are also matched. However it means this filter can be mutated by the operation. The returned * filtered block already has the matched transactions associated with it. */ public synchronized FilteredBlock applyAndUpdate(Block block) { List<Transaction> txns = block.getTransactions(); List<Sha256Hash> txHashes = new ArrayList<Sha256Hash>(txns.size()); List<Transaction> matched = Lists.newArrayList(); byte[] bits = new byte[(int) Math.ceil(txns.size() / 8.0)]; for (int i = 0; i < txns.size(); i++) { Transaction tx = txns.get(i); txHashes.add(tx.getHash()); if (applyAndUpdate(tx)) { Utils.setBitLE(bits, i); matched.add(tx); } } PartialMerkleTree pmt = PartialMerkleTree.buildFromLeaves(block.getParams(), bits, txHashes); FilteredBlock filteredBlock = new FilteredBlock(block.getParams(), block.cloneAsHeader(), pmt); for (Transaction transaction : matched) filteredBlock.provideTransaction(transaction); return filteredBlock; }
@Override public void bitcoinSerializeToStream(OutputStream stream) throws IOException { if (header.transactions == null) header.bitcoinSerializeToStream(stream); else header.cloneAsHeader().bitcoinSerializeToStream(stream); merkleTree.bitcoinSerializeToStream(stream); }