diff --git a/common-files/classes/transaction.mjs b/common-files/classes/transaction.mjs index 0c2f87a9f..fb1300e37 100644 --- a/common-files/classes/transaction.mjs +++ b/common-files/classes/transaction.mjs @@ -4,6 +4,7 @@ /** An optimistic Transaction class */ +import config from 'config'; import gen from 'general-number'; import Web3 from '../utils/web3.mjs'; import { compressProof } from '../utils/curve-maths/curves.mjs'; @@ -11,24 +12,43 @@ import { compressProof } from '../utils/curve-maths/curves.mjs'; const { generalise } = gen; const TOKEN_TYPES = { ERC20: 0, ERC721: 1, ERC1155: 2 }; +const { TRANSACTION_TYPES } = config; // function to compute the keccak hash of a transaction function keccak(preimage) { const web3 = Web3.connection(); - // compute the solidity hash, using suitable type conversions - return web3.utils.soliditySha3( - { t: 'uint112', v: preimage.value }, - ...preimage.historicRootBlockNumberL2.map(hi => ({ t: 'uint256', v: hi })), - { t: 'uint8', v: preimage.transactionType }, - { t: 'uint8', v: preimage.tokenType }, - { t: 'bytes32', v: preimage.tokenId }, - { t: 'bytes32', v: preimage.ercAddress }, - { t: 'bytes32', v: preimage.recipientAddress }, - ...preimage.commitments.map(ch => ({ t: 'bytes32', v: ch })), - ...preimage.nullifiers.map(nh => ({ t: 'bytes32', v: nh })), - ...preimage.compressedSecrets.map(es => ({ t: 'bytes32', v: es })), - ...compressProof(preimage.proof).map(p => ({ t: 'uint', v: p })), - ); + const { + value, + historicRootBlockNumberL2, + transactionType, + tokenType, + tokenId, + ercAddress, + recipientAddress, + commitments, + nullifiers, + compressedSecrets, + } = preimage; + let { proof } = preimage; + proof = compressProof(proof); + const transaction = [ + value, + historicRootBlockNumberL2, + transactionType, + tokenType, + tokenId, + ercAddress, + recipientAddress, + commitments, + nullifiers, + compressedSecrets, + proof, + ]; + const encodedTransaction = web3.eth.abi.encodeParameters([TRANSACTION_TYPES], [transaction]); + return web3.utils.soliditySha3({ + t: 'bytes', + v: encodedTransaction, + }); } class Transaction { diff --git a/config/default.js b/config/default.js index 28c4a2ada..178a9b0bd 100644 --- a/config/default.js +++ b/config/default.js @@ -78,6 +78,9 @@ module.exports = { BN128_GROUP_ORDER: 21888242871839275222246405745257275088548364400416034343698204186575808495617n, BN128_PRIME_FIELD: 21888242871839275222246405745257275088696311157297823662689037894645226208583n, TRANSACTIONS_PER_BLOCK: Number(process.env.TRANSACTIONS_PER_BLOCK) || 2, + BLOCK_TYPES: '(uint48,address,bytes32,uint256,bytes32,bytes32)', + TRANSACTION_TYPES: + '(uint112,uint64[2],uint8,uint8,bytes32,bytes32,bytes32,bytes32[2],bytes32[2],bytes32[8],uint[4])', PROPOSE_BLOCK_TYPES: [ '(uint48,address,bytes32,uint256,bytes32,bytes32)', '(uint112,uint64[2],uint8,uint8,bytes32,bytes32,bytes32,bytes32[2],bytes32[2],bytes32[8],uint[4])[]', diff --git a/nightfall-client/src/event-handlers/block-proposed.mjs b/nightfall-client/src/event-handlers/block-proposed.mjs index 3ec84fb84..ea7737194 100644 --- a/nightfall-client/src/event-handlers/block-proposed.mjs +++ b/nightfall-client/src/event-handlers/block-proposed.mjs @@ -17,7 +17,6 @@ import { saveTransaction, saveBlock, setTransactionHashSiblingInfo, - setTransactionsHashForBlock, } from '../services/database.mjs'; import { decryptCommitment } from '../services/commitment-sync.mjs'; @@ -114,7 +113,6 @@ async function blockProposedEventHandler(data) { if (await isTransactionHashWithdraw(transactionHash)) { const siblingPathTransactionHash = updatedTransctionHashesTimber.getSiblingPath(transactionHash); - await setTransactionsHashForBlock(transactionHash, block.transactionsHash); return setTransactionHashSiblingInfo( transactionHash, siblingPathTransactionHash, diff --git a/nightfall-client/src/services/database.mjs b/nightfall-client/src/services/database.mjs index e1f9297a1..92af8efa7 100644 --- a/nightfall-client/src/services/database.mjs +++ b/nightfall-client/src/services/database.mjs @@ -266,17 +266,6 @@ export async function getTransactionByTransactionHash(transactionHash) { return db.collection(TRANSACTIONS_COLLECTION).findOne(query); } -// function to set the path of the transaction hash leaf in transaction hash timber -export async function setTransactionsHashForBlock(transactionHash, transactionsHash) { - const connection = await mongo.connection(MONGO_URL); - const query = { transactionHash }; - const update = { - $set: { transactionsHash }, - }; - const db = connection.db(COMMITMENTS_DB); - return db.collection(TRANSACTIONS_COLLECTION).updateMany(query, update); -} - // function to set the path of the transaction hash leaf in transaction hash timber export async function setTransactionHashSiblingInfo( transactionHash, diff --git a/nightfall-client/src/services/finalise-withdrawal.mjs b/nightfall-client/src/services/finalise-withdrawal.mjs index 957f6e88b..de3a0ed70 100644 --- a/nightfall-client/src/services/finalise-withdrawal.mjs +++ b/nightfall-client/src/services/finalise-withdrawal.mjs @@ -40,7 +40,6 @@ export async function finaliseWithdrawal(transactionHash) { const rawTransaction = await shieldContractInstance.methods .finaliseWithdrawal( buildSolidityStruct(block), - block.transactionsHash, Transaction.buildSolidityStruct(transactions[index]), index, siblingPath, diff --git a/nightfall-client/src/services/instant-withdrawal.mjs b/nightfall-client/src/services/instant-withdrawal.mjs index e5c1c0b57..eb4a40d67 100644 --- a/nightfall-client/src/services/instant-withdrawal.mjs +++ b/nightfall-client/src/services/instant-withdrawal.mjs @@ -26,7 +26,6 @@ const setInstantWithdrawl = async ({ transactionHash }) => { const rawTransaction = await shieldContractInstance.methods .setAdvanceWithdrawalFee( buildSolidityStruct(block), - block.transactionsHash, Transaction.buildSolidityStruct(transactions[index]), index, siblingPath, diff --git a/nightfall-client/src/services/process-calldata.mjs b/nightfall-client/src/services/process-calldata.mjs index 16021b560..1bfa3efc3 100644 --- a/nightfall-client/src/services/process-calldata.mjs +++ b/nightfall-client/src/services/process-calldata.mjs @@ -63,12 +63,6 @@ async function getProposeBlockCalldata(eventData) { }); block.transactionHashes = transactions.map(t => t.transactionHash); - const encodedTransactions = `0x${tx.input.slice(394)}`; // retrieve only transactions data - block.transactionsHash = web3.utils.soliditySha3({ - t: 'bytes', - v: encodedTransactions, - }); - return { transactions, block }; } diff --git a/nightfall-client/src/services/valid-withdrawal.mjs b/nightfall-client/src/services/valid-withdrawal.mjs index 00f7d85bd..6d2c5ecc7 100644 --- a/nightfall-client/src/services/valid-withdrawal.mjs +++ b/nightfall-client/src/services/valid-withdrawal.mjs @@ -17,7 +17,6 @@ export async function isValidWithdrawal({ block, transaction, index, siblingPath const valid = await shieldContractInstance.methods .isValidWithdrawal( buildSolidityStruct(block), - block.transactionsHash, Transaction.buildSolidityStruct(transaction), index, siblingPath, diff --git a/nightfall-deployer/contracts/Challenges.sol b/nightfall-deployer/contracts/Challenges.sol index eb28d90f5..1c8a37454 100644 --- a/nightfall-deployer/contracts/Challenges.sol +++ b/nightfall-deployer/contracts/Challenges.sol @@ -39,8 +39,8 @@ contract Challenges is Stateful, Key_Registry, Config { ) external onlyBootChallenger { checkCommit(msg.data); // check if the block hash is correct and the block hash exists for the block and prior block. Also if the transactions are part of these block - state.isBlockReal(priorBlockL2, priorBlockTransactions); - state.isBlockReal(blockL2, transactions); + state.areBlockAndTransactionsReal(priorBlockL2, priorBlockTransactions); + state.areBlockAndTransactionsReal(blockL2, transactions); ChallengesUtil.libChallengeLeafCountCorrect( priorBlockL2, priorBlockTransactions, @@ -73,8 +73,8 @@ contract Challenges is Stateful, Key_Registry, Config { ) external onlyBootChallenger { checkCommit(msg.data); // check if the block hash is correct and the block hash exists for the block and prior block - state.isBlockReal(priorBlockL2, priorBlockTransactions); - state.isBlockReal(blockL2, transactions); + state.areBlockAndTransactionsReal(priorBlockL2, priorBlockTransactions); + state.areBlockAndTransactionsReal(blockL2, transactions); // see if the challenge is valid ChallengesUtil.libChallengeNewRootCorrect( priorBlockL2, @@ -102,8 +102,8 @@ contract Challenges is Stateful, Key_Registry, Config { ) external onlyBootChallenger { checkCommit(msg.data); // first, check we have real, in-train, contiguous blocks - state.isBlockReal(block1, transactions1); - state.isBlockReal(block2, transactions2); + state.areBlockAndTransactionsReal(block1, transactions1); + state.areBlockAndTransactionsReal(block2, transactions2); // If the duplicate exists in the same block, the index cannot be the same if (block1.blockNumberL2 == block2.blockNumberL2) require(transactionIndex1 != transactionIndex2, 'Cannot be the same index'); @@ -128,7 +128,7 @@ contract Challenges is Stateful, Key_Registry, Config { bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); - state.isBlockReal(blockL2, transactions); + state.areBlockAndTransactionsReal(blockL2, transactions); ChallengesUtil.libChallengeTransactionType(transactions[transactionIndex]); // Delete the latest block of the two challengeAccepted(blockL2); @@ -143,7 +143,7 @@ contract Challenges is Stateful, Key_Registry, Config { bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); - state.isBlockReal(blockL2, transactions); + state.areBlockAndTransactionsReal(blockL2, transactions); // first check the transaction and block do not overflow ChallengesUtil.libCheckOverflows(blockL2, transactions[transactionIndex]); // now we need to check that the proof is correct @@ -171,8 +171,8 @@ contract Challenges is Stateful, Key_Registry, Config { bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); - state.isBlockReal(blockL2, transactions); - state.isBlockReal( + state.areBlockAndTransactionsReal(blockL2, transactions); + state.areBlockAndTransactionsReal( blockL2ContainingHistoricRoot, transactionsOfblockL2ContainingHistoricRoot ); @@ -209,12 +209,12 @@ contract Challenges is Stateful, Key_Registry, Config { bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); - state.isBlockReal(blockL2, transactions); - state.isBlockReal( + state.areBlockAndTransactionsReal(blockL2, transactions); + state.areBlockAndTransactionsReal( blockL2ContainingHistoricRoot[0], transactionsOfblockL2ContainingHistoricRoot ); - state.isBlockReal( + state.areBlockAndTransactionsReal( blockL2ContainingHistoricRoot[1], transactionsOfblockL2ContainingHistoricRoot2 ); @@ -264,8 +264,8 @@ contract Challenges is Stateful, Key_Registry, Config { txs2[transactionIndex2], nullifierIndex2 ); - state.isBlockReal(block1, txs1); - state.isBlockReal(block2, txs2); + state.areBlockAndTransactionsReal(block1, txs1); + state.areBlockAndTransactionsReal(block2, txs2); // The blocks are different and we prune the later block of the two // as we have a block number, it's easy to see which is the latest. @@ -288,7 +288,7 @@ contract Challenges is Stateful, Key_Registry, Config { bytes32 salt ) external onlyBootChallenger { checkCommit(msg.data); - state.isBlockReal(blockL2, transactions); + state.areBlockAndTransactionsReal(blockL2, transactions); if ( transactions[transactionIndex].transactionType == Structures.TransactionTypes.DOUBLE_TRANSFER @@ -319,20 +319,6 @@ contract Challenges is Stateful, Key_Registry, Config { challengeAccepted(blockL2); } - /* - This is a challenge that transactionHashesRoot is incorrectly calculated - */ - function challengeTransactionHashesRoot( - Block memory blockL2, - Transaction[] memory transactions, - bytes32 salt - ) external onlyBootChallenger { - checkCommit(msg.data); - bytes32 currentBlockHash = state.isBlockReal(blockL2, transactions); - ChallengesUtil.libChallengeTransactionHashesRoot(blockL2, transactions, currentBlockHash); - challengeAccepted(blockL2); - } - // This gets called when a challenge succeeds function challengeAccepted(Block memory badBlock) private { // Check to ensure that the block being challenged is less than a week old diff --git a/nightfall-deployer/contracts/ChallengesUtil.sol b/nightfall-deployer/contracts/ChallengesUtil.sol index bf5e08fc7..faf9e3a68 100644 --- a/nightfall-deployer/contracts/ChallengesUtil.sol +++ b/nightfall-deployer/contracts/ChallengesUtil.sol @@ -9,9 +9,10 @@ import './Structures.sol'; library ChallengesUtil { bytes32 public constant ZERO = 0x0000000000000000000000000000000000000000000000000000000000000000; - uint256 public constant MAX31 = 2 ** 249 - 1; - uint256 public constant MAX20 = 2 ** 161 - 1; - uint256 public constant BN128_GROUP_ORDER = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + uint256 public constant MAX31 = 2**249 - 1; + uint256 public constant MAX20 = 2**161 - 1; + uint256 public constant BN128_GROUP_ORDER = + 21888242871839275222246405745257275088548364400416034343698204186575808495617; function libChallengeLeafCountCorrect( Structures.Block memory priorBlockL2, @@ -207,16 +208,19 @@ library ChallengesUtil { } function libCheckOverflows( - Structures.Block calldata blockL2, - Structures.Transaction calldata transaction + Structures.Block calldata blockL2, + Structures.Transaction calldata transaction ) public pure { - require(uint256(transaction.ercAddress) <= MAX20, 'ERC address out of range'); - require(uint256(transaction.recipientAddress) <= MAX20, 'Recipient ERC address out of range'); - require(uint256(transaction.commitments[0]) <= MAX31, 'Commitment 0 out of range'); - require(uint256(transaction.commitments[1]) <= MAX31, 'Commitment 1 out of range'); - require(uint256(transaction.nullifiers[0]) <= MAX31, 'Nullifier 0 out of range'); - require(uint256(transaction.nullifiers[1]) <= MAX31, 'Nullifier 1 out of range'); - require(uint256(blockL2.root) < BN128_GROUP_ORDER, 'root out of range'); + require(uint256(transaction.ercAddress) <= MAX20, 'ERC address out of range'); + require( + uint256(transaction.recipientAddress) <= MAX20, + 'Recipient ERC address out of range' + ); + require(uint256(transaction.commitments[0]) <= MAX31, 'Commitment 0 out of range'); + require(uint256(transaction.commitments[1]) <= MAX31, 'Commitment 1 out of range'); + require(uint256(transaction.nullifiers[0]) <= MAX31, 'Nullifier 0 out of range'); + require(uint256(transaction.nullifiers[1]) <= MAX31, 'Nullifier 1 out of range'); + require(uint256(blockL2.root) < BN128_GROUP_ORDER, 'root out of range'); } function libChallengeNullifier( @@ -234,24 +238,4 @@ library ChallengesUtil { 'Transactions need to be different' ); } - - function libChallengeTransactionHashesRoot( - Structures.Block memory blockL2, - Structures.Transaction[] memory transactions, - bytes32 currentBlockHash - ) public pure { - bytes32 correctBlockHash = - keccak256( - abi.encode( - blockL2.leafCount, - blockL2.proposer, - blockL2.root, - blockL2.blockNumberL2, - blockL2.previousBlockHash, - Utils.hashTransactionHashes(transactions), - transactions - ) - ); - require(correctBlockHash != currentBlockHash, 'txHashRoot incorrect'); - } } diff --git a/nightfall-deployer/contracts/MerkleTree_Stateless_KECCAK.sol b/nightfall-deployer/contracts/MerkleTree_Stateless_KECCAK.sol deleted file mode 100644 index 45143d603..000000000 --- a/nightfall-deployer/contracts/MerkleTree_Stateless_KECCAK.sol +++ /dev/null @@ -1,275 +0,0 @@ -// SPDX-License-Identifier: CC0-1.0 - -/** -A base contract which handles Merkle Tree inserts (and consequent updates to the root and 'frontier' (see below)). -The intention is for other 'derived' contracts to import this contract, and for those derived contracts to manage permissions to actually call the insertLeaf/insertleaves functions of this base contract. - -*Note* that this version has been modified so that it does not store any state, -Thus, it is up to the calling contract to store the updated leafCount, frontier and root; these values being returned. This is so MerkleTree.sol can be called to test if an optimistic proposal is valid, without having the Merkle tree updated with what might not be valid information (and will probably be out-of-sequence information). This means all functions are pure. - -@Author iAmMichaelConnor, ChaitanyaKonda -*/ - -pragma solidity ^0.8.0; - -library MerkleTree_Stateless_KECCAK { - /* - @notice Explanation of the Merkle Tree in this contract: - This is an append-only merkle tree; populated from left to right. - We do not store all of the merkle tree's nodes. We only store the right-most 'frontier' of nodes required to calculate the new root when the next new leaf value is added. - TREE (not stored) FRONTIER (stored) - 0 ? - / \ - 1 2 ? - / \ / \ - 3 4 5 6 ? - / \ / \ / \ / \ - 7 8 9 10 11 12 13 14 ? - / \ / \ / \ / \ / \ / \ / \ / \ - 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 ? - level row width start# end# - 4 0 2^0=1 w=0 2^1-1=0 - 3 1 2^1=2 w=1 2^2-1=2 - 2 2 2^2=4 w=3 2^3-1=6 - 1 3 2^3=8 w=7 2^4-1=14 - 0 4 2^4=16 w=15 2^5-1=30 - height = 4 - w = width = 2 ** height = 2^4 = 16 - #nodes = (2 ** (height + 1)) - 1 = 2^5-1 = 31 - */ - - /* event Output( - bytes32 left, - bytes32 right, - bytes32[1] output, - uint256 prevNodeIndex, - uint256 nodeIndex - ); // for debugging only */ - - uint256 constant treeHeight = 5; - uint256 constant treeWidth = 2**treeHeight; // 2 ** treeHeight - - bytes32 constant zero = 0x0000000000000000000000000000000000000000000000000000000000000000; - - /** - @notice Get the index of the frontier (or 'storage slot') into which we will next store a nodeValue (based on the leafIndex currently being inserted). See the top-level README for a detailed explanation. - @return slot - the index of the frontier (or 'storage slot') into which we will next store a nodeValue - */ - function getFrontierSlot(uint256 leafIndex) public pure returns (uint256 slot) { - slot = 0; - if (leafIndex % 2 == 1) { - uint256 exp1 = 1; - uint256 pow1 = 2; - uint256 pow2 = pow1 << 1; - while (slot == 0) { - if ((leafIndex + 1 - pow1) % pow2 == 0) { - slot = exp1; - } else { - pow1 = pow2; - pow2 = pow2 << 1; - exp1++; - } - } - } - } - - /** - @notice Insert a leaf into the Merkle Tree, update the root, and update any values in the (persistently stored) frontier. - @param leafValue - the value of the leaf being inserted. - @return root - the root of the merkle tree, after the insert. - */ - function insertLeaf( - bytes32 leafValue, - bytes32[treeHeight + 1] memory frontier, - uint256 leafCount - ) - public - pure - returns ( - bytes32 root, - bytes32[treeHeight + 1] memory, - uint256 - ) - { - // check that space exists in the tree: - require(treeWidth > leafCount, 'There is no space left in the tree.'); - - uint256 slot = getFrontierSlot(leafCount); - uint256 nodeIndex = leafCount + treeWidth - 1; - bytes32 nodeValue = leafValue; // nodeValue is the hash, which iteratively gets overridden to the top of the tree until it becomes the root. - - bytes32 leftInput; - bytes32 rightInput; - bytes32[1] memory output; // output of the hash function - - for (uint256 level = 0; level < treeHeight; level++) { - if (level == slot) frontier[slot] = nodeValue; - - if (nodeIndex % 2 == 0) { - // even nodeIndex - leftInput = frontier[level]; - rightInput = nodeValue; - - // compute the hash of the inputs: - output[0] = keccak256(abi.encodePacked(leftInput, rightInput)); - - nodeValue = output[0]; // the parentValue, but will become the nodeValue of the next level - nodeIndex = (nodeIndex - 1) / 2; // move one row up the tree - } else { - // odd nodeIndex - leftInput = nodeValue; - rightInput = zero; - - // compute the hash of the inputs: - output[0] = keccak256(abi.encodePacked(leftInput, rightInput)); - - nodeValue = output[0]; // the parentValue, but will become the nodeValue of the next level - nodeIndex = nodeIndex / 2; // move one row up the tree - } - } - - root = nodeValue; - - leafCount++; // the incrememnting of leafCount costs us 20k for the first leaf, and 5k thereafter - - return (root, frontier, leafCount); //the root of the tree - } - - /** - @notice Insert multiple leaves into the Merkle Tree, and then update the root, and update any values in the (persistently stored) frontier. - @param leafValues - the values of the leaves being inserted. - @return root - the root of the merkle tree, after all the inserts. - */ - function insertLeaves( - bytes32[] memory leafValues, - bytes32[treeHeight + 1] memory frontier, - uint256 leafCount - ) - public - pure - returns ( - bytes32 root, - bytes32[treeHeight + 1] memory, - uint256 - ) - { - uint256 numberOfLeaves = leafValues.length; - - // check that space exists in the tree: - require(treeWidth > leafCount, 'There is no space left in the tree.'); - if (numberOfLeaves > treeWidth - leafCount) { - uint256 numberOfExcessLeaves = numberOfLeaves - (treeWidth - leafCount); - // remove the excess leaves, because we only want to emit those we've added as an event: - for (uint256 xs = 0; xs < numberOfExcessLeaves; xs++) { - /** - CAUTION!!! This attempts to succinctly achieve leafValues.pop() on a **memory** dynamic array. Not thoroughly tested! - Credit: https://ethereum.stackexchange.com/a/51897/45916 - */ - assembly { - mstore(leafValues, sub(mload(leafValues), 1)) - } - } - numberOfLeaves = treeWidth - leafCount; - } - - uint256 slot; - uint256 nodeIndex; - /* uint256 prevNodeIndex; */ - bytes32 nodeValue; - - bytes32 leftInput; - bytes32 rightInput; - bytes32[1] memory output; // the output of the hash - /* bool success; */ - - // consider each new leaf in turn, from left to right: - for (uint256 leafIndex = leafCount; leafIndex < leafCount + numberOfLeaves; leafIndex++) { - nodeValue = leafValues[leafIndex - leafCount]; - nodeIndex = leafIndex + treeWidth - 1; // convert the leafIndex to a nodeIndex - - slot = getFrontierSlot(leafIndex); // determine at which level we will next need to store a nodeValue - - if (slot == 0) { - frontier[slot] = nodeValue; // store in frontier - continue; - } - - // hash up to the level whose nodeValue we'll store in the frontier slot: - for (uint256 level = 1; level <= slot; level++) { - if (nodeIndex % 2 == 0) { - // even nodeIndex - leftInput = frontier[level - 1]; - rightInput = nodeValue; - // compute the hash of the inputs: - output[0] = keccak256(abi.encodePacked(leftInput, rightInput)); - nodeValue = output[0]; // the parentValue, but will become the nodeValue of the next level - /* prevNodeIndex = nodeIndex; */ - nodeIndex = (nodeIndex - 1) / 2; // move one row up the tree - /* emit Output(leftInput, rightInput, output, prevNodeIndex, nodeIndex); // for debugging only */ - } else { - // odd nodeIndex - leftInput = nodeValue; - rightInput = zero; - // compute the hash of the inputs: - output[0] = keccak256(abi.encodePacked(leftInput, rightInput)); - nodeValue = output[0]; // the parentValue, but will become the nodeValue of the next level - /* prevNodeIndex = nodeIndex; */ - nodeIndex = nodeIndex / 2; // the parentIndex, but will become the nodeIndex of the next level - /* emit Output(leftInput, rightInput, output, prevNodeIndex, nodeIndex); // for debugging only */ - } - } - frontier[slot] = nodeValue; // store in frontier - } - - // So far we've added all leaves, and hashed up to a particular level of the tree. We now need to continue hashing from that level until the root: - for (uint256 level = slot + 1; level <= treeHeight; level++) { - if (nodeIndex % 2 == 0) { - // even nodeIndex - leftInput = frontier[level - 1]; - rightInput = nodeValue; - // compute the hash of the inputs: - output[0] = keccak256(abi.encodePacked(leftInput, rightInput)); - nodeValue = output[0]; // the parentValue, but will become the nodeValue of the next level - /* prevNodeIndex = nodeIndex; */ - nodeIndex = (nodeIndex - 1) / 2; // the parentIndex, but will become the nodeIndex of the next level - /* emit Output(leftInput, rightInput, output, prevNodeIndex, nodeIndex); // for debugging only */ - } else { - // odd nodeIndex - leftInput = nodeValue; - rightInput = zero; - // compute the hash of the inputs: - output[0] = keccak256(abi.encodePacked(leftInput, rightInput)); - nodeValue = output[0]; // the parentValue, but will become the nodeValue of the next level - /* prevNodeIndex = nodeIndex; */ - nodeIndex = nodeIndex / 2; // the parentIndex, but will become the nodeIndex of the next level - /* emit Output(leftInput, rightInput, output, prevNodeIndex, nodeIndex); // for debugging only */ - } - } - - root = nodeValue; - - leafCount += numberOfLeaves; // the incrememnting of leafCount costs us 20k for the first leaf, and 5k thereafter - - return (root, frontier, leafCount); //the root of the tree - } - - function checkPath( - bytes32[treeHeight + 1] memory siblingPath, - uint256 leafIndex, - bytes32 node - ) public pure returns (bool, bytes32[treeHeight + 1] memory) { - bytes32[treeHeight + 1] memory frontier; - - for (uint256 i = treeHeight; i > 0; i--) { - frontier[i] = node; - if (leafIndex % 2 == 0) { - node = keccak256(abi.encodePacked(node, siblingPath[i])); - } else { - node = keccak256(abi.encodePacked(siblingPath[i], node)); - } - leafIndex = leafIndex >> 1; - } - frontier[0] = node; - return (siblingPath[0] == node, frontier); - } -} diff --git a/nightfall-deployer/contracts/Shield.sol b/nightfall-deployer/contracts/Shield.sol index ee7c9cfe9..8e4eabd12 100644 --- a/nightfall-deployer/contracts/Shield.sol +++ b/nightfall-deployer/contracts/Shield.sol @@ -47,7 +47,7 @@ contract Shield is Stateful, Structures, Config, Key_Registry, ReentrancyGuardUp // function to enable a proposer to get paid for proposing a block function requestBlockPayment(Block memory b, Transaction[] memory ts) external { - bytes32 blockHash = state.isBlockReal(b, ts); + bytes32 blockHash = state.areBlockAndTransactionsReal(b, ts); // check that the block has been finalised uint256 time = state.getBlockData(b.blockNumberL2).time; require( @@ -97,13 +97,12 @@ contract Shield is Stateful, Structures, Config, Key_Registry, ReentrancyGuardUp */ function isValidWithdrawal( Block memory b, - bytes32 transactionsHash, Transaction memory t, uint256 index, bytes32[6] memory siblingPath ) external view returns (bool) { // check this block is a real one, in the queue, not something made up. - state.areBlockAndTransactionValid(b, transactionsHash, t, index, siblingPath); + state.areBlockAndTransactionReal(b, t, index, siblingPath); // check that the block has been finalised uint256 time = state.getBlockData(b.blockNumberL2).time; require( @@ -131,13 +130,12 @@ contract Shield is Stateful, Structures, Config, Key_Registry, ReentrancyGuardUp function finaliseWithdrawal( Block calldata b, - bytes32 transactionsHash, Transaction calldata t, uint256 index, bytes32[6] memory siblingPath ) external { // check this block is a real one, in the queue, not something made up and that the transaction exists in the block - state.areBlockAndTransactionValid(b, transactionsHash, t, index, siblingPath); + state.areBlockAndTransactionReal(b, t, index, siblingPath); // check that the block has been finalised uint256 time = state.getBlockData(b.blockNumberL2).time; require( @@ -209,7 +207,6 @@ contract Shield is Stateful, Structures, Config, Key_Registry, ReentrancyGuardUp // TODO Is there a better way to set this fee, e.g. at the point of making a transaction. function setAdvanceWithdrawalFee( Block memory b, - bytes32 transactionsHash, Transaction memory t, uint256 index, bytes32[6] memory siblingPath @@ -218,7 +215,7 @@ contract Shield is Stateful, Structures, Config, Key_Registry, ReentrancyGuardUp require(t.transactionType == TransactionTypes.WITHDRAW, 'Can only advance withdrawals'); // check this block is a real one, in the queue, not something made up. - state.areBlockAndTransactionValid(b, transactionsHash, t, index, siblingPath); + state.areBlockAndTransactionReal(b, t, index, siblingPath); bytes32 withdrawTransactionHash = Utils.hashTransaction(t); // The withdrawal has not been withdrawn diff --git a/nightfall-deployer/contracts/State.sol b/nightfall-deployer/contracts/State.sol index e7c99fc4a..fba49ffcd 100644 --- a/nightfall-deployer/contracts/State.sol +++ b/nightfall-deployer/contracts/State.sol @@ -12,7 +12,6 @@ pragma solidity ^0.8.0; import './Structures.sol'; import './Utils.sol'; import './Config.sol'; -import './MerkleTree_Stateless_KECCAK.sol'; contract State is Structures, Initializable, ReentrancyGuardUpgradeable, Config { // global state variables @@ -82,6 +81,57 @@ contract State is Structures, Initializable, ReentrancyGuardUpgradeable, Config require(b.proposer == msg.sender, 'The proposer address is not the sender'); // set the maximum tx/block to prevent unchallengably large blocks require(t.length < 33, 'The block has too many transactions'); + + bytes32 blockHash; + assembly { + let blockPos := mload(0x40) // get empty memory location pointer + calldatacopy(blockPos, 4, add(mul(t.length, 0x300), 0x100)) // copy calldata into this location. 0x300 is 768 bytes of data for each transaction. 0x100 is 192 bytes of block data, 32 bytes for transactions array memory and size each. TODO skip this by passing parameters in memory. But inline assembly to destructure struct array is not straight forward + let transactionPos := add(blockPos, 0x100) // calculate memory location of transactions data copied + let transactionHashesPos := add(transactionPos, mul(t.length, 0x300)) // calculate memory location to store transaction hashes to be calculated + // calculate and store transaction hashes + for { + let i := 0 + } lt(i, t.length) { + i := add(i, 1) + } { + mstore( + add(transactionHashesPos, mul(0x20, i)), + keccak256(add(transactionPos, mul(0x300, i)), 0x300) + ) + } + let transactionHashesRoot + // calculate and store transaction hashes root + for { + let i := 5 + } gt(i, 0) { + i := sub(i, 1) + } { + for { + let j := 0 + } lt(j, exp(2, sub(i, 1))) { + j := add(j, 1) + } { + let left := mload(add(transactionHashesPos, mul(mul(0x20, j), 2))) + let right := mload(add(transactionHashesPos, add(mul(mul(0x20, j), 2), 0x20))) + if eq(and(iszero(left), iszero(right)), 1) { + transactionHashesRoot := 0 + } // returns bool + if eq(and(iszero(left), iszero(right)), 0) { + transactionHashesRoot := keccak256( + add(transactionHashesPos, mul(mul(0x20, j), 2)), + 0x40 + ) + } // returns bool + mstore(add(transactionHashesPos, mul(0x20, j)), transactionHashesRoot) + } + } + // check if the transaction hashes root calculated equal to the one passed as part of block data + if eq(eq(mload(add(blockPos, mul(5, 0x20))), transactionHashesRoot), 0) { + revert(0, 0) + } + // calculate block hash + blockHash := keccak256(blockPos, mul(6, 0x20)) + } // We need to set the blockHash on chain here, because there is no way to // convince a challenge function of the (in)correctness by an offchain // computation; the on-chain code doesn't save the pre-image of the hash so @@ -91,12 +141,7 @@ contract State is Structures, Initializable, ReentrancyGuardUpgradeable, Config // All check pass so add the block to the list of blocks waiting to be permanently added to the state - we only save the hash of the block data plus the absolute minimum of metadata - it's up to the challenger, or person requesting inclusion of the block to the permanent contract state, to provide the block data. // blockHash is hash of all block data and hash of all the transactions data. - blockHashes.push( - BlockData({ - blockHash: keccak256(abi.encodePacked(msg.data[4:196], keccak256(msg.data[196:]))), - time: block.timestamp - }) - ); + blockHashes.push(BlockData({blockHash: blockHash, time: block.timestamp})); // Timber will listen for the BlockProposed event as well as // nightfall-optimist. The current, optimistic version of Timber does not // require the smart contract to craft NewLeaf/NewLeaves events. @@ -209,27 +254,34 @@ contract State is Structures, Initializable, ReentrancyGuardUpgradeable, Config // Checks if a block is actually referenced in the queue of blocks waiting // to go into the Shield state (stops someone challenging with a non-existent // block). - function isBlockReal(Block memory b, Transaction[] memory ts) public view returns (bytes32) { - bytes32 blockHash = Utils.hashBlock(b, ts); + function areBlockAndTransactionsReal(Block memory b, Transaction[] memory ts) + public + view + returns (bytes32) + { + bytes32 blockHash = Utils.hashBlock(b); require(blockHashes[b.blockNumberL2].blockHash == blockHash, 'This block does not exist'); + bytes32 tranasactionHashesRoot = Utils.hashTransactionHashes(ts); + require( + b.transactionHashesRoot == tranasactionHashesRoot, + 'Some of these transactions are not in this block' + ); return blockHash; } - function areBlockAndTransactionValid( + function areBlockAndTransactionReal( Block memory b, - bytes32 transactionsHash, Transaction memory t, uint256 index, bytes32[6] memory siblingPath ) public view { - bytes32 blockHash = Utils.hashBlock(b, transactionsHash); + bytes32 blockHash = Utils.hashBlock(b); require(blockHashes[b.blockNumberL2].blockHash == blockHash, 'This block does not exist'); require( b.transactionHashesRoot == siblingPath[0], 'This transaction hashes root is incorrect' ); - (bool valid, ) = - MerkleTree_Stateless_KECCAK.checkPath(siblingPath, index, Utils.hashTransaction(t)); + bool valid = Utils.checkPath(siblingPath, index, Utils.hashTransaction(t)); require(valid, 'Transaction does not exist in block'); } diff --git a/nightfall-deployer/contracts/Utils.sol b/nightfall-deployer/contracts/Utils.sol index 4c5433a18..b3157b48e 100644 --- a/nightfall-deployer/contracts/Utils.sol +++ b/nightfall-deployer/contracts/Utils.sol @@ -3,45 +3,17 @@ pragma solidity ^0.8.0; import './Structures.sol'; -import './MerkleTree_Stateless_KECCAK.sol'; library Utils { bytes32 public constant ZERO = bytes32(0); uint256 constant TRANSACTIONS_BATCH_SIZE = 6; // TODO Change this from 2 to an appropriate value to control stack too deep error function hashTransaction(Structures.Transaction memory t) internal pure returns (bytes32) { - return - keccak256( - abi.encodePacked( - t.value, - t.historicRootBlockNumberL2, - t.transactionType, - t.tokenType, - t.tokenId, - t.ercAddress, // Take in as bytes32 for consistent hashing - t.recipientAddress, - t.commitments, - t.nullifiers, - t.compressedSecrets, - t.proof - ) - ); - } - - function hashBlock(Structures.Block memory b, Structures.Transaction[] memory t) - internal - pure - returns (bytes32) - { - return keccak256(abi.encode(b, t)); + return keccak256(abi.encode(t)); } - function hashBlock(Structures.Block memory b, bytes32 transactionsHash) - internal - pure - returns (bytes32) - { - return keccak256(abi.encode(b, transactionsHash)); + function hashBlock(Structures.Block memory b) internal pure returns (bytes32) { + return keccak256(abi.encode(b)); } function hashTransactionHashes(Structures.Transaction[] memory ts) @@ -60,15 +32,7 @@ library Utils { i % TRANSACTIONS_BATCH_SIZE == TRANSACTIONS_BATCH_SIZE - 1 || i == (ts.length - 1) ) { // If the number of transactions cannot be factored by TRANSACTIONS_BATCH_SIZE, then the second condtional check ensures that this smaller transactions batch is also included - ( - transactionHashesRoot, - frontier, - transactionHashCount - ) = MerkleTree_Stateless_KECCAK.insertLeaves( - transactionHashes, - frontier, - transactionHashCount - ); + (transactionHashesRoot) = calculateMerkleRoot(transactionHashes); } } return transactionHashesRoot; @@ -222,4 +186,46 @@ library Utils { } return inputs; } + + function calculateMerkleRoot(bytes32[] memory leaves) public pure returns (bytes32 result) { + assembly { + for { + let i := 5 + } gt(i, 0) { + i := sub(i, 1) + } { + for { + let j := 0 + } lt(j, exp(2, sub(i, 1))) { + j := add(j, 1) + } { + let left := mload(add(leaves, add(0x20, mul(mul(0x20, j), 2)))) + let right := mload(add(leaves, add(add(0x20, mul(mul(0x20, j), 2)), 0x20))) + if eq(and(iszero(left), iszero(right)), 1) { + result := 0 + } // returns bool + if eq(and(iszero(left), iszero(right)), 0) { + result := keccak256(add(leaves, add(0x20, mul(mul(0x20, j), 2))), 0x40) + } // returns bool + mstore(add(leaves, add(0x20, mul(0x20, j))), result) + } + } + } + } + + function checkPath( + bytes32[6] memory siblingPath, + uint256 leafIndex, + bytes32 node + ) public pure returns (bool) { + for (uint256 i = 5; i > 0; i--) { + if (leafIndex % 2 == 0) { + node = keccak256(abi.encodePacked(node, siblingPath[i])); + } else { + node = keccak256(abi.encodePacked(siblingPath[i], node)); + } + leafIndex = leafIndex >> 1; + } + return (siblingPath[0] == node); + } } diff --git a/nightfall-deployer/migrations/2_deploy_upgradeable.js b/nightfall-deployer/migrations/2_deploy_upgradeable.js index de0f99cb8..52b8ec633 100644 --- a/nightfall-deployer/migrations/2_deploy_upgradeable.js +++ b/nightfall-deployer/migrations/2_deploy_upgradeable.js @@ -3,7 +3,6 @@ const { deployProxy } = require('@openzeppelin/truffle-upgrades'); const Verifier = artifacts.require('Verifier.sol'); const Shield = artifacts.require('Shield.sol'); const MerkleTree_Stateless = artifacts.require('MerkleTree_Stateless.sol'); -const MerkleTree_Stateless_KECCAK = artifacts.require('MerkleTree_Stateless_KECCAK.sol'); const MiMC = artifacts.require('MiMC.sol'); const Structures = artifacts.require('Structures.sol'); const Config = artifacts.require('Config.sol'); @@ -25,10 +24,8 @@ module.exports = async function (deployer) { await deployer.link(MiMC, MerkleTree_Stateless); await deployer.deploy(MerkleTree_Stateless); await deployer.link(MerkleTree_Stateless, [Challenges, ChallengesUtil]); - await deployer.deploy(MerkleTree_Stateless_KECCAK); - await deployer.link(MerkleTree_Stateless_KECCAK, [State, Utils]); await deployer.deploy(Utils); - await deployer.link(Utils, [Shield, Challenges, ChallengesUtil]); + await deployer.link(Utils, [Shield, State, Challenges, ChallengesUtil]); await deployer.deploy(ChallengesUtil); await deployer.link(ChallengesUtil, Challenges); diff --git a/nightfall-deployer/migrations/4_upgrade_upgradeable.js b/nightfall-deployer/migrations/4_upgrade_upgradeable.js index eda67d155..7f90ee621 100644 --- a/nightfall-deployer/migrations/4_upgrade_upgradeable.js +++ b/nightfall-deployer/migrations/4_upgrade_upgradeable.js @@ -3,7 +3,6 @@ const { upgradeProxy } = require('@openzeppelin/truffle-upgrades'); const Verifier = artifacts.require('Verifier.sol'); const Shield = artifacts.require('Shield.sol'); const MerkleTree_Stateless = artifacts.require('MerkleTree_Stateless.sol'); -const MerkleTree_Stateless_KECCAK = artifacts.require('MerkleTree_Stateless_KECCAK.sol'); const MiMC = artifacts.require('MiMC.sol'); const Structures = artifacts.require('Structures.sol'); const Config = artifacts.require('Config.sol'); @@ -19,10 +18,8 @@ module.exports = async function (deployer) { await deployer.link(MiMC, MerkleTree_Stateless); await deployer.deploy(MerkleTree_Stateless); await deployer.link(MerkleTree_Stateless, [Challenges, ChallengesUtil]); - await deployer.deploy(MerkleTree_Stateless_KECCAK); - await deployer.link(MerkleTree_Stateless_KECCAK, [State, Utils]); await deployer.deploy(Utils); - await deployer.link(Utils, [Shield, Challenges, ChallengesUtil]); + await deployer.link(Utils, [Shield, State, Challenges, ChallengesUtil]); await deployer.deploy(ChallengesUtil); await deployer.link(ChallengesUtil, Challenges); await upgradeProxy((await Structures.deployed()).address, Structures, { diff --git a/nightfall-optimist/src/classes/block.mjs b/nightfall-optimist/src/classes/block.mjs index 720761704..2cba90c14 100644 --- a/nightfall-optimist/src/classes/block.mjs +++ b/nightfall-optimist/src/classes/block.mjs @@ -4,17 +4,10 @@ An optimistic layer 2 Block class import config from 'config'; import Timber from 'common-files/classes/timber.mjs'; import Web3 from 'common-files/utils/web3.mjs'; -import { compressProof } from 'common-files/utils/curve-maths/curves.mjs'; import { getLatestTree, getLatestBlockInfo } from '../services/database.mjs'; -const { - ZERO, - HASH_TYPE, - TIMBER_HEIGHT, - TXHASH_TREE_HASH_TYPE, - TXHASH_TREE_HEIGHT, - PROPOSE_BLOCK_TYPES, -} = config; +const { ZERO, HASH_TYPE, TIMBER_HEIGHT, TXHASH_TREE_HASH_TYPE, TXHASH_TREE_HEIGHT, BLOCK_TYPES } = + config; /** This Block class does not have the Block components that are computed on-chain. @@ -126,17 +119,14 @@ class Block { this.localBlockNumberL2 += 1; this.localRoot = updatedTimber.root; // compute the keccak hash of the proposeBlock signature - const blockHash = this.calcHash( - { - proposer, - root: updatedTimber.root, - leafCount: timber.leafCount, - blockNumberL2, - previousBlockHash, - transactionHashesRoot: this.calcTransactionHashesRoot(transactions), - }, - transactions, - ); + const blockHash = this.calcHash({ + proposer, + root: updatedTimber.root, + leafCount: timber.leafCount, + blockNumberL2, + previousBlockHash, + transactionHashesRoot: this.calcTransactionHashesRoot(transactions), + }); this.localPreviousBlockHash = blockHash; // note that the transactionHashes array is not part of the on-chain block // but we compute it here for convenience. It needs removing before sending @@ -165,8 +155,8 @@ class Block { this.localPreviousBlockHash = ZERO; } - static checkHash(block, transactions) { - return this.calcHash(block, transactions) === block.blockHash; + static checkHash(block) { + return this.calcHash(block) === block.blockHash; } static calcTransactionHashesRoot(transactions) { @@ -181,7 +171,7 @@ class Block { return updatedTimber.root; } - static calcHash(block, transactions) { + static calcHash(block) { const web3 = Web3.connection(); const { proposer, root, leafCount, blockNumberL2, previousBlockHash, transactionHashesRoot } = block; @@ -193,47 +183,7 @@ class Block { previousBlockHash, transactionHashesRoot, ]; - const transactionsArray = transactions.map(t => { - const { - value, - historicRootBlockNumberL2, - transactionType, - tokenType, - tokenId, - ercAddress, - recipientAddress, - commitments, - nullifiers, - compressedSecrets, - proof, - } = t; - return [ - value, - historicRootBlockNumberL2, - transactionType, - tokenType, - tokenId, - ercAddress, - recipientAddress, - commitments, - nullifiers, - compressedSecrets, - compressProof(proof), - ]; - }); - let encodedTransactions = web3.eth.abi.encodeParameters(PROPOSE_BLOCK_TYPES, [ - blockArray, - transactionsArray, - ]); - encodedTransactions = `0x${encodedTransactions.slice(386)}`; // Retrieve transactions data only - const transactionsHash = web3.utils.soliditySha3({ - t: 'bytes', - v: encodedTransactions, - }); - const encoded = web3.eth.abi.encodeParameters( - [PROPOSE_BLOCK_TYPES[0], 'bytes32'], - [blockArray, transactionsHash], - ); + const encoded = web3.eth.abi.encodeParameters([BLOCK_TYPES], [blockArray]); return web3.utils.soliditySha3({ t: 'bytes', v: encoded }); } diff --git a/nightfall-optimist/src/services/challenges.mjs b/nightfall-optimist/src/services/challenges.mjs index 18edfe8ad..0a2b78162 100644 --- a/nightfall-optimist/src/services/challenges.mjs +++ b/nightfall-optimist/src/services/challenges.mjs @@ -305,18 +305,6 @@ export async function createChallenge(block, transactions, err) { .encodeABI(); break; } - // challenge incorrect transaction hashes root - case 8: { - // Create a challenge - txDataToSign = await challengeContractInstance.methods - .challengeTransactionHashesRoot( - Block.buildSolidityStruct(block), - transactions.map(t => Transaction.buildSolidityStruct(t)), - salt, - ) - .encodeABI(); - break; - } default: // code block } diff --git a/nightfall-optimist/src/services/check-block.mjs b/nightfall-optimist/src/services/check-block.mjs index 603eb2ac4..bbef2d8b5 100644 --- a/nightfall-optimist/src/services/check-block.mjs +++ b/nightfall-optimist/src/services/check-block.mjs @@ -10,7 +10,6 @@ import { getBlockByBlockNumberL2, getTreeByLeafCount, } from './database.mjs'; -import Block from '../classes/block.mjs'; /** Checks the block's properties. It will return the first inconsistency it finds @@ -25,8 +24,6 @@ async function checkBlock(block, transactions) { // being correct! // We need to get hold of the prior block to do this because the leafCount // is derrived from data in that block. - if (Block.checkHash(block, transactions) === false) - throw new BlockError('The transaction hashes Root is incorrect', 8); if (block.blockNumberL2 > 0) { const priorBlock = await getBlockByBlockNumberL2(block.blockNumberL2 - 1); if (priorBlock === null) logger.warn('Could not find prior block while checking leaf count'); diff --git a/nightfall-optimist/src/services/process-calldata.mjs b/nightfall-optimist/src/services/process-calldata.mjs index 271368810..540d20b66 100644 --- a/nightfall-optimist/src/services/process-calldata.mjs +++ b/nightfall-optimist/src/services/process-calldata.mjs @@ -70,39 +70,6 @@ export async function getProposeBlockCalldata(eventData) { block.nCommitments = transactions .map(t => t.commitments.filter(c => c !== ZERO)) .flat(Infinity).length; - // // This line grabs the blockData array and extracts the index of the block - // // that we are dealing with. TODO - this may get unmanageable with large - // // numbers of L2 blocks. Then we'll need to store it in a DB and sync - // - // // TODO - do we need this code now that the blockNumberL2 is stored in the struct?... - // // This gets all blocks that we have stored locally - could be improved by pre-filtering here - // const storedBlocks = await getBlocks(); - // const storedL2BlockNumbers = storedBlocks.map(s => s.blockNumberL2); - // // This is a kinda cool way to check for gaps since blockhashes is also zero-indexed! - // const L2BlockNumbersSequenced = storedL2BlockNumbers.filter((num, index) => num - index === 0); // This is the array of numbers that are in order. - // // This is the last block number that is in sequence order, otherwise set it as -1 - // const lastReliableL2BlockNumber = - // L2BlockNumbersSequenced[L2BlockNumbersSequenced.length - 1] || -1; - // - // const stateContractInstance = await waitForContract(STATE_CONTRACT_NAME); - // let counter = lastReliableL2BlockNumber; - // let onChainBlockData; - // do { - // counter++; - // try { - // // eslint-disable-next-line no-await-in-loop - // onChainBlockData = await stateContractInstance.methods.blockHashes(counter).call(); - // } catch (error) { - // // Getting to this means the block hash doesnt exist (perhaps its was rolled back) - // counter = Math.max(...storedL2BlockNumbers) + 1; - // break; - // // throw new Error('Could not find blockHash in blockchain record'); - // // break; - // } - // } while (onChainBlockData.blockHash !== block.blockHash); - // // counter now has the new blockNumberL2 - // block.blockNumberL2 = counter; - block.transactionHashes = transactions.map(t => t.transactionHash); // currentLeafCount holds the count of the next leaf to be added // const currentLeafCount = Number(block.nCommitments) + Number(leafCount); diff --git a/wallet/src/common-files/classes/transaction.js b/wallet/src/common-files/classes/transaction.js index 0d038094b..ce84b1a54 100644 --- a/wallet/src/common-files/classes/transaction.js +++ b/wallet/src/common-files/classes/transaction.js @@ -11,24 +11,43 @@ import { compressProof } from '../utils/curve-maths/curves'; const { generalise } = gen; const TOKEN_TYPES = { ERC20: 0, ERC721: 1, ERC1155: 2 }; +const { TRANSACTION_TYPES } = global.config; // function to compute the keccak hash of a transaction function keccak(preimage) { const web3 = Web3.connection(); - // compute the solidity hash, using suitable type conversions - return web3.utils.soliditySha3( - { t: 'uint112', v: preimage.value }, - ...preimage.historicRootBlockNumberL2.map(hi => ({ t: 'uint256', v: hi })), - { t: 'uint8', v: preimage.transactionType }, - { t: 'uint8', v: preimage.tokenType }, - { t: 'bytes32', v: preimage.tokenId }, - { t: 'bytes32', v: preimage.ercAddress }, - { t: 'bytes32', v: preimage.recipientAddress }, - ...preimage.commitments.map(ch => ({ t: 'bytes32', v: ch })), - ...preimage.nullifiers.map(nh => ({ t: 'bytes32', v: nh })), - ...preimage.compressedSecrets.map(es => ({ t: 'bytes32', v: es })), - ...compressProof(preimage.proof).map(p => ({ t: 'uint', v: p })), - ); + const { + value, + historicRootBlockNumberL2, + transactionType, + tokenType, + tokenId, + ercAddress, + recipientAddress, + commitments, + nullifiers, + compressedSecrets, + } = preimage; + let { proof } = preimage; + proof = compressProof(proof); + const transaction = [ + value, + historicRootBlockNumberL2, + transactionType, + tokenType, + tokenId, + ercAddress, + recipientAddress, + commitments, + nullifiers, + compressedSecrets, + proof, + ]; + const encodedTransaction = web3.eth.abi.encodeParameters([TRANSACTION_TYPES], [transaction]); + return web3.utils.soliditySha3({ + t: 'bytes', + v: encodedTransaction, + }); } class Transaction { diff --git a/wallet/src/contract-abis/Shield.json b/wallet/src/contract-abis/Shield.json index 94a078512..392d4d191 100644 --- a/wallet/src/contract-abis/Shield.json +++ b/wallet/src/contract-abis/Shield.json @@ -788,11 +788,6 @@ "name": "b", "type": "tuple" }, - { - "internalType": "bytes32", - "name": "transactionsHash", - "type": "bytes32" - }, { "components": [ { @@ -917,11 +912,6 @@ "name": "b", "type": "tuple" }, - { - "internalType": "bytes32", - "name": "transactionsHash", - "type": "bytes32" - }, { "components": [ { @@ -1109,11 +1099,6 @@ "name": "b", "type": "tuple" }, - { - "internalType": "bytes32", - "name": "transactionsHash", - "type": "bytes32" - }, { "components": [ { @@ -1193,4 +1178,4 @@ "type": "function", "payable": true } -] \ No newline at end of file +] diff --git a/wallet/src/nightfall-browser/event-handlers/block-proposed.js b/wallet/src/nightfall-browser/event-handlers/block-proposed.js index c58d54750..8b461da09 100644 --- a/wallet/src/nightfall-browser/event-handlers/block-proposed.js +++ b/wallet/src/nightfall-browser/event-handlers/block-proposed.js @@ -1,7 +1,5 @@ // ignore unused exports default -import Web3 from '../../common-files/utils/web3'; -import { compressProof } from '../../common-files/utils/curve-maths/curves'; import logger from '../../common-files/utils/logger'; import Timber from '../../common-files/classes/timber'; import { @@ -22,70 +20,10 @@ import { saveTree, saveTransaction, saveBlock, - setTransactionsHashForBlock, setTransactionHashSiblingInfo, } from '../services/database'; -const { - ZERO, - HASH_TYPE, - TIMBER_HEIGHT, - TXHASH_TREE_HASH_TYPE, - TXHASH_TREE_HEIGHT, - PROPOSE_BLOCK_TYPES, -} = global.config; - -const calcTransactionsHash = (block, transactions) => { - const web3 = Web3.connection(); - const { proposer, root, leafCount, blockNumberL2, previousBlockHash, transactionHashesRoot } = - block; - const blockArray = [ - leafCount, - proposer, - root, - blockNumberL2, - previousBlockHash, - transactionHashesRoot, - ]; - const transactionsArray = transactions.map(t => { - const { - value, - historicRootBlockNumberL2, - transactionType, - tokenType, - tokenId, - ercAddress, - recipientAddress, - commitments, - nullifiers, - compressedSecrets, - proof, - } = t; - return [ - value, - historicRootBlockNumberL2, - transactionType, - tokenType, - tokenId, - ercAddress, - recipientAddress, - commitments, - nullifiers, - compressedSecrets, - compressProof(proof), - ]; - }); - let encodedTransactions = web3.eth.abi.encodeParameters(PROPOSE_BLOCK_TYPES, [ - blockArray, - transactionsArray, - ]); - encodedTransactions = `0x${encodedTransactions.slice(386)}`; // Retrieve transactions data only - const transactionsHash = web3.utils.soliditySha3({ - t: 'bytes', - v: encodedTransactions, - }); - return transactionsHash; -}; +const { ZERO, HASH_TYPE, TIMBER_HEIGHT, TXHASH_TREE_HASH_TYPE, TXHASH_TREE_HEIGHT } = global.config; /** This handler runs whenever a BlockProposed event is emitted by the blockchain @@ -100,12 +38,10 @@ async function blockProposedEventHandler(data, ivks, nsks) { const blockCommitments = transactions.map(t => t.commitments.filter(c => c !== ZERO)).flat(); let tempBlockSaved = false; - const transactionsHash = calcTransactionsHash(block, transactions); if ((await countTransactionHashes(block.transactionHashes)) > 0) { await saveBlock({ blockNumber: currentBlockCount, transactionHashL1, - transactionsHash, ...block, }); await Promise.all(transactions.map(t => saveTransaction({ transactionHashL1, ...t }))); @@ -212,7 +148,6 @@ async function blockProposedEventHandler(data, ivks, nsks) { if (await isTransactionHashWithdraw(transactionHash)) { const siblingPathTransactionHash = updatedTransctionHashesTimber.getSiblingPath(transactionHash); - await setTransactionsHashForBlock(transactionHash, transactionsHash); return setTransactionHashSiblingInfo( transactionHash, siblingPathTransactionHash, diff --git a/wallet/src/nightfall-browser/services/commitment-storage.js b/wallet/src/nightfall-browser/services/commitment-storage.js index 15ae7c745..af9d7d0a1 100644 --- a/wallet/src/nightfall-browser/services/commitment-storage.js +++ b/wallet/src/nightfall-browser/services/commitment-storage.js @@ -85,7 +85,6 @@ export async function countTransactionHashes(transactionHashes) { export async function countWithdrawTransactionHashes(transactionHashes) { const db = await connectDB(); const txs = await db.getAll(COMMITMENTS_COLLECTION); - // const txs = await db.getAll(TRANSACTIONS_COLLECTION); const filtered = txs.filter(tx => { return transactionHashes.includes(tx.transactionHash) && tx.nullifierTransactionType === '3'; }); @@ -97,7 +96,6 @@ export async function countWithdrawTransactionHashes(transactionHashes) { export async function isTransactionHashWithdraw(transactionHash) { const db = await connectDB(); const txs = await db.getAll(COMMITMENTS_COLLECTION); - // const txs = await db.getAll(TRANSACTIONS_COLLECTION); const filtered = txs.filter(tx => { return tx.transactionHash === transactionHash && tx.nullifierTransactionType === '3'; }); diff --git a/wallet/src/nightfall-browser/services/database.js b/wallet/src/nightfall-browser/services/database.js index ba38f087e..9bc4beda3 100644 --- a/wallet/src/nightfall-browser/services/database.js +++ b/wallet/src/nightfall-browser/services/database.js @@ -285,30 +285,6 @@ export async function markWithdrawState(transactionHash, withdrawState) { ); } -// function to set the path of the transaction hash leaf in transaction hash timber -export async function setTransactionsHashForBlock(transactionHash, transactionsHash) { - const db = await connectDB(); - const res = await db.getAll(TRANSACTIONS_COLLECTION); - const block = res.filter(r => r._id === transactionHash); - if (block) { - const { - // transactionHashSiblingPath: a, - // transactionHashLeafIndex: b, - // transactionHashesRoot: c, - ...rest - } = block[0]; - return db.put( - TRANSACTIONS_COLLECTION, - { - transactionsHash, - ...rest, - }, - block[0]._id, - ); - } - return null; -} - // function to set the path of the transaction hash leaf in transaction hash timber export async function setTransactionHashSiblingInfo( transactionHash, diff --git a/wallet/src/nightfall-browser/services/finalise-withdrawal.js b/wallet/src/nightfall-browser/services/finalise-withdrawal.js index 2884fbe29..03497b798 100644 --- a/wallet/src/nightfall-browser/services/finalise-withdrawal.js +++ b/wallet/src/nightfall-browser/services/finalise-withdrawal.js @@ -44,7 +44,6 @@ export async function finaliseWithdrawal(transactionHash, shieldContractAddress) const rawTransaction = await shieldContractInstance.methods .finaliseWithdrawal( buildSolidityStruct(block), - block.transactionsHash, Transaction.buildSolidityStruct(transactions[index]), index, siblingPath, diff --git a/wallet/src/nightfall-browser/services/instant-withdrawal.js b/wallet/src/nightfall-browser/services/instant-withdrawal.js index 85c592ec9..0424d486e 100644 --- a/wallet/src/nightfall-browser/services/instant-withdrawal.js +++ b/wallet/src/nightfall-browser/services/instant-withdrawal.js @@ -29,7 +29,6 @@ const setInstantWithdrawl = async (transactionHash, shieldContractAddress) => { const rawTransaction = await shieldContractInstance.methods .setAdvanceWithdrawalFee( buildSolidityStruct(block), - block.transactionsHash, Transaction.buildSolidityStruct(transactions[index]), index, siblingPath, diff --git a/wallet/src/nightfall-browser/services/process-calldata.js b/wallet/src/nightfall-browser/services/process-calldata.js index 65a6526ed..231aebb02 100644 --- a/wallet/src/nightfall-browser/services/process-calldata.js +++ b/wallet/src/nightfall-browser/services/process-calldata.js @@ -64,11 +64,6 @@ async function getProposeBlockCalldata(eventData) { }); block.transactionHashes = transactions.map(t => t.transactionHash); - const encodedTransactions = `0x${tx.input.slice(394)}`; // retrieve only transactions data - block.transactionsHash = web3.utils.soliditySha3({ - t: 'bytes', - v: encodedTransactions, - }); return { transactions, block }; } diff --git a/wallet/src/nightfall-browser/services/valid-withdrawal.js b/wallet/src/nightfall-browser/services/valid-withdrawal.js index 4f9dff987..69a91aa6c 100644 --- a/wallet/src/nightfall-browser/services/valid-withdrawal.js +++ b/wallet/src/nightfall-browser/services/valid-withdrawal.js @@ -45,7 +45,6 @@ export async function isValidWithdrawal(transactionHash, shieldContractAddress) const valid = await shieldContractInstance.methods .isValidWithdrawal( buildSolidityStruct(block), - block.transactionsHash, Transaction.buildSolidityStruct(transactions[index]), index, siblingPath, diff --git a/wallet/src/static/supported-token-lists/supported-tokens-local.ts b/wallet/src/static/supported-token-lists/supported-tokens-local.ts index f5a263069..61b790f66 100644 --- a/wallet/src/static/supported-token-lists/supported-tokens-local.ts +++ b/wallet/src/static/supported-token-lists/supported-tokens-local.ts @@ -5,7 +5,7 @@ const tokensList = { name: 'Ether - ERC20 Mock', symbol: 'ETH', decimals: 9, - address: '0xe721F2D97c58b1D1ccd0C80B88256a152d27f0Fe', + address: '0x17E1009e6Eab141615811104795D98c316d97AC0', logoURI: 'https://wallet-asset.matic.network/img/tokens/eth.svg', tags: ['pos', 'erc20', 'swapable', 'metaTx'], id: 'ethereum',