├── .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 | # alt text 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 | ![alt text](img/mmr.png "MMR graphical representation") 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 | --------------------------------------------------------------------------------