Skip to content

Merkle (Patricia) proof interface and implementation #10

@slowli

Description

@slowli

Merkle (Patricia) tree proofs essentially prove that a certain element (or several elements) belongs to a certain data collection (list in the case of Merkle trees; map in the case of MPTs). Essentially, a MTs and MPTs are views of lists or maps, in which some elements are unknown. So, it could be logical to implement them as such.

Interface

Thus, it could be logical to have the following interface for MTs and MPTs:

  • constructor(json): creates an MT or MPT based on JSON retrieved from the server. Verifies the internal structure of the tree (see below) and throws TypeError if the provided JSON is invalid
  • hash(): returns the hash of the collection, calculated as per the current algorithm
  • [MT only?] length (read-only property): returns the length of the underlying collection
  • get(index): gets the element at the specified index (integer for MTs, Hash in the case of MPTs). Returns undefined if the element was not in the provided JSON
  • has(index): returns true if the requested index is in the collection view, false otherwise
  • [MPT only?] maybeHas(index): returns true if the specified index may be present in the collection (not necessarily in the current view). Hence, the proof of absence for index is !view.maybeHas(index).
  • Symbol.iterator and entries(): returns the iterator for [index, value] pairs in the view, like the JS Map (maybe, pull other methods from Map too, e.g., forEach, keys and values).

Internal construction

Merkle tree

Recursive definition:

MT<ItemType> = oneOf({
  stub: Hash, // "collapsed" part of the tree
  branch: { left: MT<ItemType>, right: MT<ItemType> },
  sprout: { child: MT<ItemType> }, // a non-branching intermediate node 
  val: ItemType, // value
});

ListView<ItemType> = {
  root: MT<ItemType>,
  length: Uint64
};

Consistency checks:

  • All values are at the depth expected by length
  • All stubs are at the depth not exceeding the same depth
  • The left child in any branch cannot be a sprout
  • (optional) The tree must be non-redundant, e.g., a branch cannot have both stub children

Methods:

  • get(index) can be implemented by traversing the tree from the root
  • hash() can be implemented by traversing the tree from the leaves

Merkle Patricia tree

Recursive definition:

MPT<ItemType> = {
  bits: BitSlice, // key bits connecting the node to its parent
  node: oneOf({
    stub: Hash, // "collapsed" part of the tree
    branch: { left: MPT<ItemType>, right: MPT<ItemType> },
    val: ItemType // value
  })
};

MapView<ItemType> = MPT<ItemType>;

bits may be woven into all type variants if deemed necessary.

Consistency checks:

  • bits may be empty only for the root node
  • For branches, left.bits must start with 0 and right.bits with 1
  • Key length of all values must be as expected (256 bits)
  • Key length of all stubs must not exceed 256 bits
  • (optional) The tree must be non-redundant, e.g., a branch cannot have both stub children

Methods:

  • get(index) and maybeHas(index) can be implemented by traversing the tree from the root
  • hash() can be implemented by traversing the tree from the leaves

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions