text: "use strict";
  var __importDefault = (this && this.__importDefault) || function (mod) {
      return (mod && mod.__esModule) ? mod : { "default": mod };
  };
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.MerkleTree = void 0;
  const buffer_reverse_1 = __importDefault(require("buffer-reverse"));
  const sha256_1 = __importDefault(require("crypto-js/sha256"));
  const Base_1 = __importDefault(require("./Base"));
  const treeify_1 = __importDefault(require("treeify"));
  /**
   * Class reprensenting a Merkle Tree
   * @namespace MerkleTree
   */
  class MerkleTree extends Base_1.default {
      /**
       * @desc Constructs a Merkle Tree.
       * All nodes and leaves are stored as Buffers.
       * Lonely leaf nodes are promoted to the next level up without being hashed again.
       * @param {Buffer[]} leaves - Array of hashed leaves. Each leaf must be a Buffer.
       * @param {Function} hashFunction - Hash function to use for hashing leaves and nodes
       * @param {Object} options - Additional options
       * @example
       *```js
       *const MerkleTree = require('merkletreejs')
       *const crypto = require('crypto')
       *
       *function sha256(data) {
       *  // returns Buffer
       *  return crypto.createHash('sha256').update(data).digest()
       *}
       *
       *const leaves = ['a', 'b', 'c'].map(value => keccak(value))
       *
       *const tree = new MerkleTree(leaves, sha256)
       *```
       */
      constructor(leaves, hashFn = sha256_1.default, options = {}) {
          super();
          this.duplicateOdd = false;
          this.hashLeaves = false;
          this.isBitcoinTree = false;
          this.leaves = [];
          this.layers = [];
          this.sortLeaves = false;
          this.sortPairs = false;
          this.sort = false;
          this.fillDefaultHash = null;
          this.isBitcoinTree = !!options.isBitcoinTree;
          this.hashLeaves = !!options.hashLeaves;
          this.sortLeaves = !!options.sortLeaves;
          this.sortPairs = !!options.sortPairs;
          if (options.fillDefaultHash) {
              if (typeof options.fillDefaultHash === 'function') {
                  this.fillDefaultHash = options.fillDefaultHash;
              }
              else if (Buffer.isBuffer(options.fillDefaultHash) || typeof options.fillDefaultHash === 'string') {
                  this.fillDefaultHash = (idx, hashFn) => options.fillDefaultHash;
              }
              else {
                  throw new Error('method "fillDefaultHash" must be a function, Buffer, or string');
              }
          }
          this.sort = !!options.sort;
          if (this.sort) {
              this.sortLeaves = true;
              this.sortPairs = true;
          }
          this.duplicateOdd = !!options.duplicateOdd;
          this.hashFn = this._bufferifyFn(hashFn);
          this.processLeaves(leaves);
      }
      processLeaves(leaves) {
          if (this.hashLeaves) {
              leaves = leaves.map(leaf => this.hashFn(leaf));
          }
          this.leaves = leaves.map(leaf => this.bufferify(leaf));
          if (this.sortLeaves) {
              this.leaves = this.leaves.sort(Buffer.compare);
          }
          if (this.fillDefaultHash) {
              for (let i = 0; i < Math.pow(2, Math.ceil(Math.log2(this.leaves.length))); i++) {
                  if (i >= this.leaves.length) {
                      this.leaves.push(this.bufferify(this.fillDefaultHash(i, this.hashFn)));
                  }
              }
          }
          this.layers = [this.leaves];
          this._createHashes(this.leaves);
      }
      _createHashes(nodes) {
          while (nodes.length > 1) {
              const layerIndex = this.layers.length;
              this.layers.push([]);
              for (let i = 0; i < nodes.length; i += 2) {
                  if (i + 1 === nodes.length) {
                      if (nodes.length % 2 === 1) {
                          let data = nodes[nodes.length - 1];
                          let hash = data;
                          // is bitcoin tree
                          if (this.isBitcoinTree) {
                              // Bitcoin method of duplicating the odd ending nodes
                              data = Buffer.concat([buffer_reverse_1.default(data), buffer_reverse_1.default(data)]);
                              hash = this.hashFn(data);
                              hash = buffer_reverse_1.default(this.hashFn(hash));
                              this.layers[layerIndex].push(hash);
                              continue;
                          }
                          else {
                              if (this.duplicateOdd) {
                                  // continue with creating layer
                              }
                              else {
                                  // push copy of hash and continue iteration
                                  this.layers[layerIndex].push(nodes[i]);
                                  continue;
                              }
                          }
                      }
                  }
                  const left = nodes[i];
                  const right = i + 1 === nodes.length ? left : nodes[i + 1];
                  let data = null;
                  let combined = null;
                  if (this.isBitcoinTree) {
                      combined = [buffer_reverse_1.default(left), buffer_reverse_1.default(right)];
                  }
                  else {
                      combined = [left, right];
                  }
                  if (this.sortPairs) {
                      combined.sort(Buffer.compare);
                  }
                  data = Buffer.concat(combined);
                  let hash = this.hashFn(data);
                  // double hash if bitcoin tree
                  if (this.isBitcoinTree) {
                      hash = buffer_reverse_1.default(this.hashFn(hash));
                  }
                  this.layers[layerIndex].push(hash);
              }
              nodes = this.layers[layerIndex];
          }
      }
      /**
       * addLeaf
       * @desc Adds a leaf to the tree and re-calculates layers.
       * @param {String|Buffer} - Leaf
       * @param {Boolean} - Set to true if the leaf should be hashed before being added to tree.
       * @example
       *```js
       *tree.addLeaf(newLeaf)
       *```
       */
      addLeaf(leaf, shouldHash = false) {
          if (shouldHash) {
              leaf = this.hashFn(leaf);
          }
          this.processLeaves([...this.leaves, leaf]);
      }
      /**
       * addLeaves
       * @desc Adds multiple leaves to the tree and re-calculates layers.
       * @param {String[]|Buffer[]} - Array of leaves
       * @param {Boolean} - Set to true if the leaves should be hashed before being added to tree.
       * @example
       *```js
       *tree.addLeaves(newLeaves)
       *```
       */
      addLeaves(leaves, shouldHash = false) {
          if (shouldHash) {
              leaves = leaves.map(leaf => this.hashFn(leaf));
          }
          this.processLeaves([...this.leaves, ...leaves]);
      }
      /**
       * getLeaves
       * @desc Returns array of leaves of Merkle Tree.
       * @return {Buffer[]}
       * @example
       *```js
       *const leaves = tree.getLeaves()
       *```
       */
      getLeaves(values) {
          if (Array.isArray(values)) {
              if (this.hashLeaves) {
                  values = values.map(value => this.hashFn(value));
                  if (this.sortLeaves) {
                      values = values.sort(Buffer.compare);
                  }
              }
              return this.leaves.filter(leaf => this._bufferIndexOf(values, leaf) !== -1);
          }
          return this.leaves;
      }
      /**
       * getLeaf
       * @desc Returns the leaf at the given index.
       * @param {Number} - Index number
       * @return {Buffer}
       * @example
       *```js
       *const leaf = tree.getLeaf(1)
       *```
       */
      getLeaf(index) {
          if (index < 0 || index > this.leaves.length - 1) {
              return Buffer.from([]);
          }
          return this.leaves[index];
      }
      /**
       * getLeafIndex
       * @desc Returns the index of the given leaf, or -1 if the leaf is not found.
       * @param {String|Buffer} - Target leaf
       * @return {number}
       * @example
       *```js
       *const leaf = Buffer.from('abc')
       *const index = tree.getLeafIndex(leaf)
       *```
       */
      getLeafIndex(target) {
          target = this.bufferify(target);
          const leaves = this.getLeaves();
          for (let i = 0; i < leaves.length; i++) {
              const leaf = leaves[i];
              if (leaf.equals(target)) {
                  return i;
              }
          }
          return -1;
      }
      /**
       * getLeafCount
       * @desc Returns the total number of leaves.
       * @return {number}
       * @example
       *```js
       *const count = tree.getLeafCount()
       *```
       */
      getLeafCount() {
          return this.leaves.length;
      }
      /**
       * getHexLeaves
       * @desc Returns array of leaves of Merkle Tree as hex strings.
       * @return {String[]}
       * @example
       *```js
       *const leaves = tree.getHexLeaves()
       *```
       */
      getHexLeaves() {
          return this.leaves.map(leaf => this.bufferToHex(leaf));
      }
      /**
       * marshalLeaves
       * @desc Returns array of leaves of Merkle Tree as a JSON string.
       * @param {String[]|Buffer[]} - Merkle tree leaves
       * @return {String} - List of leaves as JSON string
       * @example
       *```js
       *const jsonStr = MerkleTree.marshalLeaves(leaves)
       *```
       */
      static marshalLeaves(leaves) {
          return JSON.stringify(leaves.map(leaf => MerkleTree.bufferToHex(leaf)), null, 2);
      }
      /**
       * unmarshalLeaves
       * @desc Returns array of leaves of Merkle Tree as a Buffers.
       * @param {String|Object} - JSON stringified leaves
       * @return {Buffer[]} - Unmarshalled list of leaves
       * @example
       *```js
       *const leaves = MerkleTree.unmarshalLeaves(jsonStr)
       *```
       */
      static unmarshalLeaves(jsonStr) {
          let parsed = null;
          if (typeof jsonStr === 'string') {
              parsed = JSON.parse(jsonStr);
          }
          else if (jsonStr instanceof Object) {
              parsed = jsonStr;
          }
          else {
              throw new Error('Expected type of string or object');
          }
          if (!parsed) {
              return [];
          }
          if (!Array.isArray(parsed)) {
              throw new Error('Expected JSON string to be array');
          }
          return parsed.map(leaf => MerkleTree.bufferify(leaf));
      }
      /**
       * getLayers
       * @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root.
       * @return {Buffer[]}
       * @example
       *```js
       *const layers = tree.getLayers()
       *```
       */
      getLayers() {
          return this.layers;
      }
      /**
       * getHexLayers
       * @desc Returns multi-dimensional array of all layers of Merkle Tree, including leaves and root as hex strings.
       * @return {String[]}
       * @example
       *```js
       *const layers = tree.getHexLayers()
       *```
       */
      getHexLayers() {
          return this.layers.reduce((acc, item) => {
              if (Array.isArray(item)) {
                  acc.push(item.map(value => this.bufferToHex(value)));
              }
              else {
                  acc.push(item);
              }
              return acc;
          }, []);
      }
      /**
       * getLayersFlat
       * @desc Returns single flat array of all layers of Merkle Tree, including leaves and root.
       * @return {Buffer[]}
       * @example
       *```js
       *const layers = tree.getLayersFlat()
       *```
       */
      getLayersFlat() {
          const layers = this.layers.reduce((acc, item) => {
              if (Array.isArray(item)) {
                  acc.unshift(...item);
              }
              else {
                  acc.unshift(item);
              }
              return acc;
          }, []);
          layers.unshift(Buffer.from([0]));
          return layers;
      }
      /**
       * getHexLayersFlat
       * @desc Returns single flat array of all layers of Merkle Tree, including leaves and root as hex string.
       * @return {String[]}
       * @example
       *```js
       *const layers = tree.getHexLayersFlat()
       *```
       */
      getHexLayersFlat() {
          return this.getLayersFlat().map(layer => this.bufferToHex(layer));
      }
      /**
       * getLayerCount
       * @desc Returns the total number of layers.
       * @return {number}
       * @example
       *```js
       *const count = tree.getLayerCount()
       *```
       */
      getLayerCount() {
          return this.getLayers().length;
      }
      /**
       * getRoot
       * @desc Returns the Merkle root hash as a Buffer.
       * @return {Buffer}
       * @example
       *```js
       *const root = tree.getRoot()
       *```
       */
      getRoot() {
          if (this.layers.length === 0) {
              return Buffer.from([]);
          }
          return this.layers[this.layers.length - 1][0] || Buffer.from([]);
      }
      /**
       * getHexRoot
       * @desc Returns the Merkle root hash as a hex string.
       * @return {String}
       * @example
       *```js
       *const root = tree.getHexRoot()
       *```
       */
      getHexRoot() {
          return this.bufferToHex(this.getRoot());
      }
      /**
       * getProof
       * @desc Returns the proof for a target leaf.
       * @param {Buffer} leaf - Target leaf
       * @param {Number} [index] - Target leaf index in leaves array.
       * Use if there are leaves containing duplicate data in order to distinguish it.
       * @return {Object[]} - Array of objects containing a position property of type string
       * with values of 'left' or 'right' and a data property of type Buffer.
       * @example
       * ```js
       *const proof = tree.getProof(leaves[2])
       *```
       *
       * @example
       *```js
       *const leaves = ['a', 'b', 'a'].map(value => keccak(value))
       *const tree = new MerkleTree(leaves, keccak)
       *const proof = tree.getProof(leaves[2], 2)
       *```
       */
      getProof(leaf, index) {
          if (typeof leaf === 'undefined') {
              throw new Error('leaf is required');
          }
          leaf = this.bufferify(leaf);
          const proof = [];
          if (!Number.isInteger(index)) {
              index = -1;
              for (let i = 0; i < this.leaves.length; i++) {
                  if (Buffer.compare(leaf, this.leaves[i]) === 0) {
                      index = i;
                  }
              }
          }
          if (index <= -1) {
              return [];
          }
          for (let i = 0; i < this.layers.length; i++) {
              const layer = this.layers[i];
              const isRightNode = index % 2;
              const pairIndex = (isRightNode ? index - 1
                  : this.isBitcoinTree && index === layer.length - 1 && i < this.layers.length - 1
                      // Proof Generation for Bitcoin Trees
                      ? index
                      // Proof Generation for Non-Bitcoin Trees
                      : index + 1);
              if (pairIndex < layer.length) {
                  proof.push({
                      position: isRightNode ? 'left' : 'right',
                      data: layer[pairIndex]
                  });
              }
              // set index to parent index
              index = (index / 2) | 0;
          }
          return proof;
      }
      /**
       * getHexProof
       * @desc Returns the proof for a target leaf as hex strings.
       * @param {Buffer} leaf - Target leaf
       * @param {Number} [index] - Target leaf index in leaves array.
       * Use if there are leaves containing duplicate data in order to distinguish it.
       * @return {String[]} - Proof array as hex strings.
       * @example
       * ```js
       *const proof = tree.getHexProof(leaves[2])
       *```
       */
      getHexProof(leaf, index) {
          return this.getProof(leaf, index).map(item => this.bufferToHex(item.data));
      }
      /**
      * getPositionalHexProof
      * @desc Returns the proof for a target leaf as hex strings and the position in binary (left == 0).
      * @param {Buffer} leaf - Target leaf
      * @param {Number} [index] - Target leaf index in leaves array.
      * Use if there are leaves containing duplicate data in order to distinguish it.
      * @return {(string | number)[][]} - Proof array as hex strings. position at index 0
      * @example
      * ```js
      *const proof = tree.getPositionalHexProof(leaves[2])
      *```
      */
      getPositionalHexProof(leaf, index) {
          return this.getProof(leaf, index).map(item => {
              return [
                  item.position === 'left' ? 0 : 1,
                  this.bufferToHex(item.data)
              ];
          });
      }
      /**
       * marshalProof
       * @desc Returns proof array as JSON string.
       * @param {String[]|Object[]} proof - Merkle tree proof array
       * @return {String} - Proof array as JSON string.
       * @example
       * ```js
       *const jsonStr = MerkleTree.marshalProof(proof)
       *```
       */
      static marshalProof(proof) {
          const json = proof.map(item => {
              if (typeof item === 'string') {
                  return item;
              }
              if (Buffer.isBuffer(item)) {
                  return MerkleTree.bufferToHex(item);
              }
              return {
                  position: item.position,
                  data: MerkleTree.bufferToHex(item.data)
              };
          });
          return JSON.stringify(json, null, 2);
      }
      /**
       * unmarshalProof
       * @desc Returns the proof for a target leaf as a list of Buffers.
       * @param {String|Object} - Merkle tree leaves
       * @return {String|Object} - Marshalled proof
       * @example
       * ```js
       *const proof = MerkleTree.unmarshalProof(jsonStr)
       *```
       */
      static unmarshalProof(jsonStr) {
          let parsed = null;
          if (typeof jsonStr === 'string') {
              parsed = JSON.parse(jsonStr);
          }
          else if (jsonStr instanceof Object) {
              parsed = jsonStr;
          }
          else {
              throw new Error('Expected type of string or object');
          }
          if (!parsed) {
              return [];
          }
          if (!Array.isArray(parsed)) {
              throw new Error('Expected JSON string to be array');
          }
          return parsed.map(item => {
              if (typeof item === 'string') {
                  return MerkleTree.bufferify(item);
              }
              else if (item instanceof Object) {
                  return {
                      position: item.position,
                      data: MerkleTree.bufferify(item.data)
                  };
              }
              else {
                  throw new Error('Expected item to be of type string or object');
              }
          });
      }
      /**
       * getProofIndices
       * @desc Returns the proof indices for given tree indices.
       * @param {Number[]} treeIndices - Tree indices
       * @param {Number} depth - Tree depth; number of layers.
       * @return {Number[]} - Proof indices
       * @example
       * ```js
       *const proofIndices = tree.getProofIndices([2,5,6], 4)
       *console.log(proofIndices) // [ 23, 20, 19, 8, 3 ]
       *```
       */
      getProofIndices(treeIndices, depth) {
          const leafCount = Math.pow(2, depth);
          let maximalIndices = new Set();
          for (const index of treeIndices) {
              let x = leafCount + index;
              while (x > 1) {
                  maximalIndices.add(x ^ 1);
                  x = (x / 2) | 0;
              }
          }
          const a = treeIndices.map(index => leafCount + index);
          const b = Array.from(maximalIndices).sort((a, b) => a - b).reverse();
          maximalIndices = a.concat(b);
          const redundantIndices = new Set();
          const proof = [];
          for (let index of maximalIndices) {
              if (!redundantIndices.has(index)) {
                  proof.push(index);
                  while (index > 1) {
                      redundantIndices.add(index);
                      if (!redundantIndices.has(index ^ 1))
                          break;
                      index = (index / 2) | 0;
                  }
              }
          }
          return proof.filter(index => {
              return !treeIndices.includes(index - leafCount);
          });
      }
      /**
       * getMultiProof
       * @desc Returns the multiproof for given tree indices.
       * @param {Number[]} indices - Tree indices.
       * @return {Buffer[]} - Multiproofs
       * @example
       * ```js
       *const indices = [2, 5, 6]
       *const proof = tree.getMultiProof(indices)
       *```
       */
      getMultiProof(tree, indices) {
          if (!indices) {
              indices = tree;
              tree = this.getLayersFlat();
              if (!indices.every(Number.isInteger)) {
                  let els = indices;
                  if (this.sortPairs) {
                      els = els.sort(Buffer.compare);
                  }
                  let ids = els.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1);
                  if (!ids.every((idx) => idx !== -1)) {
                      throw new Error('Element does not exist in Merkle tree');
                  }
                  const hashes = [];
                  const proof = [];
                  let nextIds = [];
                  for (let i = 0; i < this.layers.length; i++) {
                      const layer = this.layers[i];
                      for (let j = 0; j < ids.length; j++) {
                          const idx = ids[j];
                          const pairElement = this._getPairNode(layer, idx);
                          hashes.push(layer[idx]);
                          if (pairElement) {
                              proof.push(pairElement);
                          }
                          nextIds.push((idx / 2) | 0);
                      }
                      ids = nextIds.filter((value, i, self) => self.indexOf(value) === i);
                      nextIds = [];
                  }
                  return proof.filter((value) => !hashes.includes(value));
              }
          }
          return this.getProofIndices(indices, this._log2((tree.length / 2) | 0)).map(index => tree[index]);
      }
      /**
       * getHexMultiProof
       * @desc Returns the multiproof for given tree indices as hex strings.
       * @param {Number[]} indices - Tree indices.
       * @return {String[]} - Multiproofs as hex strings.
       * @example
       * ```js
       *const indices = [2, 5, 6]
       *const proof = tree.getHexMultiProof(indices)
       *```
       */
      getHexMultiProof(tree, indices) {
          return this.getMultiProof(tree, indices).map((x) => this.bufferToHex(x));
      }
      /**
       * getProofFlags
       * @desc Returns list of booleans where proofs should be used instead of hashing.
       * Proof flags are used in the Solidity multiproof verifiers.
       * @param {Number[]|Buffer[]} leaves
       * @param {Buffer[]} proofs
       * @return {Boolean[]} - Boolean flags
       * @example
       * ```js
       *const indices = [2, 5, 6]
       *const proof = tree.getMultiProof(indices)
       *const proofFlags = tree.getProofFlags(leaves, proof)
       *```
       */
      getProofFlags(leaves, proofs) {
          if (!Array.isArray(leaves) || leaves.length <= 0) {
              throw new Error('Invalid Inputs!');
          }
          let ids;
          if (leaves.every(Number.isInteger)) {
              ids = leaves.sort((a, b) => a === b ? 0 : a > b ? 1 : -1); // Indices where passed
          }
          else {
              ids = leaves.map((el) => this._bufferIndexOf(this.leaves, el)).sort((a, b) => a === b ? 0 : a > b ? 1 : -1);
          }
          if (!ids.every((idx) => idx !== -1)) {
              throw new Error('Element does not exist in Merkle tree');
          }
          const _proofs = proofs.map(item => this.bufferify(item));
          const tested = [];
          const flags = [];
          for (let index = 0; index < this.layers.length; index++) {
              const layer = this.layers[index];
              ids = ids.reduce((ids, idx) => {
                  const skipped = tested.includes(layer[idx]);
                  if (!skipped) {
                      const pairElement = this._getPairNode(layer, idx);
                      const proofUsed = _proofs.includes(layer[idx]) || _proofs.includes(pairElement);
                      pairElement && flags.push(!proofUsed);
                      tested.push(layer[idx]);
                      tested.push(pairElement);
                  }
                  ids.push((idx / 2) | 0);
                  return ids;
              }, []);
          }
          return flags;
      }
      /**
       * verify
       * @desc Returns true if the proof path (array of hashes) can connect the target node
       * to the Merkle root.
       * @param {Object[]} proof - Array of proof objects that should connect
       * target node to Merkle root.
       * @param {Buffer} targetNode - Target node Buffer
       * @param {Buffer} root - Merkle root Buffer
       * @return {Boolean}
       * @example
       *```js
       *const root = tree.getRoot()
       *const proof = tree.getProof(leaves[2])
       *const verified = tree.verify(proof, leaves[2], root)
       *```
       */
      verify(proof, targetNode, root) {
          let hash = this.bufferify(targetNode);
          root = this.bufferify(root);
          if (!Array.isArray(proof) ||
              !targetNode ||
              !root) {
              return false;
          }
          for (let i = 0; i < proof.length; i++) {
              const node = proof[i];
              let data = null;
              let isLeftNode = null;
              // case for when proof is hex values only
              if (typeof node === 'string') {
                  data = this.bufferify(node);
                  isLeftNode = true;
              }
              else if (Array.isArray(node)) {
                  isLeftNode = (node[0] === 0);
                  data = this.bufferify(node[1]);
              }
              else if (Buffer.isBuffer(node)) {
                  data = node;
                  isLeftNode = true;
              }
              else if (node instanceof Object) {
                  data = this.bufferify(node.data);
                  isLeftNode = (node.position === 'left');
              }
              else {
                  throw new Error('Expected node to be of type string or object');
              }
              const buffers = [];
              if (this.isBitcoinTree) {
                  buffers.push(buffer_reverse_1.default(hash));
                  buffers[isLeftNode ? 'unshift' : 'push'](buffer_reverse_1.default(data));
                  hash = this.hashFn(Buffer.concat(buffers));
                  hash = buffer_reverse_1.default(this.hashFn(hash));
              }
              else {
                  if (this.sortPairs) {
                      if (Buffer.compare(hash, data) === -1) {
                          buffers.push(hash, data);
                          hash = this.hashFn(Buffer.concat(buffers));
                      }
                      else {
                          buffers.push(data, hash);
                          hash = this.hashFn(Buffer.concat(buffers));
                      }
                  }
                  else {
                      buffers.push(hash);
                      buffers[isLeftNode ? 'unshift' : 'push'](data);
                      hash = this.hashFn(Buffer.concat(buffers));
                  }
              }
          }
          return Buffer.compare(hash, root) === 0;
      }
      /**
       * verifyMultiProof
       * @desc Returns true if the multiproofs can connect the leaves to the Merkle root.
       * @param {Buffer} root - Merkle tree root
       * @param {Number[]} indices - Leave indices
       * @param {Buffer[]} leaves - Leaf values at indices.
       * @param {Number} depth - Tree depth
       * @param {Buffer[]} proof - Multiproofs given indices
       * @return {Boolean}
       * @example
       *```js
       *const root = tree.getRoot()
       *const treeFlat = tree.getLayersFlat()
       *const depth = tree.getDepth()
       *const indices = [2, 5, 6]
       *const proofLeaves = indices.map(i => leaves[i])
       *const proof = tree.getMultiProof(treeFlat, indices)
       *const verified = tree.verifyMultiProof(root, indices, proofLeaves, depth, proof)
       *```
       */
      verifyMultiProof(root, indices, leaves, depth, proof) {
          root = this.bufferify(root);
          leaves = leaves.map(leaf => this.bufferify(leaf));
          proof = proof.map(leaf => this.bufferify(leaf));
          const tree = {};
          for (const [index, leaf] of this._zip(indices, leaves)) {
              tree[(Math.pow(2, depth)) + index] = leaf;
          }
          for (const [index, proofitem] of this._zip(this.getProofIndices(indices, depth), proof)) {
              tree[index] = proofitem;
          }
          let indexqueue = Object.keys(tree).map(value => +value).sort((a, b) => a - b);
          indexqueue = indexqueue.slice(0, indexqueue.length - 1);
          let i = 0;
          while (i < indexqueue.length) {
              const index = indexqueue[i];
              if (index >= 2 && ({}).hasOwnProperty.call(tree, index ^ 1)) {
                  let pair = [tree[index - (index % 2)], tree[index - (index % 2) + 1]];
                  if (this.sortPairs) {
                      pair = pair.sort(Buffer.compare);
                  }
                  tree[(index / 2) | 0] = this.hashFn(Buffer.concat(pair));
                  indexqueue.push((index / 2) | 0);
              }
              i += 1;
          }
          return !indices.length || (({}).hasOwnProperty.call(tree, 1) && tree[1].equals(root));
      }
      /**
       * getDepth
       * @desc Returns the tree depth (number of layers)
       * @return {Number}
       * @example
       *```js
       *const depth = tree.getDepth()
       *```
       */
      getDepth() {
          return this.getLayers().length - 1;
      }
      /**
       * getLayersAsObject
       * @desc Returns the layers as nested objects instead of an array.
       * @example
       *```js
       *const layersObj = tree.getLayersAsObject()
       *```
       */
      getLayersAsObject() {
          const layers = this.getLayers().map((layer) => layer.map((value) => this.bufferToHex(value, false)));
          const objs = [];
          for (let i = 0; i < layers.length; i++) {
              const arr = [];
              for (let j = 0; j < layers[i].length; j++) {
                  const obj = { [layers[i][j]]: null };
                  if (objs.length) {
                      obj[layers[i][j]] = {};
                      const a = objs.shift();
                      const akey = Object.keys(a)[0];
                      obj[layers[i][j]][akey] = a[akey];
                      if (objs.length) {
                          const b = objs.shift();
                          const bkey = Object.keys(b)[0];
                          obj[layers[i][j]][bkey] = b[bkey];
                      }
                  }
                  arr.push(obj);
              }
              objs.push(...arr);
          }
          return objs[0];
      }
      /**
       * verify
       * @desc Returns true if the proof path (array of hashes) can connect the target node
       * to the Merkle root.
       * @param {Object[]} proof - Array of proof objects that should connect
       * target node to Merkle root.
       * @param {Buffer} targetNode - Target node Buffer
       * @param {Buffer} root - Merkle root Buffer
       * @param {Function} hashFunction - Hash function for hashing leaves and nodes
       * @param {Object} options - Additional options
       * @return {Boolean}
       * @example
       *```js
       *const verified = MerkleTree.verify(proof, leaf, root, sha256, options)
       *```
       */
      static verify(proof, targetNode, root, hashFn = sha256_1.default, options = {}) {
          const tree = new MerkleTree([], hashFn, options);
          return tree.verify(proof, targetNode, root);
      }
      /**
       * getMultiProof
       * @desc Returns the multiproof for given tree indices.
       * @param {Buffer[]} tree - Tree as a flat array.
       * @param {Number[]} indices - Tree indices.
       * @return {Buffer[]} - Multiproofs
       *
       *@example
       * ```js
       *const flatTree = tree.getLayersFlat()
       *const indices = [2, 5, 6]
       *const proof = MerkleTree.getMultiProof(flatTree, indices)
       *```
       */
      static getMultiProof(tree, indices) {
          const t = new MerkleTree([]);
          return t.getMultiProof(tree, indices);
      }
      /**
       * resetTree
       * @desc Resets the tree by clearing the leaves and layers.
       * @example
       *```js
       *tree.resetTree()
       *```
       */
      resetTree() {
          this.leaves = [];
          this.layers = [];
      }
      /**
       * getPairNode
       * @desc Returns the node at the index for given layer.
       * @param {Buffer[]} layer - Tree layer
       * @param {Number} index - Index at layer.
       * @return {Buffer} - Node
       *
       *@example
       * ```js
       *const node = tree.getPairNode(layer, index)
       *```
       */
      _getPairNode(layer, idx) {
          const pairIdx = idx % 2 === 0 ? idx + 1 : idx - 1;
          if (pairIdx < layer.length) {
              return layer[pairIdx];
          }
          else {
              return null;
          }
      }
      /**
       * toTreeString
       * @desc Returns a visual representation of the merkle tree as a string.
       * @return {String}
       * @example
       *```js
       *console.log(tree.toTreeString())
       *```
       */
      _toTreeString() {
          const obj = this.getLayersAsObject();
          return treeify_1.default.asTree(obj, true);
      }
      /**
       * toString
       * @desc Returns a visual representation of the merkle tree as a string.
       * @example
       *```js
       *console.log(tree.toString())
       *```
       */
      toString() {
          return this._toTreeString();
      }
  }
  exports.MerkleTree = MerkleTree;
  if (typeof window !== 'undefined') {
      ;
      window.MerkleTree = MerkleTree;
  }
  exports.default = MerkleTree;
  