Permalink
Cannot retrieve contributors at this time
Name already in use
A tag already exists with the provided branch name. Many Git commands accept both tag and branch names, so creating this branch may cause unexpected behavior. Are you sure you want to create this branch?
leap-contracts/contracts/SparseMerkleTree.sol
Go to fileThis commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
77 lines (67 sloc)
2.58 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| /** | |
| * Copyright (c) 2018-present, Leap DAO (leapdao.org) | |
| * | |
| * This source code is licensed under the Mozilla Public License, version 2, | |
| * found in the LICENSE file in the root directory of this source tree. | |
| */ | |
| pragma solidity >=0.4.21 <0.6.0; | |
| // Based on https://rinkeby.etherscan.io/address/0x881544e0b2e02a79ad10b01eca51660889d5452b#code | |
| contract SparseMerkleTree { | |
| uint8 constant DEPTH = 160; | |
| bytes32[DEPTH + 1] public defaultHashes; | |
| bytes32 public root; | |
| constructor() public { | |
| // defaultHash[0] is being set to keccak256(uint256(0)); | |
| defaultHashes[0] = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; | |
| for (uint8 i = 1; i <= DEPTH; i ++) { | |
| defaultHashes[i] = keccak256(abi.encodePacked(defaultHashes[i-1], defaultHashes[i-1])); | |
| } | |
| root = defaultHashes[DEPTH]; | |
| } | |
| function read(uint160 key, bytes32 leaf, bytes memory proof) public view returns (bool) { | |
| bytes32 calculatedRoot = getRoot(leaf, key, proof); | |
| return (calculatedRoot == root); | |
| } | |
| function write( | |
| uint160 key, | |
| bytes32 prevLeaf, | |
| bytes memory proof, | |
| bytes32 newLeaf | |
| ) public { | |
| bytes32 calculatedRoot = getRoot(prevLeaf, key, proof); | |
| require(calculatedRoot == root, "update proof not valid"); | |
| root = getRoot(newLeaf, key, proof); | |
| } | |
| function del(uint160 key, bytes32 prevLeaf, bytes memory proof) public { | |
| bytes32 calculatedRoot = getRoot(prevLeaf, key, proof); | |
| require(calculatedRoot == root, "update proof not valid"); | |
| root = getRoot(defaultHashes[0], key, proof); | |
| } | |
| // first 160 bits of the proof are the 0/1 bits | |
| function getRoot(bytes32 leaf, uint160 _index, bytes memory proof) public view returns (bytes32) { | |
| require((proof.length - 20) % 32 == 0 && proof.length <= 5140, "invalid proof format"); | |
| bytes32 proofElement; | |
| bytes32 computedHash = leaf; | |
| uint16 p = 20; | |
| uint160 proofBits; | |
| uint160 index = _index; | |
| assembly {proofBits := div(mload(add(proof, 32)), exp(256, 12))} | |
| for (uint d = 0; d < DEPTH; d++ ) { | |
| if (proofBits % 2 == 0) { // check if last bit of proofBits is 0 | |
| proofElement = defaultHashes[d]; | |
| } else { | |
| p += 32; | |
| require(proof.length >= p, "proof not long enough"); | |
| assembly { proofElement := mload(add(proof, p)) } | |
| } | |
| if (index % 2 == 0) { | |
| computedHash = keccak256(abi.encodePacked(computedHash, proofElement)); | |
| } else { | |
| computedHash = keccak256(abi.encodePacked(proofElement, computedHash)); | |
| } | |
| proofBits = proofBits / 2; // shift it right for next bit | |
| index = index / 2; | |
| } | |
| return computedHash; | |
| } | |
| } |