From 5c169b13d4b6657b34f1c1b5c5028f7c243132d7 Mon Sep 17 00:00:00 2001 From: Miguel Mota Date: Thu, 10 Nov 2022 22:15:00 -0800 Subject: [PATCH] Add marshal tree method --- docs/classes/_src_merkletree_.merkletree.md | 57 +++++++++++ src/MerkleTree.ts | 46 +++++++++ test/MerkleTree.test.js | 106 ++++++++++++++++++++ 3 files changed, 209 insertions(+) diff --git a/docs/classes/_src_merkletree_.merkletree.md b/docs/classes/_src_merkletree_.merkletree.md index b77d8f2..3670ee6 100644 --- a/docs/classes/_src_merkletree_.merkletree.md +++ b/docs/classes/_src_merkletree_.merkletree.md @@ -48,6 +48,7 @@ Class reprensenting a Merkle Tree * [getLeafIndex](_src_merkletree_.merkletree.md#getleafindex) * [getLeaves](_src_merkletree_.merkletree.md#getleaves) * [getMultiProof](_src_merkletree_.merkletree.md#getmultiproof) +* [getOptions](_src_merkletree_.merkletree.md#getoptions) * [getPositionalHexProof](_src_merkletree_.merkletree.md#getpositionalhexproof) * [getProof](_src_merkletree_.merkletree.md#getproof) * [getProofFlags](_src_merkletree_.merkletree.md#getproofflags) @@ -71,9 +72,11 @@ Class reprensenting a Merkle Tree * [linearSearch](_src_merkletree_.merkletree.md#static-linearsearch) * [marshalLeaves](_src_merkletree_.merkletree.md#static-marshalleaves) * [marshalProof](_src_merkletree_.merkletree.md#static-marshalproof) +* [marshalTree](_src_merkletree_.merkletree.md#static-marshaltree) * [print](_src_merkletree_.merkletree.md#static-print) * [unmarshalLeaves](_src_merkletree_.merkletree.md#static-unmarshalleaves) * [unmarshalProof](_src_merkletree_.merkletree.md#static-unmarshalproof) +* [unmarshalTree](_src_merkletree_.merkletree.md#static-unmarshaltree) * [verify](_src_merkletree_.merkletree.md#static-verify) ## Constructors @@ -736,6 +739,30 @@ Name | Type | Description | ___ +### getOptions + +▸ **getOptions**(): *object* + +**Returns:** *object* + +* **complete**: *boolean* = this.complete + +* **duplicateOdd**: *boolean* = this.duplicateOdd + +* **fillDefaultHash**: *string* = this.fillDefaultHash?.toString() ?? null + +* **hashLeaves**: *boolean* = this.hashLeaves + +* **isBitcoinTree**: *boolean* = this.isBitcoinTree + +* **sort**: *boolean* = this.sort + +* **sortLeaves**: *boolean* = this.sortLeaves + +* **sortPairs**: *boolean* = this.sortPairs + +___ + ### getPositionalHexProof ▸ **getPositionalHexProof**(`leaf`: Buffer | string, `index?`: number): *(string | number)[][]* @@ -1352,6 +1379,20 @@ Name | Type | Description | ___ +### `Static` marshalTree + +▸ **marshalTree**(`tree`: [MerkleTree](_src_merkletree_.merkletree.md)): *string* + +**Parameters:** + +Name | Type | +------ | ------ | +`tree` | [MerkleTree](_src_merkletree_.merkletree.md) | + +**Returns:** *string* + +___ + ### `Static` print ▸ **print**(`tree`: any): *void* @@ -1427,6 +1468,22 @@ Name | Type | ___ +### `Static` unmarshalTree + +▸ **unmarshalTree**(`jsonStr`: string | object, `hashFn`: any, `options`: [Options](../interfaces/_src_merkletree_.options.md)): *[MerkleTree](_src_merkletree_.merkletree.md)* + +**Parameters:** + +Name | Type | Default | +------ | ------ | ------ | +`jsonStr` | string | object | - | +`hashFn` | any | SHA256 | +`options` | [Options](../interfaces/_src_merkletree_.options.md) | {} | + +**Returns:** *[MerkleTree](_src_merkletree_.merkletree.md)* + +___ + ### `Static` verify ▸ **verify**(`proof`: any[], `targetNode`: Buffer | string, `root`: Buffer | string, `hashFn`: any, `options`: [Options](../interfaces/_src_merkletree_.options.md)): *boolean* diff --git a/src/MerkleTree.ts b/src/MerkleTree.ts index a0c2327..d59c503 100644 --- a/src/MerkleTree.ts +++ b/src/MerkleTree.ts @@ -112,6 +112,19 @@ export class MerkleTree extends Base { this.processLeaves(leaves) } + public getOptions() { + return { + complete: this.complete, + isBitcoinTree: this.isBitcoinTree, + hashLeaves: this.hashLeaves, + sortLeaves: this.sortLeaves, + sortPairs: this.sortPairs, + sort: this.sort, + fillDefaultHash: this.fillDefaultHash?.toString() ?? null, + duplicateOdd: this.duplicateOdd + } + } + private processLeaves (leaves: TLeaf[]) { if (this.hashLeaves) { leaves = leaves.map(this.hashFn) @@ -740,6 +753,39 @@ export class MerkleTree extends Base { }) } + static marshalTree (tree: MerkleTree):string { + const root = tree.getHexRoot() + const leaves = tree.leaves.map(leaf => MerkleTree.bufferToHex(leaf)) + const layers = tree.getHexLayers() + const options = tree.getOptions() + + return JSON.stringify({ + options, + root, + layers, + leaves, + }, null, 2) + } + + static unmarshalTree (jsonStr: string | object, hashFn = SHA256, options: Options = {}):MerkleTree { + let parsed :any = 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) { + throw new Error('could not parse json') + } + + options = Object.assign({}, parsed.options || {}, options) + + return new MerkleTree(parsed.leaves, hashFn, options) + } + /** * getProofIndices * @desc Returns the proof indices for given tree indices. diff --git a/test/MerkleTree.test.js b/test/MerkleTree.test.js index 5a6c15c..cc5374e 100644 --- a/test/MerkleTree.test.js +++ b/test/MerkleTree.test.js @@ -1046,6 +1046,112 @@ test('unmarshal proof', t => { t.equal(proof[1].data.toString('hex'), '43e061172b1177f25d0f156b2d2ed11728006fade8e167ff3d1b9dbc979a3358') }) +test('marshal tree', t => { + t.plan(7) + + const leaves = ['a', 'b', 'c'].map(keccak256) + const tree = new MerkleTree(leaves, keccak256, { sort: true }) + const jsonTree = MerkleTree.marshalTree(tree) + t.equal(typeof jsonTree, 'string') + + const parsed = JSON.parse(jsonTree) + t.deepEqual(parsed.options, { + complete: false, + isBitcoinTree: false, + hashLeaves: false, + sortLeaves: true, + sortPairs: true, + sort: true, + fillDefaultHash: null, + duplicateOdd: false + }) + t.equal(parsed.root, '0xc3b537cc8a2c6dcb3657718e1f3505ff751ff8c2eba2a70460df2cbee2b1413a') + t.equal(parsed.leaves.length, 3) + t.deepEqual(parsed.leaves, [ + '0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2', + '0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb', + '0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510' + ]) + t.equal(parsed.layers.length, 3) + t.deepEqual(parsed.layers, [ + [ + '0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2', + '0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb', + '0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510' + ], + [ + '0x7dea550f679f3caab547cbbc5ee1a4c978c8c039b572ba00af1baa6481b88360', + '0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510' + ], + [ + '0xc3b537cc8a2c6dcb3657718e1f3505ff751ff8c2eba2a70460df2cbee2b1413a' + ] + ]) +}) + +test('unmarshal tree', t => { + t.plan(4) + + const json = `{ + "options": { + "complete": false, + "isBitcoinTree": false, + "hashLeaves": false, + "sortLeaves": true, + "sortPairs": true, + "sort": true, + "fillDefaultHash": null, + "duplicateOdd": false + }, + "root": "0xc3b537cc8a2c6dcb3657718e1f3505ff751ff8c2eba2a70460df2cbee2b1413a", + "layers": [ + [ + "0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2", + "0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb", + "0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" + ], + [ + "0x7dea550f679f3caab547cbbc5ee1a4c978c8c039b572ba00af1baa6481b88360", + "0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" + ], + [ + "0xc3b537cc8a2c6dcb3657718e1f3505ff751ff8c2eba2a70460df2cbee2b1413a" + ] + ], + "leaves": [ + "0x0b42b6393c1f53060fe3ddbfcd7aadcca894465a5a438f69c87d790b2299b9b2", + "0x3ac225168df54212a25c1c01fd35bebfea408fdac2e31ddd6f80a4bbf9a5f1cb", + "0xb5553de315e0edf504d9150af82dafa5c4667fa618ed0a6f19c69b41166c5510" + ] + }` + + const parsed = JSON.parse(json) + const tree = MerkleTree.unmarshalTree(json, keccak256) + + t.equal(tree.getHexRoot(), parsed.root) + t.deepEqual(tree.getOptions(), parsed.options) + t.deepEqual(tree.getHexLeaves(), parsed.leaves) + t.deepEqual(tree.getHexLayers(), parsed.layers) +}) + +test('getOptions', t => { + t.plan(1) + + const leaves = ['a', 'b', 'c', 'd', 'e', 'f'].map(sha256) + const tree = new MerkleTree(leaves, sha256, { sortPairs: true, sortLeaves: true }) + + t.deepEqual(tree.getOptions(), { + complete: false, + isBitcoinTree: false, + hashLeaves: false, + sortLeaves: true, + sortPairs: true, + sort: false, + fillDefaultHash: null, + duplicateOdd: false + }) +}) + test('fillDefaultHashes', t => { t.plan(1)