diff --git a/.prettierrc b/.prettierrc index e368cfe..0566dd0 100644 --- a/.prettierrc +++ b/.prettierrc @@ -3,13 +3,24 @@ { "files": "*.sol", "options": { - "printWidth": 80, - "tabWidth": 4, + "printWidth": 120, + "tabWidth": 2, "useTabs": false, "singleQuote": false, "bracketSpacing": false, "explicitTypes": "always" } + }, + { + "files": "*.ts", + "options": { + "printWidth": 180, + "tabWidth": 2, + "useTabs": false, + "singleQuote": true, + "bracketSpacing": true, + "explicitTypes": "always" + } } ] } diff --git a/.soliumrc.json b/.soliumrc.json index 1adcb88..563351a 100644 --- a/.soliumrc.json +++ b/.soliumrc.json @@ -10,7 +10,7 @@ ], "indentation": [ "error", - 4 + 2 ], "linebreak-style": [ "error", diff --git a/contractAddresses.json b/contractAddresses.json index 0262c1d..dff9737 100644 --- a/contractAddresses.json +++ b/contractAddresses.json @@ -1,12 +1,12 @@ { - "AccountTree": "0xDb4ca41Aa0048F90E73a33eF2b69194b3096E22B", - "ParamManager": "0x24E1331a6BC24305203Ce58fA1Ff707c21DE7198", - "DepositManager": "0x80446876ED0a68952B90941D4765533C924a8235", - "RollupContract": "0x92F6C1485FC82CeDa3C198dEFb47a61843923621", - "ProofOfBurnContract": "0xDa880bd054deA4a5c866F7a919c8b8059DE6354f", - "RollupUtilities": "0xEf4Ec19AdF9B107b5be1f88Ec4d7395B56299b1d", - "NameRegistry": "0x8d7E5E349DDB3dB4c24B80849809602932C8EE00", - "Logger": "0xa961afE298Df64076D18910D094d843b7261574A", - "MerkleTreeUtils": "0x849eC868B8e63bc112eb47F5440945a587E99e29", - "FraudProof": "0xA44dd7c1debc7e69F9026d20032e5d19D94057c2" + "AccountTree": "0xE1c08596DEb9465184820f2815182157db42757B", + "ParamManager": "0x70eE878D560D7E17a986872f54B7C58f564B2784", + "DepositManager": "0x464c94ea90fc7D0D08Bb956cffB9AcfE3AAA5313", + "RollupContract": "0x53b94F9474c01678040BA7da4bc54DCD8c0Ed0BA", + "ProofOfBurnContract": "0x6bf5b6F9b59DF885bD241304C902C5bF7d816fbd", + "RollupUtilities": "0x46E3D301e211d2A2D3148412FCA5788F3182908d", + "NameRegistry": "0x40b13914f9886E234E1e00435E76D558FA8cf5ba", + "Logger": "0x768b5Faed6DC69816f33377d214ffaf00dcDd0cf", + "MerkleTreeUtils": "0x9e8efB8C27f3012493ce315974A64CAcDE6f4ccC", + "FraudProof": "0xFA64858C14345C0a3aD805E0da64900C4d7ec5e6" } \ No newline at end of file diff --git a/contracts/AccountTree.sol b/contracts/AccountTree.sol new file mode 100644 index 0000000..7eb5ade --- /dev/null +++ b/contracts/AccountTree.sol @@ -0,0 +1,115 @@ +pragma solidity ^0.5.15; + +contract AccountTree { + uint256 public constant DEPTH = 31; + uint256 public constant WITNESS_LENGTH = DEPTH; + uint256 public constant SET_SIZE = 1 << DEPTH; + uint256 public constant BATCH_DEPTH = 10; + uint256 public constant BATCH_SIZE = 1 << BATCH_DEPTH; + + bytes32 public rootLeft; + bytes32 public rootRight; + bytes32 public root; + uint256 public leafIndexLeft = 0; + uint256 public leafIndexRight = 0; + + bytes32[DEPTH] public zeros; + bytes32[DEPTH] public filledSubtreesLeft; + bytes32[DEPTH - BATCH_DEPTH] public filledSubtreesRight; + + constructor() public { + for (uint256 i = 1; i < DEPTH; i++) { + zeros[i] = keccak256(abi.encode(zeros[i - 1], zeros[i - 1])); + if (DEPTH > i) { + filledSubtreesLeft[i] = zeros[i]; + } + if (BATCH_DEPTH <= i && DEPTH > i) { + filledSubtreesRight[i - BATCH_DEPTH] = zeros[i]; + } + } + rootLeft = keccak256(abi.encode(zeros[DEPTH - 1], zeros[DEPTH - 1])); + rootRight = keccak256(abi.encode(zeros[DEPTH - 1], zeros[DEPTH - 1])); + root = keccak256(abi.encode(rootLeft, rootRight)); + } + + function _updateSingle(bytes32 leaf) internal returns (uint256) { + require(leafIndexLeft < SET_SIZE - 1, "AccountTree: left set is full "); + bytes32 acc = leaf; + uint256 path = leafIndexLeft; + bool subtreeSet = false; + for (uint256 i = 0; i < DEPTH; i++) { + if (path & 1 == 1) { + acc = keccak256(abi.encode(filledSubtreesLeft[i], acc)); + } else { + if (!subtreeSet) { + filledSubtreesLeft[i] = acc; + subtreeSet = true; + } + acc = keccak256(abi.encode(acc, zeros[i])); + } + path >>= 1; + } + rootLeft = acc; + root = keccak256(abi.encode(rootLeft, rootRight)); + leafIndexLeft += 1; + return leafIndexLeft - 1; + } + + function _updateBatch(bytes32[BATCH_SIZE] memory leafs) internal returns (uint256) { + require(leafIndexRight < SET_SIZE - 1 - BATCH_SIZE, "AccountTree: right set is full "); + // require(leafs.length == BATCH_SIZE, "AccountTree: invalid batch size"); + + // Fill the subtree + for (uint256 i = 0; i < BATCH_DEPTH; i++) { + uint256 n = (BATCH_DEPTH - i - 1); + for (uint256 j = 0; j < 1 << n; j++) { + uint256 k = j << 1; + leafs[j] = keccak256(abi.encode(leafs[k], leafs[k + 1])); + } + } + bytes32 acc = leafs[0]; + + // Ascend to the root + uint256 path = leafIndexRight; + bool subtreeSet = false; + for (uint256 i = 0; i < DEPTH - BATCH_DEPTH; i++) { + if (path & 1 == 1) { + acc = keccak256(abi.encode(filledSubtreesRight[i], acc)); + } else { + if (!subtreeSet) { + filledSubtreesRight[i] = acc; + subtreeSet = true; + } + acc = keccak256(abi.encode(acc, zeros[i + BATCH_DEPTH])); + } + path >>= 1; + } + rootRight = acc; + root = keccak256(abi.encode(rootLeft, rootRight)); + leafIndexRight += 1; + return leafIndexRight - 1; + } + + function _checkInclusion( + bytes32 leaf, + uint256 leafIndex, + bytes32[WITNESS_LENGTH] memory witness + ) internal view returns (bool) { + require(witness.length == DEPTH, "AccountTree: invalid witness size"); + uint256 path = leafIndex % SET_SIZE; + bytes32 acc = leaf; + for (uint256 i = 0; i < WITNESS_LENGTH - 1; i++) { + if (path & 1 == 1) { + acc = keccak256(abi.encode(witness[i], acc)); + } else { + acc = keccak256(abi.encode(acc, witness[i])); + } + path >>= 1; + } + if (leafIndex < SET_SIZE) { + return acc == rootLeft; + } else { + return acc == rootRight; + } + } +} diff --git a/contracts/BLSAccountRegistry.sol b/contracts/BLSAccountRegistry.sol new file mode 100644 index 0000000..2a62373 --- /dev/null +++ b/contracts/BLSAccountRegistry.sol @@ -0,0 +1,35 @@ +pragma solidity ^0.5.15; + +import {AccountTree} from "./AccountTree.sol"; +import {BLS} from "./libs/BLS.sol"; + +contract BLSAccountRegistry is AccountTree { + constructor() public AccountTree() {} + + function register(uint256[4] calldata pubkey) external returns (uint256) { + require(BLS.isValidPublicKey(pubkey), "BLSAccountTree: invalid pub key"); + bytes32 leaf = keccak256(abi.encodePacked(pubkey)); + uint256 accountID = _updateSingle(leaf); + return accountID; + } + + function registerBatch(uint256[4][BATCH_SIZE] calldata pubkeys) external returns (uint256) { + bytes32[BATCH_SIZE] memory leafs; + for (uint256 i = 0; i < BATCH_SIZE; i++) { + require(BLS.isValidPublicKey(pubkeys[i]), "BLSAccountTree: invalid pub key"); + bytes32 leaf = keccak256(abi.encodePacked(pubkeys[i])); + leafs[i] = leaf; + } + uint256 lowerOffset = _updateBatch(leafs); + return lowerOffset; + } + + function exists( + uint256 accountIndex, + uint256[4] calldata pubkey, + bytes32[WITNESS_LENGTH] calldata witness + ) external view returns (bool) { + bytes32 leaf = keccak256(abi.encodePacked(pubkey)); + return _checkInclusion(leaf, accountIndex, witness); + } +} diff --git a/contracts/Deployer.sol b/contracts/Deployer.sol index c1cc66d..9483d73 100644 --- a/contracts/Deployer.sol +++ b/contracts/Deployer.sol @@ -1,7 +1,6 @@ pragma solidity ^0.5.15; import {ParamManager} from "./libs/ParamManager.sol"; import {NameRegistry as Registry} from "./NameRegistry.sol"; -import {IncrementalTree} from "./IncrementalTree.sol"; import {DepositManager} from "./DepositManager.sol"; import {TestToken} from "./TestToken.sol"; import {Rollup} from "./rollup.sol"; @@ -13,69 +12,31 @@ import {Governance} from "./Governance.sol"; // Deployer is supposed to deploy new set of contracts while setting up all the utilities // libraries and other auxiallry contracts like registry contract Deployer { - constructor( - address nameregistry, - uint256 maxDepth, - uint256 maxDepositSubTree - ) public { - deployContracts(nameregistry, maxDepth, maxDepositSubTree); - } - - function deployContracts( - address nameRegistryAddr, - uint256 maxDepth, - uint256 maxDepositSubTree - ) public returns (address) { - Registry registry = Registry(nameRegistryAddr); - address governance = address( - new Governance(maxDepth, maxDepositSubTree) - ); - require( - registry.registerName(ParamManager.Governance(), governance), - "Could not register governance" - ); - address mtUtils = address(new MTUtils(nameRegistryAddr)); - require( - registry.registerName(ParamManager.MERKLE_UTILS(), mtUtils), - "Could not register merkle utils tree" - ); - - address logger = address(new Logger()); - require( - registry.registerName(ParamManager.LOGGER(), logger), - "Cannot register logger" - ); - - address tokenRegistry = address(new TokenRegistry(nameRegistryAddr)); - require( - registry.registerName(ParamManager.TOKEN_REGISTRY(), tokenRegistry), - "Cannot register token registry" - ); - - return nameRegistryAddr; - - // deploy accounts tree - // address accountsTree = address(new IncrementalTree(nameRegistryAddr)); - // require( - // registry.registerName(ParamManager.ACCOUNTS_TREE(), accountsTree), - // "Could not register accounts tree" - // ); - - // deposit manager - // address depositManager = address(new DepositManager(nameRegistryAddr)); - // require( - // registry.registerName( - // ParamManager.DEPOSIT_MANAGER(), - // depositManager - // ), - // "Cannot register deposit manager" - // ); - - // // deploy core rollup contract - // address rollup = address(new Rollup(nameRegistryAddr)); - // require( - // registry.registerName(ParamManager.ROLLUP_CORE(), rollup), - // "Cannot register core rollup" - // ); - } + constructor( + address nameregistry, + uint256 maxDepth, + uint256 maxDepositSubTree + ) public { + deployContracts(nameregistry, maxDepth, maxDepositSubTree); + } + + function deployContracts( + address nameRegistryAddr, + uint256 maxDepth, + uint256 maxDepositSubTree + ) public returns (address) { + Registry registry = Registry(nameRegistryAddr); + address governance = address(new Governance(maxDepth, maxDepositSubTree)); + require(registry.registerName(ParamManager.Governance(), governance), "Could not register governance"); + address mtUtils = address(new MTUtils(nameRegistryAddr)); + require(registry.registerName(ParamManager.MERKLE_UTILS(), mtUtils), "Could not register merkle utils tree"); + + address logger = address(new Logger()); + require(registry.registerName(ParamManager.LOGGER(), logger), "Cannot register logger"); + + address tokenRegistry = address(new TokenRegistry(nameRegistryAddr)); + require(registry.registerName(ParamManager.TOKEN_REGISTRY(), tokenRegistry), "Cannot register token registry"); + + return nameRegistryAddr; + } } diff --git a/contracts/DepositManager.sol b/contracts/DepositManager.sol index 1e53c0b..869802d 100644 --- a/contracts/DepositManager.sol +++ b/contracts/DepositManager.sol @@ -1,6 +1,5 @@ pragma solidity ^0.5.15; pragma experimental ABIEncoderV2; -import {IncrementalTree} from "./IncrementalTree.sol"; import {Types} from "./libs/Types.sol"; import {Logger} from "./logger.sol"; import {RollupUtils} from "./libs/RollupUtils.sol"; @@ -13,271 +12,211 @@ import {POB} from "./POB.sol"; import {Governance} from "./Governance.sol"; import {Rollup} from "./rollup.sol"; -contract DepositManager { - MTUtils public merkleUtils; - Registry public nameRegistry; - bytes32[] public pendingDeposits; - mapping(uint256 => bytes32) pendingFilledSubtrees; - uint256 public firstElement = 1; - uint256 public lastElement = 0; - - uint256 public depositSubTreesPackaged = 0; - - function enqueue(bytes32 newDepositSubtree) public { - lastElement += 1; - pendingFilledSubtrees[lastElement] = newDepositSubtree; - depositSubTreesPackaged++; - } +import {BLSAccountRegistry} from "./BLSAccountRegistry.sol"; - function dequeue() public returns (bytes32 depositSubtreeRoot) { - require(lastElement >= firstElement); // non-empty queue - depositSubtreeRoot = pendingFilledSubtrees[firstElement]; - delete pendingFilledSubtrees[firstElement]; - firstElement += 1; - depositSubTreesPackaged--; +contract DepositManager { + MTUtils public merkleUtils; + Registry public nameRegistry; + bytes32[] public pendingDeposits; + mapping(uint256 => bytes32) pendingFilledSubtrees; + uint256 public firstElement = 1; + uint256 public lastElement = 0; + + uint256 public depositSubTreesPackaged = 0; + + function enqueue(bytes32 newDepositSubtree) public { + lastElement += 1; + pendingFilledSubtrees[lastElement] = newDepositSubtree; + depositSubTreesPackaged++; + } + + function dequeue() public returns (bytes32 depositSubtreeRoot) { + require(lastElement >= firstElement); // non-empty queue + depositSubtreeRoot = pendingFilledSubtrees[firstElement]; + delete pendingFilledSubtrees[firstElement]; + firstElement += 1; + depositSubTreesPackaged--; + } + + uint256 public queueNumber; + uint256 public depositSubtreeHeight; + Governance public governance; + Logger public logger; + ITokenRegistry public tokenRegistry; + IERC20 public tokenContract; + BLSAccountRegistry public accountRegistry; + + bytes32 public constant ZERO_BYTES32 = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; + + modifier onlyCoordinator() { + POB pobContract = POB(nameRegistry.getContractDetails(ParamManager.POB())); + assert(msg.sender == pobContract.getCoordinator()); + _; + } + + modifier onlyRollup() { + assert(msg.sender == nameRegistry.getContractDetails(ParamManager.ROLLUP_CORE())); + _; + } + + constructor(address _registryAddr) public { + nameRegistry = Registry(_registryAddr); + governance = Governance(nameRegistry.getContractDetails(ParamManager.Governance())); + merkleUtils = MTUtils(nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS())); + tokenRegistry = ITokenRegistry(nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY())); + logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); + accountRegistry = BLSAccountRegistry(nameRegistry.getContractDetails(ParamManager.ACCOUNT_REGISTRY())); + + // FIX: cannot add if not pubkey supplied + // AddCoordinatorLeaves(); + } + + // function AddCoordinatorLeaves() internal { + // // first leaf in the incremental tree belongs to the coordinator + // accountRegistry.appendLeaf(ZERO_BYTES32); + // accountRegistry.appendLeaf(ZERO_BYTES32); + // } + + /** + * @notice Adds a deposit for an address to the deposit queue + * @param _amount Number of tokens that user wants to deposit + * @param _tokenType Type of token user is depositing + */ + function depositFor( + uint256 _amount, + uint256 _tokenType, + uint256[4] calldata _pubkey + ) external { + // check amount is greater than 0 + require(_amount > 0, "token deposit must be greater than 0"); + + // check token type exists + address tokenContractAddress = tokenRegistry.registeredTokens(_tokenType); + tokenContract = IERC20(tokenContractAddress); + + // transfer from msg.sender to this contract + require(tokenContract.transferFrom(msg.sender, address(this), _amount), "token transfer not approved"); + + // returns leaf index upon successfull append + uint256 accID = accountRegistry.register(_pubkey); + + // create a new account + Types.UserAccount memory newAccount; + newAccount.balance = _amount; + newAccount.tokenType = _tokenType; + newAccount.nonce = 0; + newAccount.ID = accID; + + // get new account hash + bytes memory accountBytes = RollupUtils.BytesFromAccount(newAccount); + + // queue the deposit + pendingDeposits.push(keccak256(accountBytes)); + + // emit the event + // logger.logDepositQueued(accID, _pubkey, accountBytes); + + queueNumber++; + uint256 tmpDepositSubtreeHeight = 0; + uint256 tmp = queueNumber; + while (tmp % 2 == 0) { + bytes32[] memory deposits = new bytes32[](2); + deposits[0] = pendingDeposits[pendingDeposits.length - 2]; + deposits[1] = pendingDeposits[pendingDeposits.length - 1]; + + pendingDeposits[pendingDeposits.length - 2] = merkleUtils.getParent(deposits[0], deposits[1]); + + // remove 1 deposit from the pending deposit queue + removeDeposit(pendingDeposits.length - 1); + tmp = tmp / 2; + + // update the temp deposit subtree height + tmpDepositSubtreeHeight++; + + // thow event for the coordinator + logger.logDepositLeafMerged(deposits[0], deposits[1], pendingDeposits[0]); } - uint256 public queueNumber; - uint256 public depositSubtreeHeight; - Governance public governance; - Logger public logger; - ITokenRegistry public tokenRegistry; - IERC20 public tokenContract; - IncrementalTree public accountsTree; - - bytes32 - public constant ZERO_BYTES32 = 0x290decd9548b62a8d60345a988386fc84ba6bc95484008f6362f93160ef3e563; - - modifier onlyCoordinator() { - POB pobContract = POB( - nameRegistry.getContractDetails(ParamManager.POB()) - ); - assert(msg.sender == pobContract.getCoordinator()); - _; + if (tmpDepositSubtreeHeight > depositSubtreeHeight) { + depositSubtreeHeight = tmpDepositSubtreeHeight; } - modifier onlyRollup() { - assert( - msg.sender == - nameRegistry.getContractDetails(ParamManager.ROLLUP_CORE()) - ); - _; - } + if (depositSubtreeHeight == governance.MAX_DEPOSIT_SUBTREE()) { + // start adding deposits to prepackaged deposit subtree root queue + enqueue(pendingDeposits[0]); - constructor(address _registryAddr) public { - nameRegistry = Registry(_registryAddr); - governance = Governance( - nameRegistry.getContractDetails(ParamManager.Governance()) - ); - merkleUtils = MTUtils( - nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) - ); - tokenRegistry = ITokenRegistry( - nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY()) - ); - logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); - accountsTree = IncrementalTree( - nameRegistry.getContractDetails(ParamManager.ACCOUNTS_TREE()) - ); - - AddCoordinatorLeaves(); - } + // emit an event to signal that a package is ready + // isnt really important for anyone tho + logger.logDepositSubTreeReady(pendingDeposits[0]); - function AddCoordinatorLeaves() internal { - // first leaf in the incremental tree belongs to the coordinator - accountsTree.appendLeaf(ZERO_BYTES32); - accountsTree.appendLeaf(ZERO_BYTES32); - } + // update the number of items in pendingDeposits + queueNumber = queueNumber - 2**depositSubtreeHeight; - /** - * @notice Adds a deposit for the msg.sender to the deposit queue - * @param _amount Number of tokens that user wants to deposit - * @param _tokenType Type of token user is depositing - */ - function deposit( - uint256 _amount, - uint256 _tokenType, - bytes memory _pubkey - ) public { - depositFor(msg.sender, _amount, _tokenType, _pubkey); - } + // empty the pending deposits queue + removeDeposit(0); - /** - * @notice Adds a deposit for an address to the deposit queue - * @param _destination Address for which we are depositing - * @param _amount Number of tokens that user wants to deposit - * @param _tokenType Type of token user is depositing - */ - function depositFor( - address _destination, - uint256 _amount, - uint256 _tokenType, - bytes memory _pubkey - ) public { - // check amount is greater than 0 - require(_amount > 0, "token deposit must be greater than 0"); - - // ensure public matches the destination address - require( - _destination == RollupUtils.calculateAddress(_pubkey), - "public key and address don't match" - ); - - // check token type exists - address tokenContractAddress = tokenRegistry.registeredTokens( - _tokenType - ); - tokenContract = IERC20(tokenContractAddress); - - // transfer from msg.sender to this contract - require( - tokenContract.transferFrom(msg.sender, address(this), _amount), - "token transfer not approved" - ); - - // Add pubkey to PDA tree - Types.PDALeaf memory newPDALeaf; - newPDALeaf.pubkey = _pubkey; - - // returns leaf index upon successfull append - uint256 accID = accountsTree.appendLeaf( - RollupUtils.PDALeafToHash(newPDALeaf) - ); - - // create a new account - Types.UserAccount memory newAccount; - newAccount.balance = _amount; - newAccount.tokenType = _tokenType; - newAccount.nonce = 0; - newAccount.ID = accID; - - // get new account hash - bytes memory accountBytes = RollupUtils.BytesFromAccount(newAccount); - - // queue the deposit - pendingDeposits.push(keccak256(accountBytes)); - - // emit the event - logger.logDepositQueued( - accID, - _pubkey, - accountBytes - ); - - queueNumber++; - uint256 tmpDepositSubtreeHeight = 0; - uint256 tmp = queueNumber; - while (tmp % 2 == 0) { - bytes32[] memory deposits = new bytes32[](2); - deposits[0] = pendingDeposits[pendingDeposits.length - 2]; - deposits[1] = pendingDeposits[pendingDeposits.length - 1]; - - pendingDeposits[pendingDeposits.length - 2] = merkleUtils.getParent( - deposits[0], - deposits[1] - ); - - // remove 1 deposit from the pending deposit queue - removeDeposit(pendingDeposits.length - 1); - tmp = tmp / 2; - - // update the temp deposit subtree height - tmpDepositSubtreeHeight++; - - // thow event for the coordinator - logger.logDepositLeafMerged( - deposits[0], - deposits[1], - pendingDeposits[0] - ); - } - - if (tmpDepositSubtreeHeight > depositSubtreeHeight) { - depositSubtreeHeight = tmpDepositSubtreeHeight; - } - - if (depositSubtreeHeight == governance.MAX_DEPOSIT_SUBTREE()) { - // start adding deposits to prepackaged deposit subtree root queue - enqueue(pendingDeposits[0]); - - // emit an event to signal that a package is ready - // isnt really important for anyone tho - logger.logDepositSubTreeReady(pendingDeposits[0]); - - // update the number of items in pendingDeposits - queueNumber = queueNumber - 2**depositSubtreeHeight; - - // empty the pending deposits queue - removeDeposit(0); - - // reset deposit subtree height - depositSubtreeHeight = 0; - } + // reset deposit subtree height + depositSubtreeHeight = 0; } - - /** - * @notice Merges the deposit tree with the balance tree by - * superimposing the deposit subtree on the balance tree - * @param _subTreeDepth Deposit tree depth or depth of subtree that is being deposited - * @param _zero_account_mp Merkle proof proving the node at which we are inserting the deposit subtree consists of all empty leaves - * @return Updates in-state merkle tree root - */ - function finaliseDeposits( - uint256 _subTreeDepth, - Types.AccountMerkleProof memory _zero_account_mp, - bytes32 latestBalanceTree - ) public onlyRollup returns (bytes32) { - bytes32 emptySubtreeRoot = merkleUtils.getRoot(_subTreeDepth); - - // from mt proof we find the root of the tree - // we match the root to the balance tree root on-chain - bool isValid = merkleUtils.verifyLeaf( - latestBalanceTree, - emptySubtreeRoot, - _zero_account_mp.accountIP.pathToAccount, - _zero_account_mp.siblings - ); - - require(isValid, "proof invalid"); - - // just dequeue from the pre package deposit subtrees - bytes32 depositsSubTreeRoot = dequeue(); - - // emit the event - logger.logDepositFinalised( - depositsSubTreeRoot, - _zero_account_mp.accountIP.pathToAccount - ); - - // return the updated merkle tree root - return (depositsSubTreeRoot); + } + + /** + * @notice Merges the deposit tree with the balance tree by + * superimposing the deposit subtree on the balance tree + * @param _subTreeDepth Deposit tree depth or depth of subtree that is being deposited + * @param _zero_account_mp Merkle proof proving the node at which we are inserting the deposit subtree consists of all empty leaves + * @return Updates in-state merkle tree root + */ + function finaliseDeposits( + uint256 _subTreeDepth, + Types.AccountMerkleProof memory _zero_account_mp, + bytes32 latestBalanceTree + ) public onlyRollup returns (bytes32) { + bytes32 emptySubtreeRoot = merkleUtils.getRoot(_subTreeDepth); + + // from mt proof we find the root of the tree + // we match the root to the balance tree root on-chain + bool isValid = merkleUtils.verifyLeaf( + latestBalanceTree, + emptySubtreeRoot, + _zero_account_mp.accountIP.pathToAccount, + _zero_account_mp.siblings + ); + + require(isValid, "proof invalid"); + + // just dequeue from the pre package deposit subtrees + bytes32 depositsSubTreeRoot = dequeue(); + + // emit the event + logger.logDepositFinalised(depositsSubTreeRoot, _zero_account_mp.accountIP.pathToAccount); + + // return the updated merkle tree root + return (depositsSubTreeRoot); + } + + /** + * @notice Removes a deposit from the pendingDeposits queue and shifts the queue + * @param _index Index of the element to remove + * @return Remaining elements of the array + */ + function removeDeposit(uint256 _index) internal { + require(_index < pendingDeposits.length, "array index is out of bounds"); + + // if we want to nuke the queue + if (_index == 0) { + uint256 numberOfDeposits = pendingDeposits.length; + for (uint256 i = 0; i < numberOfDeposits; i++) { + delete pendingDeposits[i]; + } + pendingDeposits.length = 0; + return; } - /** - * @notice Removes a deposit from the pendingDeposits queue and shifts the queue - * @param _index Index of the element to remove - * @return Remaining elements of the array - */ - function removeDeposit(uint256 _index) internal { - require( - _index < pendingDeposits.length, - "array index is out of bounds" - ); - - // if we want to nuke the queue - if (_index == 0) { - uint256 numberOfDeposits = pendingDeposits.length; - for (uint256 i = 0; i < numberOfDeposits; i++) { - delete pendingDeposits[i]; - } - pendingDeposits.length = 0; - return; - } - - if (_index == pendingDeposits.length - 1) { - delete pendingDeposits[pendingDeposits.length - 1]; - pendingDeposits.length--; - return; - } + if (_index == pendingDeposits.length - 1) { + delete pendingDeposits[pendingDeposits.length - 1]; + pendingDeposits.length--; + return; } + } } diff --git a/contracts/FraudProof.sol b/contracts/FraudProof.sol index eae14b0..7e5889f 100644 --- a/contracts/FraudProof.sol +++ b/contracts/FraudProof.sol @@ -7,363 +7,200 @@ import {IERC20} from "./interfaces/IERC20.sol"; import {ITokenRegistry} from "./interfaces/ITokenRegistry.sol"; import {Types} from "./libs/Types.sol"; +import {Tx} from "./libs/Tx.sol"; import {RollupUtils} from "./libs/RollupUtils.sol"; import {ParamManager} from "./libs/ParamManager.sol"; -import {ECVerify} from "./libs/ECVerify.sol"; import {MerkleTreeUtils as MTUtils} from "./MerkleTreeUtils.sol"; import {Governance} from "./Governance.sol"; import {NameRegistry as Registry} from "./NameRegistry.sol"; contract FraudProofSetup { - using SafeMath for uint256; - using ECVerify for bytes32; - - MTUtils public merkleUtils; - ITokenRegistry public tokenRegistry; - Registry public nameRegistry; - - bytes32 - public constant ZERO_BYTES32 = 0x0000000000000000000000000000000000000000000000000000000000000000; - - Governance public governance; + using SafeMath for uint256; + MTUtils public merkleUtils; + ITokenRegistry public tokenRegistry; + Registry public nameRegistry; + bytes32 public constant ZERO_BYTES32 = 0x0000000000000000000000000000000000000000000000000000000000000000; + Governance public governance; } contract FraudProofHelpers is FraudProofSetup { - function ValidatePubkeyAvailability( - bytes32 _accountsRoot, - Types.PDAMerkleProof memory _from_pda_proof, - uint256 from_index - ) public view { - // verify from account pubkey exists in PDA tree - // NOTE: We dont need to prove that to address has the pubkey available - Types.PDALeaf memory fromPDA = Types.PDALeaf({ - pubkey: _from_pda_proof._pda.pubkey_leaf.pubkey - }); - - require( - merkleUtils.verifyLeaf( - _accountsRoot, - RollupUtils.PDALeafToHash(fromPDA), - _from_pda_proof._pda.pathToPubkey, - _from_pda_proof.siblings - ), - "From PDA proof is incorrect" - ); - - // convert pubkey path to ID - uint256 computedID = merkleUtils.pathToIndex( - _from_pda_proof._pda.pathToPubkey, - governance.MAX_DEPTH() - ); - - // make sure the ID in transaction is the same account for which account proof was provided - require( - computedID == from_index, - "Pubkey not related to the from account in the transaction" - ); - } - - function ValidateAccountMP( - bytes32 root, - Types.AccountMerkleProof memory merkle_proof - ) public view { - bytes32 accountLeaf = RollupUtils.getAccountHash( - merkle_proof.accountIP.account.ID, - merkle_proof.accountIP.account.balance, - merkle_proof.accountIP.account.nonce, - merkle_proof.accountIP.account.tokenType - ); - - // verify from leaf exists in the balance tree - require( - merkleUtils.verifyLeaf( - root, - accountLeaf, - merkle_proof.accountIP.pathToAccount, - merkle_proof.siblings - ), - "Merkle Proof is incorrect" - ); - } - - function validateTxBasic( - Types.Transaction memory _tx, - Types.UserAccount memory _from_account - ) public view returns (Types.ErrorCode) { - // verify that tokens are registered - if (tokenRegistry.registeredTokens(_tx.tokenType) == address(0)) { - // invalid state transition - // to be slashed because the submitted transaction - // had invalid token type - return Types.ErrorCode.InvalidTokenAddress; - } - - if (_tx.amount == 0) { - // invalid state transition - // needs to be slashed because the submitted transaction - // had 0 amount. - return Types.ErrorCode.InvalidTokenAmount; - } - - // check from leaf has enough balance - if (_from_account.balance < _tx.amount) { - // invalid state transition - // needs to be slashed because the account doesnt have enough balance - // for the transfer - return Types.ErrorCode.NotEnoughTokenBalance; - } - - return Types.ErrorCode.NoError; + function ValidateAccountMP( + bytes32 root, + uint256 stateID, + Types.UserAccount memory account, + bytes32[] memory witness + ) public view { + bytes32 accountLeaf = RollupUtils.getAccountHash(account.ID, account.balance, account.nonce, account.tokenType); + require(merkleUtils.verifyLeaf(root, accountLeaf, stateID, witness), "Merkle Proof is incorrect"); + } + + function validateTxBasic(Types.Transaction memory _tx, Types.UserAccount memory _from_account) + public + view + returns (Types.ErrorCode) + { + // verify that tokens are registered + if (tokenRegistry.registeredTokens(_tx.tokenType) == address(0)) { + // invalid state transition + // to be slashed because the submitted transaction + // had invalid token type + return Types.ErrorCode.InvalidTokenAddress; } - function RemoveTokensFromAccount( - Types.UserAccount memory account, - uint256 numOfTokens - ) public pure returns (Types.UserAccount memory updatedAccount) { - return ( - RollupUtils.UpdateBalanceInAccount( - account, - RollupUtils.BalanceFromAccount(account).sub(numOfTokens) - ) - ); + if (_tx.amount == 0) { + // invalid state transition + // needs to be slashed because the submitted transaction + // had 0 amount. + return Types.ErrorCode.InvalidTokenAmount; } - /** - * @notice ApplyTx applies the transaction on the account. This is where - * people need to define the logic for the application - * @param _merkle_proof contains the siblings and path to the account - * @param transaction is the transaction that needs to be applied - * @return returns updated account and updated state root - * */ - function ApplyTx( - Types.AccountMerkleProof memory _merkle_proof, - Types.Transaction memory transaction - ) public view returns (bytes memory updatedAccount, bytes32 newRoot) { - Types.UserAccount memory account = _merkle_proof.accountIP.account; - if (transaction.fromIndex == account.ID) { - account = RemoveTokensFromAccount(account, transaction.amount); - account.nonce++; - } - - if (transaction.toIndex == account.ID) { - account = AddTokensToAccount(account, transaction.amount); - } - - newRoot = UpdateAccountWithSiblings(account, _merkle_proof); - - return (RollupUtils.BytesFromAccount(account), newRoot); + // check from leaf has enough balance + if (_from_account.balance < _tx.amount) { + // invalid state transition + // needs to be slashed because the account doesnt have enough balance + // for the transfer + return Types.ErrorCode.NotEnoughTokenBalance; } - function AddTokensToAccount( - Types.UserAccount memory account, - uint256 numOfTokens - ) public pure returns (Types.UserAccount memory updatedAccount) { - return ( - RollupUtils.UpdateBalanceInAccount( - account, - RollupUtils.BalanceFromAccount(account).add(numOfTokens) - ) - ); + return Types.ErrorCode.NoError; + } + + function RemoveTokensFromAccount(Types.UserAccount memory account, uint256 numOfTokens) + public + pure + returns (Types.UserAccount memory updatedAccount) + { + return (RollupUtils.UpdateBalanceInAccount(account, RollupUtils.BalanceFromAccount(account).sub(numOfTokens))); + } + + /** + * @notice ApplyTx applies the transaction on the account. This is where + * people need to define the logic for the application + * @param _merkle_proof contains the siblings and path to the account + * @param transaction is the transaction that needs to be applied + * @return returns updated account and updated state root + * */ + function ApplyTx(Types.AccountMerkleProof memory _merkle_proof, Types.Transaction memory transaction) + public + view + returns (bytes memory updatedAccount, bytes32 newRoot) + { + Types.UserAccount memory account = _merkle_proof.accountIP.account; + if (transaction.fromIndex == account.ID) { + account = RemoveTokensFromAccount(account, transaction.amount); + account.nonce++; } - /** - * @notice Returns the updated root and balance - */ - function UpdateAccountWithSiblings( - Types.UserAccount memory new_account, - Types.AccountMerkleProof memory _merkle_proof - ) public view returns (bytes32) { - bytes32 newRoot = merkleUtils.updateLeafWithSiblings( - keccak256(RollupUtils.BytesFromAccount(new_account)), - _merkle_proof.accountIP.pathToAccount, - _merkle_proof.siblings - ); - return (newRoot); + if (transaction.toIndex == account.ID) { + account = AddTokensToAccount(account, transaction.amount); } - function ValidateSignature( - Types.Transaction memory _tx, - Types.PDAMerkleProof memory _from_pda_proof - ) public pure returns (bool) { - require( - RollupUtils.calculateAddress( - _from_pda_proof._pda.pubkey_leaf.pubkey - ) == - RollupUtils - .getTxSignBytes( - _tx - .fromIndex, - _tx - .toIndex, - _tx - .tokenType, - _tx - .txType, - _tx - .nonce, - _tx - .amount - ) - .ecrecovery(_tx.signature), - "Signature is incorrect" - ); - } + newRoot = UpdateAccountWithSiblings(account, _merkle_proof); + + return (RollupUtils.BytesFromAccount(account), newRoot); + } + + function AddTokensToAccount(Types.UserAccount memory account, uint256 numOfTokens) + public + pure + returns (Types.UserAccount memory updatedAccount) + { + return (RollupUtils.UpdateBalanceInAccount(account, RollupUtils.BalanceFromAccount(account).add(numOfTokens))); + } + + /** + * @notice Returns the updated root and balance + */ + function UpdateAccountWithSiblings( + Types.UserAccount memory new_account, + Types.AccountMerkleProof memory _merkle_proof + ) public view returns (bytes32) { + bytes32 newRoot = merkleUtils.updateLeafWithSiblings( + keccak256(RollupUtils.BytesFromAccount(new_account)), + _merkle_proof.accountIP.pathToAccount, + _merkle_proof.siblings + ); + return (newRoot); + } } contract FraudProof is FraudProofHelpers { - /********************* - * Constructor * - ********************/ - constructor(address _registryAddr) public { - nameRegistry = Registry(_registryAddr); - - governance = Governance( - nameRegistry.getContractDetails(ParamManager.Governance()) - ); - - merkleUtils = MTUtils( - nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) - ); - - tokenRegistry = ITokenRegistry( - nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY()) - ); - } - - function generateTxRoot(Types.Transaction[] memory _txs) - public - view - returns (bytes32 txRoot) - { - // generate merkle tree from the txs provided by user - bytes[] memory txs = new bytes[](_txs.length); - for (uint256 i = 0; i < _txs.length; i++) { - txs[i] = RollupUtils.CompressTx(_txs[i]); - } - txRoot = merkleUtils.getMerkleRoot(txs); - return txRoot; - } - - /** - * @notice processBatch processes a whole batch - * @return returns updatedRoot, txRoot and if the batch is valid or not - * */ - function processBatch( - bytes32 stateRoot, - bytes32 accountsRoot, - Types.Transaction[] memory _txs, - Types.BatchValidationProofs memory batchProofs, - bytes32 expectedTxRoot - ) - public - view - returns ( - bytes32, - bytes32, - bool - ) - { - bytes32 actualTxRoot = generateTxRoot(_txs); - // if there is an expectation set, revert if it's not met - if (expectedTxRoot == ZERO_BYTES32) { - // if tx root while submission doesnt match tx root of given txs - // dispute is unsuccessful - require( - actualTxRoot == expectedTxRoot, - "Invalid dispute, tx root doesn't match" - ); - } - - bool isTxValid; - { - for (uint256 i = 0; i < _txs.length; i++) { - // call process tx update for every transaction to check if any - // tx evaluates correctly - (stateRoot, , , , isTxValid) = processTx( - stateRoot, - accountsRoot, - _txs[i], - batchProofs.pdaProof[i], - batchProofs.accountProofs[i] - ); - - if (!isTxValid) { - break; - } - } - } - return (stateRoot, actualTxRoot, !isTxValid); - } - - /** - * @notice processTx processes a transactions and returns the updated balance tree - * and the updated leaves - * conditions in require mean that the dispute be declared invalid - * if conditons evaluate if the coordinator was at fault - * @return Total number of batches submitted onchain - */ - function processTx( - bytes32 _balanceRoot, - bytes32 _accountsRoot, - Types.Transaction memory _tx, - Types.PDAMerkleProof memory _from_pda_proof, - Types.AccountProofs memory accountProofs - ) - public - view - returns ( - bytes32, - bytes memory, - bytes memory, - Types.ErrorCode, - bool - ) - { - // Step-1 Prove that from address's public keys are available - ValidatePubkeyAvailability( - _accountsRoot, - _from_pda_proof, - _tx.fromIndex - ); - - // STEP:2 Ensure the transaction has been signed using the from public key - // ValidateSignature(_tx, _from_pda_proof); - - // Validate the from account merkle proof - ValidateAccountMP(_balanceRoot, accountProofs.from); - - Types.ErrorCode err_code = validateTxBasic( - _tx, - accountProofs.from.accountIP.account - ); - if (err_code != Types.ErrorCode.NoError) return (ZERO_BYTES32, "", "", err_code, false); - - // account holds the token type in the tx - if (accountProofs.from.accountIP.account.tokenType != _tx.tokenType) - // invalid state transition - // needs to be slashed because the submitted transaction - // had invalid token type - return (ZERO_BYTES32, "", "", Types.ErrorCode.BadFromTokenType, false); - - bytes32 newRoot; - bytes memory new_from_account; - bytes memory new_to_account; - - (new_from_account, newRoot) = ApplyTx(accountProofs.from, _tx); - - // validate if leaf exists in the updated balance tree - ValidateAccountMP(newRoot, accountProofs.to); - - // account holds the token type in the tx - if (accountProofs.to.accountIP.account.tokenType != _tx.tokenType) - // invalid state transition - // needs to be slashed because the submitted transaction - // had invalid token type - return (ZERO_BYTES32, "", "", Types.ErrorCode.BadToTokenType, false); - - (new_to_account, newRoot) = ApplyTx(accountProofs.to, _tx); - - return (newRoot, new_from_account, new_to_account, Types.ErrorCode.NoError, true); + using Tx for bytes; + + /********************* + * Constructor * + ********************/ + constructor(address _registryAddr) public { + nameRegistry = Registry(_registryAddr); + + governance = Governance(nameRegistry.getContractDetails(ParamManager.Governance())); + + merkleUtils = MTUtils(nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS())); + + tokenRegistry = ITokenRegistry(nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY())); + } + + function generateTxRoot(bytes memory _txs) public view returns (bytes32 txRoot) { + merkleUtils.calculateRootTruncated(_txs.toLeafs()); + } + + /** + * @notice processBatch processes a whole batch + * @return returns updatedRoot, txRoot and if the batch is valid or not + * */ + function processBatch( + bytes32 stateRoot, + bytes memory txs, + Types.InvalidTransitionProof memory proof + ) public view returns (bytes32, bool) { + bool isTxValid; + uint256 batchSize = txs.size(); + require(batchSize > 0, "Rollup: empty batch"); + require(!txs.hasExcessData(), "Rollup: excess tx data"); + bytes32 acc = stateRoot; + for (uint256 i = 0; i < batchSize; i++) { + // A. check sender inclusion in state + uint256 senderID = txs.senderOf(i); + ValidateAccountMP(acc, senderID, proof.senderAccounts[i], proof.senderWitnesses[i]); + // FIX: cannot be an empty account + // if (proof.senderAccounts[i].isEmptyAccount()) { + // return bytes32(0), false; + // } + // B. apply diff for sender + uint256 amount = txs.amountOf(i); + Types.UserAccount memory account = proof.senderAccounts[i]; + if (account.balance < amount) { + return (bytes32(0), false); + } + account.balance -= amount; + account.nonce += 1; + if (account.nonce >= 0x100000000) { + return (bytes32(0), false); + } + acc = merkleUtils.updateLeafWithSiblings( + keccak256(RollupUtils.BytesFromAccount(account)), + senderID, + proof.senderWitnesses[i] + ); + // A. check receiver inclusion in state + uint256 receiverID = txs.receiverOf(i); + ValidateAccountMP(acc, receiverID, proof.receiverAccounts[i], proof.receiverWitnesses[i]); + // FIX: cannot be an empty account + // if (proof.receiverAccounts[i].isEmptyAccount()) { + // return bytes32(0), false; + // } + account = proof.senderAccounts[i]; + account.balance -= amount; + if (account.balance >= 0x100000000) { + return (bytes32(0), false); + } + acc = merkleUtils.updateLeafWithSiblings( + keccak256(RollupUtils.BytesFromAccount(account)), + senderID, + proof.senderWitnesses[i] + ); } + return (stateRoot, !isTxValid); + } } diff --git a/contracts/Governance.sol b/contracts/Governance.sol index 19ed67c..b327d51 100644 --- a/contracts/Governance.sol +++ b/contracts/Governance.sol @@ -4,47 +4,47 @@ pragma solidity ^0.5.15; Governance contract handles all the proof of burn related functionality */ contract Governance { - constructor(uint256 maxDepth, uint256 maxDepositSubTree) public { - _MAX_DEPTH = maxDepth; - _MAX_DEPOSIT_SUBTREE = maxDepositSubTree; - } + constructor(uint256 maxDepth, uint256 maxDepositSubTree) public { + _MAX_DEPTH = maxDepth; + _MAX_DEPOSIT_SUBTREE = maxDepositSubTree; + } - uint256 public _MAX_DEPTH = 4; + uint256 public _MAX_DEPTH = 4; - function MAX_DEPTH() public view returns (uint256) { - return _MAX_DEPTH; - } + function MAX_DEPTH() public view returns (uint256) { + return _MAX_DEPTH; + } - uint256 public _MAX_DEPOSIT_SUBTREE = 2; + uint256 public _MAX_DEPOSIT_SUBTREE = 2; - function MAX_DEPOSIT_SUBTREE() public view returns (uint256) { - return _MAX_DEPOSIT_SUBTREE; - } + function MAX_DEPOSIT_SUBTREE() public view returns (uint256) { + return _MAX_DEPOSIT_SUBTREE; + } - // finalisation time is the number of blocks required by a batch to finalise - // Delay period = 7 days. Block time = 15 seconds - uint256 public _TIME_TO_FINALISE = 7 days; + // finalisation time is the number of blocks required by a batch to finalise + // Delay period = 7 days. Block time = 15 seconds + uint256 public _TIME_TO_FINALISE = 7 days; - function TIME_TO_FINALISE() public view returns (uint256) { - return _TIME_TO_FINALISE; - } + function TIME_TO_FINALISE() public view returns (uint256) { + return _TIME_TO_FINALISE; + } - // min gas required before rollback pauses - uint256 public _MIN_GAS_LIMIT_LEFT = 100000; + // min gas required before rollback pauses + uint256 public _MIN_GAS_LIMIT_LEFT = 100000; - function MIN_GAS_LIMIT_LEFT() public view returns (uint256) { - return _MIN_GAS_LIMIT_LEFT; - } + function MIN_GAS_LIMIT_LEFT() public view returns (uint256) { + return _MIN_GAS_LIMIT_LEFT; + } - uint256 public _MAX_TXS_PER_BATCH = 10; + uint256 public _MAX_TXS_PER_BATCH = 10; - function MAX_TXS_PER_BATCH() public view returns (uint256) { - return _MAX_TXS_PER_BATCH; - } + function MAX_TXS_PER_BATCH() public view returns (uint256) { + return _MAX_TXS_PER_BATCH; + } - uint256 public _STAKE_AMOUNT = 32 ether; + uint256 public _STAKE_AMOUNT = 32 ether; - function STAKE_AMOUNT() public view returns (uint256) { - return _STAKE_AMOUNT; - } + function STAKE_AMOUNT() public view returns (uint256) { + return _STAKE_AMOUNT; + } } diff --git a/contracts/IncrementalTree.sol b/contracts/IncrementalTree.sol deleted file mode 100644 index 5f69712..0000000 --- a/contracts/IncrementalTree.sol +++ /dev/null @@ -1,94 +0,0 @@ -pragma solidity ^0.5.15; - -import {MerkleTreeUtils as MTUtils} from "./MerkleTreeUtils.sol"; -import {ParamManager} from "./libs/ParamManager.sol"; -import {NameRegistry as Registry} from "./NameRegistry.sol"; -import {Governance} from "./Governance.sol"; - -contract IncrementalTree { - Registry public nameRegistry; - MTUtils public merkleUtils; - Governance public governance; - MerkleTree public tree; - - // Merkle Tree to store the whole tree - struct MerkleTree { - // Root of the tree - bytes32 root; - // current height of the tree - uint256 height; - // Allows you to compute the path to the element (but it's not the path to - // the elements). Caching these values is essential to efficient appends. - bytes32[] filledSubtrees; - } - - // The number of inserted leaves - uint256 public nextLeafIndex = 0; - - constructor(address _registryAddr) public { - nameRegistry = Registry(_registryAddr); - merkleUtils = MTUtils( - nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) - ); - governance = Governance( - nameRegistry.getContractDetails(ParamManager.Governance()) - ); - tree.filledSubtrees = new bytes32[](governance.MAX_DEPTH()); - setMerkleRootAndHeight( - merkleUtils.getZeroRoot(), - merkleUtils.getMaxTreeDepth() - ); - bytes32 zero = merkleUtils.getDefaultHashAtLevel(0); - for (uint8 i = 1; i < governance.MAX_DEPTH(); i++) { - tree.filledSubtrees[i] = zero; - } - } - - /** - * @notice Append leaf will append a leaf to the end of the tree - * @return The sibling nodes along the way. - */ - function appendLeaf(bytes32 _leaf) public returns (uint256) { - uint256 currentIndex = nextLeafIndex; - - uint256 depth = uint256(tree.height); - require( - currentIndex < uint256(2)**depth, - "IncrementalMerkleTree: tree is full" - ); - bytes32 currentLevelHash = _leaf; - bytes32 left; - bytes32 right; - for (uint8 i = 0; i < tree.height; i++) { - if (currentIndex % 2 == 0) { - left = currentLevelHash; - right = merkleUtils.getRoot(i); - tree.filledSubtrees[i] = currentLevelHash; - } else { - left = tree.filledSubtrees[i]; - right = currentLevelHash; - } - currentLevelHash = merkleUtils.getParent(left, right); - currentIndex >>= 1; - } - tree.root = currentLevelHash; - uint256 n; - n = nextLeafIndex; - nextLeafIndex += 1; - return n; - } - - /** - * @notice Set the tree root and height of the stored tree - * @param _root The merkle root of the tree - * @param _height The height of the tree - */ - function setMerkleRootAndHeight(bytes32 _root, uint256 _height) public { - tree.root = _root; - tree.height = _height; - } - - function getTreeRoot() external view returns (bytes32) { - return tree.root; - } -} diff --git a/contracts/MerkleTreeUtils.sol b/contracts/MerkleTreeUtils.sol index f5d2b0e..c7482ab 100644 --- a/contracts/MerkleTreeUtils.sol +++ b/contracts/MerkleTreeUtils.sol @@ -5,355 +5,370 @@ import {Governance} from "./Governance.sol"; import {NameRegistry as Registry} from "./NameRegistry.sol"; contract MerkleTreeUtils { - // The default hashes - bytes32[] public defaultHashes; - uint256 public MAX_DEPTH; - Governance public governance; + // The default hashes + bytes32[] public defaultHashes; + uint256 public MAX_DEPTH; + Governance public governance; - /** - * @notice Initialize a new MerkleTree contract, computing the default hashes for the merkle tree (MT) - */ - constructor(address _registryAddr) public { - Registry nameRegistry = Registry(_registryAddr); - governance = Governance( - nameRegistry.getContractDetails(ParamManager.Governance()) - ); - MAX_DEPTH = governance.MAX_DEPTH(); - defaultHashes = new bytes32[](MAX_DEPTH); - // Calculate & set the default hashes - setDefaultHashes(MAX_DEPTH); - } + /** + * @notice Initialize a new MerkleTree contract, computing the default hashes for the merkle tree (MT) + */ + constructor(address _registryAddr) public { + Registry nameRegistry = Registry(_registryAddr); + governance = Governance(nameRegistry.getContractDetails(ParamManager.Governance())); + MAX_DEPTH = governance.MAX_DEPTH(); + defaultHashes = new bytes32[](MAX_DEPTH); + // Calculate & set the default hashes + setDefaultHashes(MAX_DEPTH); + } - /* Methods */ - - /** - * @notice Set default hashes - */ - function setDefaultHashes(uint256 depth) internal { - // Set the initial default hash. - defaultHashes[0] = keccak256(abi.encode(0)); - for (uint256 i = 1; i < depth; i++) { - defaultHashes[i] = keccak256( - abi.encode(defaultHashes[i - 1], defaultHashes[i - 1]) - ); - } - } + /* Methods */ - function getZeroRoot() public view returns (bytes32) { - return - keccak256( - abi.encode( - defaultHashes[MAX_DEPTH - 1], - defaultHashes[MAX_DEPTH - 1] - ) - ); + /** + * @notice Set default hashes + */ + function setDefaultHashes(uint256 depth) internal { + // Set the initial default hash. + defaultHashes[0] = keccak256(abi.encode(0)); + for (uint256 i = 1; i < depth; i++) { + defaultHashes[i] = keccak256(abi.encode(defaultHashes[i - 1], defaultHashes[i - 1])); } + } - function getMaxTreeDepth() public view returns (uint256) { - return MAX_DEPTH; - } + function getZeroRoot() public view returns (bytes32) { + return keccak256(abi.encode(defaultHashes[MAX_DEPTH - 1], defaultHashes[MAX_DEPTH - 1])); + } - function getRoot(uint256 index) public view returns (bytes32) { - return defaultHashes[index]; - } + function getMaxTreeDepth() public view returns (uint256) { + return MAX_DEPTH; + } - function getDefaultHashAtLevel(uint256 index) - public - view - returns (bytes32) - { - return defaultHashes[index]; - } + function getRoot(uint256 index) public view returns (bytes32) { + return defaultHashes[index]; + } - function keecakHash(bytes memory data) public pure returns (bytes32) { - return keccak256(data); - } + function getDefaultHashAtLevel(uint256 index) public view returns (bytes32) { + return defaultHashes[index]; + } - /** - * @notice Get the merkle root computed from some set of data blocks. - * @param _dataBlocks The data being used to generate the tree. - * @return the merkle tree root - * NOTE: This is a stateless operation - */ - function getMerkleRoot(bytes[] calldata _dataBlocks) - external - view - returns (bytes32) - { - uint256 nextLevelLength = _dataBlocks.length; - uint256 currentLevel = 0; - bytes32[] memory nodes = new bytes32[](nextLevelLength + 1); // Add one in case we have an odd number of leaves - // Generate the leaves - for (uint256 i = 0; i < _dataBlocks.length; i++) { - nodes[i] = keccak256(_dataBlocks[i]); - } - if (_dataBlocks.length == 1) { - return nodes[0]; - } - // Add a defaultNode if we've got an odd number of leaves - if (nextLevelLength % 2 == 1) { - nodes[nextLevelLength] = defaultHashes[currentLevel]; - nextLevelLength += 1; - } + function keecakHash(bytes memory data) public pure returns (bytes32) { + return keccak256(data); + } - // Now generate each level - while (nextLevelLength > 1) { - currentLevel += 1; - // Calculate the nodes for the currentLevel - for (uint256 i = 0; i < nextLevelLength / 2; i++) { - nodes[i] = getParent(nodes[i * 2], nodes[i * 2 + 1]); - } - nextLevelLength = nextLevelLength / 2; - // Check if we will need to add an extra node - if (nextLevelLength % 2 == 1 && nextLevelLength != 1) { - nodes[nextLevelLength] = defaultHashes[currentLevel]; - nextLevelLength += 1; - } - } - // Alright! We should be left with a single node! Return it... - return nodes[0]; + /** + * @notice Get the merkle root computed from some set of data blocks. + * @param _dataBlocks The data being used to generate the tree. + * @return the merkle tree root + * NOTE: This is a stateless operation + */ + function getMerkleRoot(bytes[] calldata _dataBlocks) external view returns (bytes32) { + uint256 nextLevelLength = _dataBlocks.length; + uint256 currentLevel = 0; + bytes32[] memory nodes = new bytes32[](nextLevelLength + 1); // Add one in case we have an odd number of leaves + // Generate the leaves + for (uint256 i = 0; i < _dataBlocks.length; i++) { + nodes[i] = keccak256(_dataBlocks[i]); + } + if (_dataBlocks.length == 1) { + return nodes[0]; + } + // Add a defaultNode if we've got an odd number of leaves + if (nextLevelLength % 2 == 1) { + nodes[nextLevelLength] = defaultHashes[currentLevel]; + nextLevelLength += 1; } - /** - * @notice Get the merkle root computed from some set of data blocks. - * @param nodes The data being used to generate the tree. - * @return the merkle tree root - * NOTE: This is a stateless operation - */ - function getMerkleRootFromLeaves(bytes32[] memory nodes) - public - view - returns (bytes32) - { - uint256 nextLevelLength = nodes.length; - uint256 currentLevel = 0; - if (nodes.length == 1) { - return nodes[0]; - } + // Now generate each level + while (nextLevelLength > 1) { + currentLevel += 1; + // Calculate the nodes for the currentLevel + for (uint256 i = 0; i < nextLevelLength / 2; i++) { + nodes[i] = getParent(nodes[i * 2], nodes[i * 2 + 1]); + } + nextLevelLength = nextLevelLength / 2; + // Check if we will need to add an extra node + if (nextLevelLength % 2 == 1 && nextLevelLength != 1) { + nodes[nextLevelLength] = defaultHashes[currentLevel]; + nextLevelLength += 1; + } + } + // Alright! We should be left with a single node! Return it... + return nodes[0]; + } - // Add a defaultNode if we've got an odd number of leaves - if (nextLevelLength % 2 == 1) { - nodes[nextLevelLength] = defaultHashes[currentLevel]; - nextLevelLength += 1; - } + /** + * @notice Get the merkle root computed from some set of data blocks. + * @param nodes The data being used to generate the tree. + * @return the merkle tree root + * NOTE: This is a stateless operation + */ + function getMerkleRootFromLeaves(bytes32[] memory nodes) public view returns (bytes32) { + uint256 nextLevelLength = nodes.length; + uint256 currentLevel = 0; + if (nodes.length == 1) { + return nodes[0]; + } - // Now generate each level - while (nextLevelLength > 1) { - currentLevel += 1; + // Add a defaultNode if we've got an odd number of leaves + if (nextLevelLength % 2 == 1) { + nodes[nextLevelLength] = defaultHashes[currentLevel]; + nextLevelLength += 1; + } - // Calculate the nodes for the currentLevel - for (uint256 i = 0; i < nextLevelLength / 2; i++) { - nodes[i] = getParent(nodes[i * 2], nodes[i * 2 + 1]); - } + // Now generate each level + while (nextLevelLength > 1) { + currentLevel += 1; - nextLevelLength = nextLevelLength / 2; - // Check if we will need to add an extra node - if (nextLevelLength % 2 == 1 && nextLevelLength != 1) { - nodes[nextLevelLength] = defaultHashes[currentLevel]; - nextLevelLength += 1; - } - } + // Calculate the nodes for the currentLevel + for (uint256 i = 0; i < nextLevelLength / 2; i++) { + nodes[i] = getParent(nodes[i * 2], nodes[i * 2 + 1]); + } - // Alright! We should be left with a single node! Return it... - return nodes[0]; + nextLevelLength = nextLevelLength / 2; + // Check if we will need to add an extra node + if (nextLevelLength % 2 == 1 && nextLevelLength != 1) { + nodes[nextLevelLength] = defaultHashes[currentLevel]; + nextLevelLength += 1; + } } - /** - * @notice Calculate root from an inclusion proof. - * @param _dataBlock The data block we're calculating root for. - * @param _path The path from the leaf to the root. - * @param _siblings The sibling nodes along the way. - * @return The next level of the tree - * NOTE: This is a stateless operation - */ - function computeInclusionProofRoot( - bytes memory _dataBlock, - uint256 _path, - bytes32[] memory _siblings - ) public pure returns (bytes32) { - // First compute the leaf node - bytes32 computedNode = keccak256(_dataBlock); + // Alright! We should be left with a single node! Return it... + return nodes[0]; + } - for (uint256 i = 0; i < _siblings.length; i++) { - bytes32 sibling = _siblings[i]; - uint8 isComputedRightSibling = getNthBitFromRight(_path, i); - if (isComputedRightSibling == 0) { - computedNode = getParent(computedNode, sibling); - } else { - computedNode = getParent(sibling, computedNode); - } - } - // Check if the computed node (_root) is equal to the provided root - return computedNode; - } + /** + * @notice Calculate root from an inclusion proof. + * @param _dataBlock The data block we're calculating root for. + * @param _path The path from the leaf to the root. + * @param _siblings The sibling nodes along the way. + * @return The next level of the tree + * NOTE: This is a stateless operation + */ + function computeInclusionProofRoot( + bytes memory _dataBlock, + uint256 _path, + bytes32[] memory _siblings + ) public pure returns (bytes32) { + // First compute the leaf node + bytes32 computedNode = keccak256(_dataBlock); - /** - * @notice Calculate root from an inclusion proof. - * @param _leaf The data block we're calculating root for. - * @param _path The path from the leaf to the root. - * @param _siblings The sibling nodes along the way. - * @return The next level of the tree - * NOTE: This is a stateless operation - */ - function computeInclusionProofRootWithLeaf( - bytes32 _leaf, - uint256 _path, - bytes32[] memory _siblings - ) public pure returns (bytes32) { - // First compute the leaf node - bytes32 computedNode = _leaf; - for (uint256 i = 0; i < _siblings.length; i++) { - bytes32 sibling = _siblings[i]; - uint8 isComputedRightSibling = getNthBitFromRight(_path, i); - if (isComputedRightSibling == 0) { - computedNode = getParent(computedNode, sibling); - } else { - computedNode = getParent(sibling, computedNode); - } - } - // Check if the computed node (_root) is equal to the provided root - return computedNode; + for (uint256 i = 0; i < _siblings.length; i++) { + bytes32 sibling = _siblings[i]; + uint8 isComputedRightSibling = getNthBitFromRight(_path, i); + if (isComputedRightSibling == 0) { + computedNode = getParent(computedNode, sibling); + } else { + computedNode = getParent(sibling, computedNode); + } } + // Check if the computed node (_root) is equal to the provided root + return computedNode; + } - /** - * @notice Verify an inclusion proof. - * @param _root The root of the tree we are verifying inclusion for. - * @param _dataBlock The data block we're verifying inclusion for. - * @param _path The path from the leaf to the root. - * @param _siblings The sibling nodes along the way. - * @return The next level of the tree - * NOTE: This is a stateless operation - */ - function verify( - bytes32 _root, - bytes memory _dataBlock, - uint256 _path, - bytes32[] memory _siblings - ) public pure returns (bool) { - // First compute the leaf node - bytes32 calculatedRoot = computeInclusionProofRoot( - _dataBlock, - _path, - _siblings - ); - return calculatedRoot == _root; + /** + * @notice Calculate root from an inclusion proof. + * @param _leaf The data block we're calculating root for. + * @param _path The path from the leaf to the root. + * @param _siblings The sibling nodes along the way. + * @return The next level of the tree + * NOTE: This is a stateless operation + */ + function computeInclusionProofRootWithLeaf( + bytes32 _leaf, + uint256 _path, + bytes32[] memory _siblings + ) public pure returns (bytes32) { + // First compute the leaf node + bytes32 computedNode = _leaf; + for (uint256 i = 0; i < _siblings.length; i++) { + bytes32 sibling = _siblings[i]; + uint8 isComputedRightSibling = getNthBitFromRight(_path, i); + if (isComputedRightSibling == 0) { + computedNode = getParent(computedNode, sibling); + } else { + computedNode = getParent(sibling, computedNode); + } } + // Check if the computed node (_root) is equal to the provided root + return computedNode; + } - /** - * @notice Verify an inclusion proof. - * @param _root The root of the tree we are verifying inclusion for. - * @param _leaf The data block we're verifying inclusion for. - * @param _path The path from the leaf to the root. - * @param _siblings The sibling nodes along the way. - * @return The next level of the tree - * NOTE: This is a stateless operation - */ - function verifyLeaf( - bytes32 _root, - bytes32 _leaf, - uint256 _path, - bytes32[] memory _siblings - ) public pure returns (bool) { - bytes32 calculatedRoot = computeInclusionProofRootWithLeaf( - _leaf, - _path, - _siblings - ); - return calculatedRoot == _root; - } + /** + * @notice Verify an inclusion proof. + * @param _root The root of the tree we are verifying inclusion for. + * @param _dataBlock The data block we're verifying inclusion for. + * @param _path The path from the leaf to the root. + * @param _siblings The sibling nodes along the way. + * @return The next level of the tree + * NOTE: This is a stateless operation + */ + function verify( + bytes32 _root, + bytes memory _dataBlock, + uint256 _path, + bytes32[] memory _siblings + ) public pure returns (bool) { + // First compute the leaf node + bytes32 calculatedRoot = computeInclusionProofRoot(_dataBlock, _path, _siblings); + return calculatedRoot == _root; + } - /** - * @notice Update a leaf using siblings and root - * This is a stateless operation - * @param _leaf The leaf we're updating. - * @param _path The path from the leaf to the root / the index of the leaf. - * @param _siblings The sibling nodes along the way. - * @return Updated root - */ - function updateLeafWithSiblings( - bytes32 _leaf, - uint256 _path, - bytes32[] memory _siblings - ) public pure returns (bytes32) { - bytes32 computedNode = _leaf; - for (uint256 i = 0; i < _siblings.length; i++) { - bytes32 parent; - bytes32 sibling = _siblings[i]; - uint8 isComputedRightSibling = getNthBitFromRight(_path, i); - if (isComputedRightSibling == 0) { - parent = getParent(computedNode, sibling); - } else { - parent = getParent(sibling, computedNode); - } - computedNode = parent; - } - return computedNode; - } + /** + * @notice Verify an inclusion proof. + * @param _root The root of the tree we are verifying inclusion for. + * @param _leaf The data block we're verifying inclusion for. + * @param _path The path from the leaf to the root. + * @param _siblings The sibling nodes along the way. + * @return The next level of the tree + * NOTE: This is a stateless operation + */ + function verifyLeaf( + bytes32 _root, + bytes32 _leaf, + uint256 _path, + bytes32[] memory _siblings + ) public pure returns (bool) { + bytes32 calculatedRoot = computeInclusionProofRootWithLeaf(_leaf, _path, _siblings); + return calculatedRoot == _root; + } - /** - * @notice Get the parent of two children nodes in the tree - * @param _left The left child - * @param _right The right child - * @return The parent node - */ - function getParent(bytes32 _left, bytes32 _right) - public - pure - returns (bytes32) - { - return keccak256(abi.encode(_left, _right)); + /** + * @notice Update a leaf using siblings and root + * This is a stateless operation + * @param _leaf The leaf we're updating. + * @param _path The path from the leaf to the root / the index of the leaf. + * @param _siblings The sibling nodes along the way. + * @return Updated root + */ + function updateLeafWithSiblings( + bytes32 _leaf, + uint256 _path, + bytes32[] memory _siblings + ) public pure returns (bytes32) { + bytes32 computedNode = _leaf; + for (uint256 i = 0; i < _siblings.length; i++) { + bytes32 parent; + bytes32 sibling = _siblings[i]; + uint8 isComputedRightSibling = getNthBitFromRight(_path, i); + if (isComputedRightSibling == 0) { + parent = getParent(computedNode, sibling); + } else { + parent = getParent(sibling, computedNode); + } + computedNode = parent; } + return computedNode; + } - /** - * @notice get the n'th bit in a uint. - * For instance, if exampleUint=binary(11), getNth(exampleUint, 0) == 1, getNth(2, 1) == 1 - * @param _intVal The uint we are extracting a bit out of - * @param _index The index of the bit we want to extract - * @return The bit (1 or 0) in a uint8 - */ - function getNthBitFromRight(uint256 _intVal, uint256 _index) - public - pure - returns (uint8) - { - return uint8((_intVal >> _index) & 1); - } + /** + * @notice Get the parent of two children nodes in the tree + * @param _left The left child + * @param _right The right child + * @return The parent node + */ + function getParent(bytes32 _left, bytes32 _right) public pure returns (bytes32) { + return keccak256(abi.encode(_left, _right)); + } + + /** + * @notice get the n'th bit in a uint. + * For instance, if exampleUint=binary(11), getNth(exampleUint, 0) == 1, getNth(2, 1) == 1 + * @param _intVal The uint we are extracting a bit out of + * @param _index The index of the bit we want to extract + * @return The bit (1 or 0) in a uint8 + */ + function getNthBitFromRight(uint256 _intVal, uint256 _index) public pure returns (uint8) { + return uint8((_intVal >> _index) & 1); + } - /** + /** * @notice Get the right sibling key. Note that these keys overwrite the first bit of the hash to signify if it is on the right side of the parent or on the left * @param _parent The parent node * @return the key for the left sibling (0 as the first bit) */ - function getLeftSiblingKey(bytes32 _parent) public pure returns (bytes32) { - return - _parent & - 0x0111111111111111111111111111111111111111111111111111111111111111; - } + function getLeftSiblingKey(bytes32 _parent) public pure returns (bytes32) { + return _parent & 0x0111111111111111111111111111111111111111111111111111111111111111; + } - /** + /** * @notice Get the right sibling key. Note that these keys overwrite the first bit of the hash to signify if it is on the right side of the parent or on the left * @param _parent The parent node * @return the key for the right sibling (1 as the first bit) */ - function getRightSiblingKey(bytes32 _parent) public pure returns (bytes32) { - return - _parent | - 0x1000000000000000000000000000000000000000000000000000000000000000; + function getRightSiblingKey(bytes32 _parent) public pure returns (bytes32) { + return _parent | 0x1000000000000000000000000000000000000000000000000000000000000000; + } + + function pathToIndex(uint256 path, uint256 height) public pure returns (uint256) { + uint256 result = 0; + for (uint256 i = 0; i < height; i++) { + uint8 temp = getNthBitFromRight(path, i); + // UNSAFE FIX THIS + result = result + (temp * (2**i)); } + return result; + } - function pathToIndex(uint256 path, uint256 height) - public - pure - returns (uint256) - { - uint256 result = 0; - for (uint256 i = 0; i < height; i++) { - uint8 temp = getNthBitFromRight(path, i); - // UNSAFE FIX THIS - result = result + (temp * (2**i)); - } - return result; + function calculateRootTruncated(bytes32[] memory buf) public view returns (bytes32) { + uint256 odd = buf.length & 1; + uint256 n = (buf.length + 1) >> 1; + uint256 level = 0; + while (true) { + uint256 i = 0; + for (; i < n - odd; i++) { + uint256 j = i << 1; + buf[i] = keccak256(abi.encode(buf[j], buf[j + 1])); + } + if (odd == 1) { + buf[i] = keccak256(abi.encode(buf[i << 1], defaultHashes[level])); + } + if (n == 1) { + break; + } + odd = (n & 1); + n = (n + 1) >> 1; + level += 1; } + return buf[0]; + } + + // function genRoot(bytes calldata _data, uint256 sliceLen) external returns (bytes32) { + // uint256 batchSize = _data.length / sliceLen; + // require(sliceLen * batchSize == _data.length, "excess data is not expected"); + + // bytes32[] memory buf = new bytes32[](batchSize); + // // Hash to leaf + // bytes memory data = _data; + // uint256 bufOff = 32; + // for (uint256 dataOff = 32; dataOff < data.length + 32; dataOff += sliceLen) { + // // solium-disable-next-line security/no-inline-assembly + // assembly { + // mstore(add(buf, bufOff), keccak256(add(data, dataOff), sliceLen)) + // bufOff := add(bufOff, 32) + // } + // } + // // Ascent to the root + // uint256 odd = batchSize & 1; + // uint256 n = (batchSize + 1) >> 1; + // uint256 level = 0; + // while (true) { + // uint256 i = 0; + // for (; i < n - odd; i++) { + // uint256 j = i << 1; + // buf[i] = keccak256(abi.encode(buf[j], buf[j + 1])); + // } + // if (odd == 1) { + // buf[i] = keccak256(abi.encode(buf[i << 1], defaultHashes[level])); + // } + // if (n == 1) { + // break; + // } + // odd = (n & 1); + // n = (n + 1) >> 1; + // level += 1; + // } + // return buf[0]; + // } } diff --git a/contracts/Migrations.sol b/contracts/Migrations.sol index 74ecda2..b56eb4d 100644 --- a/contracts/Migrations.sol +++ b/contracts/Migrations.sol @@ -1,23 +1,23 @@ pragma solidity >=0.4.21 <0.6.0; contract Migrations { - address public owner; - uint256 public last_completed_migration; + address public owner; + uint256 public last_completed_migration; - constructor() public { - owner = msg.sender; - } + constructor() public { + owner = msg.sender; + } - modifier restricted() { - if (msg.sender == owner) _; - } + modifier restricted() { + if (msg.sender == owner) _; + } - function setCompleted(uint256 completed) public restricted { - last_completed_migration = completed; - } + function setCompleted(uint256 completed) public restricted { + last_completed_migration = completed; + } - function upgrade(address new_address) public restricted { - Migrations upgraded = Migrations(new_address); - upgraded.setCompleted(last_completed_migration); - } + function upgrade(address new_address) public restricted { + Migrations upgraded = Migrations(new_address); + upgraded.setCompleted(last_completed_migration); + } } diff --git a/contracts/NameRegistry.sol b/contracts/NameRegistry.sol index 5858b30..4677099 100644 --- a/contracts/NameRegistry.sol +++ b/contracts/NameRegistry.sol @@ -1,33 +1,33 @@ pragma solidity ^0.5.15; contract NameRegistry { - struct ContractDetails { - // registered contract address - address contractAddress; - } - event RegisteredNewContract(bytes32 name, address contractAddr); - mapping(bytes32 => ContractDetails) registry; + struct ContractDetails { + // registered contract address + address contractAddress; + } + event RegisteredNewContract(bytes32 name, address contractAddr); + mapping(bytes32 => ContractDetails) registry; - function registerName(bytes32 name, address addr) external returns (bool) { - ContractDetails memory info = registry[name]; - // create info if it doesn't exist in the registry - if (info.contractAddress == address(0)) { - info.contractAddress = addr; - registry[name] = info; - // added to registry - return true; - } else { - // already was registered - return false; - } + function registerName(bytes32 name, address addr) external returns (bool) { + ContractDetails memory info = registry[name]; + // create info if it doesn't exist in the registry + if (info.contractAddress == address(0)) { + info.contractAddress = addr; + registry[name] = info; + // added to registry + return true; + } else { + // already was registered + return false; } + } - function getContractDetails(bytes32 name) external view returns (address) { - return (registry[name].contractAddress); - } + function getContractDetails(bytes32 name) external view returns (address) { + return (registry[name].contractAddress); + } - function updateContractDetails(bytes32 name, address addr) external { - // TODO not sure if we should do this - // If we do we need a plan on how to remove this - } + function updateContractDetails(bytes32 name, address addr) external { + // TODO not sure if we should do this + // If we do we need a plan on how to remove this + } } diff --git a/contracts/POB.sol b/contracts/POB.sol index 030f050..02cd5e2 100644 --- a/contracts/POB.sol +++ b/contracts/POB.sol @@ -4,13 +4,13 @@ pragma solidity ^0.5.15; POB contract handles all the proof of burn related functionality */ contract POB { - address public coordinator; + address public coordinator; - constructor() public { - coordinator = msg.sender; - } + constructor() public { + coordinator = msg.sender; + } - function getCoordinator() public view returns (address) { - return coordinator; - } + function getCoordinator() public view returns (address) { + return coordinator; + } } diff --git a/contracts/TestToken.sol b/contracts/TestToken.sol index 3892440..69482cb 100644 --- a/contracts/TestToken.sol +++ b/contracts/TestToken.sol @@ -7,9 +7,9 @@ import "@openzeppelin/contracts/ownership/Ownable.sol"; * @title TestToken is a basic ERC20 Token */ contract TestToken is ERC20, Ownable { - /** - * @dev assign totalSupply to account creating this contract */ - constructor() public { - _mint(msg.sender, 10000000000000000000000); - } + /** + * @dev assign totalSupply to account creating this contract */ + constructor() public { + _mint(msg.sender, 10000000000000000000000); + } } diff --git a/contracts/TokenRegistry.sol b/contracts/TokenRegistry.sol index 59f8b32..2ce95b3 100644 --- a/contracts/TokenRegistry.sol +++ b/contracts/TokenRegistry.sol @@ -6,53 +6,45 @@ import {ParamManager} from "./libs/ParamManager.sol"; import {POB} from "./POB.sol"; contract TokenRegistry { - address public rollupNC; - Logger public logger; - mapping(address => bool) public pendingRegistrations; - mapping(uint256 => address) public registeredTokens; - - uint256 public numTokens; - - modifier onlyCoordinator() { - POB pobContract = POB( - nameRegistry.getContractDetails(ParamManager.POB()) - ); - assert(msg.sender == pobContract.getCoordinator()); - _; - } - Registry public nameRegistry; - - constructor(address _registryAddr) public { - nameRegistry = Registry(_registryAddr); - - logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); - } - - /** - * @notice Requests addition of a new token to the chain, can be called by anyone - * @param tokenContract Address for the new token being added - */ - function requestTokenRegistration(address tokenContract) public { - require( - pendingRegistrations[tokenContract] == false, - "Token already registered." - ); - pendingRegistrations[tokenContract] = true; - logger.logRegistrationRequest(tokenContract); - } - - /** - * @notice Add new tokens to the rollup chain by assigning them an ID called tokenType from here on - * @param tokenContract Deposit tree depth or depth of subtree that is being deposited - * TODO: add a modifier to allow only coordinator - */ - function finaliseTokenRegistration(address tokenContract) public { - require( - pendingRegistrations[tokenContract], - "Token was not registered" - ); - numTokens++; - registeredTokens[numTokens] = tokenContract; // tokenType => token contract address - logger.logRegisteredToken(numTokens, tokenContract); - } + address public rollupNC; + Logger public logger; + mapping(address => bool) public pendingRegistrations; + mapping(uint256 => address) public registeredTokens; + + uint256 public numTokens; + + modifier onlyCoordinator() { + POB pobContract = POB(nameRegistry.getContractDetails(ParamManager.POB())); + assert(msg.sender == pobContract.getCoordinator()); + _; + } + Registry public nameRegistry; + + constructor(address _registryAddr) public { + nameRegistry = Registry(_registryAddr); + + logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); + } + + /** + * @notice Requests addition of a new token to the chain, can be called by anyone + * @param tokenContract Address for the new token being added + */ + function requestTokenRegistration(address tokenContract) public { + require(pendingRegistrations[tokenContract] == false, "Token already registered."); + pendingRegistrations[tokenContract] = true; + logger.logRegistrationRequest(tokenContract); + } + + /** + * @notice Add new tokens to the rollup chain by assigning them an ID called tokenType from here on + * @param tokenContract Deposit tree depth or depth of subtree that is being deposited + * TODO: add a modifier to allow only coordinator + */ + function finaliseTokenRegistration(address tokenContract) public { + require(pendingRegistrations[tokenContract], "Token was not registered"); + numTokens++; + registeredTokens[numTokens] = tokenContract; // tokenType => token contract address + logger.logRegisteredToken(numTokens, tokenContract); + } } diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol index cd7f16c..3969b99 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IERC20.sol @@ -1,12 +1,12 @@ pragma solidity ^0.5.15; - // ERC20 token interface contract IERC20 { - function transferFrom(address from, address to, uint256 value) - public - returns (bool) - {} + function transferFrom( + address from, + address to, + uint256 value + ) public returns (bool) {} - function transfer(address recipient, uint256 value) public returns (bool) {} + function transfer(address recipient, uint256 value) public returns (bool) {} } diff --git a/contracts/interfaces/IFraudProof.sol b/contracts/interfaces/IFraudProof.sol index f57987c..04d93da 100644 --- a/contracts/interfaces/IFraudProof.sol +++ b/contracts/interfaces/IFraudProof.sol @@ -4,40 +4,9 @@ pragma experimental ABIEncoderV2; import {Types} from "../libs/Types.sol"; interface IFraudProof { - function processTx( - bytes32 _balanceRoot, - bytes32 _accountsRoot, - Types.Transaction calldata _tx, - Types.PDAMerkleProof calldata _from_pda_proof, - Types.AccountProofs calldata accountProofs - ) - external - view - returns ( - bytes32, - bytes memory, - bytes memory, - Types.ErrorCode, - bool - ); - - function processBatch( - bytes32 initialStateRoot, - bytes32 accountsRoot, - Types.Transaction[] calldata _txs, - Types.BatchValidationProofs calldata batchProofs, - bytes32 expectedTxRoot - ) - external - view - returns ( - bytes32, - bytes32, - bool - ); - - function ApplyTx( - Types.AccountMerkleProof calldata _merkle_proof, - Types.Transaction calldata transaction - ) external view returns (bytes memory, bytes32 newRoot); + function processBatch( + bytes32 stateRoot, + bytes calldata txs, + Types.InvalidTransitionProof calldata proof + ) external view returns (bytes32, bool); } diff --git a/contracts/interfaces/ITokenRegistry.sol b/contracts/interfaces/ITokenRegistry.sol index 5354cb9..3ccf42b 100644 --- a/contracts/interfaces/ITokenRegistry.sol +++ b/contracts/interfaces/ITokenRegistry.sol @@ -1,13 +1,12 @@ pragma solidity ^0.5.15; - // token registry contract interface contract ITokenRegistry { - uint256 public numTokens; - mapping(address => bool) public pendingRegistrations; - mapping(uint256 => address) public registeredTokens; + uint256 public numTokens; + mapping(address => bool) public pendingRegistrations; + mapping(uint256 => address) public registeredTokens; - function requestTokenRegistration(address tokenContract) public {} + function requestTokenRegistration(address tokenContract) public {} - function finaliseTokenRegistration(address tokenContract) public {} + function finaliseTokenRegistration(address tokenContract) public {} } diff --git a/contracts/libs/BLS.sol b/contracts/libs/BLS.sol new file mode 100644 index 0000000..a4689a7 --- /dev/null +++ b/contracts/libs/BLS.sol @@ -0,0 +1,308 @@ +pragma solidity ^0.5.15; + +library BLS { + // Field order + uint256 constant N = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + + // Negated genarator of G2 + uint256 constant nG2x1 = 11559732032986387107991004021392285783925812861821192530917403151452391805634; + uint256 constant nG2x0 = 10857046999023057135944570762232829481370756359578518086990519993285655852781; + uint256 constant nG2y1 = 17805874995975841540914202342111839520379459829704422454583296818431106115052; + uint256 constant nG2y0 = 13392588948715843804641432497768002650278120570034223513918757245338268106653; + + uint256 constant FIELD_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + uint256 constant SIGN_MASK = 0x8000000000000000000000000000000000000000000000000000000000000000; + uint256 constant ODD_NUM = 0x8000000000000000000000000000000000000000000000000000000000000000; + + function verifyMultiple( + uint256[2] memory signature, + uint256[4][] memory pubkeys, + uint256[2][] memory messages + ) internal view returns (bool) { + uint256 size = pubkeys.length; + require(size > 0, "BLS: number of public key is zero"); + require(size == messages.length, "BLS: number of public keys and messages must be equal"); + uint256 inputSize = (size + 1) * 6; + uint256[] memory input = new uint256[](inputSize); + input[0] = signature[0]; + input[1] = signature[1]; + input[2] = nG2x1; + input[3] = nG2x0; + input[4] = nG2y1; + input[5] = nG2y0; + for (uint256 i = 0; i < size; i++) { + input[i * 6 + 6] = messages[i][0]; + input[i * 6 + 7] = messages[i][1]; + input[i * 6 + 8] = pubkeys[i][1]; + input[i * 6 + 9] = pubkeys[i][0]; + input[i * 6 + 10] = pubkeys[i][3]; + input[i * 6 + 11] = pubkeys[i][2]; + } + uint256[1] memory out; + bool success; + // solium-disable-next-line security/no-inline-assembly + assembly { + success := staticcall(sub(gas(), 2000), 8, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) + switch success + case 0 { + invalid() + } + } + require(success, ""); + return out[0] != 0; + } + + function hashToPoint(bytes memory data) internal view returns (uint256[2] memory p) { + return mapToPoint(keccak256(data)); + } + + function mapToPoint(bytes32 _x) internal view returns (uint256[2] memory p) { + uint256 x = uint256(_x) % N; + uint256 y; + bool found = false; + while (true) { + y = mulmod(x, x, N); + y = mulmod(y, x, N); + y = addmod(y, 3, N); + (y, found) = sqrt(y); + if (found) { + p[0] = x; + p[1] = y; + break; + } + x = addmod(x, 1, N); + } + } + + function isValidPublicKey(uint256[4] memory publicKey) internal pure returns (bool) { + if ((publicKey[0] >= N) || (publicKey[1] >= N) || (publicKey[2] >= N || (publicKey[3] >= N))) { + return false; + } else { + return isOnCurveG2(publicKey); + } + } + + function isValidSignature(uint256[2] memory signature) internal pure returns (bool) { + if ((signature[0] >= N) || (signature[1] >= N)) { + return false; + } else { + return isOnCurveG1(signature); + } + } + + function pubkeyToUncompresed(uint256[2] memory compressed, uint256[2] memory y) + internal + pure + returns (uint256[4] memory uncompressed) + { + uint256 desicion = compressed[0] & SIGN_MASK; + require(desicion == ODD_NUM || y[0] & 1 != 1, "BLS: bad y coordinate for uncompressing key"); + uncompressed[0] = compressed[0] & FIELD_MASK; + uncompressed[1] = compressed[1]; + uncompressed[2] = y[0]; + uncompressed[3] = y[1]; + } + + function signatureToUncompresed(uint256 compressed, uint256 y) + internal + pure + returns (uint256[2] memory uncompressed) + { + uint256 desicion = compressed & SIGN_MASK; + require(desicion == ODD_NUM || y & 1 != 1, "BLS: bad y coordinate for uncompressing key"); + return [compressed & FIELD_MASK, y]; + } + + function isValidCompressedPublicKey(uint256[2] memory publicKey) internal view returns (bool) { + uint256 x0 = publicKey[0] & FIELD_MASK; + uint256 x1 = publicKey[1]; + if ((x0 >= N) || (x1 >= N)) { + return false; + } else if ((x0 == 0) && (x1 == 0)) { + return false; + } else { + return isOnCurveG2([x0, x1]); + } + } + + function isValidCompressedSignature(uint256 signature) internal view returns (bool) { + uint256 x = signature & FIELD_MASK; + if (x >= N) { + return false; + } else if (x == 0) { + return false; + } + return isOnCurveG1(x); + } + + function isOnCurveG1(uint256[2] memory point) internal pure returns (bool _isOnCurve) { + // solium-disable-next-line security/no-inline-assembly + assembly { + let t0 := mload(point) + let t1 := mload(add(point, 32)) + let t2 := mulmod(t0, t0, N) + t2 := mulmod(t2, t0, N) + t2 := addmod(t2, 3, N) + t1 := mulmod(t1, t1, N) + _isOnCurve := eq(t1, t2) + } + } + + function isOnCurveG1(uint256 x) internal view returns (bool _isOnCurve) { + bool callSuccess; + // solium-disable-next-line security/no-inline-assembly + assembly { + let t0 := x + let t1 := mulmod(t0, t0, N) + t1 := mulmod(t1, t0, N) + t1 := addmod(t1, 3, N) + + let freemem := mload(0x40) + mstore(freemem, 0x20) + mstore(add(freemem, 0x20), 0x20) + mstore(add(freemem, 0x40), 0x20) + mstore(add(freemem, 0x60), t1) + // (N - 1) / 2 = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3 + mstore(add(freemem, 0x80), 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3) + // N = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + mstore(add(freemem, 0xA0), 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + callSuccess := staticcall(sub(gas(), 2000), 5, freemem, 0xC0, freemem, 0x20) + _isOnCurve := eq(1, mload(freemem)) + } + } + + function isOnCurveG2(uint256[4] memory point) internal pure returns (bool _isOnCurve) { + // solium-disable-next-line security/no-inline-assembly + assembly { + // x0, x1 + let t0 := mload(point) + let t1 := mload(add(point, 32)) + // x0 ^ 2 + let t2 := mulmod(t0, t0, N) + // x1 ^ 2 + let t3 := mulmod(t1, t1, N) + // 3 * x0 ^ 2 + let t4 := add(add(t2, t2), t2) + // 3 * x1 ^ 2 + let t5 := addmod(add(t3, t3), t3, N) + // x0 * (x0 ^ 2 - 3 * x1 ^ 2) + t2 := mulmod(add(t2, sub(N, t5)), t0, N) + // x1 * (3 * x0 ^ 2 - x1 ^ 2) + t3 := mulmod(add(t4, sub(N, t3)), t1, N) + + // x ^ 3 + b + t0 := addmod(t2, 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5, N) + t1 := addmod(t3, 0x009713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2, N) + + // y0, y1 + t2 := mload(add(point, 64)) + t3 := mload(add(point, 96)) + // y ^ 2 + t4 := mulmod(addmod(t2, t3, N), addmod(t2, sub(N, t3), N), N) + t3 := mulmod(shl(1, t2), t3, N) + + // y ^ 2 == x ^ 3 + b + _isOnCurve := and(eq(t0, t4), eq(t1, t3)) + } + } + + function isOnCurveG2(uint256[2] memory x) internal view returns (bool _isOnCurve) { + bool callSuccess; + // solium-disable-next-line security/no-inline-assembly + assembly { + // x0, x1 + let t0 := mload(add(x, 0)) + let t1 := mload(add(x, 32)) + // x0 ^ 2 + let t2 := mulmod(t0, t0, N) + // x1 ^ 2 + let t3 := mulmod(t1, t1, N) + // 3 * x0 ^ 2 + let t4 := add(add(t2, t2), t2) + // 3 * x1 ^ 2 + let t5 := addmod(add(t3, t3), t3, N) + // x0 * (x0 ^ 2 - 3 * x1 ^ 2) + t2 := mulmod(add(t2, sub(N, t5)), t0, N) + // x1 * (3 * x0 ^ 2 - x1 ^ 2) + t3 := mulmod(add(t4, sub(N, t3)), t1, N) + // x ^ 3 + b + t0 := add(t2, 0x2b149d40ceb8aaae81be18991be06ac3b5b4c5e559dbefa33267e6dc24a138e5) + t1 := add(t3, 0x009713b03af0fed4cd2cafadeed8fdf4a74fa084e52d1852e4a2bd0685c315d2) + + // is non residue ? + t0 := addmod(mulmod(t0, t0, N), mulmod(t1, t1, N), N) + let freemem := mload(0x40) + mstore(freemem, 0x20) + mstore(add(freemem, 0x20), 0x20) + mstore(add(freemem, 0x40), 0x20) + mstore(add(freemem, 0x60), t0) + // (N - 1) / 2 = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3 + mstore(add(freemem, 0x80), 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3) + // N = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + mstore(add(freemem, 0xA0), 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + callSuccess := staticcall(sub(gas(), 2000), 5, freemem, 0xC0, freemem, 0x20) + _isOnCurve := eq(1, mload(freemem)) + } + } + + function isNonResidueFP(uint256 e) internal view returns (bool isNonResidue) { + bool callSuccess; + // solium-disable-next-line security/no-inline-assembly + assembly { + let freemem := mload(0x40) + mstore(freemem, 0x20) + mstore(add(freemem, 0x20), 0x20) + mstore(add(freemem, 0x40), 0x20) + mstore(add(freemem, 0x60), e) + // (N - 1) / 2 = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3 + mstore(add(freemem, 0x80), 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3) + // N = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + mstore(add(freemem, 0xA0), 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + callSuccess := staticcall(sub(gas(), 2000), 5, freemem, 0xC0, freemem, 0x20) + isNonResidue := eq(1, mload(freemem)) + } + require(callSuccess, "BLS: isNonResidueFP modexp call failed"); + return !isNonResidue; + } + + function isNonResidueFP2(uint256[2] memory e) internal view returns (bool isNonResidue) { + uint256 a = addmod(mulmod(e[0], e[0], N), mulmod(e[1], e[1], N), N); + bool callSuccess; + // solium-disable-next-line security/no-inline-assembly + assembly { + let freemem := mload(0x40) + mstore(freemem, 0x20) + mstore(add(freemem, 0x20), 0x20) + mstore(add(freemem, 0x40), 0x20) + mstore(add(freemem, 0x60), a) + // (N - 1) / 2 = 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3 + mstore(add(freemem, 0x80), 0x183227397098d014dc2822db40c0ac2ecbc0b548b438e5469e10460b6c3e7ea3) + // N = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + mstore(add(freemem, 0xA0), 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + callSuccess := staticcall(sub(gas(), 2000), 5, freemem, 0xC0, freemem, 0x20) + isNonResidue := eq(1, mload(freemem)) + } + require(callSuccess, "BLS: isNonResidueFP2 modexp call failed"); + return !isNonResidue; + } + + function sqrt(uint256 xx) internal view returns (uint256 x, bool hasRoot) { + bool callSuccess; + // solium-disable-next-line security/no-inline-assembly + assembly { + let freemem := mload(0x40) + mstore(freemem, 0x20) + mstore(add(freemem, 0x20), 0x20) + mstore(add(freemem, 0x40), 0x20) + mstore(add(freemem, 0x60), xx) + // (N + 1) / 4 = 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52 + mstore(add(freemem, 0x80), 0xc19139cb84c680a6e14116da060561765e05aa45a1c72a34f082305b61f3f52) + // N = 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47 + mstore(add(freemem, 0xA0), 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47) + callSuccess := staticcall(sub(gas(), 2000), 5, freemem, 0xC0, freemem, 0x20) + x := mload(freemem) + hasRoot := eq(xx, mulmod(x, x, N)) + } + require(callSuccess, "BLS: sqrt modexp call failed"); + } +} diff --git a/contracts/libs/ECVerify.sol b/contracts/libs/ECVerify.sol deleted file mode 100644 index ee7065e..0000000 --- a/contracts/libs/ECVerify.sol +++ /dev/null @@ -1,63 +0,0 @@ -pragma solidity ^0.5.15; - - -library ECVerify { - function ecrecovery(bytes32 hash, bytes memory sig) - public - pure - returns (address) - { - bytes32 r; - bytes32 s; - uint8 v; - - if (sig.length != 65) { - return address(0x0); - } - - assembly { - r := mload(add(sig, 32)) - s := mload(add(sig, 64)) - v := and(mload(add(sig, 65)), 255) - } - - // https://github.com/ethereum/go-ethereum/issues/2053 - if (v < 27) { - v += 27; - } - - if (v != 27 && v != 28) { - return address(0x0); - } - - // get address out of hash and signature - address result = ecrecover(hash, v, r, s); - - // ecrecover returns zero on error - require(result != address(0x0)); - - return result; - } - - function ecrecovery(bytes32 hash, uint8 v, bytes32 r, bytes32 s) - public - pure - returns (address) - { - // get address out of hash and signature - address result = ecrecover(hash, v, r, s); - - // ecrecover returns zero on error - require(result != address(0x0)); - - return result; - } - - function ecverify(bytes32 hash, bytes memory sig, address signer) - public - pure - returns (bool) - { - return signer == ecrecovery(hash, sig); - } -} diff --git a/contracts/libs/ParamManager.sol b/contracts/libs/ParamManager.sol index 9c600d4..cc4a035 100644 --- a/contracts/libs/ParamManager.sol +++ b/contracts/libs/ParamManager.sol @@ -1,58 +1,57 @@ pragma solidity ^0.5.15; - library ParamManager { - function DEPOSIT_MANAGER() public pure returns (bytes32) { - return keccak256("deposit_manager"); - } + function DEPOSIT_MANAGER() public pure returns (bytes32) { + return keccak256("deposit_manager"); + } - function WITHDRAW_MANAGER() public pure returns (bytes32) { - return keccak256("withdraw_manager"); - } + function WITHDRAW_MANAGER() public pure returns (bytes32) { + return keccak256("withdraw_manager"); + } - function TEST_TOKEN() public pure returns (bytes32) { - return keccak256("test_token"); - } + function TEST_TOKEN() public pure returns (bytes32) { + return keccak256("test_token"); + } - function POB() public pure returns (bytes32) { - return keccak256("pob"); - } + function POB() public pure returns (bytes32) { + return keccak256("pob"); + } - function Governance() public pure returns (bytes32) { - return keccak256("governance"); - } + function Governance() public pure returns (bytes32) { + return keccak256("governance"); + } - function ROLLUP_CORE() public pure returns (bytes32) { - return keccak256("rollup_core"); - } + function ROLLUP_CORE() public pure returns (bytes32) { + return keccak256("rollup_core"); + } - function ACCOUNTS_TREE() public pure returns (bytes32) { - return keccak256("accounts_tree"); - } + function ACCOUNT_REGISTRY() public pure returns (bytes32) { + return keccak256("accounts_registry"); + } - function LOGGER() public pure returns (bytes32) { - return keccak256("logger"); - } + function LOGGER() public pure returns (bytes32) { + return keccak256("logger"); + } - function MERKLE_UTILS() public pure returns (bytes32) { - return keccak256("merkle_lib"); - } + function MERKLE_UTILS() public pure returns (bytes32) { + return keccak256("merkle_lib"); + } - function PARAM_MANAGER() public pure returns (bytes32) { - return keccak256("param_manager"); - } + function PARAM_MANAGER() public pure returns (bytes32) { + return keccak256("param_manager"); + } - function TOKEN_REGISTRY() public pure returns (bytes32) { - return keccak256("token_registry"); - } + function TOKEN_REGISTRY() public pure returns (bytes32) { + return keccak256("token_registry"); + } - function FRAUD_PROOF() public pure returns (bytes32) { - return keccak256("fraud_proof"); - } + function FRAUD_PROOF() public pure returns (bytes32) { + return keccak256("fraud_proof"); + } - bytes32 public constant _CHAIN_ID = keccak256("opru-123"); + bytes32 public constant _CHAIN_ID = keccak256("opru-123"); - function CHAIN_ID() public pure returns (bytes32) { - return _CHAIN_ID; - } + function CHAIN_ID() public pure returns (bytes32) { + return _CHAIN_ID; + } } diff --git a/contracts/libs/RollupUtils.sol b/contracts/libs/RollupUtils.sol index a8c6354..1e2b3da 100644 --- a/contracts/libs/RollupUtils.sol +++ b/contracts/libs/RollupUtils.sol @@ -4,214 +4,186 @@ pragma experimental ABIEncoderV2; import {Types} from "./Types.sol"; library RollupUtils { - // ---------- Account Related Utils ------------------- - function PDALeafToHash(Types.PDALeaf memory _PDA_Leaf) - public - pure - returns (bytes32) - { - return keccak256(abi.encode(_PDA_Leaf.pubkey)); - } - - // returns a new User Account with updated balance - function UpdateBalanceInAccount( - Types.UserAccount memory original_account, - uint256 new_balance - ) public pure returns (Types.UserAccount memory updated_account) { - original_account.balance = new_balance; - return original_account; - } - - function BalanceFromAccount(Types.UserAccount memory account) - public - pure - returns (uint256) - { - return account.balance; - } - - // AccountFromBytes decodes the bytes to account - function AccountFromBytes(bytes memory accountBytes) - public - pure - returns (uint256 ID, uint256 balance, uint256 nonce, uint256 tokenType) - { - return abi - .decode(accountBytes, (uint256, uint256, uint256, uint256)); - } - - // - // BytesFromAccount and BytesFromAccountDeconstructed do the same thing i.e encode account to bytes - // - function BytesFromAccount(Types.UserAccount memory account) - public - pure - returns (bytes memory) - { - bytes memory data= abi.encodePacked( - account.ID, - account.balance, - account.nonce, - account.tokenType - ); - - return data; - } - - function BytesFromAccountDeconstructed(uint256 ID, uint256 balance, uint256 nonce, uint256 tokenType) public pure returns (bytes memory) { - return abi.encodePacked(ID, balance, nonce, tokenType); - } - - // - // HashFromAccount and getAccountHash do the same thing i.e hash account - // - function getAccountHash( - uint256 id, - uint256 balance, - uint256 nonce, - uint256 tokenType - ) public pure returns (bytes32) { - return keccak256(BytesFromAccountDeconstructed(id,balance,nonce,tokenType)); - } - - function HashFromAccount(Types.UserAccount memory account) - public - pure - returns (bytes32) - { - return keccak256(BytesFromAccountDeconstructed( - account.ID, - account.balance, - account.nonce, - account.tokenType - )); - } - // ---------- Tx Related Utils ------------------- - function CompressTx(Types.Transaction memory _tx) - public - pure - returns (bytes memory) - { - return - abi.encode( - _tx.fromIndex, - _tx.toIndex, - _tx.amount, - _tx.signature - ); - } - - function DecompressTx(bytes memory txBytes) - public - pure - returns (uint256 from, uint256 to, uint256 nonce,bytes memory sig) - { - - return abi - .decode(txBytes, (uint256, uint256,uint256, bytes)); - } - - function CompressTxWithMessage(bytes memory message, bytes memory sig) - public - pure - returns (bytes memory) - { - Types.Transaction memory _tx = TxFromBytes(message); - return - abi.encode( - _tx.fromIndex, - _tx.toIndex, - _tx.amount, - sig - ); - } - - // Decoding transaction from bytes - function TxFromBytesDeconstructed(bytes memory txBytes) pure public returns(uint256 from, uint256 to, uint256 tokenType, uint256 nonce, uint256 txType,uint256 amount) { - return abi - .decode(txBytes, (uint256, uint256, uint256,uint256,uint256, uint256)); - } - - function TxFromBytes(bytes memory txBytes) pure public returns(Types.Transaction memory) { - Types.Transaction memory transaction; - (transaction.fromIndex, transaction.toIndex, transaction.tokenType, transaction.nonce, transaction.txType, transaction.amount) = abi - .decode(txBytes, (uint256, uint256, uint256,uint256,uint256, uint256)); - return transaction; - } - - // - // BytesFromTx and BytesFromTxDeconstructed do the same thing i.e encode transaction to bytes - // - function BytesFromTx(Types.Transaction memory _tx) - public - pure - returns (bytes memory) - { - return - abi.encodePacked( - _tx.fromIndex, - _tx.toIndex, - _tx.tokenType, - _tx.nonce, - _tx.txType, - _tx.amount - ); - } - - function BytesFromTxDeconstructed(uint256 from, uint256 to, uint256 tokenType, uint256 nonce,uint256 txType,uint256 amount) pure public returns(bytes memory){ - return abi.encodePacked(from,to,tokenType,nonce,txType,amount); - } - - // - // HashFromTx and getTxSignBytes do the same thing i.e get the tx data to be signed - // - function HashFromTx(Types.Transaction memory _tx) - public - pure - returns (bytes32) - { - return keccak256(BytesFromTxDeconstructed(_tx.fromIndex, _tx.toIndex, _tx.tokenType, _tx.nonce,_tx.txType,_tx.amount)); - } - - function getTxSignBytes( - uint256 fromIndex, - uint256 toIndex, - uint256 tokenType, - uint256 txType, - uint256 nonce, - uint256 amount - ) public pure returns (bytes32) { - return keccak256(BytesFromTxDeconstructed(fromIndex, toIndex, tokenType, nonce,txType,amount)); - } - - /** - * @notice Calculates the address from the pubkey - * @param pub is the pubkey - * @return Returns the address that has been calculated from the pubkey - */ - function calculateAddress(bytes memory pub) - public - pure - returns (address addr) - { - bytes32 hash = keccak256(pub); - assembly { - mstore(0, hash) - addr := mload(0) - - } - } - - function GetGenesisLeaves() public view returns(bytes32[2] memory leaves){ - Types.UserAccount memory account1 = Types.UserAccount({ID: 0, tokenType:0, balance:0, nonce:0}); - Types.UserAccount memory account2 = Types.UserAccount({ID:1, tokenType:0, balance:0, nonce:0}); - leaves[0]= HashFromAccount(account1); - leaves[1] = HashFromAccount(account2); - } - function GetGenesisDataBlocks() public view returns(bytes[2] memory dataBlocks){ - Types.UserAccount memory account1 = Types.UserAccount({ID: 0, tokenType:0, balance:0, nonce:0}); - Types.UserAccount memory account2 = Types.UserAccount({ID:1, tokenType:0, balance:0, nonce:0}); - dataBlocks[0]= BytesFromAccount(account1); - dataBlocks[1] = BytesFromAccount(account2); - } + // ---------- Account Related Utils ------------------- + function PDALeafToHash(Types.PDALeaf memory _PDA_Leaf) public pure returns (bytes32) { + return keccak256(abi.encode(_PDA_Leaf.pubkey)); + } + + // returns a new User Account with updated balance + function UpdateBalanceInAccount(Types.UserAccount memory original_account, uint256 new_balance) + public + pure + returns (Types.UserAccount memory updated_account) + { + original_account.balance = new_balance; + return original_account; + } + + function BalanceFromAccount(Types.UserAccount memory account) public pure returns (uint256) { + return account.balance; + } + + // AccountFromBytes decodes the bytes to account + function AccountFromBytes(bytes memory accountBytes) + public + pure + returns ( + uint256 ID, + uint256 balance, + uint256 nonce, + uint256 tokenType + ) + { + return abi.decode(accountBytes, (uint256, uint256, uint256, uint256)); + } + + // + // BytesFromAccount and BytesFromAccountDeconstructed do the same thing i.e encode account to bytes + // + function BytesFromAccount(Types.UserAccount memory account) public pure returns (bytes memory) { + bytes memory data = abi.encodePacked(account.ID, account.balance, account.nonce, account.tokenType); + + return data; + } + + function BytesFromAccountDeconstructed( + uint256 ID, + uint256 balance, + uint256 nonce, + uint256 tokenType + ) public pure returns (bytes memory) { + return abi.encodePacked(ID, balance, nonce, tokenType); + } + + // + // HashFromAccount and getAccountHash do the same thing i.e hash account + // + function getAccountHash( + uint256 id, + uint256 balance, + uint256 nonce, + uint256 tokenType + ) public pure returns (bytes32) { + return keccak256(BytesFromAccountDeconstructed(id, balance, nonce, tokenType)); + } + + function HashFromAccount(Types.UserAccount memory account) public pure returns (bytes32) { + return keccak256(BytesFromAccountDeconstructed(account.ID, account.balance, account.nonce, account.tokenType)); + } + + // ---------- Tx Related Utils ------------------- + function CompressTx(Types.Transaction memory _tx) public pure returns (bytes memory) { + return abi.encode(_tx.fromIndex, _tx.toIndex, _tx.amount, _tx.signature); + } + + function DecompressTx(bytes memory txBytes) + public + pure + returns ( + uint256 from, + uint256 to, + uint256 nonce, + bytes memory sig + ) + { + return abi.decode(txBytes, (uint256, uint256, uint256, bytes)); + } + + function CompressTxWithMessage(bytes memory message, bytes memory sig) public pure returns (bytes memory) { + Types.Transaction memory _tx = TxFromBytes(message); + return abi.encode(_tx.fromIndex, _tx.toIndex, _tx.amount, sig); + } + + // Decoding transaction from bytes + function TxFromBytesDeconstructed(bytes memory txBytes) + public + pure + returns ( + uint256 from, + uint256 to, + uint256 tokenType, + uint256 nonce, + uint256 txType, + uint256 amount + ) + { + return abi.decode(txBytes, (uint256, uint256, uint256, uint256, uint256, uint256)); + } + + function TxFromBytes(bytes memory txBytes) public pure returns (Types.Transaction memory) { + Types.Transaction memory transaction; + ( + transaction.fromIndex, + transaction.toIndex, + transaction.tokenType, + transaction.nonce, + transaction.txType, + transaction.amount + ) = abi.decode(txBytes, (uint256, uint256, uint256, uint256, uint256, uint256)); + return transaction; + } + + // + // BytesFromTx and BytesFromTxDeconstructed do the same thing i.e encode transaction to bytes + // + function BytesFromTx(Types.Transaction memory _tx) public pure returns (bytes memory) { + return abi.encodePacked(_tx.fromIndex, _tx.toIndex, _tx.tokenType, _tx.nonce, _tx.txType, _tx.amount); + } + + function BytesFromTxDeconstructed( + uint256 from, + uint256 to, + uint256 tokenType, + uint256 nonce, + uint256 txType, + uint256 amount + ) public pure returns (bytes memory) { + return abi.encodePacked(from, to, tokenType, nonce, txType, amount); + } + + // + // HashFromTx and getTxSignBytes do the same thing i.e get the tx data to be signed + // + function HashFromTx(Types.Transaction memory _tx) public pure returns (bytes32) { + return + keccak256(BytesFromTxDeconstructed(_tx.fromIndex, _tx.toIndex, _tx.tokenType, _tx.nonce, _tx.txType, _tx.amount)); + } + + function getTxSignBytes( + uint256 fromIndex, + uint256 toIndex, + uint256 tokenType, + uint256 txType, + uint256 nonce, + uint256 amount + ) public pure returns (bytes32) { + return keccak256(BytesFromTxDeconstructed(fromIndex, toIndex, tokenType, nonce, txType, amount)); + } + + /** + * @notice Calculates the address from the pubkey + * @param pub is the pubkey + * @return Returns the address that has been calculated from the pubkey + */ + function calculateAddress(bytes memory pub) public pure returns (address addr) { + bytes32 hash = keccak256(pub); + assembly { + mstore(0, hash) + addr := mload(0) + } + } + + function GetGenesisLeaves() public view returns (bytes32[2] memory leaves) { + Types.UserAccount memory account1 = Types.UserAccount({ID: 0, tokenType: 0, balance: 0, nonce: 0}); + Types.UserAccount memory account2 = Types.UserAccount({ID: 1, tokenType: 0, balance: 0, nonce: 0}); + leaves[0] = HashFromAccount(account1); + leaves[1] = HashFromAccount(account2); + } + + function GetGenesisDataBlocks() public view returns (bytes[2] memory dataBlocks) { + Types.UserAccount memory account1 = Types.UserAccount({ID: 0, tokenType: 0, balance: 0, nonce: 0}); + Types.UserAccount memory account2 = Types.UserAccount({ID: 1, tokenType: 0, balance: 0, nonce: 0}); + dataBlocks[0] = BytesFromAccount(account1); + dataBlocks[1] = BytesFromAccount(account2); + } } diff --git a/contracts/libs/StateAccount.sol b/contracts/libs/StateAccount.sol new file mode 100644 index 0000000..a35a27f --- /dev/null +++ b/contracts/libs/StateAccount.sol @@ -0,0 +1,71 @@ +pragma solidity ^0.5.15; + +library StateAccount { + // account: [zeros<18>|account_index<4>|token_type<2>|balance<4>|nonce<4>] + uint256 public constant ACCOUNT_LEN = 14; + uint256 public constant ACCOUNT_OFF = 18; // word_size - ACCOUNT_LEN; + // positions in bits + uint256 public constant POSITION_ACCOUNT_INDEX = 80; + uint256 public constant POSITION_TOKEN_TYPE = 64; + uint256 public constant POSITION_BALANCE = 32; + uint256 public constant POSITION_NONCE = 0; + // masks + uint256 public constant MASK_ACCOUNT_INDEX = 0xffffffff; + uint256 public constant MASK_TOKEN_TYPE = 0xffff; + uint256 public constant MASK_BALANCE = 0xffffffff; + uint256 public constant MASK_NONCE = 0xffffffff; + uint256 public constant MASK_NONCE_IN_PLACE = 0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff00000000; + uint256 public constant MASK_BALANCE_IN_PLACE = 0xffffffffffffffffffffffffffffffffffffffffffffffff00000000ffffffff; + + function accountID(uint256 account) internal pure returns (uint256) { + require(account < (1 << (ACCOUNT_LEN * 8)), "excess data"); + return (account >>= POSITION_ACCOUNT_INDEX) & MASK_ACCOUNT_INDEX; + } + + function tokenType(uint256 account) internal pure returns (uint256) { + require(account < (1 << (ACCOUNT_LEN * 8)), "excess data"); + return (account >>= POSITION_TOKEN_TYPE) & MASK_TOKEN_TYPE; + } + + function balance(uint256 account) internal pure returns (uint256) { + require(account < (1 << (ACCOUNT_LEN * 8)), "excess data"); + return (account >>= POSITION_BALANCE) & MASK_BALANCE; + } + + function nonce(uint256 account) internal pure returns (uint256) { + require(account < (1 << (ACCOUNT_LEN * 8)), "excess data"); + return (account >>= POSITION_NONCE) & MASK_NONCE; + } + + function incrementNonce(uint256 account) internal pure returns (uint256, bool) { + require(account < (1 << (ACCOUNT_LEN * 8)), "excess data"); + uint256 _nonce = ((account >>= POSITION_NONCE) & MASK_NONCE); + bool safe = _nonce < 0xffffffff; // require(_nonce < 0xffffffff, "nonce overflow"); + return (account + 1, safe); + } + + function balanceSafeAdd(uint256 account, uint256 amount) internal pure returns (uint256, bool) { + require(account < (1 << (ACCOUNT_LEN * 8)), "excess data"); + uint256 _balance = (account >> POSITION_BALANCE) & MASK_BALANCE; + uint256 newBalance = _balance + (amount & MASK_BALANCE); + bool safe = newBalance <= 0xffffffff; // require(newBalance <= 0xffffffff, "balance addition overflow"); + return ((account & MASK_BALANCE_IN_PLACE) | (newBalance << POSITION_BALANCE), safe); + } + + function balanceSafeSub(uint256 account, uint256 amount) internal pure returns (uint256, bool) { + require(account < (1 << (ACCOUNT_LEN * 8)), "excess data"); + uint256 _balance = (account >> POSITION_BALANCE) & MASK_BALANCE; + bool safe = _balance >= amount; // require(_balance >= amount, "subtraction overflow"); + uint256 newBalance = _balance - amount; + return ((account & MASK_BALANCE_IN_PLACE) | (newBalance << POSITION_BALANCE), safe); + } + + function hash(uint256 account) internal pure returns (bytes32 res) { + // solium-disable-next-line security/no-inline-assembly + assembly { + let mem := mload(0x40) + mstore(mem, account) + res := keccak256(add(mem, ACCOUNT_OFF), ACCOUNT_LEN) + } + } +} diff --git a/contracts/libs/Tx.sol b/contracts/libs/Tx.sol new file mode 100644 index 0000000..8b00b00 --- /dev/null +++ b/contracts/libs/Tx.sol @@ -0,0 +1,75 @@ +pragma solidity ^0.5.15; + +import {BLS} from "./BLS.sol"; + +library Tx { + // transaction: [sender<4>|receiver<4>|amount<4>] + uint256 public constant TX_LEN = 12; + uint256 public constant MASK_TX = 0xffffffffffffffffffffffff; + uint256 public constant MASK_STATE_INDEX = 0xffffffff; + uint256 public constant MASK_AMOUNT = 0xffffffff; + // positions in bytes + uint256 public constant POSITION_SENDER = 4; + uint256 public constant POSITION_RECEIVER = 8; + uint256 public constant POSITION_AMOUNT = 12; + + function hasExcessData(bytes memory txs) internal pure returns (bool) { + return txs.length % TX_LEN != 0; + } + + function size(bytes memory txs) internal pure returns (uint256) { + return txs.length / TX_LEN; + } + + function amountOf(bytes memory txs, uint256 index) internal pure returns (uint256 amount) { + // solium-disable-next-line security/no-inline-assembly + assembly { + let p_tx := add(txs, mul(index, TX_LEN)) + amount := and(mload(add(p_tx, POSITION_AMOUNT)), MASK_AMOUNT) + } + return amount; + } + + function senderOf(bytes memory txs, uint256 index) internal pure returns (uint256 sender) { + // solium-disable-next-line security/no-inline-assembly + assembly { + let p_tx := add(txs, mul(index, TX_LEN)) + sender := and(mload(add(p_tx, POSITION_SENDER)), MASK_STATE_INDEX) + } + } + + function receiverOf(bytes memory txs, uint256 index) internal pure returns (uint256 receiver) { + // solium-disable-next-line security/no-inline-assembly + assembly { + let p_tx := add(txs, mul(index, TX_LEN)) + receiver := and(mload(add(p_tx, POSITION_RECEIVER)), MASK_STATE_INDEX) + } + } + + function hashOf(bytes memory txs, uint256 index) internal pure returns (bytes32 result) { + // solium-disable-next-line security/no-inline-assembly + assembly { + let p_tx := add(txs, add(mul(index, TX_LEN), 32)) + result := keccak256(p_tx, TX_LEN) + } + } + + function mapToPoint(bytes memory txs, uint256 index) internal view returns (uint256[2] memory) { + bytes32 r; + // solium-disable-next-line security/no-inline-assembly + assembly { + let p_tx := add(txs, add(mul(index, TX_LEN), 32)) + r := keccak256(p_tx, TX_LEN) + } + return BLS.mapToPoint(r); + } + + function toLeafs(bytes memory txs) internal pure returns (bytes32[] memory) { + uint256 batchSize = size(txs); + bytes32[] memory buf = new bytes32[](batchSize); + for (uint256 i = 0; i < batchSize; i++) { + buf[i] = hashOf(txs, i); + } + return buf; + } +} diff --git a/contracts/libs/Types.sol b/contracts/libs/Types.sol index 8eeb43d..fad2827 100644 --- a/contracts/libs/Types.sol +++ b/contracts/libs/Types.sol @@ -4,94 +4,104 @@ pragma solidity ^0.5.15; * @title DataTypes */ library Types { - // PDALeaf represents the leaf in - // Pubkey DataAvailability Tree - struct PDALeaf { - bytes pubkey; - } + struct InvalidTransitionProof { + UserAccount[] senderAccounts; + bytes32[][] senderWitnesses; + UserAccount[] receiverAccounts; + bytes32[][] receiverWitnesses; + } - // Batch represents the batch submitted periodically to the ethereum chain - struct Batch { - bytes32 stateRoot; - bytes32 accountRoot; - bytes32 depositTree; - address committer; - bytes32 txRoot; - uint256 stakeCommitted; - uint256 finalisesOn; - uint256 timestamp; - } - // Transaction represents how each transaction looks like for - // this rollup chain - struct Transaction { - uint256 fromIndex; - uint256 toIndex; - uint256 tokenType; - uint256 nonce; - uint256 txType; - uint256 amount; - bytes signature; - } + // PDALeaf represents the leaf in + // Pubkey DataAvailability Tree + struct PDALeaf { + bytes pubkey; + } - // AccountInclusionProof consists of the following fields - // 1. Path to the account leaf from root in the balances tree - // 2. Actual data stored in the leaf - struct AccountInclusionProof { - uint256 pathToAccount; - UserAccount account; - } + // Batch represents the batch submitted periodically to the ethereum chain + struct Batch { + bytes32 stateRoot; + bytes32 accountRoot; + bytes32 depositTree; + address committer; + bytes32 txRoot; + bytes32 txCommit; + uint256 stakeCommitted; + uint256 finalisesOn; + uint256 timestamp; + uint256[2] signature; + } - struct TranasctionInclusionProof { - uint256 pathToTx; - Transaction data; - } + // Transaction represents how each transaction looks like for + // this rollup chain + struct Transaction { + uint256 fromIndex; + uint256 toIndex; + uint256 tokenType; + uint256 nonce; + uint256 txType; + uint256 amount; + bytes signature; + } - struct PDAInclusionProof { - uint256 pathToPubkey; - PDALeaf pubkey_leaf; - } + // AccountInclusionProof consists of the following fields + // 1. Path to the account leaf from root in the balances tree + // 2. Actual data stored in the leaf + struct AccountInclusionProof { + uint256 pathToAccount; + UserAccount account; + } - // UserAccount contains the actual data stored in the leaf of balance tree - struct UserAccount { - // ID is the path to the pubkey in the PDA tree - uint256 ID; - uint256 tokenType; - uint256 balance; - uint256 nonce; - } + struct TranasctionInclusionProof { + uint256 pathToTx; + Transaction data; + } - struct AccountMerkleProof { - AccountInclusionProof accountIP; - bytes32[] siblings; - } + struct PDAInclusionProof { + uint256 pathToPubkey; + PDALeaf pubkey_leaf; + } - struct AccountProofs { - AccountMerkleProof from; - AccountMerkleProof to; - } + // UserAccount contains the actual data stored in the leaf of balance tree + struct UserAccount { + // ID is the path to the pubkey in the PDA tree + uint256 ID; + uint256 tokenType; + uint256 balance; + uint256 nonce; + } - struct BatchValidationProofs { - AccountProofs[] accountProofs; - PDAMerkleProof[] pdaProof; - } + struct AccountMerkleProof { + AccountInclusionProof accountIP; + bytes32[] siblings; + } - struct TransactionMerkleProof { - TranasctionInclusionProof _tx; - bytes32[] siblings; - } + struct AccountProofs { + AccountMerkleProof from; + AccountMerkleProof to; + } - struct PDAMerkleProof { - PDAInclusionProof _pda; - bytes32[] siblings; - } + struct BatchValidationProofs { + AccountProofs[] accountProofs; + PDAMerkleProof[] pdaProof; + } - enum ErrorCode { - NoError, - InvalidTokenAddress, - InvalidTokenAmount, - NotEnoughTokenBalance, - BadFromTokenType, - BadToTokenType - } + struct TransactionMerkleProof { + TranasctionInclusionProof _tx; + bytes32[] siblings; + } + + struct PDAMerkleProof { + PDAInclusionProof _pda; + bytes32[] siblings; + } + + enum ErrorCode { + NoError, + InvalidTokenAddress, + InvalidTokenAmount, + NotEnoughTokenBalance, + BadFromTokenType, + BadToTokenType + } } diff --git a/contracts/logger.sol b/contracts/logger.sol index 70bd3e0..9aa5d45 100644 --- a/contracts/logger.sol +++ b/contracts/logger.sol @@ -1,119 +1,89 @@ pragma solidity ^0.5.15; contract Logger { - /********************* - * Rollup Contract * - ********************/ - event NewBatch( - address committer, - bytes32 txroot, - bytes32 updatedRoot, - uint256 index - ); - - function logNewBatch( - address committer, - bytes32 txroot, - bytes32 updatedRoot, - uint256 index - ) public { - emit NewBatch(committer, txroot, updatedRoot, index); - } - - event StakeWithdraw(address committed, uint256 amount, uint256 batch_id); - - function logStakeWithdraw( - address committed, - uint256 amount, - uint256 batch_id - ) public { - emit StakeWithdraw(committed, amount, batch_id); - } - - event BatchRollback( - uint256 batch_id, - address committer, - bytes32 stateRoot, - bytes32 txRoot, - uint256 stakeSlashed - ); - - function logBatchRollback( - uint256 batch_id, - address committer, - bytes32 stateRoot, - bytes32 txRoot, - uint256 stakeSlashed - ) public { - emit BatchRollback( - batch_id, - committer, - stateRoot, - txRoot, - stakeSlashed - ); - } - - event RollbackFinalisation(uint256 totalBatchesSlashed); - - function logRollbackFinalisation(uint256 totalBatchesSlashed) public { - emit RollbackFinalisation(totalBatchesSlashed); - } - - event RegisteredToken(uint256 tokenType, address tokenContract); - - function logRegisteredToken(uint256 tokenType, address tokenContract) - public - { - emit RegisteredToken(tokenType, tokenContract); - } - - event RegistrationRequest(address tokenContract); - - function logRegistrationRequest(address tokenContract) public { - emit RegistrationRequest(tokenContract); - } - - event DepositQueued( - uint256 AccountID, - bytes pubkey, - bytes data - ); - - function logDepositQueued( - uint256 accountID, - bytes memory pubkey, - bytes memory data - ) public { - emit DepositQueued( - accountID, - pubkey, - data - ); - } - - event DepositLeafMerged(bytes32 left, bytes32 right, bytes32 newRoot); - - function logDepositLeafMerged( - bytes32 left, - bytes32 right, - bytes32 newRoot - ) public { - emit DepositLeafMerged(left, right, newRoot); - } - - event DepositSubTreeReady(bytes32 root); - - function logDepositSubTreeReady(bytes32 root) public { - emit DepositSubTreeReady(root); - } - - event DepositsFinalised(bytes32 depositSubTreeRoot, uint256 pathToSubTree); - - function logDepositFinalised( - bytes32 depositSubTreeRoot, - uint256 pathToSubTree - ) public { - emit DepositsFinalised(depositSubTreeRoot, pathToSubTree); - } + /********************* + * Rollup Contract * + ********************/ + event NewBatch(address committer, bytes32 txroot, bytes32 updatedRoot, uint256 index); + + function logNewBatch( + address committer, + bytes32 txroot, + bytes32 updatedRoot, + uint256 index + ) public { + emit NewBatch(committer, txroot, updatedRoot, index); + } + + event StakeWithdraw(address committed, uint256 amount, uint256 batch_id); + + function logStakeWithdraw( + address committed, + uint256 amount, + uint256 batch_id + ) public { + emit StakeWithdraw(committed, amount, batch_id); + } + + event BatchRollback(uint256 batch_id, address committer, bytes32 stateRoot, bytes32 txRoot, uint256 stakeSlashed); + + function logBatchRollback( + uint256 batch_id, + address committer, + bytes32 stateRoot, + bytes32 txRoot, + uint256 stakeSlashed + ) public { + emit BatchRollback(batch_id, committer, stateRoot, txRoot, stakeSlashed); + } + + event RollbackFinalisation(uint256 totalBatchesSlashed); + + function logRollbackFinalisation(uint256 totalBatchesSlashed) public { + emit RollbackFinalisation(totalBatchesSlashed); + } + + event RegisteredToken(uint256 tokenType, address tokenContract); + + function logRegisteredToken(uint256 tokenType, address tokenContract) public { + emit RegisteredToken(tokenType, tokenContract); + } + + event RegistrationRequest(address tokenContract); + + function logRegistrationRequest(address tokenContract) public { + emit RegistrationRequest(tokenContract); + } + + event DepositQueued(uint256 AccountID, bytes pubkey, bytes data); + + function logDepositQueued( + uint256 accountID, + bytes memory pubkey, + bytes memory data + ) public { + emit DepositQueued(accountID, pubkey, data); + } + + event DepositLeafMerged(bytes32 left, bytes32 right, bytes32 newRoot); + + function logDepositLeafMerged( + bytes32 left, + bytes32 right, + bytes32 newRoot + ) public { + emit DepositLeafMerged(left, right, newRoot); + } + + event DepositSubTreeReady(bytes32 root); + + function logDepositSubTreeReady(bytes32 root) public { + emit DepositSubTreeReady(root); + } + + event DepositsFinalised(bytes32 depositSubTreeRoot, uint256 pathToSubTree); + + function logDepositFinalised(bytes32 depositSubTreeRoot, uint256 pathToSubTree) public { + emit DepositsFinalised(depositSubTreeRoot, pathToSubTree); + } } diff --git a/contracts/rollup.sol b/contracts/rollup.sol index 13157c0..704bb58 100644 --- a/contracts/rollup.sol +++ b/contracts/rollup.sol @@ -2,15 +2,13 @@ pragma solidity ^0.5.15; pragma experimental ABIEncoderV2; import {SafeMath} from "@openzeppelin/contracts/math/SafeMath.sol"; -import "solidity-bytes-utils/contracts/BytesLib.sol"; +// import "solidity-bytes-utils/contracts/BytesLib.sol"; import {IERC20} from "./interfaces/IERC20.sol"; import {ITokenRegistry} from "./interfaces/ITokenRegistry.sol"; import {IFraudProof} from "./interfaces/IFraudProof.sol"; import {ParamManager} from "./libs/ParamManager.sol"; import {Types} from "./libs/Types.sol"; import {RollupUtils} from "./libs/RollupUtils.sol"; -import {ECVerify} from "./libs/ECVerify.sol"; -import {IncrementalTree} from "./IncrementalTree.sol"; import {Logger} from "./logger.sol"; import {POB} from "./POB.sol"; import {MerkleTreeUtils as MTUtils} from "./MerkleTreeUtils.sol"; @@ -18,442 +16,358 @@ import {NameRegistry as Registry} from "./NameRegistry.sol"; import {Governance} from "./Governance.sol"; import {DepositManager} from "./DepositManager.sol"; -contract RollupSetup { - using SafeMath for uint256; - using BytesLib for bytes; - using ECVerify for bytes32; - - /********************* - * Variable Declarations * - ********************/ - - // External contracts - DepositManager public depositManager; - IncrementalTree public accountsTree; - Logger public logger; - ITokenRegistry public tokenRegistry; - Registry public nameRegistry; - Types.Batch[] public batches; - MTUtils public merkleUtils; - - IFraudProof public fraudProof; - - bytes32 - public constant ZERO_BYTES32 = 0x0000000000000000000000000000000000000000000000000000000000000000; - address payable constant BURN_ADDRESS = 0x0000000000000000000000000000000000000000; - Governance public governance; - - // this variable will be greater than 0 if - // there is rollback in progress - // will be reset to 0 once rollback is completed - uint256 public invalidBatchMarker; - - modifier onlyCoordinator() { - POB pobContract = POB( - nameRegistry.getContractDetails(ParamManager.POB()) - ); - assert(msg.sender == pobContract.getCoordinator()); - _; +import {Tx} from "./libs/Tx.sol"; +import {BLS} from "./libs/BLS.sol"; +import {BLSAccountRegistry} from "./BLSAccountRegistry.sol"; + +contract Rollup { + bytes32 public constant ZERO_BYTES32 = 0x0000000000000000000000000000000000000000000000000000000000000000; + address payable constant BURN_ADDRESS = 0x0000000000000000000000000000000000000000; + + using SafeMath for uint256; + using Tx for bytes; + + // External contracts + DepositManager public depositManager; + BLSAccountRegistry public accountRegistry; + Logger public logger; + ITokenRegistry public tokenRegistry; + Registry public nameRegistry; + MTUtils public merkleUtils; + IFraudProof public fraudProof; + Governance public governance; + + // Types.Batch[] public batches; + mapping(uint256 => Types.Batch) batches; + uint256 batchPointer = 0; + + // this variable will be greater than 0 if + // there is rollback in progress + // will be reset to 0 once rollback is completed + uint256 public invalidBatchMarker; + + modifier onlyCoordinator() { + POB pobContract = POB(nameRegistry.getContractDetails(ParamManager.POB())); + assert(msg.sender == pobContract.getCoordinator()); + _; + } + + modifier isNotRollingBack() { + assert(invalidBatchMarker == 0); + _; + } + + modifier isRollingBack() { + assert(invalidBatchMarker > 0); + _; + } + + /********************* + * Constructor * + ********************/ + constructor(address _registryAddr, bytes32 genesisStateRoot) public { + nameRegistry = Registry(_registryAddr); + + logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); + depositManager = DepositManager(nameRegistry.getContractDetails(ParamManager.DEPOSIT_MANAGER())); + + governance = Governance(nameRegistry.getContractDetails(ParamManager.Governance())); + merkleUtils = MTUtils(nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS())); + accountRegistry = BLSAccountRegistry(nameRegistry.getContractDetails(ParamManager.ACCOUNT_REGISTRY())); + + tokenRegistry = ITokenRegistry(nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY())); + + fraudProof = IFraudProof(nameRegistry.getContractDetails(ParamManager.FRAUD_PROOF())); + addNewBatch(ZERO_BYTES32, ZERO_BYTES32, genesisStateRoot, [uint256(0), uint256(0)]); + } + + /** + * @notice Returns the latest state root + */ + function getLatestBalanceTreeRoot() public view returns (bytes32) { + return batches[batchPointer - 1].stateRoot; + } + + function addNewBatch( + bytes32 txCommit, + bytes32 txRoot, + bytes32 _updatedRoot, + uint256[2] memory signature + ) internal { + Types.Batch memory newBatch = Types.Batch({ + stateRoot: _updatedRoot, + accountRoot: accountRegistry.root(), + depositTree: ZERO_BYTES32, + committer: msg.sender, + txRoot: txRoot, + txCommit: txCommit, + stakeCommitted: msg.value, + finalisesOn: block.number + governance.TIME_TO_FINALISE(), + timestamp: block.timestamp, + signature: signature + }); + + batches[batchPointer] = newBatch; + logger.logNewBatch(newBatch.committer, txCommit, _updatedRoot, batchPointer); + batchPointer += 1; + } + + function addNewBatchWithDeposit(bytes32 _updatedRoot, bytes32 depositRoot) internal { + // TODO: use different batch type w/o signature? + // TODO: txRoot can be used for deposit root + Types.Batch memory newBatch = Types.Batch({ + stateRoot: _updatedRoot, + accountRoot: accountRegistry.root(), + depositTree: depositRoot, + committer: msg.sender, + txCommit: ZERO_BYTES32, + txRoot: depositRoot, + stakeCommitted: msg.value, + finalisesOn: block.number + governance.TIME_TO_FINALISE(), + timestamp: block.timestamp, + signature: [uint256(0), uint256(0)] + }); + + batches[batchPointer] = newBatch; + logger.logNewBatch(newBatch.committer, ZERO_BYTES32, _updatedRoot, batchPointer); + batchPointer += 1; + } + + /** + * @notice SlashAndRollback slashes all the coordinator's who have built on top of the invalid batch + * and rewards challengers. Also deletes all the batches after invalid batch + */ + function SlashAndRollback() public isRollingBack { + uint256 challengerRewards = 0; + uint256 burnedAmount = 0; + uint256 totalSlashings = 0; + + for (uint256 i = batchPointer - 1; i >= invalidBatchMarker; i--) { + // if gas left is low we would like to do all the transfers + // and persist intermediate states so someone else can send another tx + // and rollback remaining batches + if (gasleft() <= governance.MIN_GAS_LIMIT_LEFT()) { + // exit loop gracefully + break; + } + + // load batch + Types.Batch memory batch = batches[i]; + + // calculate challeger's reward + uint256 _challengerReward = (batch.stakeCommitted.mul(2)).div(3); + challengerRewards += _challengerReward; + burnedAmount += batch.stakeCommitted.sub(_challengerReward); + + batches[i].stakeCommitted = 0; + + // delete batch + delete batches[i]; + + // queue deposits again + depositManager.enqueue(batch.depositTree); + + totalSlashings++; + + logger.logBatchRollback(i, batch.committer, batch.stateRoot, batch.txCommit, batch.stakeCommitted); + if (i == invalidBatchMarker) { + // we have completed rollback + // update the marker + invalidBatchMarker = 0; + break; + } } - modifier isNotRollingBack() { - assert(invalidBatchMarker == 0); - _; + // transfer reward to challenger + (msg.sender).transfer(challengerRewards); + + // burn the remaning amount + (BURN_ADDRESS).transfer(burnedAmount); + + // resize batches length + // batches.length = batches.length.sub(totalSlashings); + batchPointer -= totalSlashings; + + logger.logRollbackFinalisation(totalSlashings); + } + + /** + * @notice Submits a new batch to batches + * @param _txs Compressed transactions . + * @param _updatedRoot New balance tree root after processing all the transactions + */ + function submitBatch( + bytes calldata _txs, + bytes32 _txRoot, + bytes32 _updatedRoot, + uint256[2] calldata signature + ) external payable onlyCoordinator isNotRollingBack { + require(msg.value >= governance.STAKE_AMOUNT(), "Not enough stake committed"); + bytes memory txs = _txs; + uint256 batchSize = txs.size(); + require(batchSize > 0, "Rollup: empty batch"); + require(!txs.hasExcessData(), "Rollup: excess data"); + require(batchSize <= governance.MAX_TXS_PER_BATCH(), "Batch contains more transations than the limit"); + bytes32 txCommit = keccak256(abi.encodePacked(_txs)); + require(BLS.isValidSignature(signature), "Rollup: signature data is invalid"); + addNewBatch(txCommit, _txRoot, _updatedRoot, signature); + } + + /** + * @notice finalise deposits and submit batch + */ + function finaliseDepositsAndSubmitBatch(uint256 _subTreeDepth, Types.AccountMerkleProof calldata _zero_account_mp) + external + payable + onlyCoordinator + isNotRollingBack + { + bytes32 depositSubTreeRoot = depositManager.finaliseDeposits( + _subTreeDepth, + _zero_account_mp, + getLatestBalanceTreeRoot() + ); + // require( + // msg.value >= governance.STAKE_AMOUNT(), + // "Not enough stake committed" + // ); + + bytes32 updatedRoot = merkleUtils.updateLeafWithSiblings( + depositSubTreeRoot, + _zero_account_mp.accountIP.pathToAccount, + _zero_account_mp.siblings + ); + + // add new batch + addNewBatchWithDeposit(updatedRoot, depositSubTreeRoot); + } + + function disputeTxRoot(uint256 _batch_id, bytes calldata _txs) external { + Types.Batch memory batch = batches[_batch_id]; + + require(batch.stakeCommitted != 0, "Batch doesnt exist or is slashed already"); + + // check if batch is disputable + require(block.number < batch.finalisesOn, "Batch already finalised"); + + require( + (_batch_id < invalidBatchMarker || invalidBatchMarker == 0), + "Already successfully disputed. Roll back in process" + ); + + require(batch.txCommit != ZERO_BYTES32, "Cannot dispute blocks with no transaction"); + if (batch.txRoot != merkleUtils.calculateRootTruncated(_txs.toLeafs())) { + invalidBatchMarker = _batch_id; + SlashAndRollback(); + return; } - - modifier isRollingBack() { - assert(invalidBatchMarker > 0); - _; - } -} - -contract RollupHelpers is RollupSetup { - /** - * @notice Returns the latest state root - */ - function getLatestBalanceTreeRoot() public view returns (bytes32) { - return batches[batches.length - 1].stateRoot; - } - - /** - * @notice Returns the total number of batches submitted - */ - function numOfBatchesSubmitted() public view returns (uint256) { - return batches.length; - } - - function addNewBatch(bytes32 txRoot, bytes32 _updatedRoot) internal { - Types.Batch memory newBatch = Types.Batch({ - stateRoot: _updatedRoot, - accountRoot: accountsTree.getTreeRoot(), - depositTree: ZERO_BYTES32, - committer: msg.sender, - txRoot: txRoot, - stakeCommitted: msg.value, - finalisesOn: block.number + governance.TIME_TO_FINALISE(), - timestamp: now - }); - - batches.push(newBatch); - logger.logNewBatch( - newBatch.committer, - txRoot, - _updatedRoot, - batches.length - 1 - ); - } - - function addNewBatchWithDeposit(bytes32 _updatedRoot, bytes32 depositRoot) - internal - { - Types.Batch memory newBatch = Types.Batch({ - stateRoot: _updatedRoot, - accountRoot: accountsTree.getTreeRoot(), - depositTree: depositRoot, - committer: msg.sender, - txRoot: ZERO_BYTES32, - stakeCommitted: msg.value, - finalisesOn: block.number + governance.TIME_TO_FINALISE(), - timestamp: now - }); - - batches.push(newBatch); - logger.logNewBatch( - newBatch.committer, - ZERO_BYTES32, - _updatedRoot, - batches.length - 1 - ); + } + + struct InvalidSignatureProof { + uint256[4][] pubkeys; + bytes32[ACCOUNT_WITNESS_LENGTH][] witnesses; + } + + uint256 constant ACCOUNT_WITNESS_LENGTH = 31; + + function disputeSignature( + uint256 _batch_id, + InvalidSignatureProof calldata proof, + bytes calldata _txs + ) external { + Types.Batch memory batch = batches[_batch_id]; + + require(batch.stakeCommitted != 0, "Batch doesnt exist or is slashed already"); + + // check if batch is disputable + require(block.number < batch.finalisesOn, "Batch already finalised"); + + require( + (_batch_id < invalidBatchMarker || invalidBatchMarker == 0), + "Already successfully disputed. Roll back in process" + ); + + require(batch.txCommit != ZERO_BYTES32, "Cannot dispute blocks with no transaction"); + + bytes memory txs = _txs; + uint256 batchSize = txs.size(); + require(batchSize > 0, "Rollup: empty batch"); + require(!txs.hasExcessData(), "Rollup: excess data"); + uint256[2][] memory messages = new uint256[2][](batchSize); + for (uint256 i = 0; i < batchSize; i++) { + uint256 accountID = txs.senderOf(i); + // What if account not exists? + // Then this batch must be subjected to invalid state transition + require( + accountRegistry.exists(accountID, proof.pubkeys[i], proof.witnesses[i]), + "Rollup: account does not exists" + ); + messages[i] = txs.mapToPoint(i); } - - /** - * @notice Returns the batch - */ - function getBatch(uint256 _batch_id) - public - view - returns (Types.Batch memory batch) - { - require( - batches.length - 1 >= _batch_id, - "Batch id greater than total number of batches, invalid batch id" - ); - batch = batches[_batch_id]; + if (!BLS.verifyMultiple(batch.signature, proof.pubkeys, messages)) { + invalidBatchMarker = _batch_id; + SlashAndRollback(); + return; } - - /** - * @notice SlashAndRollback slashes all the coordinator's who have built on top of the invalid batch - * and rewards challengers. Also deletes all the batches after invalid batch - */ - function SlashAndRollback() public isRollingBack { - uint256 challengerRewards = 0; - uint256 burnedAmount = 0; - uint256 totalSlashings = 0; - - for (uint256 i = batches.length - 1; i >= invalidBatchMarker; i--) { - // if gas left is low we would like to do all the transfers - // and persist intermediate states so someone else can send another tx - // and rollback remaining batches - if (gasleft() <= governance.MIN_GAS_LIMIT_LEFT()) { - // exit loop gracefully - break; - } - - // load batch - Types.Batch memory batch = batches[i]; - - // calculate challeger's reward - uint _challengerReward = (batch.stakeCommitted.mul(2)).div(3); - challengerRewards += _challengerReward; - burnedAmount += batch.stakeCommitted.sub(_challengerReward); - - batches[i].stakeCommitted = 0; - - // delete batch - delete batches[i]; - - // queue deposits again - depositManager.enqueue(batch.depositTree); - - totalSlashings++; - - logger.logBatchRollback( - i, - batch.committer, - batch.stateRoot, - batch.txRoot, - batch.stakeCommitted - ); - if (i == invalidBatchMarker) { - // we have completed rollback - // update the marker - invalidBatchMarker = 0; - break; - } - } - - // transfer reward to challenger - (msg.sender).transfer(challengerRewards); - - // burn the remaning amount - (BURN_ADDRESS).transfer(burnedAmount); - - // resize batches length - batches.length = batches.length.sub(totalSlashings); - - logger.logRollbackFinalisation(totalSlashings); - } -} - -contract Rollup is RollupHelpers { - /********************* - * Constructor * - ********************/ - constructor(address _registryAddr, bytes32 genesisStateRoot) public { - nameRegistry = Registry(_registryAddr); - - logger = Logger(nameRegistry.getContractDetails(ParamManager.LOGGER())); - depositManager = DepositManager( - nameRegistry.getContractDetails(ParamManager.DEPOSIT_MANAGER()) - ); - - governance = Governance( - nameRegistry.getContractDetails(ParamManager.Governance()) - ); - merkleUtils = MTUtils( - nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) - ); - accountsTree = IncrementalTree( - nameRegistry.getContractDetails(ParamManager.ACCOUNTS_TREE()) - ); - - tokenRegistry = ITokenRegistry( - nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY()) - ); - - fraudProof = IFraudProof( - nameRegistry.getContractDetails(ParamManager.FRAUD_PROOF()) - ); - addNewBatch(ZERO_BYTES32, genesisStateRoot); - } - - /** - * @notice Submits a new batch to batches - * @param _txs Compressed transactions . - * @param _updatedRoot New balance tree root after processing all the transactions - */ - function submitBatch(bytes[] calldata _txs, bytes32 _updatedRoot) - external - payable - onlyCoordinator - isNotRollingBack - { - require( - msg.value >= governance.STAKE_AMOUNT(), - "Not enough stake committed" - ); - - require( - _txs.length <= governance.MAX_TXS_PER_BATCH(), - "Batch contains more transations than the limit" - ); - bytes32 txRoot = merkleUtils.getMerkleRoot(_txs); - require( - txRoot != ZERO_BYTES32, - "Cannot submit a transaction with no transactions" - ); - addNewBatch(txRoot, _updatedRoot); - } - - /** - * @notice finalise deposits and submit batch - */ - function finaliseDepositsAndSubmitBatch( - uint256 _subTreeDepth, - Types.AccountMerkleProof calldata _zero_account_mp - ) external payable onlyCoordinator isNotRollingBack { - bytes32 depositSubTreeRoot = depositManager.finaliseDeposits( - _subTreeDepth, - _zero_account_mp, - getLatestBalanceTreeRoot() - ); - // require( - // msg.value >= governance.STAKE_AMOUNT(), - // "Not enough stake committed" - // ); - - bytes32 updatedRoot = merkleUtils.updateLeafWithSiblings( - depositSubTreeRoot, - _zero_account_mp.accountIP.pathToAccount, - _zero_account_mp.siblings - ); - - // add new batch - addNewBatchWithDeposit(updatedRoot, depositSubTreeRoot); - } - - /** - * disputeBatch processes a transactions and returns the updated balance tree - * and the updated leaves. - * @notice Gives the number of batches submitted on-chain - * @return Total number of batches submitted onchain - */ - function disputeBatch( - uint256 _batch_id, - Types.Transaction[] memory _txs, - Types.BatchValidationProofs memory batchProofs - ) public { - { - // load batch - require( - batches[_batch_id].stakeCommitted != 0, - "Batch doesnt exist or is slashed already" - ); - - // check if batch is disputable - require( - block.number < batches[_batch_id].finalisesOn, - "Batch already finalised" - ); - - require( - (_batch_id < invalidBatchMarker || invalidBatchMarker == 0), - "Already successfully disputed. Roll back in process" - ); - - require( - batches[_batch_id].txRoot != ZERO_BYTES32, - "Cannot dispute blocks with no transaction" - ); - } - - bytes32 updatedBalanceRoot; - bool isDisputeValid; - bytes32 txRoot; - (updatedBalanceRoot, txRoot, isDisputeValid) = processBatch( - batches[_batch_id - 1].stateRoot, - batches[_batch_id - 1].accountRoot, - _txs, - batchProofs, - batches[_batch_id].txRoot - ); - - // dispute is valid, we need to slash and rollback :( - if (isDisputeValid) { - // before rolling back mark the batch invalid - // so we can pause and unpause - invalidBatchMarker = _batch_id; - SlashAndRollback(); - return; - } - - // if new root doesnt match what was submitted by coordinator - // slash and rollback - if (updatedBalanceRoot != batches[_batch_id].stateRoot) { - invalidBatchMarker = _batch_id; - SlashAndRollback(); - return; - } - } - - function ApplyTx( - Types.AccountMerkleProof memory _merkle_proof, - Types.Transaction memory transaction - ) public view returns (bytes memory, bytes32 newRoot) { - return fraudProof.ApplyTx(_merkle_proof, transaction); - } - - /** - * @notice processTx processes a transactions and returns the updated balance tree - * and the updated leaves - * conditions in require mean that the dispute be declared invalid - * if conditons evaluate if the coordinator was at fault - * @return Total number of batches submitted onchain - */ - function processTx( - bytes32 _balanceRoot, - bytes32 _accountsRoot, - Types.Transaction memory _tx, - Types.PDAMerkleProof memory _from_pda_proof, - Types.AccountProofs memory accountProofs - ) - public - view - returns ( - bytes32, - bytes memory, - bytes memory, - Types.ErrorCode, - bool - ) - { - return - fraudProof.processTx( - _balanceRoot, - _accountsRoot, - _tx, - _from_pda_proof, - accountProofs - ); - } - - /** - * @notice processBatch processes a batch and returns the updated balance tree - * and the updated leaves - * conditions in require mean that the dispute be declared invalid - * if conditons evaluate if the coordinator was at fault - * @return Total number of batches submitted onchain - */ - function processBatch( - bytes32 initialStateRoot, - bytes32 accountsRoot, - Types.Transaction[] memory _txs, - Types.BatchValidationProofs memory batchProofs, - bytes32 expectedTxRoot - ) - public - view - returns ( - bytes32, - bytes32, - bool - ) - { - return - fraudProof.processBatch( - initialStateRoot, - accountsRoot, - _txs, - batchProofs, - expectedTxRoot - ); + } + + /** + * disputeBatch processes a transactions and returns the updated balance tree + * and the updated leaves. + * @notice Gives the number of batches submitted on-chain + * @return Total number of batches submitted onchain + */ + function disputeBatch( + uint256 _batch_id, + bytes memory _txs, + Types.InvalidTransitionProof memory proof + ) public { + // load batch + require(batches[_batch_id].stakeCommitted != 0, "Batch doesnt exist or is slashed already"); + + // check if batch is disputable + require(block.number < batches[_batch_id].finalisesOn, "Batch already finalised"); + + require( + (_batch_id < invalidBatchMarker || invalidBatchMarker == 0), + "Already successfully disputed. Roll back in process" + ); + + require(batches[_batch_id].txCommit != ZERO_BYTES32, "Cannot dispute blocks with no transaction"); + + bytes32 updatedBalanceRoot; + bool isDisputeValid; + (updatedBalanceRoot, isDisputeValid) = fraudProof.processBatch(batches[_batch_id - 1].stateRoot, _txs, proof); + + // dispute is valid, we need to slash and rollback :( + if (isDisputeValid) { + // before rolling back mark the batch invalid + // so we can pause and unpause + invalidBatchMarker = _batch_id; + SlashAndRollback(); + return; } - /** - * @notice Withdraw delay allows coordinators to withdraw their stake after the batch has been finalised - * @param batch_id Batch ID that the coordinator submitted - */ - function WithdrawStake(uint256 batch_id) public { - Types.Batch memory committedBatch = batches[batch_id]; - require( - committedBatch.stakeCommitted != 0, - "Stake has been already withdrawn!!" - ); - require( - msg.sender == committedBatch.committer, - "You are not the correct committer for this batch" - ); - require( - block.number > committedBatch.finalisesOn, - "This batch is not yet finalised, check back soon!" - ); - - msg.sender.transfer(committedBatch.stakeCommitted); - logger.logStakeWithdraw( - msg.sender, - committedBatch.stakeCommitted, - batch_id - ); - committedBatch.stakeCommitted = 0; + // if new root doesnt match what was submitted by coordinator + // slash and rollback + if (updatedBalanceRoot != batches[_batch_id].stateRoot) { + invalidBatchMarker = _batch_id; + SlashAndRollback(); + return; } + } + + /** + * @notice Withdraw delay allows coordinators to withdraw their stake after the batch has been finalised + * @param batch_id Batch ID that the coordinator submitted + */ + function WithdrawStake(uint256 batch_id) public { + Types.Batch memory committedBatch = batches[batch_id]; + require(committedBatch.stakeCommitted != 0, "Stake has been already withdrawn!!"); + require(msg.sender == committedBatch.committer, "You are not the correct committer for this batch"); + require(block.number > committedBatch.finalisesOn, "This batch is not yet finalised, check back soon!"); + msg.sender.transfer(committedBatch.stakeCommitted); + logger.logStakeWithdraw(msg.sender, committedBatch.stakeCommitted, batch_id); + committedBatch.stakeCommitted = 0; + } } diff --git a/contracts/test/TestAccountTree.sol b/contracts/test/TestAccountTree.sol new file mode 100644 index 0000000..7f5297a --- /dev/null +++ b/contracts/test/TestAccountTree.sol @@ -0,0 +1,27 @@ +pragma solidity ^0.5.15; + +import {AccountTree} from "../AccountTree.sol"; + +contract TestAccountTree is AccountTree { + function updateSingle(bytes32 leaf) external returns (uint256) { + uint256 operationGasCost = gasleft(); + _updateSingle(leaf); + return operationGasCost - gasleft(); + } + + function updateBatch(bytes32[BATCH_SIZE] calldata leafs) external returns (uint256) { + uint256 operationGasCost = gasleft(); + _updateBatch(leafs); + return operationGasCost - gasleft(); + } + + function checkInclusion( + bytes32 leaf, + uint256 leafIndex, + bytes32[WITNESS_LENGTH] calldata witness + ) external returns (uint256, bool) { + uint256 operationGasCost = gasleft(); + bool s = _checkInclusion(leaf, leafIndex, witness); + return (operationGasCost - gasleft(), s); + } +} diff --git a/contracts/test/TestBLS.sol b/contracts/test/TestBLS.sol new file mode 100644 index 0000000..68b2509 --- /dev/null +++ b/contracts/test/TestBLS.sol @@ -0,0 +1,123 @@ +pragma solidity ^0.5.15; + +import {BLS} from "../libs/BLS.sol"; + +contract TestBLS { + uint256 constant FIELD_MASK = 0x7fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; + + function verifyMultiple( + uint256[2] calldata signature, + uint256[4][] calldata pubkeys, + uint256[2][] calldata messages + ) external view returns (bool) { + return BLS.verifyMultiple(signature, pubkeys, messages); + } + + function verifyMultipleGasCost( + uint256[2] calldata signature, + uint256[4][] calldata pubkeys, + uint256[2][] calldata messages + ) external returns (uint256) { + uint256 g = gasleft(); + require(BLS.verifyMultiple(signature, pubkeys, messages), "BLSTest: expect succesful verification"); + return g - gasleft(); + } + + function hashToPoint(bytes calldata data) external view returns (uint256[2] memory p) { + return BLS.hashToPoint(data); + } + + function hashToPointGasCost(bytes calldata data) external returns (uint256 p) { + uint256 g = gasleft(); + BLS.hashToPoint(data); + return g - gasleft(); + } + + function isOnCurveG1Compressed(uint256 point) external view returns (bool) { + // return BLS.isOnCurveG1(point & BLS.FIELD_MASK); v0.5 :/ + return BLS.isOnCurveG1(point & FIELD_MASK); + } + + function isOnCurveG1(uint256[2] calldata point) external pure returns (bool) { + return BLS.isOnCurveG1(point); + } + + function isOnCurveG1CompressedGasCost(uint256[2] calldata point) external returns (uint256) { + uint256 g = gasleft(); + BLS.isOnCurveG1(point); + return g - gasleft(); + } + + function isOnCurveG1GasCost(uint256[2] calldata point) external returns (uint256) { + uint256 g = gasleft(); + BLS.isOnCurveG1(point); + return g - gasleft(); + } + + function isOnCurveG2Compressed(uint256[2] calldata point) external view returns (bool) { + // uint256 x0 = point[0] & BLS.FIELD_MASK; + uint256 x0 = point[0] & FIELD_MASK; + uint256 x1 = point[1]; + return BLS.isOnCurveG2([x0, x1]); + } + + function isOnCurveG2(uint256[4] calldata point) external pure returns (bool) { + return BLS.isOnCurveG2(point); + } + + function isOnCurveG2CompressedGasCost(uint256[4] calldata point) external returns (uint256) { + uint256 g = gasleft(); + BLS.isOnCurveG2(point); + return g - gasleft(); + } + + function isOnCurveG2GasCost(uint256[2] calldata point) external returns (uint256) { + uint256 g = gasleft(); + BLS.isOnCurveG2(point); + return g - gasleft(); + } + + function isNonResidueFP(uint256 e) external view returns (bool) { + return BLS.isNonResidueFP(e); + } + + function isNonResidueFPGasCost(uint256 e) external returns (uint256) { + uint256 g = gasleft(); + BLS.isNonResidueFP(e); + return g - gasleft(); + } + + function isNonResidueFP2(uint256[2] calldata e) external view returns (bool) { + return BLS.isNonResidueFP2(e); + } + + function isNonResidueFP2GasCost(uint256[2] calldata e) external returns (uint256) { + uint256 g = gasleft(); + BLS.isNonResidueFP2(e); + return g - gasleft(); + } + + function pubkeyToUncompresed(uint256[2] calldata compressed, uint256[2] calldata y) + external + pure + returns (uint256[4] memory uncompressed) + { + return BLS.pubkeyToUncompresed(compressed, y); + } + + function signatureToUncompresed(uint256 compressed, uint256 y) + external + pure + returns (uint256[2] memory uncompressed) + { + return BLS.signatureToUncompresed(compressed, y); + } + + function isValidCompressedPublicKey(uint256[2] calldata compressed) external view returns (bool) { + return BLS.isValidCompressedPublicKey(compressed); + } + + function isValidCompressedSignature(uint256 compressed) external view returns (bool) { + return BLS.isValidCompressedSignature(compressed); + } +} diff --git a/contracts/test/TestStateAccount.sol b/contracts/test/TestStateAccount.sol new file mode 100644 index 0000000..4e94f32 --- /dev/null +++ b/contracts/test/TestStateAccount.sol @@ -0,0 +1,39 @@ +pragma solidity ^0.5.15; + +import {StateAccount} from "../libs/StateAccount.sol"; + +contract TestStateAccount { + using StateAccount for uint256; + + function accountID(uint256 account) external pure returns (uint256) { + return account.accountID(); + } + + function tokenType(uint256 account) external pure returns (uint256) { + return account.tokenType(); + } + + function balance(uint256 account) external pure returns (uint256) { + return account.balance(); + } + + function nonce(uint256 account) external pure returns (uint256) { + return account.nonce(); + } + + function incrementNonce(uint256 account) external pure returns (uint256, bool) { + return account.incrementNonce(); + } + + function balanceSafeAdd(uint256 account, uint256 amount) external pure returns (uint256, bool) { + return account.balanceSafeAdd(amount); + } + + function balanceSafeSub(uint256 account, uint256 amount) external pure returns (uint256, bool) { + return account.balanceSafeSub(amount); + } + + function hash(uint256 account) external pure returns (bytes32 res) { + return account.hash(); + } +} diff --git a/contracts/test/TestTx.sol b/contracts/test/TestTx.sol new file mode 100644 index 0000000..455900a --- /dev/null +++ b/contracts/test/TestTx.sol @@ -0,0 +1,33 @@ +pragma solidity ^0.5.15; + +import {Tx} from "../libs/Tx.sol"; + +contract TestTx { + function hasExcessData(bytes calldata txs) external pure returns (bool) { + return Tx.hasExcessData(txs); + } + + function size(bytes calldata txs) external pure returns (uint256) { + return Tx.size(txs); + } + + function amountOf(bytes calldata txs, uint256 index) external pure returns (uint256 amount) { + return Tx.amountOf(txs, index); + } + + function senderOf(bytes calldata txs, uint256 index) external pure returns (uint256 sender) { + return Tx.senderOf(txs, index); + } + + function receiverOf(bytes calldata txs, uint256 index) external pure returns (uint256 receiver) { + return Tx.receiverOf(txs, index); + } + + function hashOf(bytes calldata txs, uint256 index) external pure returns (bytes32 result) { + return Tx.hashOf(txs, index); + } + + function mapToPoint(bytes calldata txs, uint256 index) external view returns (uint256[2] memory) { + return Tx.mapToPoint(txs, index); + } +} diff --git a/contracts/withdrawManager.sol b/contracts/withdrawManager.sol index 319fcbd..aceaa08 100644 --- a/contracts/withdrawManager.sol +++ b/contracts/withdrawManager.sol @@ -1,7 +1,6 @@ pragma solidity ^0.5.15; pragma experimental ABIEncoderV2; -import {ECVerify} from "./libs/ECVerify.sol"; import {Types} from "./libs/Types.sol"; import {RollupUtils} from "./libs/RollupUtils.sol"; import {ParamManager} from "./libs/ParamManager.sol"; @@ -16,108 +15,83 @@ import {Governance} from "./Governance.sol"; import {Rollup} from "./rollup.sol"; contract WithdrawManager { - using ECVerify for bytes32; - - MTUtils public merkleUtils; - ITokenRegistry public tokenRegistry; - Governance public governance; - Registry public nameRegistry; - Rollup public rollup; - - // Stores transaction paths claimed per batch - bool[][] withdrawTxClaimed; - - /********************* - * Constructor * - ********************/ - constructor(address _registryAddr) public { - nameRegistry = Registry(_registryAddr); - - governance = Governance( - nameRegistry.getContractDetails(ParamManager.Governance()) - ); - merkleUtils = MTUtils( - nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS()) - ); - - rollup = Rollup( - nameRegistry.getContractDetails(ParamManager.ROLLUP_CORE()) - ); - - tokenRegistry = ITokenRegistry( - nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY()) - ); - withdrawTxClaimed = new bool[][](governance.MAX_TXS_PER_BATCH()); - } - - /** - * @notice Allows user to withdraw the balance in the leaf of the balances tree. - * User has to do the following: Prove that a transfer of X tokens was made to the burn address or leaf 0 - * The batch we are allowing withdraws from should have been already finalised, so we can assume all data in the batch to be correct - * @param _batch_id Deposit tree depth or depth of subtree that is being deposited - * @param withdraw_tx_proof contains the siblints, txPath and the txData for the withdraw transaction - */ - function Withdraw( - uint256 _batch_id, - Types.PDAMerkleProof memory _pda_proof, - Types.TransactionMerkleProof memory withdraw_tx_proof - ) public { - Types.Batch memory batch = rollup.getBatch(_batch_id); - - // check if the batch is finalised - require(block.number > batch.finalisesOn, "Batch not finalised yt"); - // verify transaction exists in the batch - merkleUtils.verify( - batch.txRoot, - RollupUtils.BytesFromTx(withdraw_tx_proof._tx.data), - withdraw_tx_proof._tx.pathToTx, - withdraw_tx_proof.siblings - ); - - // check if the transaction is withdraw transaction - // ensure the `to` leaf was the 0th leaf - require( - withdraw_tx_proof._tx.data.toIndex == 0, - "Not a withdraw transaction" - ); - - bool isClaimed = withdrawTxClaimed[_batch_id][withdraw_tx_proof - ._tx - .pathToTx]; - require(!isClaimed, "Withdraw transaction already claimed"); - withdrawTxClaimed[_batch_id][withdraw_tx_proof._tx.pathToTx] = true; - - // withdraw checks out, transfer to the account in account tree - address tokenContractAddress = tokenRegistry.registeredTokens( - withdraw_tx_proof._tx.data.tokenType - ); - - // convert pubkey path to ID - uint256 computedID = merkleUtils.pathToIndex( - _pda_proof._pda.pathToPubkey, - governance.MAX_DEPTH() - ); - - require( - computedID == withdraw_tx_proof._tx.data.fromIndex, - "Pubkey not related to the from account in the transaction" - ); - - address receiver = RollupUtils.calculateAddress( - _pda_proof._pda.pubkey_leaf.pubkey - ); - - require( - receiver == - RollupUtils.HashFromTx(withdraw_tx_proof._tx.data).ecrecovery( - withdraw_tx_proof._tx.data.signature - ), - "Signature is incorrect" - ); - - uint256 amount = withdraw_tx_proof._tx.data.amount; - - IERC20 tokenContract = IERC20(tokenContractAddress); - require(tokenContract.transfer(receiver, amount), "Unable to trasnfer"); - } + MTUtils public merkleUtils; + ITokenRegistry public tokenRegistry; + Governance public governance; + Registry public nameRegistry; + Rollup public rollup; + + // Stores transaction paths claimed per batch + bool[][] withdrawTxClaimed; + + /********************* + * Constructor * + ********************/ + constructor(address _registryAddr) public { + nameRegistry = Registry(_registryAddr); + + governance = Governance(nameRegistry.getContractDetails(ParamManager.Governance())); + merkleUtils = MTUtils(nameRegistry.getContractDetails(ParamManager.MERKLE_UTILS())); + + rollup = Rollup(nameRegistry.getContractDetails(ParamManager.ROLLUP_CORE())); + + tokenRegistry = ITokenRegistry(nameRegistry.getContractDetails(ParamManager.TOKEN_REGISTRY())); + withdrawTxClaimed = new bool[][](governance.MAX_TXS_PER_BATCH()); + } + + /** + * @notice Allows user to withdraw the balance in the leaf of the balances tree. + * User has to do the following: Prove that a transfer of X tokens was made to the burn address or leaf 0 + * The batch we are allowing withdraws from should have been already finalised, so we can assume all data in the batch to be correct + * @param _batch_id Deposit tree depth or depth of subtree that is being deposited + * @param withdraw_tx_proof contains the siblints, txPath and the txData for the withdraw transaction + */ + function Withdraw( + uint256 _batch_id, + Types.PDAMerkleProof memory _pda_proof, + Types.TransactionMerkleProof memory withdraw_tx_proof + ) public { + // Types.Batch memory batch = rollup.getBatch(_batch_id); + + // // check if the batch is finalised + // require(block.number > batch.finalisesOn, "Batch not finalised yt"); + // // verify transaction exists in the batch + // merkleUtils.verify( + // batch.txCommit, + // RollupUtils.BytesFromTx(withdraw_tx_proof._tx.data), + // withdraw_tx_proof._tx.pathToTx, + // withdraw_tx_proof.siblings + // ); + + // // check if the transaction is withdraw transaction + // // ensure the `to` leaf was the 0th leaf + // require(withdraw_tx_proof._tx.data.toIndex == 0, "Not a withdraw transaction"); + + // bool isClaimed = withdrawTxClaimed[_batch_id][withdraw_tx_proof._tx.pathToTx]; + // require(!isClaimed, "Withdraw transaction already claimed"); + // withdrawTxClaimed[_batch_id][withdraw_tx_proof._tx.pathToTx] = true; + + // // withdraw checks out, transfer to the account in account tree + // address tokenContractAddress = tokenRegistry.registeredTokens(withdraw_tx_proof._tx.data.tokenType); + + // // convert pubkey path to ID + // uint256 computedID = merkleUtils.pathToIndex(_pda_proof._pda.pathToPubkey, governance.MAX_DEPTH()); + + // require( + // computedID == withdraw_tx_proof._tx.data.fromIndex, + // "Pubkey not related to the from account in the transaction" + // ); + + // address receiver = RollupUtils.calculateAddress(_pda_proof._pda.pubkey_leaf.pubkey); + + // require( + // receiver == RollupUtils.HashFromTx(withdraw_tx_proof._tx.data).ecrecovery(withdraw_tx_proof._tx.data.signature), + // "Signature is incorrect" + // ); + + // uint256 amount = withdraw_tx_proof._tx.data.amount; + + // IERC20 tokenContract = IERC20(tokenContractAddress); + // require(tokenContract.transfer(receiver, amount), "Unable to trasnfer"); + } } diff --git a/package-lock.json b/package-lock.json index ae57cd4..fac33c4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -454,14 +454,6 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.9.tgz", "integrity": "sha512-E6QoYqCKZfgatHTdHzs1RRKP7ip4vvm+EyRUeE2RF0NblwVvb0p6jSVeNTOFxPn26QXN2o6SMfNxKp6kU8zQaw==" }, - "debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "requires": { - "ms": "2.0.0" - } - }, "elliptic": { "version": "6.3.3", "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.3.3.tgz", @@ -682,6 +674,14 @@ "web3-utils": "1.2.2" }, "dependencies": { + "@web3-js/scrypt-shim": { + "version": "github:web3-js/scrypt-shim#aafdadda13e660e25e1c525d1f5b2443f5eb1ebb", + "from": "github:web3-js/scrypt-shim", + "requires": { + "scryptsy": "^2.1.0", + "semver": "^6.3.0" + } + }, "uuid": { "version": "3.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.3.2.tgz", @@ -786,6 +786,29 @@ "underscore": "1.9.1", "web3-core-helpers": "1.2.2", "websocket": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400" + }, + "dependencies": { + "websocket": { + "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", + "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", + "requires": { + "debug": "^2.2.0", + "es5-ext": "^0.10.50", + "nan": "^2.14.0", + "typedarray-to-buffer": "^3.1.5", + "yaeti": "^0.0.6" + }, + "dependencies": { + "debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "requires": { + "ms": "2.0.0" + } + } + } + } } }, "web3-shh": { @@ -825,7 +848,6 @@ "version": "github:web3-js/WebSocket-Node#ef5ea2f41daf4a2113b80c9223df884b4d56c400", "from": "github:web3-js/WebSocket-Node#polyfill/globalThis", "requires": { - "debug": "^2.2.0", "es5-ext": "^0.10.50", "nan": "^2.14.0", "typedarray-to-buffer": "^3.1.5", @@ -5572,6 +5594,15 @@ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==" }, + "mcl-wasm": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/mcl-wasm/-/mcl-wasm-0.4.3.tgz", + "integrity": "sha512-g97fbJ+c/VvpKHeVegeI+uxhEh1iAjY+QQNrq/VQbY0uWxWiRLxMx/JRM6ZDPcwYO2ApDzF/gfNDVdd4LpvqhQ==", + "dev": true, + "requires": { + "nyc": "^11.8.0" + } + }, "md5.js": { "version": "1.3.5", "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", @@ -6050,241 +6081,2871 @@ } } }, - "oauth-sign": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", - "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" - }, - "object-assign": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", - "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" - }, - "object-inspect": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", - "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" - }, - "object-is": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", - "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.5" - } - }, - "object-keys": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", - "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" - }, - "object.assign": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", - "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", - "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" - } - }, - "object.getownpropertydescriptors": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", - "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", - "requires": { - "define-properties": "^1.1.3", - "es-abstract": "^1.17.0-next.1" - } - }, - "object.pick": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", - "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "oboe": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.4.tgz", - "integrity": "sha1-IMiM2wwVNxuwQRklfU/dNLCqSfY=", - "requires": { - "http-https": "^1.0.0" - } - }, - "on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", - "requires": { - "ee-first": "1.1.1" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "requires": { - "wrappy": "1" - } - }, - "onetime": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", - "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", - "dev": true - }, - "optionator": { - "version": "0.8.3", - "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", - "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", - "requires": { - "deep-is": "~0.1.3", - "fast-levenshtein": "~2.0.6", - "levn": "~0.3.0", - "prelude-ls": "~1.1.2", - "type-check": "~0.3.2", - "word-wrap": "~1.2.3" - } - }, - "original-require": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/original-require/-/original-require-1.0.1.tgz", - "integrity": "sha1-DxMEcVhM0zURxew4yNWSE/msXiA=" - }, - "os-homedir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", - "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" - }, - "os-locale": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", - "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", - "requires": { - "lcid": "^1.0.0" - } - }, - "os-tmpdir": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", - "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" - }, - "osenv": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", - "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", - "requires": { - "os-homedir": "^1.0.0", - "os-tmpdir": "^1.0.0" - } - }, - "p-cancelable": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", - "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" - }, - "p-finally": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", - "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" - }, - "p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "requires": { - "p-try": "^2.0.0" - } - }, - "p-locate": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", - "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", - "requires": { - "p-limit": "^2.0.0" - } - }, - "p-timeout": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", - "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", - "requires": { - "p-finally": "^1.0.0" - } - }, - "p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" - }, - "package-json": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", - "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "nyc": { + "version": "11.9.0", + "resolved": "https://registry.npmjs.org/nyc/-/nyc-11.9.0.tgz", + "integrity": "sha512-w8OdJAhXL5izerzZMdqzYKMj/pgHJyY3qEPYBjLLxrhcVoHEY9pU5ENIiZyCgG9OR7x3VcUMoD40o6PtVpfR4g==", "dev": true, "requires": { - "got": "^6.7.1", - "registry-auth-token": "^3.0.1", - "registry-url": "^3.0.3", - "semver": "^5.1.0" + "archy": "^1.0.0", + "arrify": "^1.0.1", + "caching-transform": "^1.0.0", + "convert-source-map": "^1.5.1", + "debug-log": "^1.0.1", + "default-require-extensions": "^1.0.0", + "find-cache-dir": "^0.1.1", + "find-up": "^2.1.0", + "foreground-child": "^1.5.3", + "glob": "^7.0.6", + "istanbul-lib-coverage": "^1.1.2", + "istanbul-lib-hook": "^1.1.0", + "istanbul-lib-instrument": "^1.10.0", + "istanbul-lib-report": "^1.1.3", + "istanbul-lib-source-maps": "^1.2.3", + "istanbul-reports": "^1.4.0", + "md5-hex": "^1.2.0", + "merge-source-map": "^1.1.0", + "micromatch": "^3.1.10", + "mkdirp": "^0.5.0", + "resolve-from": "^2.0.0", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.1", + "spawn-wrap": "^1.4.2", + "test-exclude": "^4.2.0", + "yargs": "11.1.0", + "yargs-parser": "^8.0.0" }, "dependencies": { - "get-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", - "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", - "dev": true - }, - "got": { - "version": "6.7.1", - "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", - "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "align-text": { + "version": "0.1.4", + "bundled": true, "dev": true, "requires": { - "create-error-class": "^3.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^3.0.0", - "is-redirect": "^1.0.0", - "is-retry-allowed": "^1.0.0", - "is-stream": "^1.0.0", - "lowercase-keys": "^1.0.0", - "safe-buffer": "^5.0.1", - "timed-out": "^4.0.0", - "unzip-response": "^2.0.1", - "url-parse-lax": "^1.0.0" + "kind-of": "^3.0.2", + "longest": "^1.0.1", + "repeat-string": "^1.5.2" } }, - "prepend-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", - "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "amdefine": { + "version": "1.0.1", + "bundled": true, "dev": true }, - "url-parse-lax": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", - "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "ansi-regex": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "bundled": true, + "dev": true + }, + "append-transform": { + "version": "0.4.0", + "bundled": true, "dev": true, "requires": { - "prepend-http": "^1.0.1" + "default-require-extensions": "^1.0.0" } - } - } - }, - "parse-asn1": { - "version": "5.1.5", - "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", - "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", - "requires": { - "asn1.js": "^4.0.0", - "browserify-aes": "^1.0.0", - "create-hash": "^1.1.0", - "evp_bytestokey": "^1.0.0", + }, + "archy": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "arr-flatten": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "arr-union": { + "version": "3.1.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "arrify": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "assign-symbols": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "async": { + "version": "1.5.2", + "bundled": true, + "dev": true + }, + "atob": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "babel-code-frame": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "chalk": "^1.1.3", + "esutils": "^2.0.2", + "js-tokens": "^3.0.2" + } + }, + "babel-generator": { + "version": "6.26.1", + "bundled": true, + "dev": true, + "requires": { + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "detect-indent": "^4.0.0", + "jsesc": "^1.3.0", + "lodash": "^4.17.4", + "source-map": "^0.5.7", + "trim-right": "^1.0.1" + } + }, + "babel-messages": { + "version": "6.23.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.22.0" + } + }, + "babel-runtime": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "core-js": "^2.4.0", + "regenerator-runtime": "^0.11.0" + } + }, + "babel-template": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "babel-traverse": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "lodash": "^4.17.4" + } + }, + "babel-traverse": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-code-frame": "^6.26.0", + "babel-messages": "^6.23.0", + "babel-runtime": "^6.26.0", + "babel-types": "^6.26.0", + "babylon": "^6.18.0", + "debug": "^2.6.8", + "globals": "^9.18.0", + "invariant": "^2.2.2", + "lodash": "^4.17.4" + } + }, + "babel-types": { + "version": "6.26.0", + "bundled": true, + "dev": true, + "requires": { + "babel-runtime": "^6.26.0", + "esutils": "^2.0.2", + "lodash": "^4.17.4", + "to-fast-properties": "^1.0.3" + } + }, + "babylon": { + "version": "6.18.0", + "bundled": true, + "dev": true + }, + "balanced-match": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "base": { + "version": "0.11.2", + "bundled": true, + "dev": true, + "requires": { + "cache-base": "^1.0.1", + "class-utils": "^0.3.5", + "component-emitter": "^1.2.1", + "define-property": "^1.0.0", + "isobject": "^3.0.1", + "mixin-deep": "^1.2.0", + "pascalcase": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "brace-expansion": { + "version": "1.1.11", + "bundled": true, + "dev": true, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "builtin-modules": { + "version": "1.1.1", + "bundled": true, + "dev": true + }, + "cache-base": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "collection-visit": "^1.0.0", + "component-emitter": "^1.2.1", + "get-value": "^2.0.6", + "has-value": "^1.0.0", + "isobject": "^3.0.1", + "set-value": "^2.0.0", + "to-object-path": "^0.3.0", + "union-value": "^1.0.0", + "unset-value": "^1.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "caching-transform": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "md5-hex": "^1.2.0", + "mkdirp": "^0.5.1", + "write-file-atomic": "^1.1.4" + } + }, + "camelcase": { + "version": "1.2.1", + "bundled": true, + "dev": true, + "optional": true + }, + "center-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.3", + "lazy-cache": "^1.0.3" + } + }, + "chalk": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "class-utils": { + "version": "0.3.6", + "bundled": true, + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "define-property": "^0.2.5", + "isobject": "^3.0.0", + "static-extend": "^0.1.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "cliui": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "center-align": "^0.1.1", + "right-align": "^0.1.1", + "wordwrap": "0.0.2" + }, + "dependencies": { + "wordwrap": { + "version": "0.0.2", + "bundled": true, + "dev": true, + "optional": true + } + } + }, + "code-point-at": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "collection-visit": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "map-visit": "^1.0.0", + "object-visit": "^1.0.0" + } + }, + "commondir": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "component-emitter": { + "version": "1.2.1", + "bundled": true, + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "bundled": true, + "dev": true + }, + "convert-source-map": { + "version": "1.5.1", + "bundled": true, + "dev": true + }, + "copy-descriptor": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "core-js": { + "version": "2.5.6", + "bundled": true, + "dev": true + }, + "cross-spawn": { + "version": "4.0.2", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "which": "^1.2.9" + } + }, + "debug": { + "version": "2.6.9", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + }, + "debug-log": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "decamelize": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "decode-uri-component": { + "version": "0.2.0", + "bundled": true, + "dev": true + }, + "default-require-extensions": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "strip-bom": "^2.0.0" + } + }, + "define-property": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.2", + "isobject": "^3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "detect-indent": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "repeating": "^2.0.0" + } + }, + "error-ex": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "is-arrayish": "^0.2.1" + } + }, + "escape-string-regexp": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "esutils": { + "version": "2.0.2", + "bundled": true, + "dev": true + }, + "execa": { + "version": "0.7.0", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^5.0.1", + "get-stream": "^3.0.0", + "is-stream": "^1.1.0", + "npm-run-path": "^2.0.0", + "p-finally": "^1.0.0", + "signal-exit": "^3.0.0", + "strip-eof": "^1.0.0" + }, + "dependencies": { + "cross-spawn": { + "version": "5.1.0", + "bundled": true, + "dev": true, + "requires": { + "lru-cache": "^4.0.1", + "shebang-command": "^1.2.0", + "which": "^1.2.9" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "extend-shallow": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "assign-symbols": "^1.0.0", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "extglob": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "fill-range": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "find-cache-dir": { + "version": "0.1.1", + "bundled": true, + "dev": true, + "requires": { + "commondir": "^1.0.1", + "mkdirp": "^0.5.1", + "pkg-dir": "^1.0.0" + } + }, + "find-up": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "locate-path": "^2.0.0" + } + }, + "for-in": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "foreground-child": { + "version": "1.5.6", + "bundled": true, + "dev": true, + "requires": { + "cross-spawn": "^4", + "signal-exit": "^3.0.0" + } + }, + "fragment-cache": { + "version": "0.2.1", + "bundled": true, + "dev": true, + "requires": { + "map-cache": "^0.2.2" + } + }, + "fs.realpath": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "get-caller-file": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "get-stream": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "get-value": { + "version": "2.0.6", + "bundled": true, + "dev": true + }, + "glob": { + "version": "7.1.2", + "bundled": true, + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "globals": { + "version": "9.18.0", + "bundled": true, + "dev": true + }, + "graceful-fs": { + "version": "4.1.11", + "bundled": true, + "dev": true + }, + "handlebars": { + "version": "4.0.11", + "bundled": true, + "dev": true, + "requires": { + "async": "^1.4.0", + "optimist": "^0.6.1", + "source-map": "^0.4.4", + "uglify-js": "^2.6" + }, + "dependencies": { + "source-map": { + "version": "0.4.4", + "bundled": true, + "dev": true, + "requires": { + "amdefine": ">=0.0.4" + } + } + } + }, + "has-ansi": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "has-flag": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "has-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "get-value": "^2.0.6", + "has-values": "^1.0.0", + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "has-values": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-number": "^3.0.0", + "kind-of": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "kind-of": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "hosted-git-info": { + "version": "2.6.0", + "bundled": true, + "dev": true + }, + "imurmurhash": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "inflight": { + "version": "1.0.6", + "bundled": true, + "dev": true, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.3", + "bundled": true, + "dev": true + }, + "invariant": { + "version": "2.2.4", + "bundled": true, + "dev": true, + "requires": { + "loose-envify": "^1.0.0" + } + }, + "invert-kv": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-arrayish": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-buffer": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "is-builtin-module": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "builtin-modules": "^1.0.0" + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "is-extendable": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "is-finite": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "is-odd": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-number": "^4.0.0" + }, + "dependencies": { + "is-number": { + "version": "4.0.0", + "bundled": true, + "dev": true + } + } + }, + "is-plain-object": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "is-stream": { + "version": "1.1.0", + "bundled": true, + "dev": true + }, + "is-utf8": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "is-windows": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "isarray": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "isexe": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "istanbul-lib-coverage": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "istanbul-lib-hook": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "append-transform": "^0.4.0" + } + }, + "istanbul-lib-instrument": { + "version": "1.10.1", + "bundled": true, + "dev": true, + "requires": { + "babel-generator": "^6.18.0", + "babel-template": "^6.16.0", + "babel-traverse": "^6.18.0", + "babel-types": "^6.18.0", + "babylon": "^6.18.0", + "istanbul-lib-coverage": "^1.2.0", + "semver": "^5.3.0" + } + }, + "istanbul-lib-report": { + "version": "1.1.3", + "bundled": true, + "dev": true, + "requires": { + "istanbul-lib-coverage": "^1.1.2", + "mkdirp": "^0.5.1", + "path-parse": "^1.0.5", + "supports-color": "^3.1.2" + }, + "dependencies": { + "supports-color": { + "version": "3.2.3", + "bundled": true, + "dev": true, + "requires": { + "has-flag": "^1.0.0" + } + } + } + }, + "istanbul-lib-source-maps": { + "version": "1.2.3", + "bundled": true, + "dev": true, + "requires": { + "debug": "^3.1.0", + "istanbul-lib-coverage": "^1.1.2", + "mkdirp": "^0.5.1", + "rimraf": "^2.6.1", + "source-map": "^0.5.3" + }, + "dependencies": { + "debug": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "ms": "2.0.0" + } + } + } + }, + "istanbul-reports": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "handlebars": "^4.0.3" + } + }, + "js-tokens": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "jsesc": { + "version": "1.3.0", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + }, + "lazy-cache": { + "version": "1.0.4", + "bundled": true, + "dev": true, + "optional": true + }, + "lcid": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "invert-kv": "^1.0.0" + } + }, + "load-json-file": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "parse-json": "^2.2.0", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0", + "strip-bom": "^2.0.0" + } + }, + "locate-path": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-locate": "^2.0.0", + "path-exists": "^3.0.0" + }, + "dependencies": { + "path-exists": { + "version": "3.0.0", + "bundled": true, + "dev": true + } + } + }, + "lodash": { + "version": "4.17.10", + "bundled": true, + "dev": true + }, + "longest": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "loose-envify": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "js-tokens": "^3.0.0" + } + }, + "lru-cache": { + "version": "4.1.3", + "bundled": true, + "dev": true, + "requires": { + "pseudomap": "^1.0.2", + "yallist": "^2.1.2" + } + }, + "map-cache": { + "version": "0.2.2", + "bundled": true, + "dev": true + }, + "map-visit": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "object-visit": "^1.0.0" + } + }, + "md5-hex": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "md5-o-matic": "^0.1.1" + } + }, + "md5-o-matic": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "mem": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "mimic-fn": "^1.0.0" + } + }, + "merge-source-map": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "bundled": true, + "dev": true + } + } + }, + "micromatch": { + "version": "3.1.10", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "mimic-fn": { + "version": "1.2.0", + "bundled": true, + "dev": true + }, + "minimatch": { + "version": "3.0.4", + "bundled": true, + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "0.0.8", + "bundled": true, + "dev": true + }, + "mixin-deep": { + "version": "1.3.1", + "bundled": true, + "dev": true, + "requires": { + "for-in": "^1.0.2", + "is-extendable": "^1.0.1" + }, + "dependencies": { + "is-extendable": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-plain-object": "^2.0.4" + } + } + } + }, + "mkdirp": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "0.0.8" + } + }, + "ms": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "nanomatch": { + "version": "1.2.9", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "fragment-cache": "^0.2.1", + "is-odd": "^2.0.0", + "is-windows": "^1.0.2", + "kind-of": "^6.0.2", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "normalize-package-data": { + "version": "2.4.0", + "bundled": true, + "dev": true, + "requires": { + "hosted-git-info": "^2.1.4", + "is-builtin-module": "^1.0.0", + "semver": "2 || 3 || 4 || 5", + "validate-npm-package-license": "^3.0.1" + } + }, + "npm-run-path": { + "version": "2.0.2", + "bundled": true, + "dev": true, + "requires": { + "path-key": "^2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "object-assign": { + "version": "4.1.1", + "bundled": true, + "dev": true + }, + "object-copy": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "requires": { + "copy-descriptor": "^0.1.0", + "define-property": "^0.2.5", + "kind-of": "^3.0.3" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "object-visit": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "isobject": "^3.0.0" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "object.pick": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isobject": "^3.0.1" + }, + "dependencies": { + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "once": { + "version": "1.4.0", + "bundled": true, + "dev": true, + "requires": { + "wrappy": "1" + } + }, + "optimist": { + "version": "0.6.1", + "bundled": true, + "dev": true, + "requires": { + "minimist": "~0.0.1", + "wordwrap": "~0.0.2" + } + }, + "os-homedir": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "os-locale": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "execa": "^0.7.0", + "lcid": "^1.0.0", + "mem": "^1.1.0" + } + }, + "p-finally": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "p-limit": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "p-try": "^1.0.0" + } + }, + "p-locate": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "p-limit": "^1.1.0" + } + }, + "p-try": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "parse-json": { + "version": "2.2.0", + "bundled": true, + "dev": true, + "requires": { + "error-ex": "^1.2.0" + } + }, + "pascalcase": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "path-exists": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "pinkie-promise": "^2.0.0" + } + }, + "path-is-absolute": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "path-key": { + "version": "2.0.1", + "bundled": true, + "dev": true + }, + "path-parse": { + "version": "1.0.5", + "bundled": true, + "dev": true + }, + "path-type": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.2", + "pify": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + }, + "pify": { + "version": "2.3.0", + "bundled": true, + "dev": true + }, + "pinkie": { + "version": "2.0.4", + "bundled": true, + "dev": true + }, + "pinkie-promise": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "pinkie": "^2.0.0" + } + }, + "pkg-dir": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + } + } + }, + "posix-character-classes": { + "version": "0.1.1", + "bundled": true, + "dev": true + }, + "pseudomap": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "read-pkg": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "load-json-file": "^1.0.0", + "normalize-package-data": "^2.3.2", + "path-type": "^1.0.0" + } + }, + "read-pkg-up": { + "version": "1.0.1", + "bundled": true, + "dev": true, + "requires": { + "find-up": "^1.0.0", + "read-pkg": "^1.0.0" + }, + "dependencies": { + "find-up": { + "version": "1.1.2", + "bundled": true, + "dev": true, + "requires": { + "path-exists": "^2.0.0", + "pinkie-promise": "^2.0.0" + } + } + } + }, + "regenerator-runtime": { + "version": "0.11.1", + "bundled": true, + "dev": true + }, + "regex-not": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^3.0.2", + "safe-regex": "^1.1.0" + } + }, + "repeat-element": { + "version": "1.1.2", + "bundled": true, + "dev": true + }, + "repeat-string": { + "version": "1.6.1", + "bundled": true, + "dev": true + }, + "repeating": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-finite": "^1.0.0" + } + }, + "require-directory": { + "version": "2.1.1", + "bundled": true, + "dev": true + }, + "require-main-filename": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "resolve-from": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "resolve-url": { + "version": "0.2.1", + "bundled": true, + "dev": true + }, + "ret": { + "version": "0.1.15", + "bundled": true, + "dev": true + }, + "right-align": { + "version": "0.1.3", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "align-text": "^0.1.1" + } + }, + "rimraf": { + "version": "2.6.2", + "bundled": true, + "dev": true, + "requires": { + "glob": "^7.0.5" + } + }, + "safe-regex": { + "version": "1.1.0", + "bundled": true, + "dev": true, + "requires": { + "ret": "~0.1.10" + } + }, + "semver": { + "version": "5.5.0", + "bundled": true, + "dev": true + }, + "set-blocking": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "set-value": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.3", + "split-string": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "shebang-command": { + "version": "1.2.0", + "bundled": true, + "dev": true, + "requires": { + "shebang-regex": "^1.0.0" + } + }, + "shebang-regex": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "signal-exit": { + "version": "3.0.2", + "bundled": true, + "dev": true + }, + "slide": { + "version": "1.1.6", + "bundled": true, + "dev": true + }, + "snapdragon": { + "version": "0.8.2", + "bundled": true, + "dev": true, + "requires": { + "base": "^0.11.1", + "debug": "^2.2.0", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "map-cache": "^0.2.2", + "source-map": "^0.5.6", + "source-map-resolve": "^0.5.0", + "use": "^3.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "snapdragon-node": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "define-property": "^1.0.0", + "isobject": "^3.0.0", + "snapdragon-util": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "snapdragon-util": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.2.0" + } + }, + "source-map": { + "version": "0.5.7", + "bundled": true, + "dev": true + }, + "source-map-resolve": { + "version": "0.5.1", + "bundled": true, + "dev": true, + "requires": { + "atob": "^2.0.0", + "decode-uri-component": "^0.2.0", + "resolve-url": "^0.2.1", + "source-map-url": "^0.4.0", + "urix": "^0.1.0" + } + }, + "source-map-url": { + "version": "0.4.0", + "bundled": true, + "dev": true + }, + "spawn-wrap": { + "version": "1.4.2", + "bundled": true, + "dev": true, + "requires": { + "foreground-child": "^1.5.6", + "mkdirp": "^0.5.0", + "os-homedir": "^1.0.1", + "rimraf": "^2.6.2", + "signal-exit": "^3.0.2", + "which": "^1.3.0" + } + }, + "spdx-correct": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-expression-parse": "^3.0.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-exceptions": { + "version": "2.1.0", + "bundled": true, + "dev": true + }, + "spdx-expression-parse": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "spdx-exceptions": "^2.1.0", + "spdx-license-ids": "^3.0.0" + } + }, + "spdx-license-ids": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "split-string": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^3.0.0" + } + }, + "static-extend": { + "version": "0.1.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "^0.2.5", + "object-copy": "^0.1.0" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + } + } + }, + "string-width": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, + "strip-ansi": { + "version": "3.0.1", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "strip-bom": { + "version": "2.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-utf8": "^0.2.0" + } + }, + "strip-eof": { + "version": "1.0.0", + "bundled": true, + "dev": true + }, + "supports-color": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "test-exclude": { + "version": "4.2.1", + "bundled": true, + "dev": true, + "requires": { + "arrify": "^1.0.1", + "micromatch": "^3.1.8", + "object-assign": "^4.1.0", + "read-pkg-up": "^1.0.1", + "require-main-filename": "^1.0.1" + }, + "dependencies": { + "arr-diff": { + "version": "4.0.0", + "bundled": true, + "dev": true + }, + "array-unique": { + "version": "0.3.2", + "bundled": true, + "dev": true + }, + "braces": { + "version": "2.3.2", + "bundled": true, + "dev": true, + "requires": { + "arr-flatten": "^1.1.0", + "array-unique": "^0.3.2", + "extend-shallow": "^2.0.1", + "fill-range": "^4.0.0", + "isobject": "^3.0.1", + "repeat-element": "^1.1.2", + "snapdragon": "^0.8.1", + "snapdragon-node": "^2.0.1", + "split-string": "^3.0.2", + "to-regex": "^3.0.1" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "expand-brackets": { + "version": "2.1.4", + "bundled": true, + "dev": true, + "requires": { + "debug": "^2.3.3", + "define-property": "^0.2.5", + "extend-shallow": "^2.0.1", + "posix-character-classes": "^0.1.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "0.2.5", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^0.1.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "is-accessor-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-data-descriptor": { + "version": "0.1.4", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "is-descriptor": { + "version": "0.1.6", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^0.1.6", + "is-data-descriptor": "^0.1.4", + "kind-of": "^5.0.0" + } + }, + "kind-of": { + "version": "5.1.0", + "bundled": true, + "dev": true + } + } + }, + "extglob": { + "version": "2.0.4", + "bundled": true, + "dev": true, + "requires": { + "array-unique": "^0.3.2", + "define-property": "^1.0.0", + "expand-brackets": "^2.1.4", + "extend-shallow": "^2.0.1", + "fragment-cache": "^0.2.1", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.1" + }, + "dependencies": { + "define-property": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "is-descriptor": "^1.0.0" + } + }, + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "fill-range": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-number": "^3.0.0", + "repeat-string": "^1.6.1", + "to-regex-range": "^2.1.0" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + } + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.0" + } + }, + "is-descriptor": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "is-accessor-descriptor": "^1.0.0", + "is-data-descriptor": "^1.0.0", + "kind-of": "^6.0.2" + } + }, + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "bundled": true, + "dev": true, + "requires": { + "is-buffer": "^1.1.5" + } + } + } + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + }, + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + }, + "micromatch": { + "version": "3.1.10", + "bundled": true, + "dev": true, + "requires": { + "arr-diff": "^4.0.0", + "array-unique": "^0.3.2", + "braces": "^2.3.1", + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "extglob": "^2.0.4", + "fragment-cache": "^0.2.1", + "kind-of": "^6.0.2", + "nanomatch": "^1.2.9", + "object.pick": "^1.3.0", + "regex-not": "^1.0.0", + "snapdragon": "^0.8.1", + "to-regex": "^3.0.2" + } + } + } + }, + "to-fast-properties": { + "version": "1.0.3", + "bundled": true, + "dev": true + }, + "to-object-path": { + "version": "0.3.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + }, + "to-regex": { + "version": "3.0.2", + "bundled": true, + "dev": true, + "requires": { + "define-property": "^2.0.2", + "extend-shallow": "^3.0.2", + "regex-not": "^1.0.2", + "safe-regex": "^1.1.0" + } + }, + "to-regex-range": { + "version": "2.1.1", + "bundled": true, + "dev": true, + "requires": { + "is-number": "^3.0.0", + "repeat-string": "^1.6.1" + }, + "dependencies": { + "is-number": { + "version": "3.0.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^3.0.2" + } + } + } + }, + "trim-right": { + "version": "1.0.1", + "bundled": true, + "dev": true + }, + "uglify-js": { + "version": "2.8.29", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "source-map": "~0.5.1", + "uglify-to-browserify": "~1.0.0", + "yargs": "~3.10.0" + }, + "dependencies": { + "yargs": { + "version": "3.10.0", + "bundled": true, + "dev": true, + "optional": true, + "requires": { + "camelcase": "^1.0.2", + "cliui": "^2.1.0", + "decamelize": "^1.0.0", + "window-size": "0.1.0" + } + } + } + }, + "uglify-to-browserify": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "optional": true + }, + "union-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "arr-union": "^3.1.0", + "get-value": "^2.0.6", + "is-extendable": "^0.1.1", + "set-value": "^0.4.3" + }, + "dependencies": { + "extend-shallow": { + "version": "2.0.1", + "bundled": true, + "dev": true, + "requires": { + "is-extendable": "^0.1.0" + } + }, + "set-value": { + "version": "0.4.3", + "bundled": true, + "dev": true, + "requires": { + "extend-shallow": "^2.0.1", + "is-extendable": "^0.1.1", + "is-plain-object": "^2.0.1", + "to-object-path": "^0.3.0" + } + } + } + }, + "unset-value": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "has-value": "^0.3.1", + "isobject": "^3.0.0" + }, + "dependencies": { + "has-value": { + "version": "0.3.1", + "bundled": true, + "dev": true, + "requires": { + "get-value": "^2.0.3", + "has-values": "^0.1.4", + "isobject": "^2.0.0" + }, + "dependencies": { + "isobject": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "isarray": "1.0.0" + } + } + } + }, + "has-values": { + "version": "0.1.4", + "bundled": true, + "dev": true + }, + "isobject": { + "version": "3.0.1", + "bundled": true, + "dev": true + } + } + }, + "urix": { + "version": "0.1.0", + "bundled": true, + "dev": true + }, + "use": { + "version": "3.1.0", + "bundled": true, + "dev": true, + "requires": { + "kind-of": "^6.0.2" + }, + "dependencies": { + "kind-of": { + "version": "6.0.2", + "bundled": true, + "dev": true + } + } + }, + "validate-npm-package-license": { + "version": "3.0.3", + "bundled": true, + "dev": true, + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "which": { + "version": "1.3.0", + "bundled": true, + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-module": { + "version": "2.0.0", + "bundled": true, + "dev": true + }, + "window-size": { + "version": "0.1.0", + "bundled": true, + "dev": true, + "optional": true + }, + "wordwrap": { + "version": "0.0.3", + "bundled": true, + "dev": true + }, + "wrap-ansi": { + "version": "2.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1" + }, + "dependencies": { + "is-fullwidth-code-point": { + "version": "1.0.0", + "bundled": true, + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "bundled": true, + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + } + } + }, + "wrappy": { + "version": "1.0.2", + "bundled": true, + "dev": true + }, + "write-file-atomic": { + "version": "1.3.4", + "bundled": true, + "dev": true, + "requires": { + "graceful-fs": "^4.1.11", + "imurmurhash": "^0.1.4", + "slide": "^1.1.5" + } + }, + "y18n": { + "version": "3.2.1", + "bundled": true, + "dev": true + }, + "yallist": { + "version": "2.1.2", + "bundled": true, + "dev": true + }, + "yargs": { + "version": "11.1.0", + "bundled": true, + "dev": true, + "requires": { + "cliui": "^4.0.0", + "decamelize": "^1.1.1", + "find-up": "^2.1.0", + "get-caller-file": "^1.0.1", + "os-locale": "^2.0.0", + "require-directory": "^2.1.1", + "require-main-filename": "^1.0.1", + "set-blocking": "^2.0.0", + "string-width": "^2.0.0", + "which-module": "^2.0.0", + "y18n": "^3.2.1", + "yargs-parser": "^9.0.2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "bundled": true, + "dev": true + }, + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + }, + "cliui": { + "version": "4.1.0", + "bundled": true, + "dev": true, + "requires": { + "string-width": "^2.1.1", + "strip-ansi": "^4.0.0", + "wrap-ansi": "^2.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "bundled": true, + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + }, + "yargs-parser": { + "version": "9.0.2", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^4.1.0" + } + } + } + }, + "yargs-parser": { + "version": "8.1.0", + "bundled": true, + "dev": true, + "requires": { + "camelcase": "^4.1.0" + }, + "dependencies": { + "camelcase": { + "version": "4.1.0", + "bundled": true, + "dev": true + } + } + } + } + }, + "oauth-sign": { + "version": "0.9.0", + "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", + "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==" + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=" + }, + "object-inspect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.8.0.tgz", + "integrity": "sha512-jLdtEOB112fORuypAyl/50VRVIBIdVQOSUUGQHzJ4xBSbit81zRarz7GThkEFZy1RceYrWYcPcBFPQwHyAc1gA==" + }, + "object-is": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.2.tgz", + "integrity": "sha512-5lHCz+0uufF6wZ7CRFWJN3hp8Jqblpgve06U5CMQ3f//6iDjPr2PEo9MWCjEssDsa+UZEL4PkFpr+BMop6aKzQ==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.5" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" + }, + "object.assign": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.0.tgz", + "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", + "requires": { + "define-properties": "^1.1.2", + "function-bind": "^1.1.1", + "has-symbols": "^1.0.0", + "object-keys": "^1.0.11" + } + }, + "object.getownpropertydescriptors": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/object.getownpropertydescriptors/-/object.getownpropertydescriptors-2.1.0.tgz", + "integrity": "sha512-Z53Oah9A3TdLoblT7VKJaTDdXdT+lQO+cNpKVnya5JDe9uLvzu1YyY1yFDFrcxrlRgWrEFH0jJtD/IbuwjcEVg==", + "requires": { + "define-properties": "^1.1.3", + "es-abstract": "^1.17.0-next.1" + } + }, + "object.pick": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/object.pick/-/object.pick-1.3.0.tgz", + "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "oboe": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/oboe/-/oboe-2.1.4.tgz", + "integrity": "sha1-IMiM2wwVNxuwQRklfU/dNLCqSfY=", + "requires": { + "http-https": "^1.0.0" + } + }, + "on-finished": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", + "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", + "requires": { + "ee-first": "1.1.1" + } + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", + "requires": { + "wrappy": "1" + } + }, + "onetime": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-1.1.0.tgz", + "integrity": "sha1-ofeDj4MUxRbwXs78vEzP4EtO14k=", + "dev": true + }, + "optionator": { + "version": "0.8.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", + "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", + "requires": { + "deep-is": "~0.1.3", + "fast-levenshtein": "~2.0.6", + "levn": "~0.3.0", + "prelude-ls": "~1.1.2", + "type-check": "~0.3.2", + "word-wrap": "~1.2.3" + } + }, + "original-require": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/original-require/-/original-require-1.0.1.tgz", + "integrity": "sha1-DxMEcVhM0zURxew4yNWSE/msXiA=" + }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=" + }, + "os-locale": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/os-locale/-/os-locale-1.4.0.tgz", + "integrity": "sha1-IPnxeuKe00XoveWDsT0gCYA8FNk=", + "requires": { + "lcid": "^1.0.0" + } + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=" + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, + "p-cancelable": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-1.1.0.tgz", + "integrity": "sha512-s73XxOZ4zpt1edZYZzvhqFa6uvQc1vwUa0K0BdtIZgQMAJj9IbebH+JkgKZc9h+B05PKHLOTl4ajG1BmNrVZlw==" + }, + "p-finally": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/p-finally/-/p-finally-1.0.0.tgz", + "integrity": "sha1-P7z7FbiZpEEjs0ttzBi3JDNqLK4=" + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz", + "integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==", + "requires": { + "p-limit": "^2.0.0" + } + }, + "p-timeout": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-1.2.1.tgz", + "integrity": "sha1-XrOzU7f86Z8QGhA4iAuwVOu+o4Y=", + "requires": { + "p-finally": "^1.0.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==" + }, + "package-json": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/package-json/-/package-json-4.0.1.tgz", + "integrity": "sha1-iGmgQBJTZhxMTKPabCEh7VVfXu0=", + "dev": true, + "requires": { + "got": "^6.7.1", + "registry-auth-token": "^3.0.1", + "registry-url": "^3.0.3", + "semver": "^5.1.0" + }, + "dependencies": { + "get-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-3.0.0.tgz", + "integrity": "sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=", + "dev": true + }, + "got": { + "version": "6.7.1", + "resolved": "https://registry.npmjs.org/got/-/got-6.7.1.tgz", + "integrity": "sha1-JAzQV4WpoY5WHcG0S0HHY+8ejbA=", + "dev": true, + "requires": { + "create-error-class": "^3.0.0", + "duplexer3": "^0.1.4", + "get-stream": "^3.0.0", + "is-redirect": "^1.0.0", + "is-retry-allowed": "^1.0.0", + "is-stream": "^1.0.0", + "lowercase-keys": "^1.0.0", + "safe-buffer": "^5.0.1", + "timed-out": "^4.0.0", + "unzip-response": "^2.0.1", + "url-parse-lax": "^1.0.0" + } + }, + "prepend-http": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/prepend-http/-/prepend-http-1.0.4.tgz", + "integrity": "sha1-1PRWKwzjaW5BrFLQ4ALlemNdxtw=", + "dev": true + }, + "url-parse-lax": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/url-parse-lax/-/url-parse-lax-1.0.0.tgz", + "integrity": "sha1-evjzA2Rem9eaJy56FKxovAYJ2nM=", + "dev": true, + "requires": { + "prepend-http": "^1.0.1" + } + } + } + }, + "parse-asn1": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.5.tgz", + "integrity": "sha512-jkMYn1dcJqF6d5CpU689bq7w/b5ALS9ROVSpQDPrZsqqesUJii9qutvoT5ltGedNXMO2e16YUWIghG9KxaViTQ==", + "requires": { + "asn1.js": "^4.0.0", + "browserify-aes": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.0", "pbkdf2": "^3.0.3", "safe-buffer": "^5.1.1" } @@ -10447,6 +13108,12 @@ "crypto-js": "^3.1.4", "utf8": "^2.1.1", "xmlhttprequest": "*" + }, + "dependencies": { + "bignumber.js": { + "version": "git+https://github.com/debris/bignumber.js.git#c7a38de919ed75e6fb6ba38051986e294b328df9", + "from": "git+https://github.com/debris/bignumber.js.git#master" + } } }, "which-module": { diff --git a/package.json b/package.json index a1f08aa..f80ffe9 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "clean": "rm -rf build types", "truffle:coverage": "truffle run coverage", "test": "truffle test", - "lint": "prettier --list-different **/*.sol", - "prettier": "prettier --write **/*.sol", + "lint": "prettier --list-different ./contracts/**/*.sol", + "prettier": "prettier --write ./contracts/**/*.sol", "truffle": "truffle", "compile": "truffle compile", "migrate": "truffle migrate", @@ -68,6 +68,7 @@ }, "devDependencies": { "@openzeppelin/contracts": "^2.4.0", + "mcl-wasm": "^0.4.3", "prettier": "^1.19.1", "prettier-plugin-solidity": "^1.0.0-alpha.51", "ts-node": "^8.8.1", diff --git a/test/account_tree.test.ts b/test/account_tree.test.ts new file mode 100644 index 0000000..19b93cd --- /dev/null +++ b/test/account_tree.test.ts @@ -0,0 +1,107 @@ +const TestAccountTree = artifacts.require('TestAccountTree'); +import { TestAccountTreeInstance } from '../types/truffle-contracts'; +import { Tree, Hasher } from './tree'; + +let DEPTH: number; +let BATCH_DEPTH: number; +contract('Account Tree', (accounts) => { + let accountTree: TestAccountTreeInstance; + let treeLeft: Tree; + let treeRight: Tree; + let hasher: Hasher; + beforeEach(async function () { + accountTree = await TestAccountTree.new(); + DEPTH = (await accountTree.DEPTH()).toNumber(); + BATCH_DEPTH = (await accountTree.BATCH_DEPTH()).toNumber(); + treeLeft = Tree.new(DEPTH); + treeRight = Tree.new(DEPTH); + hasher = treeLeft.hasher; + }); + it('empty tree construction', async function () { + for (let i = 0; i < DEPTH; i++) { + const zi = await accountTree.zeros(i); + const fstLeft = await accountTree.filledSubtreesLeft(i); + assert.equal(treeLeft.zeros[DEPTH - i], zi); + assert.equal(fstLeft, zi); + if (i < DEPTH - BATCH_DEPTH) { + const zi = await accountTree.zeros(i + BATCH_DEPTH); + const fstRight = await accountTree.filledSubtreesRight(i); + assert.equal(treeRight.zeros[DEPTH - i - BATCH_DEPTH], zi); + assert.equal(fstRight, zi); + } + } + assert.equal(treeLeft.root, await accountTree.rootLeft()); + assert.equal(treeRight.root, await accountTree.rootRight()); + const root = hasher.hash2(treeLeft.root, treeRight.root); + assert.equal(root, await accountTree.root()); + }); + it('update with single leaf', async function () { + for (let i = 0; i < 33; i++) { + const leaf = web3.utils.randomHex(32); + treeLeft.updateSingle(i, leaf); + await accountTree.updateSingle(leaf); + assert.equal(treeLeft.root, await accountTree.rootLeft()); + const root = hasher.hash2(treeLeft.root, treeRight.root); + assert.equal(root, await accountTree.root()); + } + }); + it('batch update', async function () { + const batchSize = 1 << BATCH_DEPTH; + for (let k = 0; k < 4; k++) { + let leafs = []; + for (let i = 0; i < batchSize; i++) { + leafs.push(web3.utils.randomHex(32)); + } + treeRight.updateBatch(batchSize * k, leafs); + await accountTree.updateBatch(leafs); + assert.equal(treeRight.root, await accountTree.rootRight()); + const root = hasher.hash2(treeLeft.root, treeRight.root); + assert.equal(root, await accountTree.root()); + } + }); + it('witness for left side', async function () { + let leafs = []; + for (let i = 0; i < 16; i++) { + leafs.push(web3.utils.randomHex(32)); + treeLeft.updateSingle(i, leafs[i]); + await accountTree.updateSingle(leafs[i]); + } + for (let i = 0; i < 16; i++) { + let leafIndex = i; + let leaf = leafs[i]; + let witness = treeLeft.witness(i).nodes; + let res = await accountTree.checkInclusion.call(leaf, leafIndex, witness); + assert.isTrue(res[1]); + } + }); + it('witness for right side', async function () { + let leafs = []; + const batchSize = 1 << BATCH_DEPTH; + for (let i = 0; i < batchSize; i++) { + leafs.push(web3.utils.randomHex(32)); + } + treeRight.updateBatch(0, leafs); + await accountTree.updateBatch(leafs); + let offset = web3.utils.toBN(2).pow(web3.utils.toBN(DEPTH)); + for (let i = 0; i < batchSize; i += 41) { + const leafIndex = offset.add(web3.utils.toBN(i)); + let leaf = leafs[i]; + let witness = treeRight.witness(i).nodes; + let res = await accountTree.checkInclusion.call(leaf, leafIndex, witness); + assert.isTrue(res[1]); + } + }); + it.skip('gas cost: update tree single', async function () { + const leaf = web3.utils.randomHex(32); + const gasCost = await accountTree.updateSingle.call(leaf); + console.log(gasCost.toNumber()); + }); + it.skip('gas cost: update tree batch', async function () { + const leafs = []; + for (let i = 0; i < 1 << BATCH_DEPTH; i++) { + leafs.push(web3.utils.randomHex(32)); + } + const gasCost = await accountTree.updateBatch.call(leafs); + console.log(gasCost.toNumber()); + }); +}); diff --git a/test/bls.test.ts b/test/bls.test.ts new file mode 100644 index 0000000..7eb96d4 --- /dev/null +++ b/test/bls.test.ts @@ -0,0 +1,213 @@ +import { TestBLSInstance } from '../types/truffle-contracts'; + +const TestBLS = artifacts.require('TestBLS'); +import * as mcl from './mcl'; +import { bn, bnToHex, ZERO } from './mcl'; + +contract('BLS', accounts => { + let bls: TestBLSInstance; + before(async function() { + await mcl.init(); + bls = await TestBLS.new(); + }); + it('hash to point', async function() { + for (let i = 0; i < 20; i++) { + const data = web3.utils.randomHex(12); + let expect = mcl.g1ToHex(mcl.hashToPoint(data)); + let res = await bls.hashToPoint(data); + assert.equal(expect[0], bnToHex(res[0])); + assert.equal(expect[1], bnToHex(res[1])); + } + }); + + it('verify signature', async function() { + const n = 10; + const messages = []; + const pubkeys = []; + let aggSignature = mcl.newG1(); + for (let i = 0; i < n; i++) { + const message = web3.utils.randomHex(12); + const { pubkey, secret } = mcl.newKeyPair(); + const { signature, M } = mcl.sign(message, secret); + aggSignature = mcl.aggreagate(aggSignature, signature); + messages.push(M); + pubkeys.push(pubkey); + } + let messages_ser = messages.map(p => mcl.g1ToBN(p)); + let pubkeys_ser = pubkeys.map(p => mcl.g2ToBN(p)); + let sig_ser = mcl.g1ToBN(aggSignature); + let res = await bls.verifyMultiple(sig_ser, pubkeys_ser, messages_ser); + assert.isTrue(res); + }); + it('is on curve g1', async function() { + for (let i = 0; i < 20; i++) { + const point = mcl.randG1(); + let isOnCurve = await bls.isOnCurveG1(mcl.g1ToHex(point)); + assert.isTrue(isOnCurve); + const compressed = mcl.g1ToCompressed(point); + isOnCurve = await bls.isOnCurveG1Compressed(compressed); + assert.isTrue(isOnCurve); + } + for (let i = 0; i < 20; i++) { + const point = [bn(web3.utils.randomHex(31)), bn(web3.utils.randomHex(31))]; + const isOnCurve = await bls.isOnCurveG1(point); + assert.isFalse(isOnCurve); + } + }); + it('is on curve g2', async function() { + for (let i = 0; i < 20; i++) { + const point = mcl.randG2(); + let isOnCurve = await bls.isOnCurveG2(mcl.g2ToHex(point)); + assert.isTrue(isOnCurve); + const compressed = mcl.g2ToCompressed(point); + isOnCurve = await bls.isOnCurveG2Compressed(compressed); + assert.isTrue(isOnCurve); + } + for (let i = 0; i < 20; i++) { + const point = [bn(web3.utils.randomHex(31)), bn(web3.utils.randomHex(31)), bn(web3.utils.randomHex(31)), bn(web3.utils.randomHex(31))]; + const isOnCurve = await bls.isOnCurveG2(point); + assert.isFalse(isOnCurve); + } + }); + it('fp2 is non residue', async function() { + const MINUS_ONE = web3.utils.toBN('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46'); + let r = await bls.isNonResidueFP2([MINUS_ONE, ZERO]); + assert.isFalse(r); + r = await bls.isNonResidueFP2([bn('0x09'), bn('0x01')]); + assert.isTrue(r); + const residues = [ + [bn('0x291c1493973fe1c89789dc8febbe1293297b4f4669a5ba29ccef5516b99fa8e3'), bn('0x277faf1cfd5339d418ebbeb6b7f98a36be0afa6a3c03c133a4e4ff994e3bd3e9')], + [bn('0x2a502d97952ce7dac491feb17fba2dfa6f92e9378408f2bdb83d8fffb8468edd'), bn('0x032867246dc6ba409cd717029ee0a22e3f81b158fcea8536f902d29c8e477506')], + [bn('0x0f9e5869c4d5c689cdba6589d9c84cf01cbcf724e6e3e6a1c8501def6e259afd'), bn('0x1a00ce2041dff1fe6d61d7f39e96fc5e5ffa4e7d37b0ae3b629bb45d081f20b5')], + [bn('0x07f2912b3f9756481668fdfe73a3c64afb28229be8bd35f6f1166a661aaaea68'), bn('0x21aa6705f2c7d33aa61866b54a1db8d219049dd5502c01b07a156952122f0406')], + [bn('0x16b1b7716c7861f646a5932a9160801f88888203fb46bc4f5166e9cab695ab07'), bn('0x2a4de74d279d5a227794717f186ffa3a781a3069789e4de99dcfc9217730ab53')] + ]; + const nonResidues = [ + [bn('0x04e708d308e4c80df97d911253ddc05335a7aa65065441799138bec037248cb9'), bn('0x06cc0a71cbbeb9eaee7ec9898880109418b730eb5e8a7e4fb969c3f522dad361')], + [bn('0x16945f8bfd9e611407a0569df5e671f4c1e8f5109bc27e2885a401109334c650'), bn('0x11542e36b08a2f764e3d94849f360b35597866cef9e0d8a3810e7ccb9aad3800')], + [bn('0x1caaad1c4d9be66c85838c98b78fed490c629e151bcf082f92c5b0cb187052d9'), bn('0x103da08b58de8e64e3b2e0f75ac032441ba37e9bae51d6ae7c044bdfc3ecd952')], + [bn('0x2b8d1c9600cbf0b27dacf78be1679671507b0991771918a1fcbeef1636649b27'), bn('0x28a6dfea2d4a681b754da75a689ccb15374e78a358d52494222de32cf3de1aa6')], + [bn('0x278490b783c9a02849a8e3d1e9a9f38e994348e11c5cb8ca3a9b48f44f5ae7db'), bn('0x101fce260947e70a884047ac1293e411f9e85c851d79350e588ed10f80235cab')] + ]; + for (let i = 0; i < residues.length; i++) { + r = await bls.isNonResidueFP2(residues[i]); + assert.isFalse(r); + } + for (let i = 0; i < nonResidues.length; i++) { + r = await bls.isNonResidueFP2(nonResidues[i]); + assert.isTrue(r); + } + }); + it('fp is non residue', async function() { + const MINUS_ONE = web3.utils.toBN('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46'); + let r = await bls.isNonResidueFP(MINUS_ONE); + assert.isTrue(r); + r = await bls.isNonResidueFP(bn('0x04')); + assert.isFalse(r); + const residues = [ + mcl + .randFs() + .sqr() + .umod(mcl.FIELD_ORDER), + mcl + .randFs() + .sqr() + .umod(mcl.FIELD_ORDER), + mcl + .randFs() + .sqr() + .umod(mcl.FIELD_ORDER), + mcl + .randFs() + .sqr() + .umod(mcl.FIELD_ORDER), + mcl + .randFs() + .sqr() + .umod(mcl.FIELD_ORDER) + ]; + const nonResidues = [ + bn('0x23d9bb51d142f4a4b8a533721a30648b5ff7f9387b43d4fc8232db20377611bc'), + bn('0x107662a378d9198183bd183db9f6e5ba271fbf2ec6b8b077dfc0a40119f104cb'), + bn('0x0df617c7a009e07c841d683108b8747a842ce0e76f03f0ce9939473d569ea4ba'), + bn('0x276496bfeb07b8ccfc041a1706fbe3d96f4d42ffb707edc5e31cae16690fddc7'), + bn('0x20fcdf224c9982c72a3e659884fdad7cb59b736d6d57d54799c57434b7869bb3') + ]; + for (let i = 0; i < residues.length; i++) { + r = await bls.isNonResidueFP(residues[i]); + assert.isFalse(r); + } + for (let i = 0; i < nonResidues.length; i++) { + r = await bls.isNonResidueFP(nonResidues[i]); + assert.isTrue(r); + } + }); + it('pubkey to uncompressed', async function() { + for (let i = 0; i < 20; i++) { + const { pubkey } = mcl.newKeyPair(); + const compressed = mcl.compressPubkey(pubkey); + const isValid = await bls.isValidCompressedPublicKey(compressed); + assert.isTrue(isValid); + const y = [mcl.g2ToBN(pubkey)[2], mcl.g2ToBN(pubkey)[3]]; + const uncompressed = await bls.pubkeyToUncompresed(compressed, y); + const _pubkey = mcl.newG2(); + _pubkey.setStr(`1 ${bnToHex(uncompressed[0])} ${bnToHex(uncompressed[1])} ${bnToHex(uncompressed[2])} ${bnToHex(uncompressed[3])}`); + assert.isTrue(_pubkey.isEqual(pubkey)); + } + }); + it('signature to uncompressed', async function() { + for (let i = 0; i < 20; i++) { + const signature = mcl.randG1(); + const compressed = mcl.compressSignature(signature); + const isValid = await bls.isValidCompressedSignature(compressed); + assert.isTrue(isValid); + const y = mcl.g1ToBN(signature)[1]; + const uncompressed = await bls.signatureToUncompresed(compressed, y); + const _signature = mcl.newG1(); + _signature.setStr(`1 ${bnToHex(uncompressed[0])} ${bnToHex(uncompressed[1])}`); + assert.isTrue(signature.isEqual(_signature)); + } + }); + it.skip('gas cost: verify signature', async function() { + const n = 100; + const messages = []; + const pubkeys = []; + let aggSignature = mcl.newG1(); + for (let i = 0; i < n; i++) { + const message = web3.utils.randomHex(12); + const { pubkey, secret } = mcl.newKeyPair(); + const { signature, M } = mcl.sign(message, secret); + aggSignature = mcl.aggreagate(aggSignature, signature); + messages.push(M); + pubkeys.push(pubkey); + } + let messages_ser = messages.map(p => mcl.g1ToBN(p)); + let pubkeys_ser = pubkeys.map(p => mcl.g2ToBN(p)); + let sig_ser = mcl.g1ToBN(aggSignature); + let cost = await bls.verifyMultipleGasCost.call(sig_ser, pubkeys_ser, messages_ser); + console.log(`verify signature for ${n} message: ${cost.toNumber()}`); + }); + it.skip('gas cost: hash to point', async function() { + const n = 50; + let totalCost = 0; + for (let i = 0; i < n; i++) { + const data = web3.utils.randomHex(12); + let cost = await bls.hashToPointGasCost.call(data); + totalCost += cost.toNumber(); + } + console.log(`hash to point average cost: ${totalCost / n}`); + }); + it.skip('gas cost: is on curve', async function() { + let point = mcl.randG2(); + let cost = await bls.isOnCurveG2GasCost.call(mcl.g2ToBN(point)); + console.log(`is on curve g2 gas cost: ${cost.toNumber()}`); + point = mcl.randG1(); + cost = await bls.isOnCurveG2GasCost.call(mcl.g1ToBN(point)); + console.log(`is on curve g2 gas cost: ${cost.toNumber()}`); + }); + it.skip('fp2 is non residue', async function() { + const MINUS_ONE = web3.utils.toBN('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd46'); + const cost = await bls.isNonResidueFP2GasCost.call([MINUS_ONE, ZERO]); + console.log(`is on residue gas cost: ${cost.toNumber()}`); + }); +}); diff --git a/test/mcl.ts b/test/mcl.ts new file mode 100644 index 0000000..b9a2b17 --- /dev/null +++ b/test/mcl.ts @@ -0,0 +1,177 @@ +const mcl = require('mcl-wasm'); + +export type mclG2 = any; +export type mclG1 = any; +export type mclFP = any; +export type mclFR = any; + +export const FIELD_ORDER = web3.utils.toBN('0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47'); +export const ZERO = web3.utils.toBN('0x00'); + +export async function init() { + await mcl.init(mcl.BN_SNARK1); + mcl.setMapToMode(1); +} + +export function hashToPoint(data: string) { + const e0 = web3.utils.toBN(web3.utils.soliditySha3(data)!); + let e1 = new mcl.Fp(); + e1.setStr(e0.mod(FIELD_ORDER).toString()); + return e1.mapToG1(); +} + +export function bnToHex(n: any) { + return '0x' + web3.utils.padLeft(n.toString(16), 64); +} + +export function bn(n: string) { + if (n.length > 2 && n.slice(0, 2) == '0x') { + return web3.utils.toBN(n); + } + return web3.utils.toBN('0x' + n); +} + +export function mclToHex(p: mclFP, prefix: boolean = true) { + const arr = p.serialize(); + let s = ''; + for (let i = arr.length - 1; i >= 0; i--) { + s += ('0' + arr[i].toString(16)).slice(-2); + } + return prefix ? '0x' + s : s; +} + +export function g1() { + const g1 = new mcl.G1(); + g1.setStr('1 0x01 0x02', 16); + return g1; +} + +export function g2() { + const g2 = new mcl.G2(); + g2.setStr( + '1 0x1800deef121f1e76426a00665e5c4479674322d4f75edadd46debd5cd992f6ed 0x198e9393920d483a7260bfb731fb5d25f1aa493335a9e71297e485b7aef312c2 0x12c85ea5db8c6deb4aab71808dcb408fe3d1e7690c43d37b4ce6cc0166fa7daa 0x090689d0585ff075ec9e99ad690c3395bc4b313370b38ef355acdadcd122975b' + ); + return g2; +} + +export function signOfG1(p: mclG1): boolean { + const y = bn(mclToHex(p.getY())); + return y.isOdd(); +} + +export function signOfG2(p: mclG2): boolean { + p.normalize(); + const y = mclToHex(p.getY(), false); + return bn(y.slice(64)).isOdd(); +} + +export function g1ToCompressed(p: mclG1) { + p.normalize(); + if (signOfG1(p)) { + const x = bn(mclToHex(p.getX())); + const masked = x.or(bn('8000000000000000000000000000000000000000000000000000000000000000')); + return bnToHex(masked); + } else { + return mclToHex(p.getX()); + } +} + +export function g1ToBN(p: mclG1) { + p.normalize(); + const x = bn(mclToHex(p.getX())); + const y = bn(mclToHex(p.getY())); + return [x, y]; +} + +export function g1ToHex(p: mclG1) { + p.normalize(); + const x = mclToHex(p.getX()); + const y = mclToHex(p.getY()); + return [x, y]; +} + +export function g2ToCompressed(p: mclG2) { + p.normalize(); + const x = mclToHex(p.getX(), false); + if (signOfG2(p)) { + const masked = bn(x.slice(64)).or(bn('8000000000000000000000000000000000000000000000000000000000000000')); + // return masked.toString(16, 64) + x.slice(0, 64); + return [bnToHex(masked), '0x' + x.slice(0, 64)]; + } else { + // return '0x' + x.slice(64) + x.slice(0, 64); + return ['0x' + x.slice(64), '0x' + x.slice(0, 64)]; + } +} + +export function g2ToBN(p: mclG2) { + const x = mclToHex(p.getX(), false); + const y = mclToHex(p.getY(), false); + return [bn(x.slice(64)), bn(x.slice(0, 64)), bn(y.slice(64)), bn(y.slice(0, 64))]; +} + +export function g2ToHex(p: mclG2) { + p.normalize(); + const x = mclToHex(p.getX(), false); + const y = mclToHex(p.getY(), false); + return ['0x' + x.slice(64), '0x' + x.slice(0, 64), '0x' + y.slice(64), '0x' + y.slice(0, 64)]; +} + +export function newKeyPair() { + const secret = randFr(); + const pubkey = mcl.mul(g2(), secret); + pubkey.normalize(); + return { pubkey, secret }; +} + +export function sign(message: string, secret: mclFR) { + const M = hashToPoint(message); + const signature = mcl.mul(M, secret); + signature.normalize(); + return { signature, M }; +} + +export function aggreagate(acc: mclG1 | mclG2, other: mclG1 | mclG2) { + const _acc = mcl.add(acc, other); + _acc.normalize(); + return _acc; +} + +export function compressPubkey(p: mclG2) { + return g2ToCompressed(p); +} + +export function compressSignature(p: mclG1) { + return g1ToCompressed(p); +} + +export function newG1() { + return new mcl.G1(); +} + +export function newG2() { + return new mcl.G2(); +} + +export function randFr() { + const r = web3.utils.randomHex(12); + let fr = new mcl.Fr(); + fr.setHashOf(r); + return fr; +} + +export function randFs() { + const r = bn(web3.utils.randomHex(32)); + return r.umod(FIELD_ORDER); +} + +export function randG1() { + const p = mcl.mul(g1(), randFr()); + p.normalize(); + return p; +} + +export function randG2() { + const p = mcl.mul(g2(), randFr()); + p.normalize(); + return p; +} diff --git a/test/registry.test.ts b/test/registry.test.ts new file mode 100644 index 0000000..37f1334 --- /dev/null +++ b/test/registry.test.ts @@ -0,0 +1,178 @@ +// const AccountRegistry = artifacts.require('BLSAccountRegistry'); +// import { BLSAccountRegistryInstance } from '../types/truffle-contracts'; +// import { Tree, Hasher } from './tree'; + +// import * as mcl from './mcl'; + +// let DEPTH: number; +// let BATCH_DEPTH: number; +// let hasher: Hasher; + +// type Pubkey = mcl.mclG2; + +// function pubkeyToLeaf(p: Pubkey) { +// const compressed = mcl.compressPubkey(p); +// const leaf = hasher.hash2(compressed[0], compressed[1]); +// return { compressed, leaf }; +// } + +// contract('Registry', (accounts) => { +// let registry: BLSAccountRegistryInstance; +// let treeLeft: Tree; +// let treeRight: Tree; +// beforeEach(async function () { +// await mcl.init(); +// registry = await AccountRegistry.new(); +// DEPTH = (await registry.DEPTH()).toNumber(); +// BATCH_DEPTH = (await registry.BATCH_DEPTH()).toNumber(); +// treeLeft = Tree.new(DEPTH); +// treeRight = Tree.new(DEPTH); +// hasher = treeLeft.hasher; +// }); + +// it('register a public keys', async function () { +// for (let i = 0; i < 33; i++) { +// const { pubkey } = mcl.newKeyPair(); +// const { compressed, leaf } = pubkeyToLeaf(pubkey); +// treeLeft.updateSingle(i, leaf); +// await registry.register(compressed); +// } +// assert.equal(treeLeft.root, await registry.rootLeft()); +// assert.equal(treeRight.root, await registry.rootRight()); +// const root = hasher.hash2(treeLeft.root, treeRight.root); +// assert.equal(root, await registry.root()); +// }); +// it('batch update', async function () { +// const batchSize = 1 << BATCH_DEPTH; +// for (let k = 0; k < 4; k++) { +// let leafs = []; +// let pubkeys = []; +// for (let i = 0; i < batchSize; i++) { +// const { pubkey } = mcl.newKeyPair(); +// const { compressed, leaf } = pubkeyToLeaf(pubkey); +// leafs.push(leaf); +// pubkeys.push(compressed); +// } +// treeRight.updateBatch(batchSize * k, leafs); +// await registry.registerBatch(pubkeys); +// assert.equal(treeRight.root, await registry.rootRight()); +// const root = hasher.hash2(treeLeft.root, treeRight.root); +// assert.equal(root, await registry.root()); +// } +// }); +// it('exists', async function () { +// let leafs = []; +// let pubkeys = []; +// for (let i = 0; i < 16; i++) { +// const { pubkey } = mcl.newKeyPair(); +// const { compressed, leaf } = pubkeyToLeaf(pubkey); +// leafs.push(leaf); +// pubkeys.push(pubkey); +// treeLeft.updateSingle(i, leaf); +// await registry.register(compressed); +// } +// for (let i = 0; i < 16; i++) { +// const leafIndex = i; +// const pubkey = pubkeys[i]; +// const compressed = mcl.compressPubkey(pubkey); +// const witness = treeLeft.witness(leafIndex).nodes; +// const exist = await registry.compressedExists(compressed, leafIndex, witness); +// assert.isTrue(exist, 'A'); +// } +// for (let i = 0; i < 16; i++) { +// const leafIndex = i; +// const uncompressed = mcl.g2ToHex(pubkeys[i]); +// const witness = treeLeft.witness(leafIndex).nodes; +// const exist = await registry.uncompressedExists(uncompressed, leafIndex, witness); +// assert.isTrue(exist, i.toString()); +// } +// }); +// }); + +const AccountRegistry = artifacts.require('BLSAccountRegistry'); +import { BLSAccountRegistryInstance } from '../types/truffle-contracts'; +import { Tree, Hasher } from './tree'; + +import * as mcl from './mcl'; + +let DEPTH: number; +let BATCH_DEPTH: number; +let hasher: Hasher; + +type Pubkey = mcl.mclG2; + +function pubkeyToLeaf(p: Pubkey) { + const uncompressed = mcl.g2ToHex(p); + const leaf = web3.utils.soliditySha3( + { t: 'uint256', v: uncompressed[0] }, + { t: 'uint256', v: uncompressed[1] }, + { t: 'uint256', v: uncompressed[2] }, + { t: 'uint256', v: uncompressed[3] } + ); + return { uncompressed, leaf }; +} + +contract('Registry', accounts => { + let registry: BLSAccountRegistryInstance; + let treeLeft: Tree; + let treeRight: Tree; + beforeEach(async function() { + await mcl.init(); + registry = await AccountRegistry.new(); + DEPTH = (await registry.DEPTH()).toNumber(); + BATCH_DEPTH = (await registry.BATCH_DEPTH()).toNumber(); + treeLeft = Tree.new(DEPTH); + treeRight = Tree.new(DEPTH); + hasher = treeLeft.hasher; + }); + + it('register a public keys', async function() { + for (let i = 0; i < 33; i++) { + const { pubkey } = mcl.newKeyPair(); + const { uncompressed, leaf } = pubkeyToLeaf(pubkey); + treeLeft.updateSingle(i, leaf); + await registry.register(uncompressed); + } + assert.equal(treeLeft.root, await registry.rootLeft()); + assert.equal(treeRight.root, await registry.rootRight()); + const root = hasher.hash2(treeLeft.root, treeRight.root); + assert.equal(root, await registry.root()); + }); + it('batch update', async function() { + const batchSize = 1 << BATCH_DEPTH; + for (let k = 0; k < 4; k++) { + let leafs = []; + let pubkeys = []; + for (let i = 0; i < batchSize; i++) { + const { pubkey } = mcl.newKeyPair(); + const { uncompressed, leaf } = pubkeyToLeaf(pubkey); + leafs.push(leaf); + pubkeys.push(uncompressed); + } + treeRight.updateBatch(batchSize * k, leafs); + await registry.registerBatch(pubkeys); + assert.equal(treeRight.root, await registry.rootRight()); + const root = hasher.hash2(treeLeft.root, treeRight.root); + assert.equal(root, await registry.root()); + } + }); + it('exists', async function() { + let leafs = []; + let pubkeys = []; + for (let i = 0; i < 16; i++) { + const { pubkey } = mcl.newKeyPair(); + const { uncompressed, leaf } = pubkeyToLeaf(pubkey); + leafs.push(leaf); + pubkeys.push(pubkey); + treeLeft.updateSingle(i, leaf); + await registry.register(uncompressed); + } + for (let i = 0; i < 16; i++) { + const leafIndex = i; + const uncompressed = mcl.g2ToHex(pubkeys[i]); + const witness = treeLeft.witness(leafIndex).nodes; + const exist = await registry.exists(uncompressed, leafIndex, witness); + assert.isTrue(exist); + } + }); +}); diff --git a/test/state_account_encoding.test.ts b/test/state_account_encoding.test.ts new file mode 100644 index 0000000..e11c4e7 --- /dev/null +++ b/test/state_account_encoding.test.ts @@ -0,0 +1,117 @@ +const TestStateAccount = artifacts.require('TestStateAccount'); +import { TestStateAccountInstance } from '../types/truffle-contracts'; + +interface Account { + accountIndex: number; + tokenType: number; + balance: number; + nonce: number; +} + +let accountIndexLen = 4; +let tokenTypeLen = 2; +let balanceLen = 4; +let nonceLen = 4; + +function encodeAccount(acc: Account): string { + let serialized = '0x'; + let accountIndex = web3.utils.padLeft(web3.utils.toHex(acc.accountIndex), accountIndexLen * 2); + let tokenType = web3.utils.padLeft(web3.utils.toHex(acc.tokenType), tokenTypeLen * 2); + let balance = web3.utils.padLeft(web3.utils.toHex(acc.balance), balanceLen * 2); + let nonce = web3.utils.padLeft(web3.utils.toHex(acc.nonce), nonceLen * 2); + serialized = web3.utils.padLeft(serialized + accountIndex.slice(2) + tokenType.slice(2) + balance.slice(2) + nonce.slice(2), 64); + return serialized; +} + +function decodeAccount(encoded: string): Account { + if (encoded.slice(0, 2) == '0x') { + assert.lengthOf(encoded, 66); + encoded = encoded.slice(2); + } else { + assert.lengthOf(encoded, 64); + } + assert.isTrue(web3.utils.isHex(encoded)); + let t0 = 64 - nonceLen * 2; + let t1 = 64; + const nonce = web3.utils.hexToNumber('0x' + encoded.slice(t0, t1)); + t1 = t0; + t0 = t0 - balanceLen * 2; + const balance = web3.utils.hexToNumber('0x' + encoded.slice(t0, t1)); + t1 = t0; + t0 = t0 - tokenTypeLen * 2; + const tokenType = web3.utils.hexToNumber('0x' + encoded.slice(t0, t1)); + t1 = t0; + t0 = t0 - accountIndexLen * 2; + const accountIndex = web3.utils.hexToNumber('0x' + encoded.slice(t0, t1)); + return { + accountIndex, + tokenType, + balance, + nonce + }; +} + +function hashAccount(acc: Account): string { + return web3.utils.soliditySha3({ v: acc.accountIndex, t: 'uint32' }, { v: acc.tokenType, t: 'uint16' }, { v: acc.balance, t: 'uint32' }, { v: acc.nonce, t: 'uint32' }); +} + +contract('Tx Serialization', accounts => { + let c: TestStateAccountInstance; + const account0: Account = { + accountIndex: 10, + tokenType: 1, + balance: 200, + nonce: 8 + }; + before(async function() { + c = await TestStateAccount.new(); + }); + it('encoding', async function() { + const encoded = encodeAccount(account0); + assert.equal(account0.accountIndex, (await c.accountID(encoded)).toNumber()); + assert.equal(account0.tokenType, (await c.tokenType(encoded)).toNumber()); + assert.equal(account0.balance, (await c.balance(encoded)).toNumber()); + assert.equal(account0.nonce, (await c.nonce(encoded)).toNumber()); + assert.equal(hashAccount(account0), await c.hash(encoded)); + }); + + it('increment nonce', async function() { + const encoded0 = encodeAccount(account0); + const res = await c.incrementNonce(encoded0); + assert.isTrue(res[1]); + const encoded1 = web3.utils.padLeft(res[0].toString(16), 64); + const account1 = decodeAccount(encoded1); + assert.equal(account0.accountIndex, account1.accountIndex); + assert.equal(account0.tokenType, account1.tokenType); + assert.equal(account0.balance, account1.balance); + assert.equal(account0.nonce + 1, account1.nonce); + }); + + it('balance safe add', async function() { + const amount = 500; + const encoded0 = encodeAccount(account0); + const res = await c.balanceSafeAdd(encoded0, amount); + assert.isTrue(res[1]); + const encoded1 = web3.utils.padLeft(res[0].toString(16), 64); + const account1 = decodeAccount(encoded1); + assert.equal(account0.accountIndex, account1.accountIndex); + assert.equal(account0.tokenType, account1.tokenType); + assert.equal(account0.balance + amount, account1.balance); + assert.equal(account0.nonce, account1.nonce); + // TODO: test overflow + }); + + it('balance safe sub', async function() { + const amount = 150; + const encoded0 = encodeAccount(account0); + const res = await c.balanceSafeSub(encoded0, amount); + assert.isTrue(res[1]); + const encoded1 = web3.utils.padLeft(res[0].toString(16), 64); + const account1 = decodeAccount(encoded1); + assert.equal(account0.accountIndex, account1.accountIndex); + assert.equal(account0.tokenType, account1.tokenType); + assert.equal(account0.balance - amount, account1.balance); + assert.equal(account0.nonce, account1.nonce); + // TODO: test overflow + }); +}); diff --git a/test/tree/hasher.ts b/test/tree/hasher.ts new file mode 100644 index 0000000..6dda190 --- /dev/null +++ b/test/tree/hasher.ts @@ -0,0 +1,26 @@ +export type Node = string; + +const ZERO = '0x0000000000000000000000000000000000000000000000000000000000000000'; + +export class Hasher { + static new(): Hasher { + return new Hasher(); + } + + public hash(x0: string): string { + return web3.utils.soliditySha3({ t: 'uint256', v: x0 })!; + } + + public hash2(x0: string, x1: string): string { + return web3.utils.soliditySha3({ t: 'uint256', v: x0 }, { t: 'uint256', v: x1 })!; + } + + public zeros(depth: number): Array { + const N = depth + 1; + const zeros = Array(N).fill(ZERO); + for (let i = 1; i < N; i++) { + zeros[N - 1 - i] = this.hash2(zeros[N - i], zeros[N - i]); + } + return zeros; + } +} diff --git a/test/tree/index.ts b/test/tree/index.ts new file mode 100644 index 0000000..db915dd --- /dev/null +++ b/test/tree/index.ts @@ -0,0 +1,2 @@ +export {Hasher, Node} from "./hasher"; +export {Tree, Data, Witness} from "./tree"; diff --git a/test/tree/tree.ts b/test/tree/tree.ts new file mode 100644 index 0000000..fa2fd3c --- /dev/null +++ b/test/tree/tree.ts @@ -0,0 +1,181 @@ +import { Hasher, Node } from './hasher'; + +type Level = { [node: number]: Node }; +export type Data = string; +export type Success = number; + +export type Witness = { + path: Array; + nodes: Array; + leaf: Node; + index: number; + data?: Data; + depth?: number; +}; + +export class Tree { + public readonly zeros: Array; + public readonly depth: number; + public readonly setSize: number; + public readonly hasher: Hasher; + private readonly tree: Array = []; + + public static new(depth: number, hasher?: Hasher): Tree { + return new Tree(depth, hasher || Hasher.new()); + } + + constructor(depth: number, hasher: Hasher) { + this.depth = depth; + this.setSize = 2 ** this.depth; + this.tree = []; + for (let i = 0; i < depth + 1; i++) { + this.tree.push({}); + } + this.hasher = hasher; + this.zeros = this.hasher.zeros(depth); + } + + get root(): Node { + return this.tree[0][0] || this.zeros[0]; + } + + public getNode(level: number, index: number): Node { + return this.tree[level][index] || this.zeros[level]; + } + + // witnessForBatch given merging subtree offset and depth constructs a witness + public witnessForBatch(mergeOffsetLower: number, subtreeDepth: number): Witness { + const mergeSize = 1 << subtreeDepth; + const mergeOffsetUpper = mergeOffsetLower + mergeSize; + const pathFollower = mergeOffsetLower >> subtreeDepth; + if (mergeOffsetLower >> subtreeDepth != (mergeOffsetUpper - 1) >> subtreeDepth) { + throw new Error('bad merge alignment'); + } + return this.witness(pathFollower, this.depth - subtreeDepth); + } + + // witness given index and depth constructs a witness + public witness(index: number, depth: number = this.depth): Witness { + const path = Array(depth); + const nodes = Array(depth); + let nodeIndex = index; + const leaf = this.getNode(depth, nodeIndex); + for (let i = 0; i < depth; i++) { + nodeIndex ^= 1; + nodes[i] = this.getNode(depth - i, nodeIndex); + path[i] = (nodeIndex & 1) == 1; + nodeIndex >>= 1; + } + return { path, nodes, leaf, index, depth }; + } + + // checkInclusion verifies the given witness. + // It performs root calculation rather than just looking up for the leaf or node + public checkInclusion(witness: Witness): Success { + // we check the form of witness data rather than looking up for the leaf + if (witness.nodes.length == 0) return -2; + if (witness.nodes.length != witness.path.length) return -3; + const data = witness.data; + if (data) { + if (witness.nodes.length != this.depth) return -4; + if (this.hasher.hash(data) != witness.leaf) return -5; + } + let depth = witness.depth; + if (!depth) { + depth = this.depth; + } + let acc = witness.leaf; + for (let i = 0; i < depth; i++) { + const node = witness.nodes[i]; + if (witness.path[i]) { + acc = this.hasher.hash2(acc, node); + } else { + acc = this.hasher.hash2(node, acc); + } + } + return acc == this.root ? 0 : -1; + } + + // insertSingle updates tree with a single raw data at given index + public insertSingle(leafIndex: number, data: Data): Success { + if (leafIndex >= this.setSize) { + return -1; + } + this.tree[this.depth][leafIndex] = this.hasher.hash(data); + this.ascend(leafIndex, 1); + return 0; + } + + // updateSingle updates tree with a leaf at given index + public updateSingle(leafIndex: number, leaf: Node): Success { + if (leafIndex >= this.setSize) { + return -1; + } + this.tree[this.depth][leafIndex] = leaf; + this.ascend(leafIndex, 1); + return 0; + } + + // insertBatch given multiple raw data updates tree ascending from an offset + public insertBatch(offset: number, data: Array): Success { + const len = data.length; + if (len == 0) return -1; + if (len + offset > this.setSize) return -2; + for (let i = 0; i < len; i++) { + this.tree[this.depth][offset + i] = this.hasher.hash(data[i]); + } + this.ascend(offset, len); + return 0; + } + + // updateBatch given multiple sequencial data updates tree ascending from an offset + public updateBatch(offset: number, data: Array): Success { + const len = data.length; + if (len == 0) return -1; + if (len + offset > this.setSize) return -2; + for (let i = 0; i < len; i++) { + this.tree[this.depth][offset + i] = data[i]; + } + this.ascend(offset, len); + return 0; + } + + public isZero(level: number, leafIndex: number): boolean { + return this.zeros[level] == this.getNode(level, leafIndex); + } + + private ascend(offset: number, len: number) { + for (let level = this.depth; level > 0; level--) { + if (offset & 1) { + offset -= 1; + len += 1; + } + if (len & 1) { + len += 1; + } + for (let node = offset; node < offset + len; node += 2) { + this.updateCouple(level, node); + } + offset >>= 1; + len >>= 1; + } + } + + private updateCouple(level: number, leafIndex: number) { + const n = this.hashCouple(level, leafIndex); + this.tree[level - 1][leafIndex >> 1] = n; + } + + private hashCouple(level: number, leafIndex: number) { + const X = this.getCouple(level, leafIndex); + return this.hasher.hash2(X.l, X.r); + } + + private getCouple(level: number, index: number): { l: Node; r: Node } { + index = index & ~1; + return { + l: this.getNode(level, index), + r: this.getNode(level, index + 1), + }; + } +} diff --git a/test/tx_serialization.test.ts b/test/tx_serialization.test.ts new file mode 100644 index 0000000..3d2bd2b --- /dev/null +++ b/test/tx_serialization.test.ts @@ -0,0 +1,71 @@ +const TestTx = artifacts.require('TestTx'); +import { TestTxInstance } from '../types/truffle-contracts'; +import * as mcl from './mcl'; +import { bnToHex } from './mcl'; + +interface Tx { + sender: number; + receiver: number; + amount: number; +} + +let amountLen = 4; +let senderLen = 4; +let receiverLen = 4; + +function serialize(txs: Tx[]) { + let serialized = '0x'; + for (let i = 0; i < txs.length; i++) { + let tx = txs[i]; + let sender = web3.utils.padLeft(web3.utils.toHex(tx.sender), senderLen * 2); + let receiver = web3.utils.padLeft(web3.utils.toHex(tx.receiver), receiverLen * 2); + let amount = web3.utils.padLeft(web3.utils.toHex(tx.amount), amountLen * 2); + serialized = serialized + sender.slice(2) + receiver.slice(2) + amount.slice(2); + } + return serialized; +} + +function hash(tx: Tx) { + return web3.utils.soliditySha3({ v: tx.sender, t: 'uint32' }, { v: tx.receiver, t: 'uint32' }, { v: tx.amount, t: 'uint32' }); +} + +function mapToPoint(tx: Tx) { + const e = hash(tx); + return mcl.g1ToHex(mcl.mapToPoint(e)); +} + +contract('Tx Serialization', accounts => { + let c: TestTxInstance; + before(async function() { + await mcl.init(); + c = await TestTx.new(); + }); + it('parse transaction', async function() { + const txSize = 128; + const txs: Tx[] = []; + for (let i = 0; i < txSize; i++) { + const sender = web3.utils.hexToNumber(web3.utils.randomHex(senderLen)); + const receiver = web3.utils.hexToNumber(web3.utils.randomHex(receiverLen)); + const amount = web3.utils.hexToNumber(web3.utils.randomHex(amountLen)); + txs.push({ sender, receiver, amount }); + } + const serialized = serialize(txs); + assert.equal(txSize, (await c.size(serialized)).toNumber()); + assert.isFalse(await c.hasExcessData(serialized)); + for (let i = 0; i < txSize; i++) { + let amount = (await c.amountOf(serialized, i)).toNumber(); + assert.equal(amount, txs[i].amount); + let sender = (await c.senderOf(serialized, i)).toNumber(); + assert.equal(sender, txs[i].sender); + let receiver = (await c.receiverOf(serialized, i)).toNumber(); + assert.equal(receiver, txs[i].receiver); + let hash0 = hash(txs[i]); + let hash1 = await c.hashOf(serialized, i); + assert.equal(hash0, hash1); + let p0 = await c.mapToPoint(serialized, i); + let p1 = mapToPoint(txs[i]); + assert.equal(p1[0], bnToHex(p0[0].toString(16))); + assert.equal(p1[1], bnToHex(p0[1].toString(16))); + } + }); +}); diff --git a/truffle-config.js b/truffle-config.js index 9039199..cfa2390 100644 --- a/truffle-config.js +++ b/truffle-config.js @@ -42,10 +42,10 @@ module.exports = { settings: { // See the solidity docs for advice about optimization and evmVersion optimizer: { - enabled: false, - runs: 200, + enabled: true, + runs: 999999, }, - evmVersion: "byzantium", + evmVersion: "istanbul", }, }, },