// SPDX-License-Identifier: MIT pragma solidity ^0.8.0; pragma experimental ABIEncoderV2; import "./Interfaces/IElasticERC20.sol"; struct Transaction { bytes32 id; bytes32[16] commitments; bytes32[16] nullifier_hashes; bytes32[16] recipients; bytes32[16] withdrawals; bytes32 deposit; bytes32[161] proof; bytes32[16] aggregation_object; } struct BatchPublicInputs { bytes32 key_hash; bytes32 oracle; bytes32 old_root; bytes32 new_root; Transaction[] transactions; } interface IVerifier { function verify( bytes calldata, bytes32[] calldata ) external view returns (bool); function getVerificationKeyHash() external pure returns (bytes32); } contract TechnicalPreview { IVerifier public verifier; IElasticERC20 public token; bytes32[] public validRoots; bytes32 public keyHash; uint256 public MAX_FIELD_SIZE = 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000; bytes32 public ZERO_VALUE = 0x016a430aa58685aba1311244a973a3bc358859da86784be51094368e8fb6f720; mapping(bytes32 => bytes32) public utxoPrevRoots; mapping(bytes32 => bool) public nullifierHashes; mapping(bytes32 => bool) public commitments; mapping(bytes32 => bytes32[]) public utxo; uint256 public MAX_ITEMS = 16; mapping(uint256 => Transaction[]) public batch; uint256 public batchNumber = 0; bytes32 public merkleRoot = ZERO_VALUE; constructor(IVerifier _verifier, address _token, bytes32 _keyHash) { verifier = _verifier; token = IElasticERC20(_token); validRoots.push(merkleRoot); keyHash = _keyHash; } function getLatestAggregationObject() public view returns (bytes32[16] memory) { return batch[batchNumber][batch[batchNumber].length - 1].aggregation_object; } function getSpentNullifiers(bytes32[] calldata _nullifierHashes) external view returns (bool[] memory spent) { uint256 nullifierHashesLength = _nullifierHashes.length; spent = new bool[](nullifierHashesLength); for (uint256 i; i < nullifierHashesLength; i++) { if (isSpent(_nullifierHashes[i])) { spent[i] = true; } } } function getCommitment(bytes32 _commitment) public view returns (bool) { return commitments[_commitment]; } function getUtxoFromRoot(bytes32 _root) public view returns (bytes32[] memory) { return utxo[_root]; } function getRootFromUtxo(bytes32 _utxo) public view returns (bytes32) { return utxoPrevRoots[_utxo]; } function getValidRoots() public view returns (bytes32[] memory) { return validRoots; } function getCurrentBatch() public view returns (Transaction[] memory) { return batch[batchNumber]; } function enqueue(Transaction calldata _tx) public { require(MAX_ITEMS > batch[batchNumber].length, "queue is full"); batch[batchNumber].push(_tx); for (uint256 i = 0; i < 16; i++) { if (_tx.commitments[i] != ZERO_VALUE) { require(!commitments[_tx.commitments[i]], "commitment exists"); commitments[_tx.commitments[i]] = true; } } } function publish( bytes calldata _proof, BatchPublicInputs calldata _batch ) public payable { require(uint256(_batch.old_root) == uint256(merkleRoot), "invalid root"); BatchPublicInputs memory batchPublicInputs = _batch; batchPublicInputs.transactions = batch[batchNumber]; for (uint256 i = 0; i < batchPublicInputs.transactions.length; i++) { for (uint256 j = 0; j < 16; j++) { if (batchPublicInputs.transactions[i].nullifier_hashes[j] != ZERO_VALUE) { require(!nullifierHashes[batchPublicInputs.transactions[i].nullifier_hashes[j]], "nullifier spent"); nullifierHashes[batchPublicInputs.transactions[i].nullifier_hashes[j]] = true; } if (batchPublicInputs.transactions[i].commitments[j] != ZERO_VALUE) { utxo[batchPublicInputs.old_root].push(batchPublicInputs.transactions[i].commitments[j]); utxoPrevRoots[batchPublicInputs.transactions[i].commitments[j]] = batchPublicInputs.old_root; } if (batchPublicInputs.transactions[i].recipients[j] != ZERO_VALUE) { token.mint(address(uint160(uint256(batchPublicInputs.transactions[i].recipients[j]))), uint256(batchPublicInputs.transactions[i].withdrawals[j])); } } } validRoots.push(batchPublicInputs.new_root); merkleRoot = batchPublicInputs.new_root; batchNumber++; require(verifier.verify(_proof, prepareBatchPublicInputs(_batch)), "invalid proof"); } function dropQueue(bytes calldata _preimage) public { require(keccak256(_preimage) > 0xff00000000000000000000000000000000000000000000000000000000000000); for (uint256 i = 0; i < MAX_ITEMS; i++) { for (uint256 j = 0; j < 16; j++) { commitments[batch[batchNumber][i].commitments[j]] = false; } } batchNumber++; } function isSpent(bytes32 _nullifierHash) public view returns (bool) { return nullifierHashes[_nullifierHash]; } function verifyProof( bytes calldata _proof, bytes32[] memory _publicInputs ) public view returns (bool) { return verifier.verify(_proof, _publicInputs); } function prepareBatchPublicInputs(BatchPublicInputs memory input) public view returns (bytes32[] memory) { bytes32[] memory flatArray = new bytes32[](36); uint256 idx = 0; flatArray[idx++] = keyHash; for (uint256 i = 0; i < 16; i++) { if (i < input.transactions.length) { flatArray[idx++] = input.transactions[i].id; } else { flatArray[idx++] = ZERO_VALUE; } } flatArray[idx++] = input.old_root; flatArray[idx++] = input.new_root; flatArray[idx++] = input.oracle; for (uint256 i = 0; i < 16; i++ ) { flatArray[idx++] = input.transactions[(input.transactions.length - 1)].aggregation_object[i]; } for (uint256 i = 0; i < flatArray.length; i++) { require(uint256(flatArray[i]) < 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000000, "too large!"); } return flatArray; } }