MerkleMountainRange rollUp

This commit is contained in:
Miguel Mota 2021-04-03 11:34:02 -07:00
parent 62b273556b
commit ffd7c4fa9a
No known key found for this signature in database
GPG Key ID: 67EC1161588A00F9
2 changed files with 238 additions and 58 deletions

View File

@ -2,6 +2,9 @@ import Base from './Base'
import SHA256 from 'crypto-js/sha256'
// @credit: https://github.com/wanseob/solidity-mmr
/**
* @desc The index of this MMR implementation starts from 1 not 0.
*/
export class MerkleMountainRange extends Base {
root: Buffer = Buffer.alloc(0)
size: number = 0
@ -26,7 +29,11 @@ export class MerkleMountainRange extends Base {
}
}
append (data: any) {
/**
* @desc This only stores the hashed value of the leaf.
* If you need to retrieve the detail data later, use a map to store them.
*/
append (data: Buffer | string) {
data = this.bufferify(data)
const dataHash = this.hashFn(data)
const dataHashHex = this.bufferToHex(dataHash)
@ -54,6 +61,10 @@ export class MerkleMountainRange extends Base {
this.root = this.peakBagging(this.width, peaks)
}
/**
* @desc It returns the hash of a leaf node with hash(M | DATA )
* M is the index of the node.
*/
hashLeaf (index: number, dataHash: Buffer | string) {
dataHash = this.bufferify(dataHash)
if (this.hashLeafFn) {
@ -62,6 +73,10 @@ export class MerkleMountainRange extends Base {
return this.hashFn(Buffer.concat([this.bufferify(index), dataHash]))
}
/**
* @desc It returns the hash a parent node with hash(M | Left child | Right child)
* M is the index of the node.
*/
hashBranch (index: number, left: any, right: any): any {
if (this.hashBranchFn) {
return this.bufferify(this.hashBranchFn(index, left, right))
@ -86,6 +101,10 @@ export class MerkleMountainRange extends Base {
return this.getSize(width - 1) + 1
}
/**
* @desc It returns all peaks of the smallest merkle mountain range tree which includes
* the given index(size).
*/
getPeakIndexes (width: number): number[] {
const numPeaks = this.numOfPeaks(width)
const peakIndexes = []
@ -103,7 +122,7 @@ export class MerkleMountainRange extends Base {
}
if (count !== peakIndexes.length) {
throw new Error('Invalid bit calculation')
throw new Error('invalid bit calculation')
}
return peakIndexes
@ -124,7 +143,11 @@ export class MerkleMountainRange extends Base {
peakBagging (width: number, peaks: any[]): any {
const size = this.getSize(width)
if (this.numOfPeaks(width) !== peaks.length) {
throw new Error('Received invalid number of peaks')
throw new Error('received invalid number of peaks')
}
if (width === 0 && !peaks.length) {
return Buffer.alloc(0)
}
if (this.peakBaggingFn) {
@ -134,10 +157,16 @@ export class MerkleMountainRange extends Base {
return this.hashFn(Buffer.concat([this.bufferify(size), ...peaks.map(this.bufferify)]))
}
/**
* @desc It returns the size of the tree.
*/
getSize (width: number): number {
return (width << 1) - this.numOfPeaks(width)
}
/**
* @desc It returns the root value of the tree.
*/
getRoot (): any {
return this.root
}
@ -146,10 +175,16 @@ export class MerkleMountainRange extends Base {
return this.bufferToHex(this.getRoot())
}
/**
* @dev It returns the hash value of a node for the given position. Note that the index starts from 1.
*/
getNode (index: number): any {
return this.hashes[index]
}
/**
* @desc It returns the height of the highest peak.
*/
mountainHeight (size: number): number {
let height = 1
while (1 << height <= size + height) {
@ -158,6 +193,9 @@ export class MerkleMountainRange extends Base {
return height - 1
}
/**
* @desc It returns the height of the index.
*/
heightAt (index: number): number {
let reducedIndex = index
let peakIndex = 0
@ -174,41 +212,35 @@ export class MerkleMountainRange extends Base {
return height - (peakIndex - reducedIndex)
}
/**
* @desc It returns whether the index is the leaf node or not
*/
isLeaf (index: number): boolean {
return this.heightAt(index) === 1
}
/**
* @desc It returns the children when it is a parent node.
*/
getChildren (index: number) {
const left = index - (1 << (this.heightAt(index) - 1))
const right = index - 1
if (left === right) {
throw new Error('Not a parent')
throw new Error('not a parent')
}
return [left, right]
}
_getOrCreateNode (index: number) {
if (index > this.size) {
throw new Error('Out of range')
}
if (!this.hashes[index]) {
const [leftIndex, rightIndex] = this.getChildren(index)
const leftHash = this._getOrCreateNode(leftIndex)
const rightHash = this._getOrCreateNode(rightIndex)
this.hashes[index] = this.hashBranch(index, leftHash, rightHash)
}
return this.hashes[index]
}
/**
* @desc It returns a merkle proof for a leaf. Note that the index starts from 1.
*/
getMerkleProof (index: number) {
if (index >= this.size) {
throw new Error('Out of range')
throw new Error('out of range')
}
if (!this.isLeaf(index)) {
throw new Error('Not a leaf')
throw new Error('not a leaf')
}
const root = this.root
@ -255,16 +287,19 @@ export class MerkleMountainRange extends Base {
}
}
/**
* @desc It returns true when the given params verifies that the given value exists in the tree or reverts the transaction.
*/
verify (root: any, width: number, index: number, value: Buffer | string, peaks: any[], siblings: any[]) {
value = this.bufferify(value)
const size = this.getSize(width)
if (size < index) {
throw new Error('Index is out of range')
throw new Error('index is out of range')
}
// check the root equals the peak bagging hash
if (!root.equals(this.peakBagging(width, peaks))) {
throw new Error('Invalid root hash from the peaks')
throw new Error('invalid root hash from the peaks')
}
// find the mountain where the target index belongs to
@ -332,19 +367,124 @@ export class MerkleMountainRange extends Base {
}
peaksToPeakMap (width: number, peaks: any[]) {
throw new Error('not implemented')
const peakMap = {}
let bitIndex = 0
let peakRef = 0
let count = peaks.length
for (let height = 1; height <= 32; height++) {
// index starts from the right most bit
bitIndex = 32 - height
peakRef = 1 << (height - 1)
if ((width & peakRef) !== 0) {
peakMap[bitIndex] = peaks[--count]
} else {
peakMap[bitIndex] = 0
}
}
if (count !== 0) {
throw new Error('invalid number of peaks')
}
return peakMap
}
peakMapToPeaks (width: number, peakMap: any) {
throw new Error('not implemented')
const arrLength = this.numOfPeaks(width)
const peaks = new Array(arrLength)
let count = 0
for (let i = 0; i < 32; i++) {
if (peakMap[i] !== 0) {
peaks[count++] = peakMap[i]
}
}
if (count !== arrLength) {
throw new Error('invalid number of peaks')
}
return peaks
}
peakUpdate (width: number, prevPeakMap: any, itemHash: any) {
throw new Error('not implemented')
const nextPeakMap = {}
const newWidth = width + 1
let cursorIndex = this.getLeafIndex(newWidth)
let cursorNode = this.hashLeaf(cursorIndex, itemHash)
let bitIndex = 0
let peakRef = 0
let prevPeakExist = false
let nextPeakExist = false
let obtained = false
for (let height = 1; height <= 32; height++) {
// index starts from the right most bit
bitIndex = 32 - height
if (obtained) {
nextPeakMap[bitIndex] = prevPeakMap[bitIndex]
} else {
peakRef = 1 << (height - 1)
prevPeakExist = (width & peakRef) !== 0
nextPeakExist = (newWidth & peakRef) !== 0
// get new cursor node with hashing the peak and the current cursor
cursorIndex++
if (prevPeakExist) {
cursorNode = this.hashBranch(cursorIndex, prevPeakMap[bitIndex], cursorNode)
}
// if new peak exists for the bit index
if (nextPeakExist) {
// if prev peak exists for the bit index
if (prevPeakExist) {
nextPeakMap[bitIndex] = prevPeakMap[bitIndex]
} else {
nextPeakMap[bitIndex] = cursorNode
}
obtained = true
} else {
nextPeakMap[bitIndex] = 0
}
}
}
return nextPeakMap
}
rollUp (root: any, width: number, peaks: any[], itemHashes: any[]) {
throw new Error('not implemented')
// check the root equals the peak bagging hash
if (!root.equals(this.peakBagging(width, peaks))) {
throw new Error('invalid root hash from the peaks')
}
let tmpWidth = width
let tmpPeakMap = this.peaksToPeakMap(width, peaks)
for (let i = 0; i < itemHashes.length; i++) {
tmpPeakMap = this.peakUpdate(tmpWidth, tmpPeakMap, itemHashes[i])
tmpWidth++
}
return this.peakBagging(tmpWidth, this.peakMapToPeaks(tmpWidth, tmpPeakMap))
}
/**
* @desc It returns the hash value of the node for the index.
* If the hash already exists it simply returns the stored value. On the other hand,
* it computes hashes recursively downward.
* Only appending an item calls this function.
*/
private _getOrCreateNode (index: number) {
if (index > this.size) {
throw new Error('out of range')
}
if (!this.hashes[index]) {
const [leftIndex, rightIndex] = this.getChildren(index)
const leftHash = this._getOrCreateNode(leftIndex)
const rightHash = this._getOrCreateNode(rightIndex)
this.hashes[index] = this.hashBranch(index, leftHash, rightHash)
}
return this.hashes[index]
}
}

View File

@ -5,6 +5,34 @@ const { keccak256 } = require('ethereumjs-util')
const { MerkleMountainRange } = require('../')
const { soliditySha3 } = require('web3-utils')
const hashLeaf = (index, dataHash) => {
const hash = soliditySha3({ t: 'uint256', v: index }, { t: 'bytes32', v: '0x' + dataHash.toString('hex') })
return hash
}
const peakBagging = (size, peaks) => {
const a = [{ t: 'uint256', v: size }, ...peaks.map(x => ({ t: 'bytes32', v: x.toString('hex') }))]
const x = soliditySha3(...a)
const b = [{ t: 'uint256', v: size }, { t: 'bytes32', v: x.toString('hex') }]
const res = soliditySha3(...b)
return res
}
const hashBranch = (index, left, right) => {
const args = []
if (index !== undefined) {
args.push({ t: 'uint256', v: index })
}
if (left !== undefined) {
args.push({ t: 'bytes32', v: '0x' + left.toString('hex') })
}
if (right !== undefined) {
args.push({ t: 'bytes32', v: '0x' + right.toString('hex') })
}
const hash = soliditySha3(...args)
return hash
}
test('merkle mountain range', t => {
t.plan(6)
@ -31,39 +59,11 @@ test('merkle mountain range', t => {
'0x1b6a474c3e33a28104dd0e75484414b9d35e93c878823bc3b4d9fb5a5109e9eb'
]
const hashLeaf = (index, dataHash) => {
const hash = soliditySha3({ t: 'uint256', v: index }, { t: 'bytes32', v: '0x' + dataHash.toString('hex') })
return hash
}
const peakBagging = (size, peaks) => {
const a = [{ t: 'uint256', v: size }, ...peaks.map(x => ({ t: 'bytes32', v: x.toString('hex') }))]
const x = soliditySha3(...a)
const b = [{ t: 'uint256', v: size }, { t: 'bytes32', v: x.toString('hex') }]
const res = soliditySha3(...b)
return res
}
const hashBranch = (index, left, right) => {
const args = []
if (index !== undefined) {
args.push({ t: 'uint256', v: index })
}
if (left !== undefined) {
args.push({ t: 'bytes32', v: '0x' + left.toString('hex') })
}
if (right !== undefined) {
args.push({ t: 'bytes32', v: '0x' + right.toString('hex') })
}
const hash = soliditySha3(...args)
return hash
}
const tree = new MerkleMountainRange(keccak256, leaves, hashLeaf, peakBagging, hashBranch)
const root = tree.getHexRoot()
const proof = tree.getMerkleProof(2)
t.equal(root, '0x5d6caccebed1b6720659b4a6c022b77d7ed5706ece976aaf44b7cf8e34650eb7')
t.equal(proof.root.toString('hex'), '5d6caccebed1b6720659b4a6c022b77d7ed5706ece976aaf44b7cf8e34650eb7')
t.equal(proof.width, leaves.length)
t.deepEqual(proof.peakBagging.map(x => x.toString('hex')), [
@ -77,10 +77,50 @@ test('merkle mountain range', t => {
'37bc386efc898c6c36b1c2e5a754dafd614398abf146904bcea607af5068605c'
])
t.equal(root, '0x5d6caccebed1b6720659b4a6c022b77d7ed5706ece976aaf44b7cf8e34650eb7')
const leaf = leaves[1]
const index = 2
const verified = tree.verify(proof.root, proof.width, index, leaf, proof.peakBagging, proof.siblings)
t.equal(verified, true)
})
test('merkle mountain range - rollUp', t => {
t.plan(3)
const leaves = [
'0xf6c5123c17fe0d9c8fc675504ffa8cc8f1613c185d16fc8099ffab31cc0f39b8',
'0x6579ce25f7cc6797b1386af5acc4ff8acc9f8054b65dad5fb0c3b0a32c6c4610',
'0x6423808868aca9b46c7ccf2d060ff079cb8cf252f87abe767ea3bdce6c933d0c',
'0xf0f21a35fc749991e54d26c2a7e6ae3509667bc0d897ac7bcf6a462d1bbe7996',
'0x2db0abbe3ef9894b15b7e5e25b538b68ce19812792e3245746cb16c6cfc14345',
'0xfcd354a2e527cb6040e15b1f04aa6df4eb5cd7f250ead21809ef16a348beca6b',
'0xe747a507a27368d45b0e9df1b6cc2cff173e3f739daff1e8bbdd7159263ba8ad',
'0x07d438f3191418363c43a92bcf0555a8b2cf5451d6854932259ede6096c59ed4',
'0xd49690a48a2c2a6772092af6d6eec62cc4285a88997cae9fa4835bf0835c2b4d',
'0x50b25445c2139a3cec3c593b49273f1f47af0eb7d9d9ed37667ef5673d163bb9',
'0x22a6e30c867f4109491e90313ebd0b1438bd8b559cbe85ee9239aa8902a67e74',
'0x1b70b853c66181ccf5d149c263c7cf5e1123d6655bcc0f668c9957db1d98e038',
'0x527efa47060e8eead056eb3b2b7c20821bf6e246828fdd835f3ccc16946568b1',
'0x22a33bbf9177edebe708656bcbe42b7cf077965d2d5fe268dff8878654db06d6',
'0x02c4f6983e8616f6b3e600b3c8b768e71755220a04173b8281440c6b8eb91cd9',
'0x3ed1f8b63a683529477f8813c0955ae058048b9a4fd9a706454f73f86baf977f',
'0x70153ddc9ab0028e922b6b2f9eb319a9a6f1ae37866e7f9771f2866dfbecbc1d',
'0x4c26cfe12f5b2e8b2d992d99c9359a261d0bf50450acb10a5060231b5d41c31c',
'0x86da6c8cc46b60f9eec99353c42e144525a202880db5c877d6d55ee0568c66e4',
'0x1b6a474c3e33a28104dd0e75484414b9d35e93c878823bc3b4d9fb5a5109e9eb'
]
const tree = new MerkleMountainRange(keccak256, [], hashLeaf, peakBagging, hashBranch)
const root = tree.getHexRoot()
t.equal(root, '0x')
const hashedLeaves = leaves.map(x => keccak256(Buffer.from(x.replace('0x', ''), 'hex')))
const rollupRoot = tree.rollUp(tree.root, tree.width, tree.getPeaks(), hashedLeaves)
for (const leaf of leaves) {
tree.append(leaf)
}
const newRoot = tree.getHexRoot()
t.equal(newRoot, '0x5d6caccebed1b6720659b4a6c022b77d7ed5706ece976aaf44b7cf8e34650eb7')
t.equal(newRoot, tree.bufferToHex(rollupRoot))
})