Bitcoin Internals: Verifying Merkle Roots using Merkle Proofs in JavaScript

In this video, we expand on the previous one, in which we computed a Merkle root for a given list of transactions using Merkle trees. We will now compute a Merkle proof for a given transaction, allowing clients to validate that a transaction exists in a block without having to download its entire contents. This technique is used widely in lightweight Bitcoin clients (using Simplified Payment Verification). We codify a JavaScript solution that creates a Merkle proof for a transaction and then does the inverse by validating that proof’s claim based on the block’s Merkle root.

Source

Below is a copy of the source code which accompanies the video.

const fetchLatestBlock = () =>
  fetch(`https://blockchain.info/q/latesthash?cors=true`).then(r => r.text());

const fetchMerkleRootAndTransactions = block =>
  fetch(`https://blockchain.info/rawblock/${block}?cors=true`)
    .then(r => r.json())
    .then(d => [d.mrkl_root, d.tx.map(t => t.hash)]);

const random = arr => arr[Math.floor(Math.random() * arr.length)];

const toBytes = hex =>
  hex.match(/../g).reduce((acc, hex) => [...acc, parseInt(hex, 16)], []);

const toHex = bytes =>
  bytes.reduce((acc, bytes) => acc + bytes.toString(16).padStart(2, '0'), '');

const toPairs = arr =>
  Array.from(Array(Math.ceil(arr.length / 2)), (_, i) =>
    arr.slice(i * 2, i * 2 + 2)
  );

const hashPair = (a, b = a) => {
  const bytes = toBytes(`${b}${a}`).reverse();
  const hashed = sha256.array(sha256.array(bytes));
  return toHex(hashed.reverse());
};

const merkleProof = (txs, tx, proof = []) => {
  if (txs.length === 1) {
    return proof;
  }

  const tree = [];

  toPairs(txs).forEach(pair => {
    const hash = hashPair(...pair);

    if (pair.includes(tx)) {
      const idx = (pair[0] === tx) | 0;
      proof.push([idx, pair[idx]]);
      tx = hash;
    }

    tree.push(hash);
  });

  return merkleProof(tree, tx, proof);
};

const merkleProofRoot = (proof, tx) =>
  proof.reduce(
    (root, [idx, tx]) => (idx ? hashPair(root, tx) : hashPair(tx, root)),
    tx
  );

fetchLatestBlock()
  .then(fetchMerkleRootAndTransactions)
  .then(([root, txs]) => {
    const tx = random(txs);
    const proof = merkleProof(txs, tx);

    const isValid = merkleProofRoot(proof, tx) === root;
    console.log(isValid);
  });

Resources