├── .gitignore
├── LICENSE.md
├── README.md
├── img
├── logo.png
├── mmr.jpg
└── mmr.png
├── index.js
├── package-lock.json
├── package.json
├── src
├── db
│ ├── fileBasedDb.js
│ ├── levelDbBasedDb.js
│ └── memoryBasedDb.js
├── digests.js
├── merkleMountainRange.js
└── position.js
└── test
├── fixtures
├── etcLeafData.js
└── etcleafdataFile.mmr
├── mmrInstance.js
└── mmrStatic.js
/.gitignore:
--------------------------------------------------------------------------------
1 | build
2 | /**/build
3 | .DS_Store
4 | node_modules
5 | gitignore
6 | .vscode/
7 | test/etcLeafDataLevelDb/
8 |
--------------------------------------------------------------------------------
/LICENSE.md:
--------------------------------------------------------------------------------
1 | Copyright (c) 2019 Zac Mitton
2 |
3 | Permission is hereby granted, free of charge, to any person obtaining a copy
4 | of this software and associated documentation files (the "Software"), to deal
5 | in the Software without restriction, including without limitation the rights
6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7 | copies of the Software, and to permit persons to whom the Software is
8 | furnished to do so, subject to the following conditions:
9 |
10 | The above copyright notice and this permission notice shall be included in all
11 | copies or substantial portions of the Software.
12 |
13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19 | SOFTWARE.
20 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | #
Merkle Mountain Range
2 |
3 | A type of merkle tree that can be visualized as many (perfect) merkle trees which are then combined into 1, by creating a single root from all of their peaks. The rules for making the tree(s) however are rigidly deterministic such that the entire structure depends only on the number of items put into it. When appending leaves, nodes are not updated, only appended. This makes for a minimal amount of total hash computations (~2n), while maintaining the property that no nodes are updated when appending new leaves (nodes are merely added). Because of this, the _merkle inclusion proofs_ are _also_ only appended to meaning that later proofs are sufficient (supersets of) earlier proofs.
4 |
5 | A Golang version of this can be found [here](https://github.com/zmitton/go-merklemountainrange).
6 |
7 | These unique properties make it optimal for proving the work of an entire PoW blockchain. A protocol that does this is [FlyClient](https://youtu.be/BPNs9EVxWrA?t=8386).
8 |
9 | 
10 |
11 | ## Resources
12 |
13 | MMR data structure as described by Peter Todd:
14 | ([Reyzin and Yakoubov](https://eprint.iacr.org/2015/718.pdf) may have proposed at a similar data structure in 2015)
15 |
16 | - [Basics](https://github.com/opentimestamps/opentimestamps-server/blob/master/doc/merkle-mountain-range.md)
17 | - [Detailed properties and python code](https://github.com/proofchains/python-proofmarshal/blob/master/proofmarshal/mmr.py)
18 | - [implimentation / feture recomendations](https://github.com/mimblewimble/grin/blob/master/doc/mmr.md)
19 | - [an early proposed application](https://lists.linuxfoundation.org/pipermail/bitcoin-dev/2016-May/012715.html)
20 |
21 | I have improved the structure slightly from the above references (by simplifying it). The difference is in the "bagging the peaks" process (used to calculate the merkle-root). Todd's process takes the peaks and creates another merkle tree from them in order to attain a single root. My process is to simply digest the peaks as a single concatenated array to attain a single root.
22 |
23 | Why?
24 |
25 | 1. Using Todd's process it was ([by his own admission](https://github.com/proofchains/python-proofmarshal/blob/master/proofmarshal/mmr.py#L139)) very difficult to logically determine leaf position from an inclusion proof.
26 |
27 | 2. The `getRoot` method concatenates the peaks and hashes them on the fly (the root is never persisted). This process is very cheap because there are only ~log(n)/2 peaks.
28 |
29 |
30 |
31 | ## Use
32 |
33 | ```
34 | npm install merkle-mountain-range
35 | ```
36 |
37 | ```javascript
38 | const { MMR, keccak256FlyHash } = require('merkle-mountain-range')
39 | let mmr = new MMR(keccak256FlyHash)
40 | let genesisHash = Buffer.from('d4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3','hex')
41 | let genesisDifficulty = Buffer.from('0000000000000000000000000000000000000000000000000000000400000000','hex')
42 | let zeroithLeaf = Buffer.concat([genesisHash, genesisDifficulty])
43 | mmr.append(zeroithLeaf, 0).then(()=>{ console.log(mmr) })
44 | ```
45 |
46 | #### Statistics
47 |
48 | With 1000 64-byte leaves (2015 macbookpro)
49 | - MemoryBasedDb
50 | - Time per `append()` = 0.000119s
51 | - Time per `get()` = 0.000289s
52 | - Time per `mmr._getNodeValue(MMR.getNodePosition(leafIndex))` about 0.00002
53 | - FileBasedDb
54 | - Time per `append()` = 0.000301s
55 | - Time per `get()` = 0.000719s
56 | - Time per `mmr._getNodeValue(MMR.getNodePosition(leafIndex))` about 0.00004
57 | - LevelDbBasedDb
58 | - Time per `append()` = 0.000384s
59 | - Time per `get()` = 0.000832s
60 |
61 |
62 | The cost of `mmr.get(leafIndex)` can be reduced by instead using `mmr._getNodeValue(MMR.getNodePosition(leafIndex))`. Because `get()` verifies as it traverses down the tree. Makes it easy to not mess up verification. You can technically get a leaf much faster with a single read (that does not verify) by calculating the position and reading it directly (O(1) instead of O(logn)).
63 |
64 | ### Contributing
65 |
66 | How to contribute, build and release are outlined in [CONTRIBUTING.md](https://github.com/zmitton/pristine/blob/master/CONTRIBUTING.md), [BUILDING.md](https://github.com/zmitton/pristine/blob/master/BUILDING.md) and [RELEASING.md](https://github.com/zmitton/pristine/blob/master/RELEASING.md) respectively. Try to follow the [CONVENTIONAL_COMMITS.md](https://github.com/zmitton/pristine/blob/master/CONVENTIONAL_COMMITS.md) specification.
67 |
68 | Using semantic versioning (more info [here](https://github.com/zmitton/pristine/blob/master/VERSIONING.md))
69 |
70 | Testing uses mocha. It should work to simply pull down the repo, do an `npm install`, and use `npm run test` to run the tests.
71 |
72 |
73 | #### Changelog V0 -> V1
74 |
75 | - `.mmr` file has slightly different (incompatible) format to store `wordsize` (which is no longer locked to 64 bytes)
76 | - FileBasedDb must now be created using `.open` or `.create`. `.open` takes an additional `wordSize` argument (default = 64 bytes)
77 | - Support for serialized proof tree (or sparse tree)
78 |
79 |
80 | - `Digests` has been deprecated (now `_Digests`) because it doesn't belong in this repo. You should include your own hash function package. If it does not have the required function signature - i.e: `` in `` out, then you will have to wrap in in one that does before using it. The digest functions used in Flyclient will be available through a seperate "flyclient" npm package.
81 |
82 | - Added support for LevelDb based databases with optional "key prefix" for data name spacing
83 |
84 | ### Merkle Proofs & Serialization Format
85 |
86 |
87 | The most elegant way of dealing with merkle-proofs is to basically think of them as a sparse tree in which only _some_ of its nodes are present. As long as while performing a `get` the software walks from peak to leaf checking each hash properly matches its children along the way, this proves inclusion before returning the leaf. If any node required in this path traversal is not present, the sparse tree is _insufficient_ to prove this particular leaf (software currently throws Missing node error); If any hash does not match its parents, the proof is _fraudulent_ (throws Hash mismatch error).
88 |
89 |
90 | The merkle Proof data is essentially the same data of a regular MMR (leafLength and nodes) except with only _some_ of the nodes (rather than of all of them). For example the data of a sprase mmr might look like this:
91 |
92 | ```
93 | leafLength: 34
94 | nodes:
95 | {
96 | 30 : <12 34 56 78 90>,
97 | 32 : <12 34 12 34 34>,
98 | 33 : <21 43 65 87 09>
99 | }
100 | ```
101 | (whereas a similar _full_ mmr of length 34 would contain all the nodes from 0-33)
102 |
103 | To serialize this data for remote transmission we first arrange it into very specifically arranged arrays (of byte arrays) for RLP encoding. On the other end, the software knows that the zeroith item is leafLength and the first item is an array of nodes (in which each node is of length 2 to represent the node key pairs):
104 |
105 | ```
106 | [
107 | <22>,
108 | [ <1e>, <12 34 56 78 90> ],
109 | [ <20>, <12 34 12 34 34> ],
110 | [ <21>, <21 43 65 87 09> ]
111 | ]
112 | ```
113 |
114 | Finally we can `rlp.encode()` this data:
115 |
116 | ```
117 |
118 |
119 | ```
120 | `rlp.decode()` gets you exactly back to the above arrays, and the software can reinterpret the items as described above to rebuild the sparse tree. Note that these serialized bytes can be passed from Golang package to JS package and vise-versa!
121 |
122 |
123 | #### NOTES (mostly to self):
124 |
125 | there probably should be a whole framework for requesting more nodes (to complete insufficient proofs). A few ideas on this.
126 |
127 | - somehow the verifier must get the `diff`of node positions that is has, vs ones that it needs.
128 | - it would be maximally flexible to be able to request individual nodes - so we need a `getNodes(nodeIndexes)` method that the prover can use to return them.
129 | - however it would often be more concise to request particular leafIndexes and the prover would calculate which nodes to return to satisfy that. Of course this would likely return some nodes the verifier already has (like always the peaks).
130 | - It would be useful for these requests to include the `leafLength` AND the `root` which would LOCK the prover to an EXACT response any variation from which would be known to be fraudulent (without this, the prover could return nodes from some different tree that it genuinely thinks you are talking about (for instance a different micro-fork/re-org in our blockchain context)).
131 |
132 |
193 |
194 |
195 |
196 |
--------------------------------------------------------------------------------
/img/logo.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zmitton/merkle-mountain-range/ed26683b7ee75ecab8cc2f385ef810da50dfbfd4/img/logo.png
--------------------------------------------------------------------------------
/img/mmr.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zmitton/merkle-mountain-range/ed26683b7ee75ecab8cc2f385ef810da50dfbfd4/img/mmr.jpg
--------------------------------------------------------------------------------
/img/mmr.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zmitton/merkle-mountain-range/ed26683b7ee75ecab8cc2f385ef810da50dfbfd4/img/mmr.png
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | const MMR = require('./src/merkleMountainRange.js')
2 | const MemoryBasedDb = require('./src/db/memoryBasedDB.js')
3 | const FileBasedDb = require('./src/db/fileBasedDB.js')
4 |
5 | const Position = require('./src/position.js')
6 | const _Digests = require('./src/digests')
7 |
8 | module.exports = { MMR, MemoryBasedDb, FileBasedDb, Position, _Digests }
9 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "merkle-mountain-range",
3 | "version": "1.0.0",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "abstract-leveldown": {
8 | "version": "6.2.2",
9 | "resolved": "https://registry.npmjs.org/abstract-leveldown/-/abstract-leveldown-6.2.2.tgz",
10 | "integrity": "sha512-/a+Iwj0rn//CX0EJOasNyZJd2o8xur8Ce9C57Sznti/Ilt/cb6Qd8/k98A4ZOklXgTG+iAYYUs1OTG0s1eH+zQ==",
11 | "requires": {
12 | "level-concat-iterator": "~2.0.0",
13 | "level-supports": "~1.0.0",
14 | "xtend": "~4.0.0"
15 | }
16 | },
17 | "ansi-colors": {
18 | "version": "3.2.3",
19 | "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-3.2.3.tgz",
20 | "integrity": "sha512-LEHHyuhlPY3TmuUYMh2oz89lTShfvgbmzaBcxve9t/9Wuy7Dwf4yoAKcND7KFT1HAQfqZ12qtc+DUrBMeKF9nw==",
21 | "dev": true
22 | },
23 | "ansi-regex": {
24 | "version": "3.0.0",
25 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
26 | "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
27 | "dev": true
28 | },
29 | "ansi-styles": {
30 | "version": "3.2.1",
31 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
32 | "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
33 | "dev": true,
34 | "requires": {
35 | "color-convert": "^1.9.0"
36 | }
37 | },
38 | "argparse": {
39 | "version": "1.0.10",
40 | "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
41 | "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
42 | "dev": true,
43 | "requires": {
44 | "sprintf-js": "~1.0.2"
45 | }
46 | },
47 | "assertion-error": {
48 | "version": "1.1.0",
49 | "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
50 | "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
51 | "dev": true
52 | },
53 | "balanced-match": {
54 | "version": "1.0.0",
55 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
56 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=",
57 | "dev": true
58 | },
59 | "base64-js": {
60 | "version": "1.3.1",
61 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.3.1.tgz",
62 | "integrity": "sha512-mLQ4i2QO1ytvGWFWmcngKO//JXAQueZvwEKtjgQFM4jIK0kU+ytMfplL8j+n5mspOfjHwoAg+9yhb7BwAHm36g=="
63 | },
64 | "bignumber.js": {
65 | "version": "8.1.1",
66 | "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-8.1.1.tgz",
67 | "integrity": "sha512-QD46ppGintwPGuL1KqmwhR0O+N2cZUg8JG/VzwI2e28sM9TqHjQB10lI4QAaMHVbLzwVLLAwEglpKPViWX+5NQ=="
68 | },
69 | "bn.js": {
70 | "version": "4.11.8",
71 | "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.8.tgz",
72 | "integrity": "sha512-ItfYfPLkWHUjckQCk8xC+LwxgK8NYcXywGigJgSwOP8Y2iyWT4f2vsZnoOXTTbo+o5yXmIUJ4gn5538SO5S3gA=="
73 | },
74 | "brace-expansion": {
75 | "version": "1.1.11",
76 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
77 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
78 | "dev": true,
79 | "requires": {
80 | "balanced-match": "^1.0.0",
81 | "concat-map": "0.0.1"
82 | }
83 | },
84 | "browser-stdout": {
85 | "version": "1.3.1",
86 | "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
87 | "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
88 | "dev": true
89 | },
90 | "buffer": {
91 | "version": "5.4.3",
92 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.4.3.tgz",
93 | "integrity": "sha512-zvj65TkFeIt3i6aj5bIvJDzjjQQGs4o/sNoezg1F1kYap9Nu2jcUdpwzRSJTHMMzG0H7bZkn4rNQpImhuxWX2A==",
94 | "requires": {
95 | "base64-js": "^1.0.2",
96 | "ieee754": "^1.1.4"
97 | }
98 | },
99 | "camelcase": {
100 | "version": "5.3.1",
101 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
102 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
103 | "dev": true
104 | },
105 | "chai": {
106 | "version": "4.2.0",
107 | "resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
108 | "integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
109 | "dev": true,
110 | "requires": {
111 | "assertion-error": "^1.1.0",
112 | "check-error": "^1.0.2",
113 | "deep-eql": "^3.0.1",
114 | "get-func-name": "^2.0.0",
115 | "pathval": "^1.1.0",
116 | "type-detect": "^4.0.5"
117 | }
118 | },
119 | "chalk": {
120 | "version": "2.4.2",
121 | "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
122 | "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
123 | "dev": true,
124 | "requires": {
125 | "ansi-styles": "^3.2.1",
126 | "escape-string-regexp": "^1.0.5",
127 | "supports-color": "^5.3.0"
128 | },
129 | "dependencies": {
130 | "supports-color": {
131 | "version": "5.5.0",
132 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
133 | "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
134 | "dev": true,
135 | "requires": {
136 | "has-flag": "^3.0.0"
137 | }
138 | }
139 | }
140 | },
141 | "check-error": {
142 | "version": "1.0.2",
143 | "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
144 | "integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII=",
145 | "dev": true
146 | },
147 | "cliui": {
148 | "version": "4.1.0",
149 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz",
150 | "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==",
151 | "dev": true,
152 | "requires": {
153 | "string-width": "^2.1.1",
154 | "strip-ansi": "^4.0.0",
155 | "wrap-ansi": "^2.0.0"
156 | }
157 | },
158 | "code-point-at": {
159 | "version": "1.1.0",
160 | "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
161 | "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=",
162 | "dev": true
163 | },
164 | "color-convert": {
165 | "version": "1.9.3",
166 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
167 | "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
168 | "dev": true,
169 | "requires": {
170 | "color-name": "1.1.3"
171 | }
172 | },
173 | "color-name": {
174 | "version": "1.1.3",
175 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
176 | "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=",
177 | "dev": true
178 | },
179 | "concat-map": {
180 | "version": "0.0.1",
181 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
182 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=",
183 | "dev": true
184 | },
185 | "cross-spawn": {
186 | "version": "6.0.5",
187 | "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
188 | "integrity": "sha512-eTVLrBSt7fjbDygz805pMnstIs2VTBNkRm0qxZd+M7A5XDdxVRWO5MxGBXZhjY4cqLYLdtrGqRf8mBPmzwSpWQ==",
189 | "dev": true,
190 | "requires": {
191 | "nice-try": "^1.0.4",
192 | "path-key": "^2.0.1",
193 | "semver": "^5.5.0",
194 | "shebang-command": "^1.2.0",
195 | "which": "^1.2.9"
196 | }
197 | },
198 | "debug": {
199 | "version": "3.2.6",
200 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz",
201 | "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==",
202 | "dev": true,
203 | "requires": {
204 | "ms": "^2.1.1"
205 | }
206 | },
207 | "decamelize": {
208 | "version": "1.2.0",
209 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
210 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA=",
211 | "dev": true
212 | },
213 | "deep-eql": {
214 | "version": "3.0.1",
215 | "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
216 | "integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
217 | "dev": true,
218 | "requires": {
219 | "type-detect": "^4.0.0"
220 | }
221 | },
222 | "deferred-leveldown": {
223 | "version": "5.3.0",
224 | "resolved": "https://registry.npmjs.org/deferred-leveldown/-/deferred-leveldown-5.3.0.tgz",
225 | "integrity": "sha512-a59VOT+oDy7vtAbLRCZwWgxu2BaCfd5Hk7wxJd48ei7I+nsg8Orlb9CLG0PMZienk9BSUKgeAqkO2+Lw+1+Ukw==",
226 | "requires": {
227 | "abstract-leveldown": "~6.2.1",
228 | "inherits": "^2.0.3"
229 | }
230 | },
231 | "define-properties": {
232 | "version": "1.1.3",
233 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
234 | "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
235 | "dev": true,
236 | "requires": {
237 | "object-keys": "^1.0.12"
238 | }
239 | },
240 | "diff": {
241 | "version": "3.5.0",
242 | "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz",
243 | "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==",
244 | "dev": true
245 | },
246 | "emoji-regex": {
247 | "version": "7.0.3",
248 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
249 | "integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA==",
250 | "dev": true
251 | },
252 | "encoding-down": {
253 | "version": "6.3.0",
254 | "resolved": "https://registry.npmjs.org/encoding-down/-/encoding-down-6.3.0.tgz",
255 | "integrity": "sha512-QKrV0iKR6MZVJV08QY0wp1e7vF6QbhnbQhb07bwpEyuz4uZiZgPlEGdkCROuFkUwdxlFaiPIhjyarH1ee/3vhw==",
256 | "requires": {
257 | "abstract-leveldown": "^6.2.1",
258 | "inherits": "^2.0.3",
259 | "level-codec": "^9.0.0",
260 | "level-errors": "^2.0.0"
261 | }
262 | },
263 | "end-of-stream": {
264 | "version": "1.4.1",
265 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.1.tgz",
266 | "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==",
267 | "dev": true,
268 | "requires": {
269 | "once": "^1.4.0"
270 | }
271 | },
272 | "errno": {
273 | "version": "0.1.7",
274 | "resolved": "https://registry.npmjs.org/errno/-/errno-0.1.7.tgz",
275 | "integrity": "sha512-MfrRBDWzIWifgq6tJj60gkAwtLNb6sQPlcFrSOflcP1aFmmruKQ2wRnze/8V6kgyz7H3FF8Npzv78mZ7XLLflg==",
276 | "requires": {
277 | "prr": "~1.0.1"
278 | }
279 | },
280 | "es-abstract": {
281 | "version": "1.13.0",
282 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.13.0.tgz",
283 | "integrity": "sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg==",
284 | "dev": true,
285 | "requires": {
286 | "es-to-primitive": "^1.2.0",
287 | "function-bind": "^1.1.1",
288 | "has": "^1.0.3",
289 | "is-callable": "^1.1.4",
290 | "is-regex": "^1.0.4",
291 | "object-keys": "^1.0.12"
292 | }
293 | },
294 | "es-to-primitive": {
295 | "version": "1.2.0",
296 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.0.tgz",
297 | "integrity": "sha512-qZryBOJjV//LaxLTV6UC//WewneB3LcXOL9NP++ozKVXsIIIpm/2c13UDiD9Jp2eThsecw9m3jPqDwTyobcdbg==",
298 | "dev": true,
299 | "requires": {
300 | "is-callable": "^1.1.4",
301 | "is-date-object": "^1.0.1",
302 | "is-symbol": "^1.0.2"
303 | }
304 | },
305 | "escape-string-regexp": {
306 | "version": "1.0.5",
307 | "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
308 | "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=",
309 | "dev": true
310 | },
311 | "esprima": {
312 | "version": "4.0.1",
313 | "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
314 | "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
315 | "dev": true
316 | },
317 | "execa": {
318 | "version": "1.0.0",
319 | "resolved": "https://registry.npmjs.org/execa/-/execa-1.0.0.tgz",
320 | "integrity": "sha512-adbxcyWV46qiHyvSp50TKt05tB4tK3HcmF7/nxfAdhnox83seTDbwnaqKO4sXRy7roHAIFqJP/Rw/AuEbX61LA==",
321 | "dev": true,
322 | "requires": {
323 | "cross-spawn": "^6.0.0",
324 | "get-stream": "^4.0.0",
325 | "is-stream": "^1.1.0",
326 | "npm-run-path": "^2.0.0",
327 | "p-finally": "^1.0.0",
328 | "signal-exit": "^3.0.0",
329 | "strip-eof": "^1.0.0"
330 | }
331 | },
332 | "find-up": {
333 | "version": "3.0.0",
334 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
335 | "integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
336 | "dev": true,
337 | "requires": {
338 | "locate-path": "^3.0.0"
339 | }
340 | },
341 | "flat": {
342 | "version": "4.1.0",
343 | "resolved": "https://registry.npmjs.org/flat/-/flat-4.1.0.tgz",
344 | "integrity": "sha512-Px/TiLIznH7gEDlPXcUD4KnBusa6kR6ayRUVcnEAbreRIuhkqow/mun59BuRXwoYk7ZQOLW1ZM05ilIvK38hFw==",
345 | "dev": true,
346 | "requires": {
347 | "is-buffer": "~2.0.3"
348 | }
349 | },
350 | "fs.realpath": {
351 | "version": "1.0.0",
352 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
353 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=",
354 | "dev": true
355 | },
356 | "function-bind": {
357 | "version": "1.1.1",
358 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
359 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
360 | "dev": true
361 | },
362 | "get-caller-file": {
363 | "version": "2.0.5",
364 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
365 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
366 | "dev": true
367 | },
368 | "get-func-name": {
369 | "version": "2.0.0",
370 | "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
371 | "integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE=",
372 | "dev": true
373 | },
374 | "get-stream": {
375 | "version": "4.1.0",
376 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-4.1.0.tgz",
377 | "integrity": "sha512-GMat4EJ5161kIy2HevLlr4luNjBgvmj413KaQA7jt4V8B4RDsfpHk7WQ9GVqfYyyx8OS/L66Kox+rJRNklLK7w==",
378 | "dev": true,
379 | "requires": {
380 | "pump": "^3.0.0"
381 | }
382 | },
383 | "glob": {
384 | "version": "7.1.3",
385 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz",
386 | "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==",
387 | "dev": true,
388 | "requires": {
389 | "fs.realpath": "^1.0.0",
390 | "inflight": "^1.0.4",
391 | "inherits": "2",
392 | "minimatch": "^3.0.4",
393 | "once": "^1.3.0",
394 | "path-is-absolute": "^1.0.0"
395 | }
396 | },
397 | "growl": {
398 | "version": "1.10.5",
399 | "resolved": "https://registry.npmjs.org/growl/-/growl-1.10.5.tgz",
400 | "integrity": "sha512-qBr4OuELkhPenW6goKVXiv47US3clb3/IbuWF9KNKEijAy9oeHxU9IgzjvJhHkUzhaj7rOUD7+YGWqUjLp5oSA==",
401 | "dev": true
402 | },
403 | "has": {
404 | "version": "1.0.3",
405 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
406 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
407 | "dev": true,
408 | "requires": {
409 | "function-bind": "^1.1.1"
410 | }
411 | },
412 | "has-flag": {
413 | "version": "3.0.0",
414 | "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
415 | "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=",
416 | "dev": true
417 | },
418 | "has-symbols": {
419 | "version": "1.0.0",
420 | "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.0.tgz",
421 | "integrity": "sha1-uhqPGvKg/DllD1yFA2dwQSIGO0Q=",
422 | "dev": true
423 | },
424 | "he": {
425 | "version": "1.2.0",
426 | "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
427 | "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
428 | "dev": true
429 | },
430 | "ieee754": {
431 | "version": "1.1.13",
432 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.13.tgz",
433 | "integrity": "sha512-4vf7I2LYV/HaWerSo3XmlMkp5eZ83i+/CDluXi/IGTs/O1sejBNhTtnxzmRZfvOUqj7lZjqHkeTvpgSFDlWZTg=="
434 | },
435 | "immediate": {
436 | "version": "3.2.3",
437 | "resolved": "https://registry.npmjs.org/immediate/-/immediate-3.2.3.tgz",
438 | "integrity": "sha1-0UD6j2FGWb1lQSMwl92qwlzdmRw="
439 | },
440 | "inflight": {
441 | "version": "1.0.6",
442 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
443 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
444 | "dev": true,
445 | "requires": {
446 | "once": "^1.3.0",
447 | "wrappy": "1"
448 | }
449 | },
450 | "inherits": {
451 | "version": "2.0.3",
452 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz",
453 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4="
454 | },
455 | "invert-kv": {
456 | "version": "2.0.0",
457 | "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-2.0.0.tgz",
458 | "integrity": "sha512-wPVv/y/QQ/Uiirj/vh3oP+1Ww+AWehmi1g5fFWGPF6IpCBCDVrhgHRMvrLfdYcwDh3QJbGXDW4JAuzxElLSqKA==",
459 | "dev": true
460 | },
461 | "is-buffer": {
462 | "version": "2.0.3",
463 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz",
464 | "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==",
465 | "dev": true
466 | },
467 | "is-callable": {
468 | "version": "1.1.4",
469 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.4.tgz",
470 | "integrity": "sha512-r5p9sxJjYnArLjObpjA4xu5EKI3CuKHkJXMhT7kwbpUyIFD1n5PMAsoPvWnvtZiNz7LjkYDRZhd7FlI0eMijEA==",
471 | "dev": true
472 | },
473 | "is-date-object": {
474 | "version": "1.0.1",
475 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz",
476 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=",
477 | "dev": true
478 | },
479 | "is-fullwidth-code-point": {
480 | "version": "2.0.0",
481 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz",
482 | "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=",
483 | "dev": true
484 | },
485 | "is-regex": {
486 | "version": "1.0.4",
487 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz",
488 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=",
489 | "dev": true,
490 | "requires": {
491 | "has": "^1.0.1"
492 | }
493 | },
494 | "is-stream": {
495 | "version": "1.1.0",
496 | "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-1.1.0.tgz",
497 | "integrity": "sha1-EtSj3U5o4Lec6428hBc66A2RykQ=",
498 | "dev": true
499 | },
500 | "is-symbol": {
501 | "version": "1.0.2",
502 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.2.tgz",
503 | "integrity": "sha512-HS8bZ9ox60yCJLH9snBpIwv9pYUAkcuLhSA1oero1UB5y9aiQpRA8y2ex945AOtCZL1lJDeIk3G5LthswI46Lw==",
504 | "dev": true,
505 | "requires": {
506 | "has-symbols": "^1.0.0"
507 | }
508 | },
509 | "isexe": {
510 | "version": "2.0.0",
511 | "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
512 | "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
513 | "dev": true
514 | },
515 | "js-sha3": {
516 | "version": "0.8.0",
517 | "resolved": "https://registry.npmjs.org/js-sha3/-/js-sha3-0.8.0.tgz",
518 | "integrity": "sha512-gF1cRrHhIzNfToc802P800N8PpXS+evLLXfsVpowqmAFR9uwbi89WvXg2QspOmXL8QL86J4T1EpFu+yUkwJY3Q=="
519 | },
520 | "js-yaml": {
521 | "version": "3.13.1",
522 | "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.13.1.tgz",
523 | "integrity": "sha512-YfbcO7jXDdyj0DGxYVSlSeQNHbD7XPWvrVWeVUujrQEoZzWJIRrCPoyk6kL6IAjAG2IolMK4T0hNUe0HOUs5Jw==",
524 | "dev": true,
525 | "requires": {
526 | "argparse": "^1.0.7",
527 | "esprima": "^4.0.0"
528 | }
529 | },
530 | "lcid": {
531 | "version": "2.0.0",
532 | "resolved": "https://registry.npmjs.org/lcid/-/lcid-2.0.0.tgz",
533 | "integrity": "sha512-avPEb8P8EGnwXKClwsNUgryVjllcRqtMYa49NTsbQagYuT1DcXnl1915oxWjoyGrXR6zH/Y0Zc96xWsPcoDKeA==",
534 | "dev": true,
535 | "requires": {
536 | "invert-kv": "^2.0.0"
537 | }
538 | },
539 | "level": {
540 | "version": "6.0.0",
541 | "resolved": "https://registry.npmjs.org/level/-/level-6.0.0.tgz",
542 | "integrity": "sha512-3oAi7gXLLNr7pHj8c4vbI6lHkXf35m8qb7zWMrNTrOax6CXBVggQAwL1xnC/1CszyYrW3BsLXsY5TMgTxtKfFA==",
543 | "requires": {
544 | "level-js": "^5.0.0",
545 | "level-packager": "^5.1.0",
546 | "leveldown": "^5.4.0",
547 | "opencollective-postinstall": "^2.0.0"
548 | }
549 | },
550 | "level-codec": {
551 | "version": "9.0.1",
552 | "resolved": "https://registry.npmjs.org/level-codec/-/level-codec-9.0.1.tgz",
553 | "integrity": "sha512-ajFP0kJ+nyq4i6kptSM+mAvJKLOg1X5FiFPtLG9M5gCEZyBmgDi3FkDrvlMkEzrUn1cWxtvVmrvoS4ASyO/q+Q=="
554 | },
555 | "level-concat-iterator": {
556 | "version": "2.0.1",
557 | "resolved": "https://registry.npmjs.org/level-concat-iterator/-/level-concat-iterator-2.0.1.tgz",
558 | "integrity": "sha512-OTKKOqeav2QWcERMJR7IS9CUo1sHnke2C0gkSmcR7QuEtFNLLzHQAvnMw8ykvEcv0Qtkg0p7FOwP1v9e5Smdcw=="
559 | },
560 | "level-errors": {
561 | "version": "2.0.1",
562 | "resolved": "https://registry.npmjs.org/level-errors/-/level-errors-2.0.1.tgz",
563 | "integrity": "sha512-UVprBJXite4gPS+3VznfgDSU8PTRuVX0NXwoWW50KLxd2yw4Y1t2JUR5In1itQnudZqRMT9DlAM3Q//9NCjCFw==",
564 | "requires": {
565 | "errno": "~0.1.1"
566 | }
567 | },
568 | "level-iterator-stream": {
569 | "version": "4.0.2",
570 | "resolved": "https://registry.npmjs.org/level-iterator-stream/-/level-iterator-stream-4.0.2.tgz",
571 | "integrity": "sha512-ZSthfEqzGSOMWoUGhTXdX9jv26d32XJuHz/5YnuHZzH6wldfWMOVwI9TBtKcya4BKTyTt3XVA0A3cF3q5CY30Q==",
572 | "requires": {
573 | "inherits": "^2.0.4",
574 | "readable-stream": "^3.4.0",
575 | "xtend": "^4.0.2"
576 | },
577 | "dependencies": {
578 | "inherits": {
579 | "version": "2.0.4",
580 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
581 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
582 | }
583 | }
584 | },
585 | "level-js": {
586 | "version": "5.0.1",
587 | "resolved": "https://registry.npmjs.org/level-js/-/level-js-5.0.1.tgz",
588 | "integrity": "sha512-Jse0/UP2qFl2YN8HISnxwQIRHNM3vJLafkTKjw4tZs9Vi8VGUFVmGvg/WMn5To/6KHyYJTXZvUzuouoaZuPGXA==",
589 | "requires": {
590 | "abstract-leveldown": "~6.2.1",
591 | "immediate": "~3.2.3",
592 | "inherits": "^2.0.3",
593 | "ltgt": "^2.1.2"
594 | }
595 | },
596 | "level-packager": {
597 | "version": "5.1.1",
598 | "resolved": "https://registry.npmjs.org/level-packager/-/level-packager-5.1.1.tgz",
599 | "integrity": "sha512-HMwMaQPlTC1IlcwT3+swhqf/NUO+ZhXVz6TY1zZIIZlIR0YSn8GtAAWmIvKjNY16ZkEg/JcpAuQskxsXqC0yOQ==",
600 | "requires": {
601 | "encoding-down": "^6.3.0",
602 | "levelup": "^4.3.2"
603 | }
604 | },
605 | "level-supports": {
606 | "version": "1.0.1",
607 | "resolved": "https://registry.npmjs.org/level-supports/-/level-supports-1.0.1.tgz",
608 | "integrity": "sha512-rXM7GYnW8gsl1vedTJIbzOrRv85c/2uCMpiiCzO2fndd06U/kUXEEU9evYn4zFggBOg36IsBW8LzqIpETwwQzg==",
609 | "requires": {
610 | "xtend": "^4.0.2"
611 | }
612 | },
613 | "leveldown": {
614 | "version": "5.4.1",
615 | "resolved": "https://registry.npmjs.org/leveldown/-/leveldown-5.4.1.tgz",
616 | "integrity": "sha512-3lMPc7eU3yj5g+qF1qlALInzIYnkySIosR1AsUKFjL9D8fYbTLuENBAeDRZXIG4qeWOAyqRItOoLu2v2avWiMA==",
617 | "requires": {
618 | "abstract-leveldown": "~6.2.1",
619 | "napi-macros": "~2.0.0",
620 | "node-gyp-build": "~4.1.0"
621 | }
622 | },
623 | "levelup": {
624 | "version": "4.3.2",
625 | "resolved": "https://registry.npmjs.org/levelup/-/levelup-4.3.2.tgz",
626 | "integrity": "sha512-cRTjU4ktWo59wf13PHEiOayHC3n0dOh4i5+FHr4tv4MX9+l7mqETicNq3Aj07HKlLdk0z5muVoDL2RD+ovgiyA==",
627 | "requires": {
628 | "deferred-leveldown": "~5.3.0",
629 | "level-errors": "~2.0.0",
630 | "level-iterator-stream": "~4.0.0",
631 | "level-supports": "~1.0.0",
632 | "xtend": "~4.0.0"
633 | }
634 | },
635 | "locate-path": {
636 | "version": "3.0.0",
637 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
638 | "integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
639 | "dev": true,
640 | "requires": {
641 | "p-locate": "^3.0.0",
642 | "path-exists": "^3.0.0"
643 | }
644 | },
645 | "lodash": {
646 | "version": "4.17.15",
647 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz",
648 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==",
649 | "dev": true
650 | },
651 | "log-symbols": {
652 | "version": "2.2.0",
653 | "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-2.2.0.tgz",
654 | "integrity": "sha512-VeIAFslyIerEJLXHziedo2basKbMKtTw3vfn5IzG0XTjhAVEJyNHnL2p7vc+wBDSdQuUpNw3M2u6xb9QsAY5Eg==",
655 | "dev": true,
656 | "requires": {
657 | "chalk": "^2.0.1"
658 | }
659 | },
660 | "ltgt": {
661 | "version": "2.2.1",
662 | "resolved": "https://registry.npmjs.org/ltgt/-/ltgt-2.2.1.tgz",
663 | "integrity": "sha1-81ypHEk/e3PaDgdJUwTxezH4fuU="
664 | },
665 | "map-age-cleaner": {
666 | "version": "0.1.3",
667 | "resolved": "https://registry.npmjs.org/map-age-cleaner/-/map-age-cleaner-0.1.3.tgz",
668 | "integrity": "sha512-bJzx6nMoP6PDLPBFmg7+xRKeFZvFboMrGlxmNj9ClvX53KrmvM5bXFXEWjbz4cz1AFn+jWJ9z/DJSz7hrs0w3w==",
669 | "dev": true,
670 | "requires": {
671 | "p-defer": "^1.0.0"
672 | }
673 | },
674 | "mem": {
675 | "version": "4.3.0",
676 | "resolved": "https://registry.npmjs.org/mem/-/mem-4.3.0.tgz",
677 | "integrity": "sha512-qX2bG48pTqYRVmDB37rn/6PT7LcR8T7oAX3bf99u1Tt1nzxYfxkgqDwUwolPlXweM0XzBOBFzSx4kfp7KP1s/w==",
678 | "dev": true,
679 | "requires": {
680 | "map-age-cleaner": "^0.1.1",
681 | "mimic-fn": "^2.0.0",
682 | "p-is-promise": "^2.0.0"
683 | }
684 | },
685 | "mimic-fn": {
686 | "version": "2.1.0",
687 | "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz",
688 | "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==",
689 | "dev": true
690 | },
691 | "minimatch": {
692 | "version": "3.0.4",
693 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
694 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
695 | "dev": true,
696 | "requires": {
697 | "brace-expansion": "^1.1.7"
698 | }
699 | },
700 | "minimist": {
701 | "version": "0.0.8",
702 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz",
703 | "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=",
704 | "dev": true
705 | },
706 | "mkdirp": {
707 | "version": "0.5.1",
708 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz",
709 | "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=",
710 | "dev": true,
711 | "requires": {
712 | "minimist": "0.0.8"
713 | }
714 | },
715 | "mocha": {
716 | "version": "6.1.4",
717 | "resolved": "https://registry.npmjs.org/mocha/-/mocha-6.1.4.tgz",
718 | "integrity": "sha512-PN8CIy4RXsIoxoFJzS4QNnCH4psUCPWc4/rPrst/ecSJJbLBkubMiyGCP2Kj/9YnWbotFqAoeXyXMucj7gwCFg==",
719 | "dev": true,
720 | "requires": {
721 | "ansi-colors": "3.2.3",
722 | "browser-stdout": "1.3.1",
723 | "debug": "3.2.6",
724 | "diff": "3.5.0",
725 | "escape-string-regexp": "1.0.5",
726 | "find-up": "3.0.0",
727 | "glob": "7.1.3",
728 | "growl": "1.10.5",
729 | "he": "1.2.0",
730 | "js-yaml": "3.13.1",
731 | "log-symbols": "2.2.0",
732 | "minimatch": "3.0.4",
733 | "mkdirp": "0.5.1",
734 | "ms": "2.1.1",
735 | "node-environment-flags": "1.0.5",
736 | "object.assign": "4.1.0",
737 | "strip-json-comments": "2.0.1",
738 | "supports-color": "6.0.0",
739 | "which": "1.3.1",
740 | "wide-align": "1.1.3",
741 | "yargs": "13.2.2",
742 | "yargs-parser": "13.0.0",
743 | "yargs-unparser": "1.5.0"
744 | }
745 | },
746 | "ms": {
747 | "version": "2.1.1",
748 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz",
749 | "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==",
750 | "dev": true
751 | },
752 | "napi-macros": {
753 | "version": "2.0.0",
754 | "resolved": "https://registry.npmjs.org/napi-macros/-/napi-macros-2.0.0.tgz",
755 | "integrity": "sha512-A0xLykHtARfueITVDernsAWdtIMbOJgKgcluwENp3AlsKN/PloyO10HtmoqnFAQAcxPkgZN7wdfPfEd0zNGxbg=="
756 | },
757 | "nice-try": {
758 | "version": "1.0.5",
759 | "resolved": "https://registry.npmjs.org/nice-try/-/nice-try-1.0.5.tgz",
760 | "integrity": "sha512-1nh45deeb5olNY7eX82BkPO7SSxR5SSYJiPTrTdFUVYwAl8CKMA5N9PjTYkHiRjisVcxcQ1HXdLhx2qxxJzLNQ==",
761 | "dev": true
762 | },
763 | "node-environment-flags": {
764 | "version": "1.0.5",
765 | "resolved": "https://registry.npmjs.org/node-environment-flags/-/node-environment-flags-1.0.5.tgz",
766 | "integrity": "sha512-VNYPRfGfmZLx0Ye20jWzHUjyTW/c+6Wq+iLhDzUI4XmhrDd9l/FozXV3F2xOaXjvp0co0+v1YSR3CMP6g+VvLQ==",
767 | "dev": true,
768 | "requires": {
769 | "object.getownpropertydescriptors": "^2.0.3",
770 | "semver": "^5.7.0"
771 | }
772 | },
773 | "node-gyp-build": {
774 | "version": "4.1.1",
775 | "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.1.1.tgz",
776 | "integrity": "sha512-dSq1xmcPDKPZ2EED2S6zw/b9NKsqzXRE6dVr8TVQnI3FJOTteUMuqF3Qqs6LZg+mLGYJWqQzMbIjMtJqTv87nQ=="
777 | },
778 | "npm-run-path": {
779 | "version": "2.0.2",
780 | "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-2.0.2.tgz",
781 | "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=",
782 | "dev": true,
783 | "requires": {
784 | "path-key": "^2.0.0"
785 | }
786 | },
787 | "number-is-nan": {
788 | "version": "1.0.1",
789 | "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
790 | "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=",
791 | "dev": true
792 | },
793 | "object-keys": {
794 | "version": "1.1.1",
795 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
796 | "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
797 | "dev": true
798 | },
799 | "object.assign": {
800 | "version": "4.1.0",
801 | "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz",
802 | "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==",
803 | "dev": true,
804 | "requires": {
805 | "define-properties": "^1.1.2",
806 | "function-bind": "^1.1.1",
807 | "has-symbols": "^1.0.0",
808 | "object-keys": "^1.0.11"
809 | }
810 | },
811 | "object.getownpropertydescriptors": {
812 | "version": "2.0.3",
813 | "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.0.3.tgz",
814 | "integrity": "sha1-h1jIRvW0B62rDyNuCYbxSwUcqhY=",
815 | "dev": true,
816 | "requires": {
817 | "define-properties": "^1.1.2",
818 | "es-abstract": "^1.5.1"
819 | }
820 | },
821 | "once": {
822 | "version": "1.4.0",
823 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
824 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
825 | "dev": true,
826 | "requires": {
827 | "wrappy": "1"
828 | }
829 | },
830 | "opencollective-postinstall": {
831 | "version": "2.0.2",
832 | "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.2.tgz",
833 | "integrity": "sha512-pVOEP16TrAO2/fjej1IdOyupJY8KDUM1CvsaScRbw6oddvpQoOfGk4ywha0HKKVAD6RkW4x6Q+tNBwhf3Bgpuw=="
834 | },
835 | "os-locale": {
836 | "version": "3.1.0",
837 | "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-3.1.0.tgz",
838 | "integrity": "sha512-Z8l3R4wYWM40/52Z+S265okfFj8Kt2cC2MKY+xNi3kFs+XGI7WXu/I309QQQYbRW4ijiZ+yxs9pqEhJh0DqW3Q==",
839 | "dev": true,
840 | "requires": {
841 | "execa": "^1.0.0",
842 | "lcid": "^2.0.0",
843 | "mem": "^4.0.0"
844 | }
845 | },
846 | "p-defer": {
847 | "version": "1.0.0",
848 | "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz",
849 | "integrity": "sha1-n26xgvbJqozXQwBKfU+WsZaw+ww=",
850 | "dev": true
851 | },
852 | "p-finally": {
853 | "version": "1.0.0",
854 | "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz",
855 | "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=",
856 | "dev": true
857 | },
858 | "p-is-promise": {
859 | "version": "2.1.0",
860 | "resolved": "https://registry.npmjs.org/p-is-promise/-/p-is-promise-2.1.0.tgz",
861 | "integrity": "sha512-Y3W0wlRPK8ZMRbNq97l4M5otioeA5lm1z7bkNkxCka8HSPjR0xRWmpCmc9utiaLP9Jb1eD8BgeIxTW4AIF45Pg==",
862 | "dev": true
863 | },
864 | "p-limit": {
865 | "version": "2.2.0",
866 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.2.0.tgz",
867 | "integrity": "sha512-pZbTJpoUsCzV48Mc9Nh51VbwO0X9cuPFE8gYwx9BTCt9SF8/b7Zljd2fVgOxhIF/HDTKgpVzs+GPhyKfjLLFRQ==",
868 | "dev": true,
869 | "requires": {
870 | "p-try": "^2.0.0"
871 | }
872 | },
873 | "p-locate": {
874 | "version": "3.0.0",
875 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
876 | "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
877 | "dev": true,
878 | "requires": {
879 | "p-limit": "^2.0.0"
880 | }
881 | },
882 | "p-try": {
883 | "version": "2.2.0",
884 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
885 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
886 | "dev": true
887 | },
888 | "path-exists": {
889 | "version": "3.0.0",
890 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
891 | "integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU=",
892 | "dev": true
893 | },
894 | "path-is-absolute": {
895 | "version": "1.0.1",
896 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
897 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=",
898 | "dev": true
899 | },
900 | "path-key": {
901 | "version": "2.0.1",
902 | "resolved": "https://registry.npmjs.org/path-key/-/path-key-2.0.1.tgz",
903 | "integrity": "sha1-QRyttXTFoUDTpLGRDUDYDMn0C0A=",
904 | "dev": true
905 | },
906 | "pathval": {
907 | "version": "1.1.0",
908 | "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
909 | "integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA=",
910 | "dev": true
911 | },
912 | "prr": {
913 | "version": "1.0.1",
914 | "resolved": "https://registry.npmjs.org/prr/-/prr-1.0.1.tgz",
915 | "integrity": "sha1-0/wRS6BplaRexok/SEzrHXj19HY="
916 | },
917 | "pump": {
918 | "version": "3.0.0",
919 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
920 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
921 | "dev": true,
922 | "requires": {
923 | "end-of-stream": "^1.1.0",
924 | "once": "^1.3.1"
925 | }
926 | },
927 | "readable-stream": {
928 | "version": "3.4.0",
929 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.4.0.tgz",
930 | "integrity": "sha512-jItXPLmrSR8jmTRmRWJXCnGJsfy85mB3Wd/uINMXA65yrnFo0cPClFIUWzo2najVNSl+mx7/4W8ttlLWJe99pQ==",
931 | "requires": {
932 | "inherits": "^2.0.3",
933 | "string_decoder": "^1.1.1",
934 | "util-deprecate": "^1.0.1"
935 | }
936 | },
937 | "require-directory": {
938 | "version": "2.1.1",
939 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
940 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=",
941 | "dev": true
942 | },
943 | "require-main-filename": {
944 | "version": "2.0.0",
945 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
946 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==",
947 | "dev": true
948 | },
949 | "rlp": {
950 | "version": "2.2.3",
951 | "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.3.tgz",
952 | "integrity": "sha512-l6YVrI7+d2vpW6D6rS05x2Xrmq8oW7v3pieZOJKBEdjuTF4Kz/iwk55Zyh1Zaz+KOB2kC8+2jZlp2u9L4tTzCQ==",
953 | "requires": {
954 | "bn.js": "^4.11.1",
955 | "safe-buffer": "^5.1.1"
956 | }
957 | },
958 | "safe-buffer": {
959 | "version": "5.1.2",
960 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
961 | "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="
962 | },
963 | "semaphore-async-await": {
964 | "version": "1.5.1",
965 | "resolved": "https://registry.npmjs.org/semaphore-async-await/-/semaphore-async-await-1.5.1.tgz",
966 | "integrity": "sha1-hXvvXjZEYBykuVcLh+nfXKEpdPo="
967 | },
968 | "semver": {
969 | "version": "5.7.0",
970 | "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.0.tgz",
971 | "integrity": "sha512-Ya52jSX2u7QKghxeoFGpLwCtGlt7j0oY9DYb5apt9nPlJ42ID+ulTXESnt/qAQcoSERyZ5sl3LDIOw0nAn/5DA==",
972 | "dev": true
973 | },
974 | "set-blocking": {
975 | "version": "2.0.0",
976 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
977 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc=",
978 | "dev": true
979 | },
980 | "sha.js": {
981 | "version": "2.4.11",
982 | "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.11.tgz",
983 | "integrity": "sha512-QMEp5B7cftE7APOjk5Y6xgrbWu+WkLVQwk8JNjZ8nKRciZaByEW6MubieAiToS7+dwvrjGhH8jRXz3MVd0AYqQ==",
984 | "requires": {
985 | "inherits": "^2.0.1",
986 | "safe-buffer": "^5.0.1"
987 | }
988 | },
989 | "shebang-command": {
990 | "version": "1.2.0",
991 | "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-1.2.0.tgz",
992 | "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=",
993 | "dev": true,
994 | "requires": {
995 | "shebang-regex": "^1.0.0"
996 | }
997 | },
998 | "shebang-regex": {
999 | "version": "1.0.0",
1000 | "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-1.0.0.tgz",
1001 | "integrity": "sha1-2kL0l0DAtC2yypcoVxyxkMmO/qM=",
1002 | "dev": true
1003 | },
1004 | "signal-exit": {
1005 | "version": "3.0.2",
1006 | "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.2.tgz",
1007 | "integrity": "sha1-tf3AjxKH6hF4Yo5BXiUTK3NkbG0=",
1008 | "dev": true
1009 | },
1010 | "sprintf-js": {
1011 | "version": "1.0.3",
1012 | "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
1013 | "integrity": "sha1-BOaSb2YolTVPPdAVIDYzuFcpfiw=",
1014 | "dev": true
1015 | },
1016 | "string-width": {
1017 | "version": "2.1.1",
1018 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
1019 | "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
1020 | "dev": true,
1021 | "requires": {
1022 | "is-fullwidth-code-point": "^2.0.0",
1023 | "strip-ansi": "^4.0.0"
1024 | }
1025 | },
1026 | "string_decoder": {
1027 | "version": "1.3.0",
1028 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
1029 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
1030 | "requires": {
1031 | "safe-buffer": "~5.2.0"
1032 | },
1033 | "dependencies": {
1034 | "safe-buffer": {
1035 | "version": "5.2.0",
1036 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.0.tgz",
1037 | "integrity": "sha512-fZEwUGbVl7kouZs1jCdMLdt95hdIv0ZeHg6L7qPeciMZhZ+/gdesW4wgTARkrFWEpspjEATAzUGPG8N2jJiwbg=="
1038 | }
1039 | }
1040 | },
1041 | "strip-ansi": {
1042 | "version": "4.0.0",
1043 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
1044 | "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
1045 | "dev": true,
1046 | "requires": {
1047 | "ansi-regex": "^3.0.0"
1048 | }
1049 | },
1050 | "strip-eof": {
1051 | "version": "1.0.0",
1052 | "resolved": "https://registry.npmjs.org/strip-eof/-/strip-eof-1.0.0.tgz",
1053 | "integrity": "sha1-u0P/VZim6wXYm1n80SnJgzE2Br8=",
1054 | "dev": true
1055 | },
1056 | "strip-json-comments": {
1057 | "version": "2.0.1",
1058 | "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz",
1059 | "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=",
1060 | "dev": true
1061 | },
1062 | "supports-color": {
1063 | "version": "6.0.0",
1064 | "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-6.0.0.tgz",
1065 | "integrity": "sha512-on9Kwidc1IUQo+bQdhi8+Tijpo0e1SS6RoGo2guUwn5vdaxw8RXOF9Vb2ws+ihWOmh4JnCJOvaziZWP1VABaLg==",
1066 | "dev": true,
1067 | "requires": {
1068 | "has-flag": "^3.0.0"
1069 | }
1070 | },
1071 | "type-detect": {
1072 | "version": "4.0.8",
1073 | "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
1074 | "integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g==",
1075 | "dev": true
1076 | },
1077 | "util-deprecate": {
1078 | "version": "1.0.2",
1079 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
1080 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
1081 | },
1082 | "which": {
1083 | "version": "1.3.1",
1084 | "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz",
1085 | "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==",
1086 | "dev": true,
1087 | "requires": {
1088 | "isexe": "^2.0.0"
1089 | }
1090 | },
1091 | "which-module": {
1092 | "version": "2.0.0",
1093 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
1094 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho=",
1095 | "dev": true
1096 | },
1097 | "wide-align": {
1098 | "version": "1.1.3",
1099 | "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz",
1100 | "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==",
1101 | "dev": true,
1102 | "requires": {
1103 | "string-width": "^1.0.2 || 2"
1104 | }
1105 | },
1106 | "wrap-ansi": {
1107 | "version": "2.1.0",
1108 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz",
1109 | "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=",
1110 | "dev": true,
1111 | "requires": {
1112 | "string-width": "^1.0.1",
1113 | "strip-ansi": "^3.0.1"
1114 | },
1115 | "dependencies": {
1116 | "ansi-regex": {
1117 | "version": "2.1.1",
1118 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz",
1119 | "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=",
1120 | "dev": true
1121 | },
1122 | "is-fullwidth-code-point": {
1123 | "version": "1.0.0",
1124 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz",
1125 | "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=",
1126 | "dev": true,
1127 | "requires": {
1128 | "number-is-nan": "^1.0.0"
1129 | }
1130 | },
1131 | "string-width": {
1132 | "version": "1.0.2",
1133 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz",
1134 | "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=",
1135 | "dev": true,
1136 | "requires": {
1137 | "code-point-at": "^1.0.0",
1138 | "is-fullwidth-code-point": "^1.0.0",
1139 | "strip-ansi": "^3.0.0"
1140 | }
1141 | },
1142 | "strip-ansi": {
1143 | "version": "3.0.1",
1144 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz",
1145 | "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=",
1146 | "dev": true,
1147 | "requires": {
1148 | "ansi-regex": "^2.0.0"
1149 | }
1150 | }
1151 | }
1152 | },
1153 | "wrappy": {
1154 | "version": "1.0.2",
1155 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
1156 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=",
1157 | "dev": true
1158 | },
1159 | "xtend": {
1160 | "version": "4.0.2",
1161 | "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
1162 | "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ=="
1163 | },
1164 | "y18n": {
1165 | "version": "4.0.0",
1166 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
1167 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w==",
1168 | "dev": true
1169 | },
1170 | "yargs": {
1171 | "version": "13.2.2",
1172 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.2.tgz",
1173 | "integrity": "sha512-WyEoxgyTD3w5XRpAQNYUB9ycVH/PQrToaTXdYXRdOXvEy1l19br+VJsc0vcO8PTGg5ro/l/GY7F/JMEBmI0BxA==",
1174 | "dev": true,
1175 | "requires": {
1176 | "cliui": "^4.0.0",
1177 | "find-up": "^3.0.0",
1178 | "get-caller-file": "^2.0.1",
1179 | "os-locale": "^3.1.0",
1180 | "require-directory": "^2.1.1",
1181 | "require-main-filename": "^2.0.0",
1182 | "set-blocking": "^2.0.0",
1183 | "string-width": "^3.0.0",
1184 | "which-module": "^2.0.0",
1185 | "y18n": "^4.0.0",
1186 | "yargs-parser": "^13.0.0"
1187 | },
1188 | "dependencies": {
1189 | "ansi-regex": {
1190 | "version": "4.1.0",
1191 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
1192 | "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg==",
1193 | "dev": true
1194 | },
1195 | "string-width": {
1196 | "version": "3.1.0",
1197 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
1198 | "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
1199 | "dev": true,
1200 | "requires": {
1201 | "emoji-regex": "^7.0.1",
1202 | "is-fullwidth-code-point": "^2.0.0",
1203 | "strip-ansi": "^5.1.0"
1204 | }
1205 | },
1206 | "strip-ansi": {
1207 | "version": "5.2.0",
1208 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
1209 | "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
1210 | "dev": true,
1211 | "requires": {
1212 | "ansi-regex": "^4.1.0"
1213 | }
1214 | }
1215 | }
1216 | },
1217 | "yargs-parser": {
1218 | "version": "13.0.0",
1219 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.0.0.tgz",
1220 | "integrity": "sha512-w2LXjoL8oRdRQN+hOyppuXs+V/fVAYtpcrRxZuF7Kt/Oc+Jr2uAcVntaUTNT6w5ihoWfFDpNY8CPx1QskxZ/pw==",
1221 | "dev": true,
1222 | "requires": {
1223 | "camelcase": "^5.0.0",
1224 | "decamelize": "^1.2.0"
1225 | }
1226 | },
1227 | "yargs-unparser": {
1228 | "version": "1.5.0",
1229 | "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-1.5.0.tgz",
1230 | "integrity": "sha512-HK25qidFTCVuj/D1VfNiEndpLIeJN78aqgR23nL3y4N0U/91cOAzqfHlF8n2BvoNDcZmJKin3ddNSvOxSr8flw==",
1231 | "dev": true,
1232 | "requires": {
1233 | "flat": "^4.1.0",
1234 | "lodash": "^4.17.11",
1235 | "yargs": "^12.0.5"
1236 | },
1237 | "dependencies": {
1238 | "get-caller-file": {
1239 | "version": "1.0.3",
1240 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
1241 | "integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==",
1242 | "dev": true
1243 | },
1244 | "require-main-filename": {
1245 | "version": "1.0.1",
1246 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz",
1247 | "integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=",
1248 | "dev": true
1249 | },
1250 | "yargs": {
1251 | "version": "12.0.5",
1252 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz",
1253 | "integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==",
1254 | "dev": true,
1255 | "requires": {
1256 | "cliui": "^4.0.0",
1257 | "decamelize": "^1.2.0",
1258 | "find-up": "^3.0.0",
1259 | "get-caller-file": "^1.0.1",
1260 | "os-locale": "^3.0.0",
1261 | "require-directory": "^2.1.1",
1262 | "require-main-filename": "^1.0.1",
1263 | "set-blocking": "^2.0.0",
1264 | "string-width": "^2.0.0",
1265 | "which-module": "^2.0.0",
1266 | "y18n": "^3.2.1 || ^4.0.0",
1267 | "yargs-parser": "^11.1.1"
1268 | }
1269 | },
1270 | "yargs-parser": {
1271 | "version": "11.1.1",
1272 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz",
1273 | "integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==",
1274 | "dev": true,
1275 | "requires": {
1276 | "camelcase": "^5.0.0",
1277 | "decamelize": "^1.2.0"
1278 | }
1279 | }
1280 | }
1281 | }
1282 | }
1283 | }
1284 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "merkle-mountain-range",
3 | "version": "2.0.0",
4 | "description": "Merkle tree optimized for lists of sequentially appended data and its proofs",
5 | "main": "index.js",
6 | "scripts": {
7 | "test": "npx mocha --timeout 50000"
8 | },
9 | "repository": {
10 | "type": "git",
11 | "url": "git+https://github.com/zmitton/merkle-mountain-range.git"
12 | },
13 | "keywords": [
14 | "Merkle",
15 | "Mountain",
16 | "Range",
17 | "MerkleMountainRange",
18 | "FlyClient",
19 | "Ethereum"
20 | ],
21 | "author": "Zac Mitton",
22 | "license": "MIT",
23 | "homepage": "https://github.com/zmitton/merkle-mountain-range#readme",
24 | "dependencies": {
25 | "bignumber.js": "^8.1.1",
26 | "buffer": "^5.4.3",
27 | "js-sha3": "^0.8.0",
28 | "level": "^6.0.0",
29 | "rlp": "^2.2.3",
30 | "semaphore-async-await": "^1.5.1",
31 | "sha.js": "^2.4.11"
32 | },
33 | "devDependencies": {
34 | "chai": "^4.2.0",
35 | "mocha": "^6.1.3"
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/src/db/fileBasedDb.js:
--------------------------------------------------------------------------------
1 | const fileSystem = require('fs')
2 | // The fist 16 bytes of any fileBasedDb (`.mmr`) file contain the wordSize and the leafLength respectively. For
3 | // instance 0000 0000 0000 0040 0000 0000 0000 03e8 is a db with wordsize 64 and leafLength 1000.
4 |
5 | class FileBasedDB {
6 | constructor(){
7 | throw new Error('Please use the static `create` and `open` methods to construct a FileBasedDB')
8 | }
9 | static create(filePath, wordSize = 64){// throws if file already exists
10 | return this.openOrCreate(filePath, 'ax+', wordSize)
11 | }
12 | static open(filePath){// throws if file does not exist
13 | return this.openOrCreate(filePath, 'r+')
14 | }
15 | static openOrCreate(filePath, fileSystemFlags, wordSize){
16 | let db = Object.create(this.prototype)
17 | db.filePath = filePath
18 | db.fd = fileSystem.openSync(filePath, fileSystemFlags)
19 | if(wordSize){
20 | db._setWordSize(wordSize)
21 | }
22 | return db
23 | }
24 |
25 | async get(_index){
26 | let index = _index + 1 // shift 1 because index zero holds meta-data (wordSize and leafLength)
27 | let wordSize = await this._getWordSize()
28 | var indexToFirstByte = index*wordSize
29 | var chunk = Buffer.alloc(wordSize)
30 | return new Promise((resolve, reject)=>{
31 | fileSystem.read(this.fd, chunk, 0, wordSize, indexToFirstByte, (e, r)=>{
32 | if(e){
33 | reject(e)
34 | }else{
35 | if(chunk.equals(Buffer.alloc(wordSize))){
36 | resolve(null)
37 | }else{
38 | resolve(chunk)
39 | }
40 | }
41 | })
42 | })
43 | }
44 | async set(value, index){
45 | let wordSize = await this._getWordSize()
46 | if(value == undefined || Buffer.alloc(wordSize).equals(value)){
47 | throw new Error('Can not set nodeValue as an empty buffer')
48 | }
49 | return new Promise((resolve, reject)=>{
50 | fileSystem.write(this.fd, value, 0, wordSize, ((index + 1) * wordSize), (e, r) => {
51 | if(e){
52 | reject(e)
53 | }else{
54 | resolve(r)
55 | }
56 | })
57 | })
58 | }
59 |
60 | async getLeafLength(){
61 | var leafLengthBuffer = Buffer.alloc(4)
62 | return new Promise((resolve, reject)=>{
63 | fileSystem.read(this.fd, leafLengthBuffer, 0, 4, 12, (e, r)=>{
64 | if(e){
65 | reject(e)
66 | }else{
67 | resolve(leafLengthBuffer.readUInt32BE(0))
68 | }
69 | })
70 | })
71 | }
72 |
73 | async setLeafLength(leafLength){ // to do: deallocate the deleted part of the file
74 | let lengthBuffer = Buffer.alloc(4)
75 | lengthBuffer.writeUInt32BE(leafLength, 0)
76 | return new Promise((resolve, reject)=>{
77 | fileSystem.write(this.fd, lengthBuffer, 0, 4, 0, (e, r) => {
78 | if(e){
79 | reject(e)
80 | }else{
81 | fileSystem.fsync(this.fd, (e, r) => {
82 | if(e){
83 | reject(e)
84 | }else{
85 | resolve(r)
86 | }
87 | })
88 | }
89 | })
90 | })
91 | }
92 | async getNodes(){
93 | let wordSize = await this._getWordSize()
94 | let stats = fs.statSync(this.filePath)
95 | let nodeLength = (stats.size - wordSize) / wordSize
96 |
97 | let nodes = {}
98 | for (var i = 0; i < nodeLength; i++) {
99 | node[i] = await this.get(i)
100 | }
101 | return nodes
102 | }
103 | _setWordSize(wordSize){
104 | if(!wordSize || wordSize < 16){
105 | throw new Error('Wordsize of' + wordSize + 'not supported for FileBasedDB')
106 | }
107 | let wordSizeBuffer = Buffer.alloc(16)
108 | wordSizeBuffer.writeUInt32BE(wordSize, 4)
109 | fileSystem.writeSync(this.fd, wordSizeBuffer, 0, 16, 0)
110 | }
111 | async _getWordSize(){
112 | if (!this._wordSize){
113 | var wordSizeBuffer = Buffer.alloc(4)
114 | return new Promise((resolve, reject)=>{
115 | fileSystem.read(this.fd, wordSizeBuffer, 0, 4, 4, (e, r)=>{
116 | if(e){
117 | reject(e)
118 | }else{
119 | if(wordSizeBuffer.equals(Buffer.alloc(4))){
120 | reject(new Error("Db has undefined wordSize" + wordSizeBuffer))
121 | }else{
122 | this._wordSize = wordSizeBuffer.readUInt32BE(0)
123 | resolve(this._wordSize)
124 | }
125 | }
126 | })
127 | })
128 | }
129 | return this._wordSize
130 | }
131 | }
132 |
133 | module.exports = FileBasedDB
134 |
--------------------------------------------------------------------------------
/src/db/levelDbBasedDb.js:
--------------------------------------------------------------------------------
1 | const fileSystem = require('fs')
2 | const Level = require('level')
3 | // const rlp = require('rlp')
4 |
5 | class LevelDbBasedDb{
6 | constructor(levelDb, keyPrefix){
7 | // Returns wrapper for existing and ALREADY OPEN db
8 | // Warning: There are not any checks that the db exists or is formated correctly (i.e. binary)
9 | this.levelDb = levelDb
10 | this.keyPrefix = keyPrefix || Buffer.alloc(0)
11 | }
12 |
13 | // Unused key (outside normal range) to store the leafLength data
14 | static LEAF_LENGTH_KEY = Buffer.from('FFFFFFFFF','hex')
15 |
16 | static async openOrCreate(filePath, keyPrefix, options = {}){
17 | let db = Object.create(this.prototype)
18 | options.keyEncoding = 'binary'
19 | options.valueEncoding = 'binary'
20 | db.levelDb = await Level(filePath, options)
21 | // adding 4 bytes here as hack to support 8 byte indexes (instead of 4)
22 | db.keyPrefix = Buffer.concat([Buffer.alloc(4), keyPrefix || Buffer.alloc(0)])
23 |
24 | if(!(await db._dbExists())){
25 | await db.setLeafLength(0)
26 | }
27 | return db
28 | }
29 |
30 | async get(index){
31 | let value = null
32 | let indexBuffer = Buffer.alloc(4)
33 | indexBuffer.writeUInt32BE(index, 0)
34 | let key = Buffer.concat([this.keyPrefix, indexBuffer])
35 | try{
36 | value = await this.levelDb.get(key)
37 | }catch{}
38 | return value
39 | }
40 | async set(value, index){
41 | let indexBuffer = Buffer.alloc(4)
42 | indexBuffer.writeUInt32BE(index, 0)
43 | let key = Buffer.concat([this.keyPrefix, indexBuffer])
44 | return this.levelDb.put(key, value)
45 | }
46 | async getLeafLength(){
47 | let value = null
48 | let key = Buffer.concat([this.keyPrefix, LevelDbBasedDb.LEAF_LENGTH_KEY])
49 | try{
50 | let leafLengthBuffer = await this.levelDb.get(key)
51 | value = leafLengthBuffer.readUInt32BE(0)
52 | }catch{}
53 | return value
54 | }
55 | async setLeafLength(leafLength){
56 | let key = Buffer.concat([this.keyPrefix, LevelDbBasedDb.LEAF_LENGTH_KEY])
57 | let lengthBuffer = Buffer.alloc(4)
58 | lengthBuffer.writeUInt32BE(leafLength, 0)
59 | return this.levelDb.put(key, lengthBuffer)
60 | }
61 | async _dbExists(){
62 | return !!(await this.getLeafLength())
63 | }
64 | }
65 |
66 | module.exports = LevelDbBasedDb
67 |
--------------------------------------------------------------------------------
/src/db/memoryBasedDb.js:
--------------------------------------------------------------------------------
1 | const rlp = require('rlp')
2 | const Bn = require('bignumber.js')
3 |
4 | class MemoryBasedDb {
5 | constructor(...args){
6 | if(args[0] == undefined || typeof args[0] == 'number'){
7 | this.leafLength = args[0] || 0
8 | this.nodes = args[1] || {} // i.e. { 3 : , 7 : }
9 | // }else if(typeof arg[0] == 'object'){
10 | // this.nodes = args[0].nodes
11 | // this.leafLength = args[0].leafLength
12 | // }else if(typeof arg[0] == 'string'){
13 | // let obj = JSON.parse(arg[0])
14 | // this.length = parseInt(obj.length)
15 | // let indexes = Object.keys(obj.nodes)
16 | // for (var i = 0; i < indexes.length; i++) {
17 | // if(typeof obj.nodes[i] == 'string'){
18 | // obj.nodes[i] = obj.nodes[i] === '0x0' ? Buffer.from([]) : Buffer.from(obj.nodes[i].slice(2), 'hex')
19 | // }
20 | // }
21 | // this.nodes = obj.nodes
22 | // }else{
23 | // throw new Error('MemoryBasedDb constructor arguments not recognized')
24 | }
25 | }
26 | static fromSerialized(inputBytes){
27 | let arr = rlp.decode(inputBytes)
28 | // console.log("GGGGG", arr)
29 | let db = Object.create(this.prototype)
30 | db.leafLength = new Bn('0x' + arr[0].toString('hex')).toNumber()
31 | db.nodes = {}
32 | for (var i = 0; i < arr[1].length; i++) {
33 | db.nodes[new Bn('0x' + arr[1][i][0].toString('hex')).toNumber()] = arr[1][i][1]
34 | }
35 | return db
36 | }
37 |
38 | async get(index){
39 | return this.nodes[index]
40 | }
41 | async set(value, index){
42 | this.nodes[index] = value
43 | }
44 | async getLeafLength(){
45 | return this.leafLength
46 | }
47 | async setLeafLength(leafLength){
48 | return this.leafLength = leafLength
49 | }
50 | async getNodes(){
51 | return this.nodes
52 | }
53 |
54 | // async serialize(){
55 | // let numToBuf = (num) => {
56 | // let str = num.toString(16)
57 | // return str.length % 2 == 0 ? Buffer.from(str, 'hex') : Buffer.from('0' + str, 'hex')
58 | // }
59 | // let bufferedNodes = []
60 | // let indexes = Object.keys(this.nodes)
61 | // for (var i = 0; i < indexes.length; i++) {
62 | // let bufferedKey =
63 | // bufferedNodes.push([numToBuf(parseInt(indexes[i])), this.nodes[indexes[i]]])
64 | // }
65 | // return rlp.encode([this.leafLength, bufferedNodes])
66 | // }
67 | }
68 |
69 | module.exports = MemoryBasedDb
70 |
--------------------------------------------------------------------------------
/src/digests.js:
--------------------------------------------------------------------------------
1 | const { keccak256 } = require('js-sha3')
2 | const shajs = require('sha.js')
3 | const Bn = require('bignumber.js')
4 |
5 | // for variable difficulty used in flyClient
6 | let hashAndSum = (hashingFunction, ...nodeValues) => {
7 | let _numberToBytes32 = (input) => {
8 | let str = input.toString(16).padStart(64, '0')
9 | return Buffer.from(str, 'hex')
10 | }
11 | let diffucultySum = new Bn(0)
12 | for (let i = 0; i < nodeValues.length; i++) {
13 | let currentDifficulty = new Bn('0x' + nodeValues[i].slice(32).toString('hex'))
14 | diffucultySum = diffucultySum.plus(currentDifficulty)
15 | }
16 | let finalHash = Buffer.from(hashingFunction(Buffer.concat(nodeValues)), 'hex')
17 | let difficultySumBytes = _numberToBytes32(diffucultySum)
18 |
19 | return Buffer.concat([finalHash, difficultySumBytes])
20 | }
21 | let keccak256FlyHash = (...nodeValues) => {
22 | return hashAndSum(keccak256, ...nodeValues)
23 | }
24 | let sha256FlyHash = (...nodeValues) => {
25 | let sha256 = (x) => { return shajs('sha256').update(x).digest('hex') }
26 | return hashAndSum(sha256, ...nodeValues)
27 | }
28 | let keccak = (a, b) => {
29 | return Buffer.from(keccak256(Buffer.concat([a, b])),'hex')
30 | }
31 |
32 | module.exports = { keccak256FlyHash, sha256FlyHash, keccak, shajs, hashAndSum }
33 |
--------------------------------------------------------------------------------
/src/merkleMountainRange.js:
--------------------------------------------------------------------------------
1 | const { Lock } = require('semaphore-async-await')
2 | const rlp = require('rlp')
3 | const Position = require('./position')
4 | const MemoryBasedDb = require('./db/memoryBasedDb')
5 |
6 | class MMR{
7 | constructor(hashingFunction, db = new MemoryBasedDb()){
8 | this.digest = hashingFunction
9 | this.db = db
10 | this.lock = new Lock(1)
11 | }
12 |
13 | static fromSerialized(hashingFunction, serializedDb){
14 | return new this(hashingFunction, MemoryBasedDb.fromSerialized(serializedDb))
15 | }
16 | // async addSerialized(serializedDb){ //untested function
17 | // let newMmr = MMR.fromSerialized(serializedDb)
18 | // let newNodes = await newMmr.db.getNodes()
19 | // let indexes = Object.keys(newNodes)
20 | // for (var i = 0; i < indexes.length; i++) {
21 | // let existingValue = await this.get(indexes[i])
22 | // if(!!existingValue && !newNodes[indexes[i]].equals(existingValue)){
23 | // new Error('Node ' + indexes[i].toString + ' already exists.')
24 | // }
25 | // await this.db.set(newNodes[indexes[i]], indexes[i])
26 | // }
27 |
28 | // let newLeafLength = await newMmr.getLeafLength()
29 | // let leafLength = await this.getLeafLength()
30 | // if(newLeafLength >= leafLength){
31 | // await this._setLeafLength(newLeafLength)
32 | // }
33 | // // new plan:
34 | // // extendLength(proof)
35 | // // make sure the new leafLength is greater than this's
36 | // // create a temp mem proofMmr from serializedDb
37 | // // then get this mmr's peaks, add them to proofs db nodes (*overwriting* any duplicates)
38 | // // then call a `get()` on each peak position (but this is a different get because we only have
39 | // // the nodeIndex (not the leaf index))
40 | // // if get(nodeIndex) passes verification, add all proof nodes to this mmr (but dont
41 | // // overwrite). You have now verified *all* previously verified leaves are in the new one.
42 | // }
43 |
44 | async serialize(){
45 | let numToBuf = (num) => {
46 | let str = num.toString(16)
47 | return str.length % 2 == 0 ? Buffer.from(str, 'hex') : Buffer.from('0' + str, 'hex')
48 | }
49 | let bufferedNodes = []
50 | let nodes = await this.db.getNodes()
51 | let indexes = Object.keys(nodes)
52 | for (var i = 0; i < indexes.length; i++) {
53 | let bufferedKey =
54 | bufferedNodes.push([numToBuf(parseInt(indexes[i])), nodes[indexes[i]]])
55 | }
56 | let leafLength = await this.getLeafLength()
57 | return rlp.encode([leafLength, bufferedNodes])
58 | // return rlp.encode([this.digest(), leafLength, bufferedNodes])
59 | }
60 |
61 | async get(leafIndex){
62 | let leafValue
63 | await this.lock.acquire()
64 | try{
65 | let leafLength = await this.getLeafLength()
66 | if(leafIndex >= leafLength){ throw new Error('Leaf not in tree') }
67 | let leafPosition = MMR.getNodePosition(leafIndex)
68 | let localPeakPosition = MMR.localPeakPosition(leafIndex, leafLength)
69 | let localPeakValue = await this._getNodeValue(localPeakPosition)
70 | leafValue = await this._verifyPath(localPeakPosition, localPeakValue, leafPosition)
71 | }finally{
72 | this.lock.release()
73 | }
74 | return leafValue
75 | }
76 | async _get(nodePosition){
77 | let nodeValue
78 | await this.lock.acquire()
79 | try{
80 | let nodeLength = await this.getNodeLength()
81 | let leafLength = await this.getLeafLength()
82 | if(nodePosition.i >= nodeLength){ throw new Error('Node not in tree') }
83 | let peakPositions = MMR.peakPositions(leafLength - 1)
84 | let localPeakPosition
85 | for (let i = 0; i < peakPositions.length; i++) {
86 | if(peakPositions[i].i >= nodePosition.i){
87 | localPeakPosition = peakPositions[i]
88 | break
89 | }
90 | }
91 | let localPeakValue = await this._getNodeValue(localPeakPosition)
92 | nodeValue = await this._verifyPath(localPeakPosition, localPeakValue, nodePosition)
93 | }finally{
94 | this.lock.release()
95 | }
96 | return nodeValue
97 | }
98 | async append(value, leafIndex){
99 | await this.lock.acquire()
100 | try{
101 | let leafLength = await this.getLeafLength()
102 | if(leafIndex == undefined || leafIndex == leafLength){
103 | let nodePosition = MMR.getNodePosition(leafLength)
104 | let mountainPositions = MMR.mountainPositions(MMR.localPeakPosition(leafLength, leafLength), nodePosition.i)
105 | await this.db.set(value, nodePosition.i)
106 | await this._hashUp(mountainPositions)
107 | await this._setLeafLength(leafLength + 1)
108 | } else{
109 | throw new Error('Can only append to end of MMR (leaf '+leafLength+'). Index '+leafIndex+' given.')
110 | }
111 | }finally{
112 | this.lock.release()
113 | }
114 | }
115 | async appendMany(values, startLeafIndex){
116 | if(startLeafIndex == undefined){
117 | startLeafIndex = await this.getLeafLength()
118 | }
119 | for (let i = 0; i < values.length; i++) {
120 | await this.append(values[i], startLeafIndex + i)
121 | }
122 | }
123 | async getRoot(leafIndex){
124 | let peakValues = []
125 | await this.lock.acquire()
126 | try{
127 | if(leafIndex == undefined){
128 | leafIndex = await this.getLeafLength() - 1
129 | }
130 | let peakPositions = MMR.peakPositions(leafIndex)
131 | for (let i = 0; i < peakPositions.length; i++) {
132 | peakValues.push(await this._getNodeValue(peakPositions[i]))
133 | }
134 | }finally{
135 | this.lock.release()
136 | }
137 | // note: a single peak differs from its MMR root in that it gets hashed a second time
138 | return this.digest(...peakValues)
139 | }
140 | async getNodeLength(){ return MMR.getNodePosition(await this.getLeafLength()).i }
141 | async getLeafLength(){ // caching
142 | if(this._leafLength == undefined){ // dirty length
143 | this._leafLength = await this.db.getLeafLength()
144 | }
145 | return this._leafLength
146 | }
147 | async delete(leafIndex){ // logically deletes everything after (and including) leafIndex
148 | await this.lock.acquire()
149 | try{
150 | let leafLength = await this.getLeafLength()
151 | if(leafIndex < leafLength){
152 | await this._setLeafLength(leafIndex)
153 | }
154 | }finally{
155 | this.lock.release()
156 | }
157 | }
158 | async getProof(leafIndexes, referenceTreeLength){ // returns a sparse MMR containing the leaves specified
159 | let proofMmr
160 | await this.lock.acquire()
161 | try{
162 | referenceTreeLength = referenceTreeLength || await this.getLeafLength()
163 |
164 | let positions = MMR.proofPositions(leafIndexes, referenceTreeLength)
165 | let nodes = {}
166 |
167 | let nodeIndexes = Object.keys(positions)
168 | await Promise.all(nodeIndexes.map( async (i) => {
169 | let nodeValue = await this._getNodeValue(positions[i])
170 | nodes[i] = nodeValue
171 | }))
172 | proofMmr = new MMR(this.digest, new MemoryBasedDb(referenceTreeLength, nodes))
173 |
174 | }finally{
175 | this.lock.release()
176 | return proofMmr
177 | }
178 | }
179 |
180 | async _getNodeValue(position){
181 | // caller's responsibility to request a position within leafLength
182 | let nodeValue = await this.db.get(position.i)
183 | if(nodeValue){
184 | return nodeValue
185 | }else if(position.h > 0){ // implied node
186 | let leftChildValue = await this._getNodeValue(MMR.leftChildPosition(position))
187 | let rightChildValue = await this._getNodeValue(MMR.rightChildPosition(position))
188 | return this.digest(leftChildValue, rightChildValue)
189 | }else{
190 | throw new Error('Missing node in db')
191 | }
192 | }
193 | async _verifyPath(currentPosition, currentValue, destinationPosition) { // verifies as it walks
194 | if (currentPosition.i == destinationPosition.i) { // base case
195 | return currentValue
196 | } else {
197 | let leftChildPosition = MMR.leftChildPosition(currentPosition)
198 | let rightChildPosition = MMR.rightChildPosition(currentPosition)
199 | let leftValue = await this._getNodeValue(leftChildPosition)
200 | let rightValue = await this._getNodeValue(rightChildPosition)
201 | if (!currentValue.equals(this.digest(leftValue, rightValue))) {
202 | throw new Error('Hash mismatch of node #' + currentPosition.i + ' and its children')
203 | }
204 | if (destinationPosition.i > currentPosition.i - 2 ** currentPosition.h - currentPosition.h + 1) { //umm yeah, check this line
205 | return this._verifyPath(rightChildPosition, rightValue, destinationPosition)
206 | } else {
207 | return this._verifyPath(leftChildPosition, leftValue, destinationPosition)
208 | }
209 | }
210 | }
211 | async _setLeafLength(leafLength){
212 | await this.db.setLeafLength(leafLength)
213 | this._leafLength = leafLength
214 | }
215 | async _hashUp(positionPairs){
216 | for (let i = positionPairs.length - 1; i >= 0 ; i--) {
217 | let leftValue = await this._getNodeValue(positionPairs[i][0])
218 | let rightValue = await this._getNodeValue(positionPairs[i][1])
219 | let writeIndex = MMR.parentIndex(positionPairs[i][0])
220 | await this.db.set(this.digest(leftValue, rightValue), writeIndex)
221 | }
222 | }
223 |
224 |
225 | static leftChildPosition(position){
226 | if(position.h <= 0){ throw new Error('Height 0 does not have child')}
227 | return new Position(position.i - 2**position.h, position.h - 1, false)
228 | }
229 | static rightChildPosition(position){
230 | if (position.h <= 0) { throw new Error('Height 0 does not have child') }
231 | return new Position(position.i - 1, position.h - 1, true)
232 | }
233 | static siblingPosition(position){
234 | let multiplier = position.r ? -1 : 1
235 | return new Position (position.i + multiplier * (2**(position.h + 1) - 1), position.h, !position.r)
236 | }
237 | static parentIndex(position){
238 | if(position.r){
239 | return position.i + 1
240 | }else{
241 | return position.i + 2**(position.h + 1)
242 | }
243 | }
244 | static peakPositions(leafIndex){
245 | let currentPosition = this.godPeakFromLeafIndex(leafIndex)
246 | let peakPositions = []
247 | while(leafIndex >= 0){
248 | currentPosition = this.leftChildPosition(currentPosition)
249 | if(leafIndex >= 2**currentPosition.h - 1){
250 | peakPositions.push(currentPosition)
251 | currentPosition = this.siblingPosition(currentPosition)
252 | leafIndex -= 2**currentPosition.h // leafIndex becomes a kindof accumulator
253 | }
254 | }
255 | return peakPositions
256 | }
257 | static localPeakPosition(leafIndex, leafLength){
258 | let lastLeafIndex = leafLength <= leafIndex ? leafIndex : leafLength - 1
259 | return MMR._localPeakPosition(leafIndex, MMR.peakPositions(lastLeafIndex))
260 | }
261 | static _localPeakPosition(leafIndex, peakPositions){
262 | for (let i = 0; i < peakPositions.length; i++) {
263 | let currentRange = 2**(peakPositions[i].h)
264 | if(leafIndex < currentRange){
265 | return peakPositions[i]
266 | }else{
267 | leafIndex -= currentRange
268 | }
269 | }
270 | }
271 | static mountainPositions(currentPosition, targetNodeIndex){ // positions to hash after appending
272 | let mountainPositions = []
273 | while (currentPosition.h > 0) {
274 | let children = [this.leftChildPosition(currentPosition), this.rightChildPosition(currentPosition)]
275 | mountainPositions.push(children)
276 | if(targetNodeIndex > currentPosition.i - 2**currentPosition.h - currentPosition.h + 1){
277 | currentPosition = children[1]
278 | }else{
279 | currentPosition = children[0]
280 | }
281 | }
282 | return mountainPositions
283 | }
284 | static godPeakFromLeafIndex(leafIndex){ // imaginary peak that is above all nodes
285 | let peakHeight = 0
286 | while(2**peakHeight <= leafIndex + 1){ peakHeight++ }
287 | return new Position(2**(peakHeight + 1) - 2, peakHeight, false)
288 | }
289 | static getNodePosition(leafIndex){
290 | let currentPosition = this.godPeakFromLeafIndex(leafIndex)
291 | let accumulator = 0
292 | while(currentPosition.h > 0){
293 | let serviceRange = 2**(currentPosition.h - 1)
294 | if(leafIndex >= accumulator + serviceRange){
295 | currentPosition = this.rightChildPosition(currentPosition)
296 | accumulator += serviceRange
297 | }else{
298 | currentPosition = this.leftChildPosition(currentPosition)
299 | }
300 | }
301 | return currentPosition
302 | }
303 | static proofPositions(leafIndexes, referenceTreeLength){
304 | let positions = {}
305 | let finalPeakPositions = MMR.peakPositions(referenceTreeLength - 1)
306 | // add peak positions
307 | for (let i = 0; i < finalPeakPositions.length; i++) { // log(n)/2
308 | positions[finalPeakPositions[i].i] = finalPeakPositions[i]
309 | }
310 | //add local mountain proof positions for each leaf
311 | for (let i = 0; i < leafIndexes.length; i++) { // k*2log(n)
312 | let nodePosition = MMR.getNodePosition(leafIndexes[i])
313 | let finalLocalPeak = MMR._localPeakPosition(leafIndexes[i], finalPeakPositions)
314 | // positions[finalLocalPeak.i] = finalLocalPeak // ?? should already have all peaks
315 | let mountainPositions = MMR.mountainPositions(finalLocalPeak, nodePosition.i)
316 | for (let j = 0; j < mountainPositions.length; j++) {
317 | positions[mountainPositions[j][0].i] = mountainPositions[j][0]
318 | positions[mountainPositions[j][1].i] = mountainPositions[j][1]
319 | }
320 | }
321 | // find implied positions (ones which can be calculated based on child positions that are present)
322 | let positionIndexes = Object.keys(positions)
323 | let impliedIndexes = []
324 | for (let j = 0; j < positionIndexes.length; j++) { // k*log(n)
325 | if(positions[positionIndexes[j]].h > 0){
326 | let hasLeftChild = MMR._hasPosition(positions, MMR.leftChildPosition(positions[positionIndexes[j]]))
327 | let hasRightChild = MMR._hasPosition(positions, MMR.rightChildPosition(positions[positionIndexes[j]]))
328 | if(hasLeftChild && hasRightChild){
329 | impliedIndexes.push(positionIndexes[j]) // don't remove them yet because recursion will be slower
330 | }
331 | }
332 | }
333 | // finally remove implied nodes
334 | for (var i = 0; i < impliedIndexes.length; i++) { // k*log(n)
335 | impliedIndexes[i]
336 | delete positions[impliedIndexes[i]]
337 | }
338 | return positions
339 | }
340 | static _hasPosition(nodes, position){
341 | let has = !!nodes[position.i]
342 | if (!has && position.h > 0){
343 | if(MMR._hasPosition(nodes, MMR.leftChildPosition(position))
344 | && MMR._hasPosition(nodes, MMR.rightChildPosition(position))
345 | ){
346 | has = true
347 | }
348 | }
349 | return has
350 | }
351 | }
352 |
353 | module.exports = MMR
354 |
--------------------------------------------------------------------------------
/src/position.js:
--------------------------------------------------------------------------------
1 | // structure to hold a node's position. An `index` is sufficient to describe the full position
2 | // but object exist to cache of height/rightness, because recomputing height and rightness
3 | // from only index require Log(n) operations
4 | class Position{
5 | constructor(index, height, rightness){
6 | this.i = index
7 | this.h = height
8 | this.r = rightness // inherent unchanging property of every node index
9 | }
10 | }
11 |
12 | module.exports = Position
13 |
--------------------------------------------------------------------------------
/test/fixtures/etcleafdataFile.mmr:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/zmitton/merkle-mountain-range/ed26683b7ee75ecab8cc2f385ef810da50dfbfd4/test/fixtures/etcleafdataFile.mmr
--------------------------------------------------------------------------------
/test/mmrInstance.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert')
2 | const fileSystem = require('fs')
3 | assert.rejects = async (promiseThatShouldReject) => {
4 | await promiseThatShouldReject.then(
5 | () => { throw new Error('Expected method to reject.') },
6 | (error) => { assert.strictEqual(!!error, true) }
7 | )
8 | }
9 | const MMR = require('./../src/merkleMountainRange')
10 | const Position = require('./../src/position')
11 | const MemoryBasedDb = require('./../src/db/memoryBasedDb')
12 | const FileBasedDb = require('./../src/db/fileBasedDb')
13 | const LevelDbBasedDb = require('./../src/db/levelDbBasedDb')
14 | const { keccak256FlyHash } = require('../src/digests')
15 |
16 | describe('MerkleMountinRange (MMR) instance/async functions', () => {
17 | let fileBasedMmr, levelDbBasedMmr, mmr, proofMmr
18 | let levelDbBasedDb
19 | let etcLeafData = []
20 |
21 | context('#append', () => {
22 | it('open a file based mmr; check leaf/node lengths', async () => {
23 | let fileBasedDb = FileBasedDb.open('./test/fixtures/etcLeafDataFile.mmr')
24 | fileBasedMmr = new MMR(keccak256FlyHash, fileBasedDb)
25 | let nodeLength = await fileBasedMmr.getNodeLength()
26 | let leafLength = await fileBasedMmr.getLeafLength()
27 | assert.strictEqual(leafLength, 1000)
28 | assert.strictEqual(nodeLength, 1994) // observation only
29 | })
30 |
31 | it('create an in-memory mmr with some leaves for testing; check leaf/node lengths', async () => {
32 | mmr = new MMR(keccak256FlyHash)
33 |
34 | for (var i = 0; i < 1000; i++) {
35 | let leaf = await fileBasedMmr.db.get(MMR.getNodePosition(i).i)
36 | etcLeafData.push(leaf) // for easy testing against later
37 | await mmr.append(leaf, i)
38 | }
39 | let nodeLength = await fileBasedMmr.getNodeLength()
40 | let leafLength = await fileBasedMmr.getLeafLength()
41 | assert.strictEqual(leafLength, 1000)
42 | assert.strictEqual(nodeLength, 1994) // observation only
43 | })
44 | })
45 |
46 | context('BENCHMARKS', () => {
47 | it('performance/timing', async () => {
48 | let tempMmr = new MMR(keccak256FlyHash)
49 | let b
50 | let NUM_LOOPS = 250
51 |
52 | b = Date.now()
53 | for (var i = 0; i < NUM_LOOPS; i++) {
54 | await mmr.get(i)
55 | }
56 | console.log(" Seconds for 1 memoryBased get ( ~1000 leaves) ", ((Date.now() - b) / 1000) / NUM_LOOPS)
57 |
58 | b = Date.now()
59 | for (var i = 0; i < NUM_LOOPS; i++) {
60 | await tempMmr.append(etcLeafData[i], i)
61 | }
62 | console.log(" Seconds for 1 memoryBased append (0 to 250 leaves) ", ((Date.now() - b) / 1000) / NUM_LOOPS)
63 |
64 | b = Date.now()
65 | for (var i = 0; i < NUM_LOOPS; i++) {
66 | await fileBasedMmr.get(i)
67 | }
68 | console.log(" Seconds for 1 fileBased get (tree ~250 leaves) ", ((Date.now() - b) / 1000) / NUM_LOOPS)
69 |
70 | let tempFileBasedDb = FileBasedDb.create('./test/temp.mmr', 64)
71 | let tempFileBasedMmr = new MMR(keccak256FlyHash, tempFileBasedDb)
72 |
73 | await tempFileBasedMmr.delete(0) // reset database
74 | b = Date.now()
75 | for (var i = 0; i < NUM_LOOPS; i++) {
76 | await tempFileBasedMmr.append(etcLeafData[i])
77 | }
78 | console.log(" Seconds for 1 fileBased append (tree ~250 leaves) ", ((Date.now() - b) / 1000) / NUM_LOOPS)
79 |
80 | levelDbBasedDb = await LevelDbBasedDb.openOrCreate('./test/etcLeafDataLevelDb', Buffer.from('c12f','hex'))
81 | levelDbBasedMmr = new MMR(keccak256FlyHash, levelDbBasedDb)
82 |
83 | assert.equal(await levelDbBasedDb.getLeafLength(), 0)
84 |
85 | b = Date.now()
86 | for (var i = 0; i < NUM_LOOPS; i++) {
87 | await levelDbBasedMmr.append(etcLeafData[i])
88 | }
89 | console.log(" Seconds for 1 levelDbBased append (tree ~250 leaves)", ((Date.now() - b) / 1000) / NUM_LOOPS)
90 |
91 | b = Date.now()
92 | for (var i = 0; i < NUM_LOOPS; i++) {
93 | await levelDbBasedMmr.get(i)
94 | }
95 | console.log(" Seconds for 1 levelDbBased get (tree ~250 leaves) ", ((Date.now() - b) / 1000) / NUM_LOOPS)
96 | assert.equal(await levelDbBasedDb.getLeafLength(), NUM_LOOPS)
97 | assert.strictEqual( etcLeafData[0].equals(await levelDbBasedMmr.get(0)), true)
98 | assert.strictEqual( etcLeafData[1].equals(await levelDbBasedMmr.get(1)), true)
99 | assert.strictEqual( etcLeafData[3].equals(await levelDbBasedMmr.get(3)), true)
100 | assert.strictEqual( etcLeafData[8].equals(await levelDbBasedMmr.get(8)), true)
101 | assert.strictEqual( etcLeafData[10].equals(await levelDbBasedMmr.get(10)), true)
102 | assert.strictEqual( etcLeafData[45].equals(await levelDbBasedMmr.get(45)), true)
103 |
104 | // swapping the db should not affect any nodes or roots
105 | assert.equal((await mmr.getRoot(249)).toString('hex'),(await tempMmr.getRoot(249)).toString('hex'))
106 | assert.equal((await mmr.getRoot(249)).toString('hex'),(await fileBasedMmr.getRoot(249)).toString('hex'))
107 | assert.equal((await mmr.getRoot(249)).toString('hex'),(await tempFileBasedMmr.getRoot(249)).toString('hex'))
108 | assert.equal((await mmr.getRoot(249)).toString('hex'),(await levelDbBasedMmr.getRoot(249)).toString('hex'))
109 | })
110 | })
111 |
112 | after(function(done){
113 | if (fileSystem.existsSync('./test/temp.mmr')) {
114 | let error = fileSystem.unlinkSync('./test/temp.mmr')
115 | if(error){
116 | throw error
117 | }
118 | }
119 | if (fileSystem.existsSync('./test/etcLeafDataLevelDb')) {
120 | levelDbBasedDb.levelDb.clear(function(e){
121 | if(e){
122 | throw e
123 | }
124 | done()
125 | })
126 | }else{
127 | done()
128 | }
129 | })
130 |
131 | context('#get', () => {
132 | it('a few targeted `get`s 0, 1, 3, 8 ...999', async () => {
133 | assert.strictEqual( etcLeafData[0].equals(await mmr.get(0)), true)
134 | assert.strictEqual( etcLeafData[1].equals(await mmr.get(1)), true)
135 | assert.strictEqual( etcLeafData[3].equals(await mmr.get(3)), true)
136 | assert.strictEqual( etcLeafData[8].equals(await mmr.get(8)), true)
137 | assert.strictEqual( etcLeafData[10].equals(await mmr.get(10)), true)
138 | assert.strictEqual( etcLeafData[45].equals(await mmr.get(45)), true)
139 | assert.strictEqual( etcLeafData[409].equals(await mmr.get(409)), true)
140 | assert.strictEqual( etcLeafData[671].equals(await mmr.get(671)), true)
141 | assert.strictEqual( etcLeafData[998].equals(await mmr.get(998)), true)
142 | assert.strictEqual( etcLeafData[999].equals(await mmr.get(999)), true)
143 | })
144 | it('`get`s every item from etcLeafData individually', async () => {
145 | let b = Date.now()
146 | for (let i = 0; i < etcLeafData.length; i++) {
147 | const leaf = await mmr.get(i)
148 | assert.strictEqual(etcLeafData[i].equals(leaf), true)
149 | assert.strictEqual(leaf, etcLeafData[i])
150 | }
151 | // console.log(" Time for 1 fileBased get and then 1 memoryBased append (250 leaves) = ", ((Date.now() - b) / 1000) / etcLeafData.length)
152 | })
153 | })
154 |
155 | context('#_getNodeValue', () => {
156 | it('get node index 2, which is the hash of 0 and 1', async () => {
157 | let expectedNode2Value = '8749599828a524ab56cc1fdf5b3676b0318d0825ac027dbca5544adb18b07b9e00000000000000000000000000000000000000000000000000000007ff800000'
158 | let computedNode2Value = await mmr._getNodeValue(new Position(2, 1, false))
159 | assert.strictEqual(computedNode2Value.toString('hex'), expectedNode2Value)
160 | })
161 | it('get node index 61, which is the hash of 45 and 60', async () => {
162 | let expectedNode2Value = '7fc8112768dccd0e5444e7fb0f36c1b1cc1990395695537ca2a3e30b12bd2421000000000000000000000000000000000000000000000000000000404c2c0f34'
163 | let computedNode2Value = await mmr._getNodeValue(new Position(61, 4, true))
164 | assert.strictEqual(computedNode2Value.toString('hex'), expectedNode2Value)
165 | })
166 | })
167 |
168 | context('#getRoot', () => {
169 | it('current/final root is returned when no arguments are given', async () => {
170 | // difficulty at block 999 is 21991996248790 -> 0x1400691fd2d6
171 | let expectedFinalRootValue = '1d6e5c69d70d3ac8847ccf63f61303f607382bd988d0d8b559ce53e3305e7b6700000000000000000000000000000000000000000000000000001400691fd2d6'
172 | let computedFinalRoot = await mmr.getRoot()
173 | console.log((await levelDbBasedMmr.getRoot()).toString('hex'))
174 | let computed999thRoot = await mmr.getRoot(999)
175 | assert.strictEqual(computedFinalRoot.toString('hex'), expectedFinalRootValue)
176 | assert.strictEqual(computed999thRoot.toString('hex'), expectedFinalRootValue)
177 | })
178 | it('root of 0rd leaf should be the hash of node 0', async () => {
179 | let node0 = await mmr.db.get(0)
180 | let root0 = await mmr.getRoot(0)
181 | assert.strictEqual(root0.toString('hex'), '4ef0d4100c84abf7f877cde7ae268676b3bab9341cdac33ae7c5de5ca8d865660000000000000000000000000000000000000000000000000000000400000000')
182 | assert.strictEqual(root0.toString('hex'), keccak256FlyHash(node0).toString('hex'))
183 | })
184 | it('root of 1rd leaf should be the hash of node 2', async () => {
185 | let node2 = await mmr.db.get(2)
186 | let root1 = await mmr.getRoot(1)
187 | assert.strictEqual(root1.toString('hex'), 'b31315e249f2d6814113099367c3ab9092eb94a9e6ea2f3539b78a2da8589ee400000000000000000000000000000000000000000000000000000007ff800000')
188 | assert.strictEqual(root1.toString('hex'), keccak256FlyHash(node2).toString('hex'))
189 | })
190 | it('root of 2rd leaf should be the hash of nodes 2 and 3', async () => {
191 | let node2 = await mmr.db.get(2)
192 | let node3 = await mmr.db.get(3)
193 | let root2 = await mmr.getRoot(2)
194 | assert.strictEqual(root2.toString('hex'), '76bf715a5208daa07aabcd414d6759ad6e55254617909235730c17089153e2bc0000000000000000000000000000000000000000000000000000000bfe801000')
195 | assert.strictEqual(root2.toString('hex'), keccak256FlyHash(node2, node3).toString('hex'))
196 | })
197 | it('root of 3rd leaf should be the hash of node 6', async () => {
198 | let node6 = await mmr.db.get(6)
199 | let root3 = await mmr.getRoot(3)
200 | assert.strictEqual(root3.toString('hex'), 'c1371662e5123efcdf1d1fa786d040b8434df32e9af9e1e25c34dcde5332f14b0000000000000000000000000000000000000000000000000000000ffd003ffe')
201 | assert.strictEqual(root3.toString('hex'), keccak256FlyHash(node6).toString('hex'))
202 | })
203 | it('root of 4rd leaf should be the hash of nodes 6 and 7', async () => {
204 | let node6 = await mmr.db.get(6)
205 | let node7 = await mmr.db.get(7)
206 | let root4 = await mmr.getRoot(4)
207 | assert.strictEqual(root4.toString('hex'), 'b25df4fe1d364645020187f1ce1b2ec15fb5a4bb26075458d230dfe28dc395b600000000000000000000000000000000000000000000000000000013fb009ff7')
208 | assert.strictEqual(root4.toString('hex'), keccak256FlyHash(node6, node7).toString('hex'))
209 | })
210 |
211 | it('root of 59rd leaf should be the hash of nodes 62, 93, 108, 115 ', async () => {
212 | let node62 = await mmr.db.get(62)
213 | let node93 = await mmr.db.get(93)
214 | let node108 = await mmr.db.get(108)
215 | let node115 = await mmr.db.get(115)
216 | let root59 = await mmr.getRoot(59)
217 | assert.strictEqual(root59.toString('hex'), '5847b83a713a3da7cadf901093768652f0eb9b2fb058e67961434ec5b7bf34ae000000000000000000000000000000000000000000000000000000f1fba4e525')
218 | assert.strictEqual(root59.toString('hex'), keccak256FlyHash(node62, node93, node108, node115).toString('hex'))
219 | })
220 | it('root of 22rd leaf should be the hash of nodes 30, 37, 40, 41 ', async () => {
221 | let node30 = await mmr.db.get(30)
222 | let node37 = await mmr.db.get(37)
223 | let node40 = await mmr.db.get(40)
224 | let node41 = await mmr.db.get(41)
225 | let root22 = await mmr.getRoot(22)
226 | assert.strictEqual(root22.toString('hex'), '92d471d2d496e5852cb37fc14c8495e32e327ee73a4ccc86679d58480ee55afa0000000000000000000000000000000000000000000000000000005c0480cf27')
227 | assert.strictEqual(root22.toString('hex'), keccak256FlyHash(node30, node37, node40, node41).toString('hex'))
228 | })
229 | })
230 |
231 | context('#delete', () => {
232 | it('should be able to delete everything after leaf 33', async () => {
233 | let oldLeafLength = await mmr.getLeafLength()
234 | assert.strictEqual(oldLeafLength, etcLeafData.length)
235 | await mmr.get(34) // should not reject
236 | await mmr.delete(34)
237 | await assert.rejects(mmr.get(34)) // should reject (34 has been deleted)
238 | await assert.rejects(mmr.get(35)) // everything after 34 also deleted
239 | await assert.rejects(mmr.get(36))
240 | await mmr.get(33) // should not reject
241 | let newLeafLength = await mmr.getLeafLength()
242 | assert.strictEqual(newLeafLength, 34)
243 | })
244 | })
245 |
246 | context('#getProof', () => {
247 | it('should build and return a proof tree', async () => {
248 | proofMmr = await mmr.getProof([18])
249 | // console.log(proofMmr)
250 | assert.deepEqual(Object.keys(proofMmr.db.nodes), [30, 33, 34, 35, 44, 60, 65])
251 |
252 | await proofMmr.get(18) // should not reject
253 | await proofMmr.get(19) // the sibling leaf is also contained in the proof
254 | await assert.rejects(proofMmr.get(17)) // insufficient to prove any other leaves
255 | await assert.rejects(proofMmr.get(20)) // insufficient to prove any other leaves
256 |
257 | proofMmr.db.nodes[31] = etcLeafData[16]
258 | await assert.rejects(proofMmr.get(16)) // insufficient to prove any other leaves
259 | proofMmr.db.nodes[32] = etcLeafData[17]
260 | await proofMmr.get(16) // sufficient now to prove leaves 16 and 17
261 | await proofMmr.get(17)
262 | })
263 | })
264 |
265 | context('#serialize, #fromSerialized', () => {
266 | it('should build and return a proof tree', async () => {
267 | proofMmr = await mmr.getProof([18])
268 | let serialied = await proofMmr.serialize()
269 | let dbFromSerialized = MemoryBasedDb.fromSerialized(serialied)
270 | let mmrFromSerialized = MMR.fromSerialized(proofMmr.digest, serialied)
271 | // console.log("dbFromSerialized ", dbFromSerialized)
272 | // console.log("mmrFromSerialized ", mmrFromSerialized)
273 | // console.log("proofMmr.db", proofMmr.db)
274 | assert.deepEqual(proofMmr.db, dbFromSerialized)
275 | assert.deepEqual(proofMmr.db, mmrFromSerialized.db)
276 |
277 | let dataFromGolangImplementation = `f901e722f901e3f8432cb8405b3913c31a16b669b5630be116285cc03ee8d5cdfdd6bf975092c5a25d2434c7000000000000000000000000000000000000000000000000000000100f047fc8f84322b840480ff3f8a495b764e4361a6c2e296f34e8721cf1ec54fe5c46827937353bf1180000000000000000000000000000000000000000000000000000000401ffefcdf84341b840276b0cdd50d55b2d9fb229b4a8ff08831678949cd41eadca0af142bee8f06d6c0000000000000000000000000000000000000000000000000000000812936bdcf8433cb840e6dd80f5983f930ddd64d34886ee3ca3daa3761f835c5ee72a4a35ae7b7a27d8000000000000000000000000000000000000000000000000000000203628101cf84321b840fbc5eb6b0c8a2b0be83bfde711eef57782d6c4a8949bfe51233015d2b63a77c900000000000000000000000000000000000000000000000000000008027f5fb9f84323b840ec888de9fa46cb7a47b7bd812a2f601d948d89e5317cf9f68976a0dec92b1ee20000000000000000000000000000000000000000000000000000000402802fcaf8431eb8409dd966e87aaf98d54442be98dd9b9f195e7dedf913d21c12e9df5250b005dc330000000000000000000000000000000000000000000000000000003ff2fea031`
278 | let goSerialized = Buffer.from(dataFromGolangImplementation, 'hex')
279 | let dbFromGo = MemoryBasedDb.fromSerialized(goSerialized)
280 | // console.log("dbFromGo ", dbFromGo)
281 | // console.log("proofMmr.db ", proofMmr.db)
282 | assert.deepEqual(proofMmr.db, dbFromGo)
283 | })
284 | })
285 |
286 | context('#_getNodeValue', () => {
287 | it('has implied node through recursive method on sparce tree', async () => {
288 | let node45 = await proofMmr._getNodeValue(new Position(45, 3, false))
289 | await assert.rejects(proofMmr._getNodeValue(new Position(13, 2, true)))
290 | assert.strictEqual(node45.toString('hex'), 'c61c4b29aaec6f0a2fedafd23fb6a4559d66f46c8ff393de7ec6ebd3d8f6a6ca000000000000000000000000000000000000000000000000000000201603ff18')
291 | })
292 | })
293 | })
294 |
295 | // getRoot getNodeLength getLeafLength delete getProof _getNodeValue _hasNode _verifyPath _setLeafLength _hashUp
296 |
297 |
298 |
299 |
300 |
--------------------------------------------------------------------------------
/test/mmrStatic.js:
--------------------------------------------------------------------------------
1 | const assert = require('assert');
2 | const MMR = require('./../src/merkleMountainRange')
3 | const Position = require('./../src/position')
4 |
5 | describe('MerkleMountinRange (MMR) static/synchronous class functions' , () => {
6 | context('#getNodePosition', () => {
7 | it('single index', () => { assert.equal(MMR.getNodePosition(5).i, 8) })
8 | it('single index', () => { assert.equal(MMR.getNodePosition(11).i, 19) })
9 | it('single index', () => { assert.equal(MMR.getNodePosition(32).i, 63) })
10 | it('matches many known indexes', () => {
11 | let leafIndexToNodeIndexMappings = [
12 | [0, 0], [1, 1], [2, 3], [3, 4], [4,7], [5,8], [6,10], [7,11],
13 | [8,15], [9,16], [10,18], [11,19], [12,22], [13,23], [14,25],
14 | [15,26], [16,31], [17,32], [18,34], [19,35], [20,38],[21,39],
15 | [22,41], [23,42], [24,46], [25,47], [26,49], [27,50], [28,53],
16 | [29,54], [30,56], [31,57], [32,63], [33,64]
17 | ]
18 | leafIndexToNodeIndexMappings.forEach((pair)=>{
19 | let leafIndex = pair[0]
20 | let nodeIndex = pair[1]
21 | assert.equal(MMR.getNodePosition(leafIndex).i, nodeIndex)
22 | })
23 | })
24 | })
25 |
26 | context('#leftChildPosistion', () => {
27 | it('for 62, 30, 45, 51, 60', () => {
28 | assert.deepEqual(
29 | MMR.leftChildPosition(new Position(62, 5, false)),
30 | new Position(30, 4, false)
31 | )
32 | assert.deepEqual(
33 | MMR.leftChildPosition(new Position(30, 4,false)),
34 | new Position(14, 3, false)
35 | )
36 | assert.deepEqual(
37 | MMR.leftChildPosition(new Position(45, 3, false)),
38 | new Position(37, 2, false)
39 | )
40 | assert.deepEqual(
41 | MMR.leftChildPosition(new Position(51, 1, true)),
42 | new Position(49, 0, false)
43 | )
44 | assert.deepEqual(
45 | MMR.leftChildPosition(new Position(60, 3, true)),
46 | new Position(52, 2, false)
47 | )
48 | })
49 | })
50 |
51 | context('#richtChildPosition', () => {
52 | it('for 62, 61, 5, 65, 44', () => {
53 | assert.deepEqual(
54 | MMR.rightChildPosition(new Position(62, 5, false)),
55 | new Position(61, 4, true)
56 | )
57 | assert.deepEqual(
58 | MMR.rightChildPosition(new Position(61, 4, true)),
59 | new Position(60, 3, true)
60 | )
61 | assert.deepEqual(
62 | MMR.rightChildPosition(new Position(5, 1, true)),
63 | new Position(4, 0, true)
64 | )
65 | assert.deepEqual(
66 | MMR.rightChildPosition(new Position(65, 1, false)),
67 | new Position(64, 0, true)
68 | )
69 | assert.deepEqual(
70 | MMR.rightChildPosition(new Position(44, 2, true)),
71 | new Position(43, 1, true)
72 | )
73 | })
74 | })
75 |
76 | context('#siblingPosition', () => {
77 | it('for 62, 61, 5, 65, 44', () => {
78 | assert.deepEqual(
79 | MMR.siblingPosition(new Position(62, 5, false)),
80 | new Position(125, 5, true)
81 | )
82 | assert.deepEqual(
83 | MMR.siblingPosition(new Position(61, 4, true)),
84 | new Position(30, 4, false)
85 | )
86 | assert.deepEqual(
87 | MMR.siblingPosition(new Position(5, 1, true)),
88 | new Position(2, 1, false)
89 | )
90 | assert.deepEqual(
91 | MMR.siblingPosition(new Position(65, 1, false)),
92 | new Position(68, 1, true)
93 | )
94 | assert.deepEqual(
95 | MMR.siblingPosition(new Position(44, 2, true)),
96 | new Position(37, 2, false)
97 | )
98 | })
99 | })
100 |
101 | context('#parentIndex', () => {
102 | it('for 62, 61, 5, 65, 44', () => {
103 | assert.deepEqual(MMR.parentIndex(new Position(62, 5, false)), 126)
104 | assert.deepEqual(MMR.parentIndex(new Position(61, 4, true)), 62)
105 | assert.deepEqual(MMR.parentIndex(new Position(5, 1, true)), 6)
106 | assert.deepEqual(MMR.parentIndex(new Position(65, 1, false)), 69)
107 | assert.deepEqual(MMR.parentIndex(new Position(44, 2, true)), 45)
108 | })
109 | })
110 |
111 | context('#peakPosition', () => {
112 | it('of 0', () => {
113 | let computedPeaks = MMR.peakPositions(0)
114 | let expectedPeaks = [ new Position(0, 0, false) ]
115 | assert.deepEqual(expectedPeaks, computedPeaks)
116 | })
117 | it('of 1', () => {
118 | let computedPeaks = MMR.peakPositions(1)
119 | let expectedPeaks = [ new Position(2, 1, false) ]
120 | assert.deepEqual(expectedPeaks, computedPeaks)
121 | })
122 | it('of 2', () => {
123 | let computedPeaks = MMR.peakPositions(2)
124 | let expectedPeaks = [
125 | new Position(2, 1, false),
126 | new Position(3, 0, false)
127 | ]
128 | assert.deepEqual(expectedPeaks, computedPeaks)
129 | })
130 | it('of 9', () => {
131 | let computedPeaks = MMR.peakPositions(9)
132 | let expectedPeaks = [
133 | new Position(14, 3, false),
134 | new Position(17, 1, false)
135 | ]
136 | assert.deepEqual(expectedPeaks, computedPeaks)
137 | })
138 | it('of 30', () => {
139 | let computedPeaks = MMR.peakPositions(30)
140 | let expectedPeaks = [
141 | new Position(30, 4, false),
142 | new Position(45, 3, false),
143 | new Position(52, 2, false),
144 | new Position(55, 1, false),
145 | new Position(56, 0, false)
146 | ]
147 | assert.deepEqual(expectedPeaks, computedPeaks)
148 | })
149 | it('of 31', () => {
150 | let computedPeaks = MMR.peakPositions(31)
151 | let expectedPeaks = [
152 | new Position(62, 5, false)
153 | ]
154 | assert.deepEqual(expectedPeaks, computedPeaks)
155 | })
156 | it('of 32', () => {
157 | let computedPeaks = MMR.peakPositions(32)
158 | let expectedPeaks = [
159 | new Position(62, 5, false),
160 | new Position(63, 0, false)
161 | ]
162 | assert.deepEqual(expectedPeaks, computedPeaks)
163 | })
164 | it('of 33', () => {
165 | let computedPeaks = MMR.peakPositions(33)
166 | let expectedPeaks = [
167 | new Position(62, 5, false),
168 | new Position(65, 1, false)
169 | ]
170 | assert.deepEqual(expectedPeaks, computedPeaks)
171 | })
172 | })
173 |
174 | context('#localPeakPosition', () => {
175 | it('of 0, 0', () => { // re-check first 4 - 7 of these
176 | let computedPeak = MMR.localPeakPosition(0, 0)
177 | let expectedPeak = new Position(0, 0, false)
178 | assert.deepEqual(computedPeak, expectedPeak)
179 | })
180 | it('of 1, 0', () => {
181 | let computedPeak = MMR.localPeakPosition(1, 0)
182 | let expectedPeak = new Position(2, 1, false)
183 | assert.deepEqual(computedPeak, expectedPeak)
184 | })
185 | it('of 2, 2', () => {
186 | let computedPeak = MMR.localPeakPosition(2, 2)
187 | let expectedPeak = new Position(3, 0, false)
188 | assert.deepEqual(computedPeak, expectedPeak)
189 | })
190 | it('of 9, 6', () => {
191 | let computedPeak = MMR.localPeakPosition(9, 6)
192 | let expectedPeak = new Position(17, 1, false)
193 | assert.deepEqual(computedPeak, expectedPeak)
194 | })
195 | it('of 30, 14', () => {
196 | let computedPeak = MMR.localPeakPosition(30, 14)
197 | let expectedPeak = new Position(56, 0, false)
198 | assert.deepEqual(computedPeak, expectedPeak)
199 | })
200 | it('of 30, 31', () => {
201 | let computedPeak = MMR.localPeakPosition(30, 31)
202 | let expectedPeak = new Position(56, 0, false)
203 | assert.deepEqual(computedPeak, expectedPeak)
204 | })
205 | it('of 31, 30', () => {
206 | let computedPeak = MMR.localPeakPosition(31, 30)
207 | let expectedPeak = new Position(62, 5, false)
208 | assert.deepEqual(computedPeak, expectedPeak)
209 | })
210 | it('of 32, 30', () => {
211 | let computedPeak = MMR.localPeakPosition(32, 30)
212 | let expectedPeak = new Position(63, 0, false)
213 | assert.deepEqual(computedPeak, expectedPeak)
214 | })
215 | it('of 31, 55', () => {
216 | let computedPeak = MMR.localPeakPosition(31, 55)
217 | let expectedPeak = new Position(62, 5, false)
218 | assert.deepEqual(computedPeak, expectedPeak)
219 | })
220 | it('of 32 32', () => {
221 | let computedPeak = MMR.localPeakPosition(32, 32)
222 | let expectedPeak = new Position(63, 0, false)
223 | assert.deepEqual(computedPeak, expectedPeak)
224 | })
225 | it('of 33, 33', () => {
226 | let computedPeak = MMR.localPeakPosition(33, 33)
227 | let expectedPeak = new Position(65, 1, false)
228 | assert.deepEqual(computedPeak, expectedPeak)
229 | })
230 | it('of 33, 34', () => {
231 | let computedPeak = MMR.localPeakPosition(33, 34)
232 | let expectedPeak = new Position(65, 1, false)
233 | assert.deepEqual(computedPeak, expectedPeak)
234 | })
235 | it('of 33, 35', () => {
236 | let computedPeak = MMR.localPeakPosition(33, 35)
237 | let expectedPeak = new Position(65, 1, false)
238 | assert.deepEqual(computedPeak, expectedPeak)
239 | })
240 | it('of 33, 36', () => {
241 | let computedPeak = MMR.localPeakPosition(33, 36)
242 | let expectedPeak = new Position(69, 2, false)
243 | assert.deepEqual(computedPeak, expectedPeak)
244 | })
245 | it('of 33, 45', () => {
246 | let computedPeak = MMR.localPeakPosition(33, 45)
247 | let expectedPeak = new Position(77, 3, false)
248 | assert.deepEqual(computedPeak, expectedPeak)
249 | })
250 | it('of 33, 47', () => {
251 | let computedPeak = MMR.localPeakPosition(33, 47)
252 | let expectedPeak = new Position(77, 3, false)
253 | assert.deepEqual(computedPeak, expectedPeak)
254 | })
255 | it('of 33, 49', () => {
256 | let computedPeak = MMR.localPeakPosition(33, 49)
257 | let expectedPeak = new Position(93, 4, false)
258 | assert.deepEqual(computedPeak, expectedPeak)
259 | })
260 | it('of 33, 69', () => {
261 | let computedPeak = MMR.localPeakPosition(33, 69)
262 | let expectedPeak = new Position(126, 6, false)
263 | assert.deepEqual(computedPeak, expectedPeak)
264 | })
265 | })
266 |
267 | context('#godPeakFromLeafIndex', () => {
268 | it('of 0,1,2,3,4,5,6,7,8,14,15,16,26,30,31', () => {
269 | assert.deepEqual(new Position(2, 1, false), MMR.godPeakFromLeafIndex(0))
270 | assert.deepEqual(new Position(6, 2, false), MMR.godPeakFromLeafIndex(1))
271 | assert.deepEqual(new Position(6, 2, false), MMR.godPeakFromLeafIndex(2))
272 | assert.deepEqual(new Position(14, 3, false), MMR.godPeakFromLeafIndex(3))
273 | assert.deepEqual(new Position(14, 3, false), MMR.godPeakFromLeafIndex(4))
274 | assert.deepEqual(new Position(14, 3, false), MMR.godPeakFromLeafIndex(5))
275 | assert.deepEqual(new Position(14, 3, false), MMR.godPeakFromLeafIndex(6))
276 | assert.deepEqual(new Position(30, 4, false), MMR.godPeakFromLeafIndex(7))
277 | assert.deepEqual(new Position(30, 4, false), MMR.godPeakFromLeafIndex(8))
278 | assert.deepEqual(new Position(30, 4, false), MMR.godPeakFromLeafIndex(14))
279 | assert.deepEqual(new Position(62, 5, false), MMR.godPeakFromLeafIndex(15))
280 | assert.deepEqual(new Position(62, 5, false), MMR.godPeakFromLeafIndex(16))
281 | assert.deepEqual(new Position(62, 5, false), MMR.godPeakFromLeafIndex(26))
282 | assert.deepEqual(new Position(62, 5, false), MMR.godPeakFromLeafIndex(30))
283 | assert.deepEqual(new Position(126, 6, false), MMR.godPeakFromLeafIndex(31))
284 | })
285 | })
286 |
287 | context('#mountainPositions', () => {
288 | it('of peakPosition 0 -> 0', () => {
289 | assert.deepEqual([], MMR.mountainPositions(new Position(0, 0, false), 0))
290 | })
291 | it('of peakPosition 2 -> 0', () => {
292 | let expectedPositions = [[
293 | new Position(0, 0, false),
294 | new Position(1, 0, true)
295 | ]]
296 | let computedPositions = MMR.mountainPositions(new Position(2, 1, false), 0)
297 | assert.deepEqual(computedPositions, expectedPositions)
298 | })
299 | it('of peakPosition 2 -> 1', () => {
300 | let expectedPositions = [[
301 | new Position(0, 0, false),
302 | new Position(1, 0, true)
303 | ]]
304 | let computedPositions = MMR.mountainPositions(new Position(2, 1, false), 1)
305 | assert.deepEqual(computedPositions, expectedPositions)
306 | })
307 | it('of peakPosition 3 -> 3', () => {
308 | let expectedPositions = []
309 | let computedPositions = MMR.mountainPositions(new Position(3, 0, false), 3)
310 | assert.deepEqual(computedPositions, expectedPositions)
311 | })
312 | it('of peakPosition 6 -> 0 and 6 -> 1', () => {
313 | let expectedPositions = [
314 | [
315 | new Position(2, 1, false),
316 | new Position(5, 1, true)
317 | ],[
318 | new Position(0, 0, false),
319 | new Position(1, 0, true)
320 | ]
321 | ]
322 | let computedPositions = MMR.mountainPositions(new Position(6, 2, false), 0)
323 | assert.deepEqual(computedPositions, expectedPositions)
324 | computedPositions = MMR.mountainPositions(new Position(6, 2, false), 1)
325 | assert.deepEqual(computedPositions, expectedPositions)
326 | })
327 | it('of peakPosition 6 -> 3 and 6 -> 4', () => {
328 | let expectedPositions = [
329 | [
330 | new Position(2, 1, false),
331 | new Position(5, 1, true)
332 | ],[
333 | new Position(3, 0, false),
334 | new Position(4, 0, true)
335 | ]
336 | ]
337 | let computedPositions = MMR.mountainPositions(new Position(6, 2, false), 3)
338 | assert.deepEqual(computedPositions, expectedPositions)
339 | computedPositions = MMR.mountainPositions(new Position(6, 2, false), 4)
340 | assert.deepEqual(computedPositions, expectedPositions)
341 | })
342 | it('of peakPosition 7 -> 7', () => {
343 | let expectedPositions = []
344 | let computedPositions = MMR.mountainPositions(new Position(7, 0, false), 7)
345 | assert.deepEqual(computedPositions, expectedPositions)
346 | })
347 | it('of peakPosition 9 -> 7 and 9 -> 8', () => {
348 | let expectedPositions = [[
349 | new Position(7, 0, false),
350 | new Position(8, 0, true)
351 | ]]
352 | let computedPositions = MMR.mountainPositions(new Position(9, 1, false), 7)
353 | assert.deepEqual(computedPositions, expectedPositions)
354 | computedPositions = MMR.mountainPositions(new Position(9, 1, false), 8)
355 | assert.deepEqual(computedPositions, expectedPositions)
356 | })
357 | it('of peakPosition 10 -> 10', () => {
358 | let expectedPositions = []
359 | let computedPositions = MMR.mountainPositions(new Position(10, 0, false), 10)
360 | assert.deepEqual(computedPositions, expectedPositions)
361 | })
362 | it('of peakPosition 14 -> 3 and 14 -> 4', () => {
363 | let expectedPositions = [
364 | [
365 | new Position(6, 2, false),
366 | new Position(13, 2, true)
367 | ], [
368 | new Position(2, 1, false),
369 | new Position(5, 1, true)
370 | ], [
371 | new Position(3, 0, false),
372 | new Position(4, 0, true)
373 | ]
374 | ]
375 | let computedPositions = MMR.mountainPositions(new Position(14, 3, false), 3)
376 | assert.deepEqual(computedPositions, expectedPositions)
377 | computedPositions = MMR.mountainPositions(new Position(14, 3, false), 4)
378 | assert.deepEqual(computedPositions, expectedPositions)
379 | })
380 | it('of peakPosition 14 -> 7 and 14 -> 8', () => {
381 | let expectedPositions = [
382 | [
383 | new Position(6, 2, false),
384 | new Position(13, 2, true)
385 | ], [
386 | new Position(9, 1, false),
387 | new Position(12, 1, true)
388 | ], [
389 | new Position(7, 0, false),
390 | new Position(8, 0, true)
391 | ]
392 | ]
393 | let computedPositions = MMR.mountainPositions(new Position(14, 3, false), 7)
394 | assert.deepEqual(computedPositions, expectedPositions)
395 | computedPositions = MMR.mountainPositions(new Position(14, 3, false), 8)
396 | assert.deepEqual(computedPositions, expectedPositions)
397 | })
398 | it('of peakPosition 45 -> 31 and 45 -> 32', () => {
399 | let expectedPositions = [
400 | [
401 | new Position(37, 2, false),
402 | new Position(44, 2, true)
403 | ], [
404 | new Position(33, 1, false),
405 | new Position(36, 1, true)
406 | ], [
407 | new Position(31, 0, false),
408 | new Position(32, 0, true)
409 | ]
410 | ]
411 | let computedPositions = MMR.mountainPositions(new Position(45, 3, false), 31)
412 | assert.deepEqual(computedPositions, expectedPositions)
413 | computedPositions = MMR.mountainPositions(new Position(45, 3, false), 32)
414 | assert.deepEqual(computedPositions, expectedPositions)
415 | })
416 | it('of peakPosition 45 -> 34 and 45 -> 35', () => {
417 | let expectedPositions = [
418 | [
419 | new Position(37, 2, false),
420 | new Position(44, 2, true)
421 | ], [
422 | new Position(33, 1, false),
423 | new Position(36, 1, true)
424 | ], [
425 | new Position(34, 0, false),
426 | new Position(35, 0, true)
427 | ]
428 | ]
429 | let computedPositions = MMR.mountainPositions(new Position(45, 3, false), 34)
430 | assert.deepEqual(computedPositions, expectedPositions)
431 | computedPositions = MMR.mountainPositions(new Position(45, 3, false), 35)
432 | assert.deepEqual(computedPositions, expectedPositions)
433 | })
434 | it('of peakPosition 45 -> 38 and 45 -> 39', () => {
435 | let expectedPositions = [
436 | [
437 | new Position(37, 2, false),
438 | new Position(44, 2, true)
439 | ], [
440 | new Position(40, 1, false),
441 | new Position(43, 1, true)
442 | ], [
443 | new Position(38, 0, false),
444 | new Position(39, 0, true)
445 | ]
446 | ]
447 | let computedPositions = MMR.mountainPositions(new Position(45, 3, false), 38)
448 | assert.deepEqual(computedPositions, expectedPositions)
449 | computedPositions = MMR.mountainPositions(new Position(45, 3, false), 39)
450 | assert.deepEqual(computedPositions, expectedPositions)
451 | })
452 | it('of peakPosition 45 -> 41 and 45 -> 42', () => {
453 | let expectedPositions = [
454 | [
455 | new Position(37, 2, false),
456 | new Position(44, 2, true)
457 | ], [
458 | new Position(40, 1, false),
459 | new Position(43, 1, true)
460 | ], [
461 | new Position(41, 0, false),
462 | new Position(42, 0, true)
463 | ]
464 | ]
465 | let computedPositions = MMR.mountainPositions(new Position(45, 3, false), 41)
466 | assert.deepEqual(computedPositions, expectedPositions)
467 | computedPositions = MMR.mountainPositions(new Position(45, 3, false), 42)
468 | assert.deepEqual(computedPositions, expectedPositions)
469 | })
470 | it('of peakPosition 62 -> 22 and 62 -> 23', () => {
471 | let expectedPositions = [
472 | [
473 | new Position(30, 4, false),
474 | new Position(61, 4, true)
475 | ],[
476 | new Position(14, 3, false),
477 | new Position(29, 3, true)
478 | ],[
479 | new Position(21, 2, false),
480 | new Position(28, 2, true)
481 | ],[
482 | new Position(24, 1, false),
483 | new Position(27, 1, true)
484 | ],[
485 | new Position(22, 0, false),
486 | new Position(23, 0, true)
487 | ]
488 | ]
489 | let computedPositions = MMR.mountainPositions(new Position(62, 5, false), 22)
490 | assert.deepEqual(computedPositions, expectedPositions)
491 | computedPositions = MMR.mountainPositions(new Position(62, 5, false), 23)
492 | assert.deepEqual(computedPositions, expectedPositions)
493 | })
494 | })
495 | })
496 |
--------------------------------------------------------------------------------