Massif blob pre-calculated offsets

Lookup tables for navigating the dynamic, but computable, offsets into the Merkle log binary format

This page provides lookup tables for navigating the dynamic, but computable, offsets into the Merkle log binary format. The algorithms to reproduce this are relatively simple. DataTrails provides open-source implementations, but in many contexts it is simpler to use these pre-calculations. These tables can be made for any log configuration at any time, in part or in whole, without access to any specific log.

This is a quick review of the log format. We explain this in more detail at Navigating the Merkle Log

Each Log Is Comprised of Many Blobs, Each Containing a Fixed Number of Leaves

    +----------+ +----------+ .. +----------+
    | massif 0 | | massif 1 |    | massif n
    +----------+ +----------+ .. +----------+

New leaves are added to the last blob in the log.

Each Individual Blob Has a Fixed Size Portion and Two Variably Sized Sections

    |--------.----------.----------.---------|
    |  fixed  size      | computable size    |
    |--------.----------.----------.---------|
    | header | trieData |peak stack| mmrData |
    |--------.----------.----------.---------|

The Peak Stack and MMR Data Sizes Are Computable

…but it is not always convenient to do so.

Using veracity, the following command line can reproduce a canonical “illustrative” log from Navigating the Merkle Log

veracity --height 2 massifs --count 6

generates:

                       14
                          \
                6           13             21
                  \            \              \
    h=2 1 |  2  |  5  |   9  |  12   |  17   | 20  |
          |     |     |      |       |       |     |
        0 |0   1| 3  4| 7   8|10   11|15   16|18 19| MMR INDICES
        0 |0   1| 2  3| 4   5|6     7|8     9|10 11| LEAF INDICES

The following table Stack Start and mmr Start are byte offsets from the start of the file. Stack Start is the end of the fixed header information, the relevant parts of which are described in Navigating the Merkle Log.

The leaf values are indices into the trie fields (not considered further in this page) and the node values are indices into the array of 32-byte nodes starting at mmr Start

MassifStack Startmmr StartFirst leafLast LeafFirst NodeLast NodePeak Stack
05445440102[]
15445762336[2]
25445764579[6]
3544608671014[6,9]
4544576891517[14]
554460810111821[14,17]

It is fairly easy to validate the leaves and nodes by hand. Reproducing the Stack Start needs details from Navigating the Merkle Log.

Pre-computes for Your First Million Events

DataTrails production logs currently have a massif height of 14, which is results in 8129 leaves, which is $$1^{14-1}$$

MassifStack Startmmr StartFirst leafLast LeafFirst NodeLast NodePeak Stack
01048864104886408191016382[]
1104886410488968192163831638332766[16382]
21048864104889616384245753276749149[32766]
31048864104892824576327674915065534[32766,49149]
41048864104889632768409596553581917[65534]
51048864104892840960491518191898301[65534,81917]
610488641048928491525734398302114684[65534,98301]
7104886410489605734465535114685131070[65534,98301,114684]
8104886410488966553673727131071147453[131070]
9104886410489287372881919147454163837[131070,147453]
10104886410489288192090111163838180220[131070,163837]
11104886410489609011298303180221196605[131070,163837,180220]
121048864104892898304106495196606212988[131070,196605]
1310488641048960106496114687212989229372[131070,196605,212988]
1410488641048960114688122879229373245755[131070,196605,229372]
1510488641048992122880131071245756262142[131070,196605,229372,245755]
1610488641048896131072139263262143278525[262142]
1710488641048928139264147455278526294909[262142,278525]
1810488641048928147456155647294910311292[262142,294909]
1910488641048960155648163839311293327677[262142,294909,311292]
2010488641048928163840172031327678344060[262142,327677]
2110488641048960172032180223344061360444[262142,327677,344060]
2210488641048960180224188415360445376827[262142,327677,360444]
2310488641048992188416196607376828393213[262142,327677,360444,376827]
2410488641048928196608204799393214409596[262142,393213]
2510488641048960204800212991409597425980[262142,393213,409596]
2610488641048960212992221183425981442363[262142,393213,425980]
2710488641048992221184229375442364458748[262142,393213,425980,442363]
2810488641048960229376237567458749475131[262142,393213,458748]
2910488641048992237568245759475132491515[262142,393213,458748,475131]
3010488641048992245760253951491516507898[262142,393213,458748,491515]
3110488641049024253952262143507899524286[262142,393213,458748,491515,507898]
3210488641048896262144270335524287540669[524286]
3310488641048928270336278527540670557053[524286,540669]
3410488641048928278528286719557054573436[524286,557053]
3510488641048960286720294911573437589821[524286,557053,573436]
3610488641048928294912303103589822606204[524286,589821]
3710488641048960303104311295606205622588[524286,589821,606204]
3810488641048960311296319487622589638971[524286,589821,622588]
3910488641048992319488327679638972655357[524286,589821,622588,638971]
4010488641048928327680335871655358671740[524286,655357]
4110488641048960335872344063671741688124[524286,655357,671740]
4210488641048960344064352255688125704507[524286,655357,688124]
4310488641048992352256360447704508720892[524286,655357,688124,704507]
4410488641048960360448368639720893737275[524286,655357,720892]
4510488641048992368640376831737276753659[524286,655357,720892,737275]
4610488641048992376832385023753660770042[524286,655357,720892,753659]
4710488641049024385024393215770043786429[524286,655357,720892,753659,770042]
4810488641048928393216401407786430802812[524286,786429]
4910488641048960401408409599802813819196[524286,786429,802812]
5010488641048960409600417791819197835579[524286,786429,819196]
5110488641048992417792425983835580851964[524286,786429,819196,835579]
5210488641048960425984434175851965868347[524286,786429,851964]
5310488641048992434176442367868348884731[524286,786429,851964,868347]
5410488641048992442368450559884732901114[524286,786429,851964,884731]
5510488641049024450560458751901115917500[524286,786429,851964,884731,901114]
5610488641048960458752466943917501933883[524286,786429,917500]
5710488641048992466944475135933884950267[524286,786429,917500,933883]
5810488641048992475136483327950268966650[524286,786429,917500,950267]
5910488641049024483328491519966651983035[524286,786429,917500,950267,966650]
6010488641048992491520499711983036999418[524286,786429,917500,983035]
61104886410490244997125079039994191015802[524286,786429,917500,983035,999418]

The Algorithms Backing the Table Generation

In combination with the format information at Navigating the Merkle Log the pre-computed tables above can be generated using these examples using the DataTrails open source, go-lang based veracity tooling.

function massifIndex(mmrIndex, massifHeight) {
  const nl =  Number(leafCount(mmrIndex + 1n));
  const f = Number(1n << massifHeight);
  const massifIndex = Math.floor(nl / f);
  return massifIndex;
}

// returns the number of leaves in the largest mmr whose size is <= the supplied size
function leafCount(mmrSize) {
  let pos = BigInt(mmrSize);
  if (pos == BigInt(0)) return 0n;
  let peakSize = ((1n << 64n) - 1n) >> BigInt(clz64(mmrSize));
  let peakMap = 0n;
  for (; peakSize > 0n;) {
    peakMap <<= 1n
    if (pos >= peakSize) {
      pos -= peakSize;
      peakMap |= 1n;
    }
    peakSize >>= 1n;
  }
  return peakMap;
}

function clz64(num) {
  if (!typeof num === 'bigint') throw new Error(`num must be bigint not ${typeof num}`);
  num = BigInt.asUintN(64, num);

  const hi = num >> 32n;
  let lz = Math.clz32(Number(hi));
  if (lz !== 0) return lz;
  const lo = Number((num & ((1n << 32n) - 1n)));
  return 32 + Math.clz32(lo);
}
function treeIndex(iLeaf) {
  let sum = 0n; // Assuming iLeaf can be very large, use BigInt for accuracy.
  let i = BigInt(iLeaf); // Ensure iLeaf is treated as BigInt

  while (i > 0n) {
      const height = log2Uint64(i) + 1n;
      sum += spurSumHeight(height) + BigInt(height);
      const half = 1n << (height - 1n);
      i -= half;
  }

  return sum;
}

// spurSumHeight counts the interior 'spur' nodes required for the given height
function spurSumHeight(height) {
  height = BigInt(height); // Ensure height is treated as BigInt

  if (height === BigInt(0)) {
      return BigInt(0);
  }

  let sum = BigInt(0);
  for (let i = BigInt(1); i <= height - BigInt(1); i += BigInt(1)) {
      sum += (BigInt(1) << (height - BigInt(1) - i)) * i;
  }
  return sum;
}

function log2Uint64(num) {
  if (typeof num === 'bigint') {
      if (num <= 1n) return 0n; // log2(1) = 0 and log2(0) is undefined, handled as 0 for simplicity
      let log = 0n;
      while (num > 1n) {
          num >>= 1n;
          log += 1n;
      }
      return log;
  }

  if (num <= 1) return 0; // Similarly, handle log2(1) = 0 and log2(0) as 0
  return Math.floor(Math.log2(num));
}
/** Calculate the size of a massifs peak stack by passing the massifIndex in place of iLeaf */
function leafMinusSpurSum(iLeaf) {
  let sum = BigInt(iLeaf);
  iLeaf = sum >> BigInt(1);

  while (iLeaf > 0) {
      sum -= iLeaf;
      iLeaf >>= BigInt(1);
  }
  return sum;
}