org.ccnx.ccn.impl.security.crypto.MerkleTree.java Source code

Java tutorial

Introduction

Here is the source code for org.ccnx.ccn.impl.security.crypto.MerkleTree.java

Source

/*
 * Part of the CCNx Java Library.
 *
 * Copyright (C) 2008, 2009, 2011 Palo Alto Research Center, Inc.
 *
 * This library is free software; you can redistribute it and/or modify it
 * under the terms of the GNU Lesser General Public License version 2.1
 * as published by the Free Software Foundation. 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details. You should have received
 * a copy of the GNU Lesser General Public License along with this library;
 * if not, write to the Free Software Foundation, Inc., 51 Franklin Street,
 * Fifth Floor, Boston, MA 02110-1301 USA.
 */

package org.ccnx.ccn.impl.security.crypto;

import java.security.NoSuchAlgorithmException;

import org.bouncycastle.asn1.DEROctetString;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.ccnx.ccn.impl.support.Log;

/**
 * Implementation of a Merkle hash tree. 
 * 
 * Representation based on Knuth, Vol 1, section 2.3.4.5. We represent
 * trees as a special sublcass of extended binary
 * trees, where empty subtrees are only present in one end
 * of the tree.
 * 
 * Tree nodes are numbered starting with 1, which is the
 * root. 
 * 
 * Tree nodes are stored in an array, with node i stored at index
 * i-1 into the array.
 * 
 * Incomplete binary trees are represented as multi-level extended
 * binary trees -- lower-numbered leaves are represented in the
 * upper half of the tree, in a layer one closer to the root than
 * leaves in the complete subtree.
 * 
 * Total number of nodes in the tree = 2n + 1, where n is the number of leaves.
 * 
 * Taken in terms of node indices (where root == 1), the parent
 * of node k is node floor(k/2), and the children of node k are
 * nodes 2k and 2k+1. Leaves are numbered from node n+1 through 
 * 2n+1, where n is the number of leaves.
 * 
 * The sibling index of node k is (k xor 1).
 * 
 * Should we want to get fancy, we could have t-ary trees; the
 * construction above works for tree with internal nodes (non-leaves)
 * {1,2,...,n}.
 * 
 * The parent of node k is the node floor((k+t-2)/t) = ceil((k-1)/t).
 * The children of node k are:
 * t(k-1)+2, t(k-1)+3,..., tk+1
 * 
 * In the methods below, we refer to nodes as having a "nodeIndex" -- their
 * 1-based index into the node array as described above. Leaf nodes also have
 * a "leafIndex" -- their index into the set of n leaves. Convenience
 * methods are provided to convert between the two.
 * 
 * Store node digests internally as DEROctetStrings for more efficient
 * encoding. 
 */
public class MerkleTree {

    /**
     * Node index of 1 (array index of 0).
     */
    protected static final int ROOT_NODE = 1;

    protected DEROctetString[] _tree;
    protected int _numLeaves;
    protected String _digestAlgorithm;

    /**
     * The OID prefix we use to represent Merkle trees. Derived from PARC-s sub-arc of Xerox's OID.
     */
    protected static final String MERKLE_OID_PREFIX = "1.2.840.113550.11.1.2";

    /**
     * Build a MerkleTree. This initializes the tree with content, builds the leaf
     * and intermediate digests, and derives the root digest.
     * @param digestAlgorithm the digest algorithm to use for computing leaf and
     *   interior node digests of this tree
     * @param contentBlocks the segmented leaf content to be hashed into this 
     * Merkle hash tree. One block per leaf.
     * @param isDigest are the content blocks raw content (false), or are they already digested
     *      with digestAlgorithm? (default algorithm: CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM)
     * @param blockCount the number of those blocks to include (e.g. we may not
     *    have filled our contentBlocks buffers prior to building the tree). Must be
     *  at least 2.
     * @param baseBlockIndex the offset into the contentBlocks array at which to start.
     * @param lastBlockLength the number of bytes of the last block to use
     * @throws NoSuchAlgorithmException 
     */
    public MerkleTree(String digestAlgorithm, byte contentBlocks[][], boolean isDigest, int blockCount,
            int baseBlockIndex, int lastBlockLength) throws NoSuchAlgorithmException {
        this(digestAlgorithm, blockCount);
        initializeTree(contentBlocks, isDigest, baseBlockIndex, lastBlockLength);
    }

    /**
     * Segment content and build a MerkleTree. This initializes the tree with content, builds the leaf
     * and intermediate digests, and derives the root digest. Uses CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM.
     * @param content the content to segment into leaves and hash into this 
     * Merkle hash tree. One blockWidth of content per leaf, except for the last leaf which may
     * be shorter.
     * @param offset offset into content at which to start processing data.
     * @param length number of bytes of content to process
     * @param blockWidth the length of leaf blocks to create
     */
    public MerkleTree(byte[] content, int offset, int length, int blockWidth) {
        this(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, blockCount(length, blockWidth));
        try {
            initializeTree(content, offset, length, blockWidth);
        } catch (NoSuchAlgorithmException e) {
            // DKS --big configuration problem
            Log.warning("Fatal Error: cannot find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM);
            throw new RuntimeException("Error: can't find default algorithm "
                    + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM + "!  " + e.toString());
        }
    }

    /**
     * Segment content and build a MerkleTree. This initializes the tree with content, builds the leaf
     * and intermediate digests, and derives the root digest. 
     * @param digestAlgorithm the digest algorithm to use for computing leaf and
     *   interior node digests of this tree
     * @param content the content to segment into leaves and hash into this 
     * Merkle hash tree. One blockWidth of content per leaf, except for the last leaf which may
     * be shorter.
     * @param offset offset into content at which to start processing data.
     * @param length number of bytes of content to process
     * @param blockWidth the length of leaf blocks to create
     */
    public MerkleTree(String digestAlgorithm, byte[] content, int offset, int length, int blockWidth)
            throws NoSuchAlgorithmException {
        this(digestAlgorithm, blockCount(length, blockWidth));
        initializeTree(content, offset, length, blockWidth);
    }

    /**
     * Build a MerkleTree. This initializes the tree with content, builds the leaf
     * and intermediate digests, and derives the root digest. Uses CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM.
     * @param contentBlocks the segmented leaf content to be hashed into this 
     * Merkle hash tree. One block per leaf.
     * @param isDigest are the content blocks raw content (false), or are they already digested
     *      with digestAlgorithm? (default algorithm: CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM)
     * @param blockCount the number of those blocks to include (e.g. we may not
     *    have filled our contentBlocks buffers prior to building the tree). Must be
     *  at least 2.
     * @param baseBlockIndex the offset into the contentBlocks array at which to start.
     * @param lastBlockLength the amount of the last block to use
     * @throws NoSuchAlgorithmException 
     */
    public MerkleTree(byte contentBlocks[][], boolean isDigest, int blockCount, int baseBlockIndex,
            int lastBlockLength) {
        this(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, blockCount);
        try {
            initializeTree(contentBlocks, isDigest, baseBlockIndex, lastBlockLength);
        } catch (NoSuchAlgorithmException e) {
            // DKS --big configuration problem
            Log.warning("Fatal Error: cannot find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM);
            throw new RuntimeException("Error: can't find default algorithm "
                    + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM + "!  " + e.toString());
        }
    }

    /**
     * Subclass constructor.
     * @param digestAlgorithm digest algorithm to use. If null, use CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM.
     * @param numLeaves the number of leaf nodes to reserve space for
     */
    protected MerkleTree(String digestAlgorithm, int numLeaves) {
        if (numLeaves < 2) {
            throw new IllegalArgumentException("MerkleTrees must have 2 or more nodes!");
        }
        _digestAlgorithm = (null == digestAlgorithm) ? CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM : digestAlgorithm;
        _numLeaves = numLeaves;
        _tree = new DEROctetString[nodeCount()];
        // Let calling constructor handle building the tree.
    }

    /**
     * Method called by constructors to fill leaf nodes with digests and compute intermediate
     * node values up the tree. Does its work by calling computeLeafValues(byte [][], boolean, int, int)
     * and computeNodeValues().
     * Separate this out to allow subclasses to initialize members before
     * building tree.
     * @throws NoSuchAlgorithmException if the digestAlgorithm specified for this tree is unknown
     */
    protected void initializeTree(byte contentBlocks[][], boolean isDigest, int baseBlockIndex, int lastBlockLength)
            throws NoSuchAlgorithmException {
        if ((baseBlockIndex < 0) || (contentBlocks.length - baseBlockIndex < numLeaves()))
            throw new IllegalArgumentException("MerkleTree: cannot build tree from more blocks than given! Have "
                    + (contentBlocks.length - baseBlockIndex) + " blocks, asked to use: " + (numLeaves()));

        computeLeafValues(contentBlocks, isDigest, baseBlockIndex, lastBlockLength);
        computeNodeValues();
    }

    /**
     * Method called by constructors to fill leaf nodes with digests and compute intermediate
     * node values up the tree. Does its work by calling computeLeafValues(byte [], int, int, int)
     * and computeNodeValues().
     * Separate this out to allow subclasses to initialize members before
     * building tree.
     * @throws NoSuchAlgorithmException if the digestAlgorithm specified for this tree is unknown
     */
    protected void initializeTree(byte[] content, int offset, int length, int blockWidth)
            throws NoSuchAlgorithmException {
        if ((offset < 0) || (length > content.length) || (blockCount(length, blockWidth) < numLeaves()))
            throw new IllegalArgumentException("MerkleTree: cannot build tree from more blocks than given! Have "
                    + blockCount(length, blockWidth) + " blocks, asked to use: " + (numLeaves()));

        computeLeafValues(content, offset, length, blockWidth);
        computeNodeValues();
    }

    /**
     * Returns the digest algorithm used by this tree.
     * @return the digest algorithm used
     */
    public String digestAlgorithm() {
        return _digestAlgorithm;
    }

    /**
     * Find the index of the parent of this node. 
     * @param nodeIndex is a (1-based) node index whose parent we want to find.
     * @return Returns 0 if this node has no parent (is the root), otherwise
     *    the parent's index
     */
    public static int parent(int nodeIndex) {
        return nodeIndex / 2;
    }

    /**
     * Find the index of the left child of a given node.
     * @param nodeIndex the (1-based) index of the node whose child we want to find
     * @return the index of the left child, or size() if no left child.
     */
    public int leftChild(int nodeIndex) {
        return 2 * nodeIndex;
    }

    /**
     * Find the index of the right child of a given node.
     * @param nodeIndex the (1-based) index of the node whose child we want to find
     * @return the index of the right child, or size() if no left child.
     */
    public int rightChild(int nodeIndex) {
        return 2 * nodeIndex + 1;
    }

    /**
     * Find the index of this node's sibling.
     * Everything always has a sibling, in this formulation of 
     * (not-necessarily-complete binary trees). For root, returns 0.
     * @param nodeIndex the (1-based) index of the node whose sibling we want to find
     * @return the (1-based) index of the sibling, or 0 for if nodeIndex is the root.
     */
    public static int sibling(int nodeIndex) {
        return nodeIndex ^ 1; // Java has xor! who knew?
    }

    /**
     * Check internal node index (not translated to leaves) to see if it
     * is a left or right child. Internal nodes for a layer always start
     * with an even index, as 1 is the root and the only layer with one
     * member. Every other layer has an even number of nodes (except for
     * possibly a dangling child at the end). So, left nodes have even
     * indices, and right nodes have odd ones.
     * @param nodeIndex node to check whether it is a right child
     * @return true if it is a right child, false if a left child
     */
    public static boolean isRight(int nodeIndex) {
        return (0 != (nodeIndex % 2));
    }

    /**
     * Check internal node index (not translated to leaves) to see if it
     * is a left or right child. Internal nodes for a layer always start
     * with an even index, as 1 is the root and the only layer with one
     * member. Every other layer has an even number of nodes (except for
     * possibly a dangling child at the end). So, left nodes have even
     * indices, and right nodes have odd ones.
     * @param nodeIndex node to check whether it is a left child
     * @return true if it is a left child, false if a right child
     */
    public static boolean isLeft(int nodeIndex) {
        return (0 == (nodeIndex % 2));
    }

    /**
     * Return the root digest
     * @return the root digest
     */
    public byte[] root() {
        if ((null == _tree) || (_tree.length == 0))
            return new byte[0];
        return get(ROOT_NODE);
    }

    /**
     * Get the DEROctetString wrapped digest of the root node.
     * @return a DEROctetString object containing the root node digest.
     */
    public DEROctetString derRoot() {
        return derGet(ROOT_NODE);
    }

    /**
     * Get the size of the tree, in nodes. (This is the number of nodes,
     * not the number of leaves.)
     * @return the tree size.
     */
    public int size() {
        return _tree.length;
    }

    /**
     * Returns the digest at the specified node.
     * @param nodeIndex 1-based node index
     * @return the digest for this node
     */
    public byte[] get(int nodeIndex) {
        DEROctetString dv = derGet(nodeIndex);
        if (null == dv)
            return null;
        return dv.getOctets();
    }

    /**
     * Returns the digest at the specified node as a DEROctetString
     * @param nodeIndex 1-based node index
     * @return the digest for this node
     */
    public DEROctetString derGet(int nodeIndex) {
        if ((nodeIndex < ROOT_NODE) || (nodeIndex > size()))
            return null;
        return _tree[nodeIndex - 1];
    }

    /**
     * Get the number of leaves in the tree.
     * @return returns the number of leaves
     */
    public int numLeaves() {
        return _numLeaves;
    }

    /**
     * Calculate the number of nodes in a tree with a given number of leaves.
     * @param numLeaves the number of leaves
     * @return the number of nodes in the tree
     */
    public static int nodeCount(int numLeaves) {
        // How many entries do we need? 2*numLeaves + 1
        return 2 * numLeaves - 1;
    }

    /**
     * Calculates the number of nodes in this tree
     * @return  the number of nodes
     */
    public int nodeCount() {
        return nodeCount(numLeaves());
    }

    /**
     * Returns the node index of the first leaf.
     * The node index of the first leaf is either size()-numleaves(), or
     * nodeIndex = numLeaves.
     * @return the first leaf's node index
     */
    public int firstLeaf() {
        return numLeaves();
    }

    /**
     * Get the node index of a given leaf
     * @param leafIndex the index of a leaf
     * @return its node index
     */
    public int leafNodeIndex(int leafIndex) {
        return firstLeaf() + leafIndex;
    }

    /**
     * Retrieve the digest of a given leaf node. Returns null if there is
     * no leaf leafIndex.
     * @param leafIndex leaf index, starting at 0 for the first leaf.
     * @return its digest
     */
    public byte[] leaf(int leafIndex) {
        return get(leafNodeIndex(leafIndex));
    }

    /**
     * Generate a MerklePath for a given leaf, to use in verifying that
     * leaf.
     * 
     * There are a variety of traversal algorithms for 
     * computing/reading Merkle hash trees.
     * 
     * We need to represent the leaves so that the user
     * a) knows what order they come in, and b) also knows
     * which is the leaf being represented. The cheapest
     * way to do that is to represent the leaves in order,
     * and also start out with an indication of whether
     * this leaf is the left or right of the last pair.
     * To make this most general and easy to use, we
     * will represent this path as
     *  
     * MerklePath ::= SEQUENCE {
     *    nodeIndex INTEGER, 
     *  nodes NodeList 
     * }
     *  
     * NodeList ::= SEQUENCE OF OCTET STRING
     *  
     * the nodeIndex here is the index of the leaf node in
     * the tree as a whole (not just among the leaves), and
     * the nodes list contains neither the digest of the
     * leaf itself nor the root of the tree.
     * 
     * We could probably save a few bytes by not encoding this
     * as DER, and simply packing in the bytes to represent this
     * data -- this encoding offers a fair amount of ease of parsing
     * and clarity, at the cost of probably 5 + 2*pathLength bytes of overhead,
     * or 20 bytes in typical paths. At some point this may
     * seem too much, and we will move to a more compact encoding.
     *  
     * @param leafNum the leaf index of the leaf
     * @return the MerklePath for verifying that leaf
     * @see MerklePath
     */
    public MerklePath path(int leafNum) {

        // Start at the leaf, pushing siblings. We know we always have
        // a complete path to the leaf.
        int leafNode = leafNodeIndex(leafNum);
        // We want to push nodes of the path onto the path structure
        // in reverse order. We'd then like to turn them into bare
        // arrays for efficiency. Java's stacks, though, turn them
        // into arrays in the wrong order. So, make an array. With
        // the extended binary tree, all paths are complete for their
        // region of the tree.
        DEROctetString[] resultStack = new DEROctetString[maxPathLength(leafNode)];

        // Start at the leaf, pushing siblings.
        int node = leafNode;
        int index = resultStack.length - 1;

        while (node != ROOT_NODE) {

            int siblingIdx = sibling(node);
            // returns null if siblingIdx is too large, or if
            // there is no child at that index (empty subtree)
            DEROctetString sibling = derGet(siblingIdx);

            if (null != sibling) {
                resultStack[index--] = sibling;
            }

            node = parent(node);
        }
        return new MerklePath(leafNode, resultStack);
    }

    /**
     * What is the maximum path length to a node with this node index,
     * including its sibling but not including the root?
     * @param nodeIndex the node to find the path length for
     * @return the maximum path length
     */
    public static int maxPathLength(int nodeIndex) {
        int baseLog = (int) Math.floor(MerkleTree.log2(nodeIndex));
        return baseLog;
    }

    /**
     * What is the maximum depth of a Merkle tree with
     * a given number of leaves. If the tree isn't balanced,
     * many nodes may have shorter paths than maxDepth.
     * @param numLeaves the number of leaves in the tree.
     * @return the maximum depth of the tree
     */
    public static int maxDepth(int numLeaves) {

        if (numLeaves == 0)
            return 0;
        if (numLeaves == 1)
            return 1;
        int pathLength = (int) Math.ceil(log2(numLeaves));
        //Library.info("numLeaves: " + numLeaves + " log2(nl+1) " + log2(numLeaves+1));
        return pathLength;
    }

    /**
     * Get the maximum depth of this MerkleTree.
     * @return the depth
     */
    public int maxDepth() {
        return maxDepth(numLeaves());
    }

    /**
     * Compute the raw digest of the leaf content blocks, and format them appropriately.
     * @param contentBlocks the leaf content, one leaf per array
     * @param isDigest have these been digested already, or do we need to digest
     *    them using computeBlockDigest(int, byte [][], int, int)?
     * @param baseBlockIndex first block in the array to use
     * @param lastBlockLength number of bytes of the last block to use; N/A if isDigest is true
     * @throws NoSuchAlgorithmException if digestAlgorithm is unknown
     */
    protected void computeLeafValues(byte contentBlocks[][], boolean isDigest, int baseBlockIndex,
            int lastBlockLength) throws NoSuchAlgorithmException {
        // Hash the leaves
        for (int i = 0; i < numLeaves(); ++i) {
            _tree[leafNodeIndex(i) - 1] = new DEROctetString((isDigest ? contentBlocks[i + baseBlockIndex]
                    : computeBlockDigest(i, contentBlocks, baseBlockIndex, lastBlockLength)));
        }
    }

    /**
     * Compute the raw digest of the leaf content blocks, and format them appropriately.
     * uses computeBlockDigest(int, byte[], int, int) to compute the leaf digest.
     * @param content the content to segment into leaves and hash into this 
     * Merkle hash tree. One blockWidth of content per leaf, except for the last leaf which may
     * be shorter.
     * @param offset offset into content at which to start processing data.
     * @param length number of bytes of content to process
     * @param blockWidth the length of leaf blocks to create
     * @throws NoSuchAlgorithmException if digestAlgorithm is unknown
     */
    protected void computeLeafValues(byte[] content, int offset, int length, int blockWidth)
            throws NoSuchAlgorithmException {
        // Hash the leaves
        for (int i = 0; i < numLeaves(); ++i) {
            _tree[leafNodeIndex(i) - 1] = new DEROctetString(
                    (computeBlockDigest(i, content, offset + (blockWidth * i),
                            ((i < numLeaves() - 1) ? blockWidth : (length - (blockWidth * i))))));
        }
    }

    /**
     * Compute the intermediate node values by digesting the concatenation of the
     * left and right children (or the left child alone if there is no right child).
     * @throws NoSuchAlgorithmException if digestAlgorithm is unknown
     */
    protected void computeNodeValues() throws NoSuchAlgorithmException {
        // Climb the tree
        int firstNode = firstLeaf() - 1;
        for (int i = firstNode; i >= ROOT_NODE; --i) {
            byte[] nodeDigest = CCNDigestHelper.digest(digestAlgorithm(), get(leftChild(i)), get(rightChild(i)));
            _tree[i - 1] = new DEROctetString(nodeDigest);
        }
    }

    /**
     * Function for validating paths. Given a digest, it returns what node in
     * the tree has that digest. If no node has that digest, returns 0. 
     * If argument is null, returns -1. Slow.
     * @param node the node digest to validate
     * @return the nodeIndex of the node with that digest
     */
    public int getNodeIndex(DEROctetString node) {
        if (null == node)
            return -1;
        for (int i = 1; i <= size(); ++i) {
            if (node.equals(derGet(i)))
                return i;
        }
        return 0;
    }

    /**
     * Get the root node as an encoded PKCS#1 DigestInfo.
     * @return the encoded DigestInfo
     */
    public byte[] getRootAsEncodedDigest() {
        // Take root and wrap it up as an encoded DigestInfo
        return CCNDigestHelper.digestEncoder(digestAlgorithm(), root());
    }

    /**
     * Compute the digest of a leaf node.
     * Separate this out so that it can be overridden.
     * @param leafIndex The index of the leaf we are computing the digest of.
     * @param contentBlocks The array of content blocks containing the leaf content.
     * @param baseBlockIndex The first content block in the array containing leaf content (if rolling buffers).
     *                  numLeaves() blocks contain leaf content, so the last block used is blockOffset+numLeaves().
     * @param lastBlockLength the number of bytes of the last block to use, can be smaller than
     *    the number available
     * @return the digest for this leaf
     * @throws NoSuchAlgorithmException 
     */
    protected byte[] computeBlockDigest(int leafIndex, byte contentBlocks[][], int baseBlockIndex,
            int lastBlockLength) throws NoSuchAlgorithmException {
        if ((leafIndex + baseBlockIndex) > contentBlocks.length)
            throw new IllegalArgumentException("Cannot ask for a leaf beyond the number of available blocks!");
        // Are we on the last block?
        if ((leafIndex + baseBlockIndex) == (baseBlockIndex + numLeaves() - 1))
            computeBlockDigest(leafIndex, contentBlocks[leafIndex + baseBlockIndex], 0, lastBlockLength);
        return computeBlockDigest(_digestAlgorithm, contentBlocks[leafIndex + baseBlockIndex]);
    }

    /**
     * Compute the digest of a leaf node.
     * Separate this out so that it can be overridden.
     * @param leafIndex The index of the leaf we are computing the digest of.
     * @param content the content to segment into leaves and hash into this 
     * Merkle hash tree.
     * @param offset offset into content at which this leaf starts
     * @param length number of bytes of content in this leaf
     * @return the digest for this leaf
     * @throws NoSuchAlgorithmException if digestAlgorithm is unknown
     */
    protected byte[] computeBlockDigest(int leafIndex, byte[] content, int offset, int length)
            throws NoSuchAlgorithmException {
        return CCNDigestHelper.digest(_digestAlgorithm, content, offset, length);
    }

    /**
     * Compute the digest of a leaf node.
     * @param digestAlgorithm the digest algorithm to use
     * @param content the content of this leaf
     * @return the digest for this leaf
     * @throws NoSuchAlgorithmException if digestAlgorithm is unknown
     */
    public static byte[] computeBlockDigest(String digestAlgorithm, byte[] content)
            throws NoSuchAlgorithmException {
        return CCNDigestHelper.digest(digestAlgorithm, content);
    }

    /**
     * Compute the digest of a leaf node.
     * @param digestAlgorithm the digest algorithm to use
     * @param content the content to segment into leaves and hash into this 
     * Merkle hash tree.
     * @param offset offset into content at which this leaf starts
     * @param length number of bytes of content in this leaf
     * @return the digest for this leaf
     * @throws NoSuchAlgorithmException if digestAlgorithm is unknown
     */
    public static byte[] computeBlockDigest(String digestAlgorithm, byte[] content, int offset, int length)
            throws NoSuchAlgorithmException {
        return CCNDigestHelper.digest(digestAlgorithm, content, offset, length);
    }

    /**
     * Compute the digest of a block using CCNDigestHelper#DEFAULT_DIGEST_ALGORITHM.
     * DKS TODO - check -- was being by MerklePath to compute digest for root without
     * properly recovering OID from encoded path.
     * @param block block to digest
     * @return block digest
     */
    public static byte[] computeBlockDigest(byte[] block) {
        try {
            return computeBlockDigest(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, block);
        } catch (NoSuchAlgorithmException e) {
            // DKS --big configuration problem
            Log.warning("Fatal Error: cannot find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM);
            throw new RuntimeException("Error: can't find default algorithm "
                    + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM + "!  " + e.toString());
        }
    }

    /**
     * Compute the digest for an intermediate node. If this is a last left child (right is null),
     * simply hash left alone.
     * @throws NoSuchAlgorithmException 
     */
    public static byte[] computeNodeDigest(String algorithm, byte[] left, byte[] right)
            throws NoSuchAlgorithmException {
        return CCNDigestHelper.digest(algorithm, left, right);
    }

    /**
     * Compute the digest for an intermediate node with two children.
     * @param left left child
     * @param right right child
     * @return parent digest
     */
    public static byte[] computeNodeDigest(byte[] left, byte[] right) {
        try {
            return computeNodeDigest(CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM, left, right);
        } catch (NoSuchAlgorithmException e) {
            // DKS --big configuration problem
            Log.warning("Fatal Error: cannot find default algorithm " + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM);
            throw new RuntimeException("Error: can't find default algorithm "
                    + CCNDigestHelper.DEFAULT_DIGEST_ALGORITHM + "!  " + e.toString());
        }
    }

    /**
     * Does this algorithm identifier indicate a Merkle tree?
     * @param algorithmId the algorithm identifier
     * @return true if its a merkle tree, false otherwise
     */
    public static boolean isMerkleTree(AlgorithmIdentifier algorithmId) {
        // Use a hack -- all MHT OIDs use same prefix.
        String strAlg = algorithmId.toString();
        if (strAlg.startsWith(MERKLE_OID_PREFIX))
            return true;
        return false;
    }

    /**
     * Helper method
     * @param arg
     * @return log base 2 of arg
     */
    public static double log2(int arg) {
        return Math.log(arg) / Math.log(2);
    }

    /**
     * The number of blocks of blockWidth (bytes) necessary to hold length (bytes)
     * @param length the buffer length
     * @param blockWidth the segment with
     * @return the number of blocks
     */
    public static int blockCount(int length, int blockWidth) {
        if (0 == length)
            return 0;
        return (length + blockWidth - 1) / blockWidth;
        //   return (int)Math.ceil((1.0*length)/blockWidth);
    }
}