├── .gitignore ├── README.md ├── jest.config.js ├── package.json ├── ts ├── __tests__ │ ├── Hashers.test.ts │ ├── MemStorage.test.ts │ └── MerkleTree.test.ts ├── hashers │ ├── ihasher.ts │ ├── index.ts │ ├── mimc7.ts │ ├── mimcsponge.ts │ └── poseidon.ts ├── index.ts ├── merkletree.ts └── storage │ ├── index.ts │ ├── istorage.ts │ └── memstorage.ts └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | package-lock.json 2 | build/ 3 | node_modules 4 | .DS_Store 5 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # semaphore-merkle-tree 2 | 3 | This package offers a Merkle tree implementation which is meant to work with 4 | the [Semaphore](https://github.com/kobigurk/semaphore) zero-knowledge 5 | signalling system. 6 | 7 | ## Installation 8 | 9 | ```bash 10 | npm i semaphore-merkle-tree 11 | ``` 12 | 13 | ## Usage 14 | 15 | ```ts 16 | import { storage, hashers, tree } from 'semaphore-merkle-tree' 17 | 18 | const storage = new storage.MemStorage() 19 | const hasher = new hashers.MimcSpongeHasher(); 20 | const prefix = 'semaphore'; 21 | const default_value = '0'; 22 | const depth = 2 23 | 24 | const tree = new tree.MerkleTree( 25 | prefix, 26 | storage, 27 | hasher, 28 | depth, 29 | default_value, 30 | ) 31 | ``` 32 | 33 | ## Functions 34 | 35 | ### `tree.update(index, value)` 36 | 37 | Adds `value` to the leaf at `index` 38 | 39 | ### `tree.path(index)` 40 | 41 | Returns the Merkle path to the leaf at the specified index 42 | 43 | ### `tree.rollback(updates)` 44 | 45 | Rolls back the tree by the specified number of updates 46 | 47 | ## Building 48 | 49 | ```bash 50 | git clone https://github.com/weijiekoh/semaphore-merkle-tree.git && \ 51 | cd semaphore-merkle-tree && \ 52 | npm i && \ 53 | npm run build 54 | ``` 55 | 56 | ## Testing 57 | 58 | ```bash 59 | npm run test 60 | ``` 61 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | verbose: true, 3 | transform: { 4 | "^.+\\.tsx?$": 'ts-jest' 5 | }, 6 | testPathIgnorePatterns: [ 7 | "/build/", 8 | "/node_modules/", 9 | ], 10 | testRegex: '/__tests__/.*\\.test\\.ts$', 11 | moduleFileExtensions: [ 12 | 'ts', 13 | 'tsx', 14 | 'js', 15 | 'jsx', 16 | 'json', 17 | 'node' 18 | ], 19 | globals: { 20 | 'ts-jest': { 21 | diagnostics: { 22 | // Do not fail on TS compilation errors 23 | // https://kulshekhar.github.io/ts-jest/user/config/diagnostics#do-not-fail-on-first-error 24 | warnOnly: true 25 | } 26 | } 27 | }, 28 | testEnvironment: 'node' 29 | } 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "semaphore-merkle-tree", 3 | "version": "1.0.13", 4 | "description": "A TypeScript implementation of the Merkle tree used in the Semaphore zero-knowledge signalling system", 5 | "main": "build/index.js", 6 | "scripts": { 7 | "test": "jest", 8 | "build": "tsc", 9 | "prepare": "npm run build", 10 | "prepublishOnly": "npm test", 11 | "watch": "tsc --watch" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/weijiekoh/semaphore-merkle-tree.git" 16 | }, 17 | "author": "Koh Wei Jie; Kobi Gurkan", 18 | "license": "GPL-3.0", 19 | "bugs": { 20 | "url": "https://github.com/weijiekoh/semaphore-merkle-tree/issues" 21 | }, 22 | "homepage": "https://github.com/weijiekoh/semaphore-merkle-tree#readme", 23 | "devDependencies": { 24 | "chai": "^4.2.0", 25 | "chai-as-promised": "^7.1.1", 26 | "jest": "^24.8.0", 27 | "ts-jest": "^24.0.2", 28 | "typescript": "^3.5.3" 29 | }, 30 | "dependencies": { 31 | "@types/jest": "^24.0.15", 32 | "await-lock": "^1.1.3", 33 | "circomlib": "0.0.21", 34 | "snarkjs": "0.1.20" 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /ts/__tests__/Hashers.test.ts: -------------------------------------------------------------------------------- 1 | import { MemStorage } from '../storage' 2 | import { PoseidonHasher } from '../hashers' 3 | 4 | jest.setTimeout(9000) 5 | 6 | describe('Hash functions', function () { 7 | const hasher = new PoseidonHasher() 8 | 9 | it('poseidon', async () => { 10 | const output = hasher.hash(null, 1, 2) 11 | expect(output.toString()).toEqual('2104035019328376391822106787753454168168617545136592089411833517434990977743') 12 | }) 13 | }) 14 | -------------------------------------------------------------------------------- /ts/__tests__/MemStorage.test.ts: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const chaiAsPromised = require('chai-as-promised'); 3 | chai.use(chaiAsPromised); 4 | const path = require('path'); 5 | const uuidv4 = require('uuid/v4'); 6 | 7 | const assert = chai.assert; 8 | const expect = chai.expect; 9 | 10 | import { MemStorage } from '../storage' 11 | 12 | const db = new MemStorage() 13 | describe('db test', function () { 14 | 15 | it('tests simple put/get', async () => { 16 | const testkey = 'testkey'; 17 | const rand_string = uuidv4(); 18 | await db.put(testkey, rand_string); 19 | assert.equal(await db.get(testkey), rand_string); 20 | assert.notEqual(await db.get(testkey), ''); 21 | expect(db.get('b')).to.be.rejected; 22 | assert.equal(await db.get_or_element('b', 5), 5); 23 | assert.notEqual(await db.get_or_element('b', 6), 5); 24 | }) 25 | 26 | it('tries put batch', async () => { 27 | const testkey1 = 'testkey1'; 28 | const rand_string1 = uuidv4(); 29 | const testkey2 = 'testkey2'; 30 | const rand_string2 = uuidv4(); 31 | const rand_string3 = uuidv4(); 32 | await db.put_batch([ 33 | { key: testkey1, value: rand_string1 }, 34 | { key: testkey2, value: rand_string2 }, 35 | { key: testkey1, value: rand_string3 }, 36 | ]); 37 | assert.equal(await db.get(testkey1), rand_string3); 38 | assert.equal(await db.get(testkey2), rand_string2); 39 | expect(db.get('testkey3')).to.be.rejected; 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /ts/__tests__/MerkleTree.test.ts: -------------------------------------------------------------------------------- 1 | const chai = require('chai'); 2 | const chaiAsPromised = require('chai-as-promised'); 3 | chai.use(chaiAsPromised); 4 | 5 | const assert = chai.assert; 6 | 7 | import { MemStorage } from '../storage' 8 | import { Mimc7Hasher } from '../hashers' 9 | import MerkleTree from '../merkletree' 10 | 11 | const storage = new MemStorage() 12 | 13 | jest.setTimeout(9000) 14 | 15 | describe('tree test', function () { 16 | const prefix = 'test'; 17 | const default_value = '4'; 18 | const depth = 2 19 | const hasher = new Mimc7Hasher(); 20 | let rollback_root 21 | 22 | const tree = new MerkleTree( 23 | prefix, 24 | storage, 25 | hasher, 26 | depth, 27 | default_value, 28 | ); 29 | 30 | it('tests index', async () => { 31 | assert.equal( 32 | MerkleTree.index_to_key('test', 5, 20), 33 | "test_tree_5_20", 34 | ); 35 | }); 36 | 37 | it('tests empty get', async () => { 38 | let { root, path_elements, path_index } = await tree.path(2); 39 | const calculated_root = hasher.hash(1, 40 | path_elements[1], 41 | hasher.hash(0, default_value, path_elements[0]), 42 | ); 43 | assert.equal(root, calculated_root); 44 | }); 45 | 46 | it('tests insert', async () => { 47 | await tree.update(0, '5'); 48 | rollback_root = (await tree.path(0)).root; 49 | let { root, path_elements, path_index } = await tree.path(0); 50 | const calculated_root = hasher.hash(1, 51 | hasher.hash(0, '5', path_elements[0]), 52 | path_elements[1], 53 | ); 54 | assert.equal(root, calculated_root); 55 | }); 56 | 57 | it('tests updated', async () => { 58 | await tree.update(1, '6'); 59 | await tree.update(2, '9'); 60 | await tree.update(2, '8'); 61 | await tree.update(2, '82'); 62 | let { root, path_elements, path_index } = await tree.path(0); 63 | const calculated_root = hasher.hash(1, 64 | hasher.hash(0, '5', path_elements[0]), 65 | path_elements[1], 66 | ); 67 | assert.equal(root, calculated_root); 68 | const wrong_calculated_root = hasher.hash(1, 69 | hasher.hash(0, '6', path_elements[0]), 70 | path_elements[1], 71 | ); 72 | assert.notEqual(root, wrong_calculated_root); 73 | }); 74 | 75 | it('tests update log', async () => { 76 | const update_log_key = MerkleTree.update_log_to_key(prefix); 77 | const update_log_index = await tree.storage.get(update_log_key); 78 | assert.equal(update_log_index, 4); 79 | const update_log_element_key = MerkleTree.update_log_element_to_key(prefix, update_log_index); 80 | const update_log_element = JSON.parse(await tree.storage.get(update_log_element_key)); 81 | assert.equal(update_log_element.old_element, '8'); 82 | assert.equal(update_log_element.new_element, '82'); 83 | }); 84 | 85 | it('tests rollback', async () => { 86 | { 87 | await tree.rollback(1); 88 | const update_log_key = MerkleTree.update_log_to_key(prefix); 89 | const update_log_index = await tree.storage.get(update_log_key); 90 | assert.equal(update_log_index, 3); 91 | const update_log_element_key = MerkleTree.update_log_element_to_key(prefix, update_log_index); 92 | const update_log_element = JSON.parse(await tree.storage.get(update_log_element_key)); 93 | assert.equal(update_log_element.old_element, '9'); 94 | assert.equal(update_log_element.new_element, '8'); 95 | } 96 | 97 | { 98 | await tree.rollback(1); 99 | const update_log_key = MerkleTree.update_log_to_key(prefix); 100 | const update_log_index = await tree.storage.get(update_log_key); 101 | assert.equal(update_log_index, 2); 102 | const update_log_element_key = MerkleTree.update_log_element_to_key(prefix, update_log_index); 103 | const update_log_element = JSON.parse(await tree.storage.get(update_log_element_key)); 104 | assert.equal(update_log_element.old_element, '4'); 105 | assert.equal(update_log_element.new_element, '9'); 106 | } 107 | 108 | { 109 | await tree.rollback_to_root(rollback_root) 110 | const update_log_key = MerkleTree.update_log_to_key(prefix); 111 | const update_log_index = await tree.storage.get(update_log_key); 112 | assert.equal(update_log_index, 1); 113 | const update_log_element_key = MerkleTree.update_log_element_to_key(prefix, update_log_index); 114 | const update_log_element = JSON.parse(await tree.storage.get(update_log_element_key)); 115 | assert.equal(update_log_element.old_element, '4'); 116 | assert.equal(update_log_element.new_element, '6'); 117 | } 118 | }) 119 | 120 | it('tests if path consistent', async () => { 121 | await tree.update(1, '6'); 122 | await tree.update(2, '9'); 123 | await tree.update(3, '8'); 124 | await tree.update(4, '82'); 125 | 126 | const p1 = await tree.path(2); 127 | 128 | const index = await tree.element_index('9') 129 | const p2 = await tree.path(index) 130 | 131 | assert.strictEqual(index, 2) 132 | assert.equal(typeof (index), "number") 133 | assert.equal(p1.path_elements.toString(), p2.path_elements.toString()); 134 | }); 135 | }) 136 | -------------------------------------------------------------------------------- /ts/hashers/ihasher.ts: -------------------------------------------------------------------------------- 1 | interface IHasher { 2 | hash: Function 3 | } 4 | 5 | export default IHasher 6 | -------------------------------------------------------------------------------- /ts/hashers/index.ts: -------------------------------------------------------------------------------- 1 | import IHasher from './ihasher' 2 | import Mimc7Hasher from './mimc7' 3 | import MimcSpongeHasher from './mimcsponge' 4 | import PoseidonHasher from './poseidon' 5 | 6 | export { 7 | IHasher, 8 | Mimc7Hasher, 9 | MimcSpongeHasher, 10 | PoseidonHasher, 11 | } 12 | -------------------------------------------------------------------------------- /ts/hashers/mimc7.ts: -------------------------------------------------------------------------------- 1 | import IHasher from './ihasher' 2 | import * as circomlib from 'circomlib' 3 | import * as snarkjs from 'snarkjs' 4 | 5 | const mimc7 = circomlib.mimc7 6 | const bigInt = snarkjs.bigInt 7 | 8 | class Mimc7Hasher implements IHasher { 9 | public hash(_, left, right) { 10 | return mimc7.multiHash([bigInt(left), bigInt(right)]).toString() 11 | } 12 | } 13 | 14 | export default Mimc7Hasher 15 | 16 | -------------------------------------------------------------------------------- /ts/hashers/mimcsponge.ts: -------------------------------------------------------------------------------- 1 | import IHasher from './ihasher' 2 | import * as circomlib from 'circomlib' 3 | import * as snarkjs from 'snarkjs' 4 | const mimcsponge = circomlib.mimcsponge 5 | 6 | const bigInt = snarkjs.bigInt; 7 | 8 | class MimcSpongeHasher implements IHasher { 9 | public hash(_, left, right) { 10 | return mimcsponge.multiHash([bigInt(left), bigInt(right)]).toString() 11 | } 12 | } 13 | 14 | export default MimcSpongeHasher 15 | -------------------------------------------------------------------------------- /ts/hashers/poseidon.ts: -------------------------------------------------------------------------------- 1 | import IHasher from './ihasher' 2 | import * as circomlib from 'circomlib' 3 | import * as snarkjs from 'snarkjs' 4 | const poseidon = circomlib.poseidon 5 | 6 | const bigInt = snarkjs.bigInt; 7 | 8 | class PoseidonHasher implements IHasher { 9 | private hashFunc: Function 10 | 11 | constructor() { 12 | this.hashFunc = poseidon.createHash(3, 8, 57, 'poseidon') 13 | } 14 | 15 | public hash(_, left, right) { 16 | return this.hashFunc([left, right]) 17 | } 18 | } 19 | 20 | export default PoseidonHasher 21 | -------------------------------------------------------------------------------- /ts/index.ts: -------------------------------------------------------------------------------- 1 | import { IStorage, MemStorage } from './storage' 2 | import { IHasher, Mimc7Hasher, MimcSpongeHasher, PoseidonHasher } from './hashers' 3 | import MerkleTree from './merkletree' 4 | 5 | export = { 6 | storage: { 7 | MemStorage, 8 | }, 9 | hashers: { 10 | Mimc7Hasher, 11 | MimcSpongeHasher, 12 | PoseidonHasher, 13 | }, 14 | tree: { 15 | MerkleTree, 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /ts/merkletree.ts: -------------------------------------------------------------------------------- 1 | import { IStorage } from './storage' 2 | import { IHasher } from './hashers' 3 | 4 | const AwaitLock = require('await-lock') 5 | 6 | export default class MerkleTree { 7 | public prefix: string 8 | public storage: IStorage 9 | public hasher: IHasher 10 | public n_levels: number 11 | public zero_values: any[] 12 | public lock: any 13 | 14 | constructor(prefix, storage, hasher, n_levels, zero_value) { 15 | this.prefix = prefix; 16 | this.storage = storage; 17 | this.hasher = hasher; 18 | this.n_levels = n_levels; 19 | this.zero_values = []; 20 | 21 | let current_zero_value = zero_value; 22 | this.zero_values.push(current_zero_value); 23 | for (let i = 0; i < n_levels; i++) { 24 | current_zero_value = this.hasher.hash(i, current_zero_value, current_zero_value); 25 | this.zero_values.push( 26 | current_zero_value.toString(), 27 | ); 28 | } 29 | this.lock = new AwaitLock(); 30 | } 31 | 32 | static index_to_key(prefix, level, index) { 33 | const key = `${prefix}_tree_${level}_${index}`; 34 | return key; 35 | } 36 | 37 | static element_to_key(prefix, element) { 38 | const key = `${prefix}_element_${element}`; 39 | return key; 40 | } 41 | 42 | static update_log_to_key(prefix) { 43 | return `${prefix}_update_log_index`; 44 | } 45 | 46 | static update_log_element_to_key(prefix, update_log_index) { 47 | return `${prefix}_update_log_element_${update_log_index}`; 48 | } 49 | 50 | async update_log(index, old_element, new_element, update_log_index, should_put_element_update) { 51 | let ops: any[] = [] 52 | 53 | const update_log_key = MerkleTree.update_log_to_key(this.prefix); 54 | ops.push({ 55 | type: 'put', 56 | key: update_log_key, 57 | value: update_log_index.toString(), 58 | }); 59 | 60 | if (should_put_element_update) { 61 | const update_log_element_key = MerkleTree.update_log_element_to_key(this.prefix, update_log_index); 62 | ops.push({ 63 | type: 'put', 64 | key: update_log_element_key, 65 | value: JSON.stringify({ 66 | index, 67 | old_element, 68 | new_element, 69 | }) 70 | }); 71 | } 72 | await this.storage.put_batch(ops); 73 | } 74 | 75 | async root() { 76 | let root = await this.storage.get_or_element( 77 | MerkleTree.index_to_key(this.prefix, this.n_levels, 0), 78 | this.zero_values[this.n_levels], 79 | ); 80 | 81 | return root; 82 | } 83 | 84 | async element_index(element) { 85 | const element_key = MerkleTree.element_to_key(this.prefix, element); 86 | const index = await this.storage.get_or_element(element_key, -1); 87 | return parseInt(index); 88 | } 89 | 90 | async path(index) { 91 | class PathTraverser { 92 | public prefix: string 93 | public storage: IStorage 94 | public zero_values: any[] 95 | public path_elements: string[] 96 | public path_index: number[] 97 | constructor(prefix, storage, zero_values) { 98 | this.prefix = prefix; 99 | this.storage = storage; 100 | this.zero_values = zero_values; 101 | this.path_elements = []; 102 | this.path_index = []; 103 | } 104 | 105 | async handle_index(level, element_index, sibling_index) { 106 | const sibling = await this.storage.get_or_element( 107 | MerkleTree.index_to_key(this.prefix, level, sibling_index), 108 | this.zero_values[level], 109 | ); 110 | this.path_elements.push(sibling); 111 | this.path_index.push(element_index % 2); 112 | } 113 | } 114 | let traverser = new PathTraverser(this.prefix, this.storage, this.zero_values); 115 | const root = await this.storage.get_or_element( 116 | MerkleTree.index_to_key(this.prefix, this.n_levels, 0), 117 | this.zero_values[this.n_levels], 118 | ); 119 | 120 | const element = await this.storage.get_or_element( 121 | MerkleTree.index_to_key(this.prefix, 0, index), 122 | this.zero_values[0], 123 | ); 124 | 125 | await this.traverse(index, traverser); 126 | return { 127 | root, 128 | path_elements: traverser.path_elements, 129 | path_index: traverser.path_index, 130 | element 131 | }; 132 | } 133 | 134 | async update(index, leaf, update_log_index?: number, lock_already_acquired?: boolean) { 135 | const element = leaf.toString() 136 | if (!lock_already_acquired) { 137 | await this.lock.acquireAsync(); 138 | } 139 | try { 140 | //console.log(`updating ${index}, ${element}`); 141 | class UpdateTraverser { 142 | public prefix: string 143 | public storage: IStorage 144 | public zero_values: any[] 145 | public current_element: any 146 | public original_element: any 147 | public hasher: IHasher 148 | public key_values_to_put: any[] 149 | 150 | constructor(prefix, storage, hasher, element, zero_values) { 151 | this.prefix = prefix; 152 | this.current_element = element; 153 | this.zero_values = zero_values; 154 | this.storage = storage; 155 | this.hasher = hasher; 156 | this.key_values_to_put = []; 157 | } 158 | 159 | async handle_index(level, element_index, sibling_index) { 160 | if (level == 0) { 161 | this.original_element = await this.storage.get_or_element( 162 | MerkleTree.index_to_key(this.prefix, level, element_index), 163 | this.zero_values[level], 164 | ); 165 | this.key_values_to_put.push({ 166 | key: MerkleTree.element_to_key(this.prefix, element), 167 | value: index.toString(), 168 | }); 169 | 170 | } 171 | const sibling = await this.storage.get_or_element( 172 | MerkleTree.index_to_key(this.prefix, level, sibling_index), 173 | this.zero_values[level], 174 | ); 175 | let left, right; 176 | if (element_index % 2 == 0) { 177 | left = this.current_element; 178 | right = sibling; 179 | } else { 180 | left = sibling; 181 | right = this.current_element; 182 | } 183 | 184 | this.key_values_to_put.push({ 185 | key: MerkleTree.index_to_key(this.prefix, level, element_index), 186 | value: this.current_element, 187 | }); 188 | //console.log(`left: ${left}, right: ${right}`); 189 | this.current_element = this.hasher.hash(level, left, right); 190 | //console.log(`current_element: ${this.current_element}`); 191 | } 192 | } 193 | let traverser = new UpdateTraverser( 194 | this.prefix, 195 | this.storage, 196 | this.hasher, 197 | element, 198 | this.zero_values 199 | ); 200 | 201 | await this.traverse(index, traverser); 202 | //console.log(`traverser.current_element: ${traverser.current_element}`); 203 | traverser.key_values_to_put.push({ 204 | key: MerkleTree.index_to_key(this.prefix, this.n_levels, 0), 205 | value: traverser.current_element, 206 | }); 207 | 208 | if (update_log_index == undefined) { 209 | const update_log_key = MerkleTree.update_log_to_key(this.prefix); 210 | let update_log_index_from_db = await this.storage.get_or_element(update_log_key, -1); 211 | update_log_index = parseInt(update_log_index_from_db) + 1; 212 | await this.update_log(index, traverser.original_element, element, update_log_index, true); 213 | } else { 214 | await this.update_log(index, traverser.original_element, element, update_log_index, false); 215 | } 216 | 217 | await this.storage.del(MerkleTree.element_to_key(this.prefix, traverser.original_element)); 218 | //traverser.key_values_to_put.forEach((e) => console.log(`key_values: ${JSON.stringify(e)}`)); 219 | await this.storage.put_batch(traverser.key_values_to_put); 220 | 221 | const root = await this.root(); 222 | //console.log(`updated root ${root}`); 223 | } finally { 224 | if (!lock_already_acquired) { 225 | this.lock.release(); 226 | } 227 | } 228 | } 229 | 230 | async traverse(index, handler) { 231 | let current_index = index; 232 | for (let i = 0; i < this.n_levels; i++) { 233 | let sibling_index = current_index; 234 | if (current_index % 2 == 0) { 235 | sibling_index += 1; 236 | } else { 237 | sibling_index -= 1; 238 | } 239 | await handler.handle_index(i, current_index, sibling_index); 240 | current_index = Math.floor(current_index / 2); 241 | } 242 | } 243 | 244 | async rollback(updates) { 245 | await this.lock.acquireAsync(); 246 | try { 247 | const update_log_key = MerkleTree.update_log_to_key(this.prefix); 248 | const update_log_index = await this.storage.get(update_log_key); 249 | for (let i = 0; i < updates; i++) { 250 | const update_log_element_key = MerkleTree.update_log_element_to_key(this.prefix, update_log_index - i); 251 | const update_element_log = JSON.parse(await this.storage.get(update_log_element_key)); 252 | 253 | await this.update(update_element_log.index, update_element_log.old_element, update_log_index - i - 1, true); 254 | } 255 | } finally { 256 | this.lock.release(); 257 | } 258 | } 259 | 260 | async rollback_to_root(root) { 261 | await this.lock.acquireAsync(); 262 | try { 263 | const update_log_key = MerkleTree.update_log_to_key(this.prefix); 264 | let update_log_index = await this.storage.get(update_log_key); 265 | while (update_log_index >= 0) { 266 | const update_log_element_key = MerkleTree.update_log_element_to_key(this.prefix, update_log_index); 267 | const update_element_log = JSON.parse(await this.storage.get(update_log_element_key)); 268 | 269 | await this.update(update_element_log.index, update_element_log.old_element, update_log_index, true); 270 | const current_root = await this.root(); 271 | if (current_root == root) { 272 | break; 273 | } 274 | update_log_index -= 1; 275 | } 276 | if (await this.root() != root) { 277 | throw new Error(`could not rollback to root ${root}`); 278 | } 279 | } finally { 280 | this.lock.release(); 281 | } 282 | } 283 | } 284 | 285 | -------------------------------------------------------------------------------- /ts/storage/index.ts: -------------------------------------------------------------------------------- 1 | import MemStorage from './memstorage' 2 | import IStorage from './istorage' 3 | 4 | export { MemStorage, IStorage } 5 | -------------------------------------------------------------------------------- /ts/storage/istorage.ts: -------------------------------------------------------------------------------- 1 | interface IStorage { 2 | get: Function 3 | get_or_element: Function 4 | put: Function 5 | put_batch: Function 6 | del: Function 7 | } 8 | 9 | export default IStorage 10 | -------------------------------------------------------------------------------- /ts/storage/memstorage.ts: -------------------------------------------------------------------------------- 1 | import IStorage from './istorage' 2 | 3 | class MemStorage implements IStorage { 4 | private db: any = {} 5 | 6 | async get(key) { 7 | return await this.db[key] 8 | } 9 | 10 | public async get_or_element(key, element) { 11 | if (!this.db.hasOwnProperty(key)) { 12 | return element 13 | } else { 14 | return await this.get(key) 15 | } 16 | } 17 | 18 | public async put(key, value) { 19 | this.db[key] = value 20 | } 21 | 22 | public async del(key) { 23 | delete this.db[key] 24 | } 25 | 26 | public async put_batch(key_values) { 27 | for (var i = 0; i < key_values.length; i++) { 28 | await this.put(key_values[i].key, key_values[i].value) 29 | } 30 | } 31 | } 32 | 33 | export default MemStorage 34 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "experimentalDecorators": true, 4 | "alwaysStrict": true, 5 | "allowJs": true, 6 | "noImplicitAny": false, 7 | "forceConsistentCasingInFileNames": true, 8 | "noUnusedLocals": false, 9 | "noUnusedParameters": true, 10 | "noImplicitReturns": true, 11 | "noFallthroughCasesInSwitch": true, 12 | "sourceMap": true, 13 | "strict": true, 14 | "outDir": "./build", 15 | "lib": [ "es2015" ] 16 | }, 17 | "include": [ 18 | "./ts" 19 | ] 20 | } 21 | --------------------------------------------------------------------------------