diff --git a/.travis.yml b/.travis.yml index fdbd26d95..2dbea044b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -21,6 +21,12 @@ addons: matrix: include: + - env: CI_TASK=contracts_check + os: linux + dist: bionic + language: node_js + node_js: + - "10.16.3" - env: CI_TASK=pyclient_check CI_USE_DOCKER=1 os: linux language: minimal diff --git a/pyClient/commands/utils.py b/pyClient/commands/utils.py index f02170d04..7cc58e138 100644 --- a/pyClient/commands/utils.py +++ b/pyClient/commands/utils.py @@ -179,7 +179,11 @@ def _do_sync() -> int: if wait_tx: _do_sync() - web3.eth.waitForTransactionReceipt(wait_tx, 10000) + tx_receipt = web3.eth.waitForTransactionReceipt(wait_tx, 10000) + gas_used = tx_receipt.gasUsed + status = tx_receipt.status + print(f"{wait_tx[0:8]}: gasUsed={gas_used}, status={status}") + return _do_sync() diff --git a/pyClient/test/test_contract_base_mixer.py b/pyClient/test/test_contract_base_mixer.py index f72a3ae3c..ce766c5ba 100644 --- a/pyClient/test/test_contract_base_mixer.py +++ b/pyClient/test/test_contract_base_mixer.py @@ -4,14 +4,12 @@ # # SPDX-License-Identifier: LGPL-3.0+ -import os -from typing import Any -from solcx import compile_files # type: ignore -import test_commands.mock as mock - from zeth.constants import JS_INPUTS, JS_OUTPUTS, ZETH_MERKLE_TREE_DEPTH,\ PUBLIC_VALUE_LENGTH -import zeth.contracts as contracts +from zeth.joinsplit import ZethClient +from zeth.zksnark import get_zksnark_provider +from typing import Any +import test_commands.mock as mock # The UNPACKED_PRIMARY_INPUTS variable represents a dummy primary input, @@ -149,34 +147,16 @@ def main() -> None: # Ethereum addresses deployer_eth_address = eth.accounts[0] - contracts_dir = os.environ['ZETH_CONTRACTS_DIR'] - path_to_mixer = os.path.join(contracts_dir, "BaseMixer.sol") - compiled_sol = compile_files([path_to_mixer]) - mixer_interface = compiled_sol[path_to_mixer + ':' + "BaseMixer"] - - hasher_interface, _ = contracts.compile_util_contracts() - # Deploy MiMC contract - _, hasher_address = contracts.deploy_mimc_contract( - web3, hasher_interface, deployer_eth_address) - - token_address = "0x0000000000000000000000000000000000000000" - - mixer = web3.eth.contract( - abi=mixer_interface['abi'], bytecode=mixer_interface['bin']) - tx_hash = mixer.constructor( - depth=ZETH_MERKLE_TREE_DEPTH, - token_address=token_address, - hasher_address=hasher_address - ).transact({'from': deployer_eth_address}) - - # Get tx receipt to get Mixer contract address - tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash, 10000) - mixer_address = tx_receipt['contractAddress'] - # Get the mixer contract instance - mixer_instance = web3.eth.contract( - address=mixer_address, - abi=mixer_interface['abi'] - ) + zksnark = get_zksnark_provider("GROTH16") + prover_client = mock.open_test_prover_client() + zeth_client = ZethClient.deploy( + web3, + prover_client, + ZETH_MERKLE_TREE_DEPTH, + deployer_eth_address, + zksnark) + + mixer_instance = zeth_client.mixer_instance # We can now call the instance and test its functions. print("[INFO] 4. Running tests") diff --git a/pyClient/zeth/constants.py b/pyClient/zeth/constants.py index 3cb4268a5..883cf6f3e 100644 --- a/pyClient/zeth/constants.py +++ b/pyClient/zeth/constants.py @@ -11,20 +11,15 @@ # GROTH16 constants GROTH16_ZKSNARK: str = "GROTH16" -GROTH16_VERIFIER_CONTRACT: str = "Groth16Verifier" GROTH16_MIXER_CONTRACT: str = "Groth16Mixer" # PGHR13 constants PGHR13_ZKSNARK: str = "PGHR13" -PGHR13_VERIFIER_CONTRACT: str = "Pghr13Verifier" PGHR13_MIXER_CONTRACT: str = "Pghr13Mixer" # Set of valid snarks VALID_ZKSNARKS: List[str] = [GROTH16_ZKSNARK, PGHR13_ZKSNARK] -# OTSCHNORR constants -SCHNORR_VERIFIER_CONTRACT: str = "OTSchnorrVerifier" - # Merkle tree depth ZETH_MERKLE_TREE_DEPTH: int = 4 diff --git a/pyClient/zeth/contracts.py b/pyClient/zeth/contracts.py index 12af81fcd..db2a84df0 100644 --- a/pyClient/zeth/contracts.py +++ b/pyClient/zeth/contracts.py @@ -5,7 +5,6 @@ # SPDX-License-Identifier: LGPL-3.0+ from __future__ import annotations -import zeth.constants as constants from zeth.encryption import EncryptionPublicKey, encode_encryption_public_key from zeth.signing import SigningVerificationKey from zeth.zksnark import IZKSnarkProvider, GenericProof, GenericVerificationKey @@ -14,7 +13,7 @@ import os from web3 import Web3 # type: ignore -from solcx import compile_files, set_solc_version, install_solc +import solcx from typing import Tuple, Dict, List, Iterator, Optional, Any # Avoid trying to read too much data into memory @@ -77,71 +76,48 @@ def get_block_number(web3: Any) -> int: def install_sol() -> None: - install_solc(SOL_COMPILER_VERSION) + solcx.install_solc(SOL_COMPILER_VERSION) -def compile_contracts( - zksnark: IZKSnarkProvider) -> Tuple[Interface, Interface, Interface]: - contracts_dir = get_contracts_dir() - (proof_verifier_name, mixer_name) = zksnark.get_contract_names() - otsig_verifier_name = constants.SCHNORR_VERIFIER_CONTRACT - - path_to_proof_verifier = os.path.join( - contracts_dir, proof_verifier_name + ".sol") - path_to_otsig_verifier = os.path.join( - contracts_dir, otsig_verifier_name + ".sol") - path_to_mixer = os.path.join(contracts_dir, mixer_name + ".sol") - - set_solc_version(SOL_COMPILER_VERSION) - compiled_sol = compile_files( - [path_to_proof_verifier, path_to_otsig_verifier, path_to_mixer]) - - proof_verifier_interface = \ - compiled_sol[path_to_proof_verifier + ':' + proof_verifier_name] - otsig_verifier_interface = \ - compiled_sol[path_to_otsig_verifier + ':' + otsig_verifier_name] - mixer_interface = compiled_sol[path_to_mixer + ':' + mixer_name] - - return (proof_verifier_interface, otsig_verifier_interface, mixer_interface) +def compile_files(files: List[str]) -> Any: + """ + Wrapper around solcx which ensures the required version of the compiler is + used. + """ + solcx.set_solc_version(SOL_COMPILER_VERSION) + return solcx.compile_files(files, optimize=True) -def compile_util_contracts() -> Tuple[Interface, Interface]: +def compile_mixer(zksnark: IZKSnarkProvider) -> Interface: contracts_dir = get_contracts_dir() - path_to_pairing = os.path.join(contracts_dir, "Pairing.sol") - path_to_mimc7 = os.path.join(contracts_dir, "MiMC7.sol") - path_to_tree = os.path.join(contracts_dir, "MerkleTreeMiMC7.sol") - set_solc_version(SOL_COMPILER_VERSION) - compiled_sol = compile_files( - [path_to_pairing, path_to_mimc7, path_to_tree]) - mimc_interface = compiled_sol[path_to_mimc7 + ':' + "MiMC7"] - tree_interface = compiled_sol[path_to_tree + ':' + "MerkleTreeMiMC7"] - return mimc_interface, tree_interface + mixer_name = zksnark.get_contract_name() + path_to_mixer = os.path.join(contracts_dir, mixer_name + ".sol") + compiled_sol = compile_files([path_to_mixer]) + return compiled_sol[path_to_mixer + ':' + mixer_name] def deploy_mixer( web3: Any, - proof_verifier_address: str, - otsig_verifier_address: str, - mixer_interface: Interface, mk_tree_depth: int, + mixer_interface: Interface, + vk: GenericVerificationKey, deployer_address: str, deployment_gas: int, token_address: str, - hasher_address: str) -> Tuple[Any, str]: + zksnark: IZKSnarkProvider) -> Tuple[Any, str]: """ Common function to deploy a mixer contract. Returns the mixer and the initial merkle root of the commitment tree """ - # Deploy the Mixer contract once the Verifier is successfully deployed + # Deploy the Mixer mixer = web3.eth.contract( abi=mixer_interface['abi'], bytecode=mixer_interface['bin']) + verification_key_params = zksnark.verification_key_parameters(vk) tx_hash = mixer.constructor( - snark_ver=proof_verifier_address, - sig_ver=otsig_verifier_address, mk_depth=mk_tree_depth, token=token_address, - hasher=hasher_address + **verification_key_params ).transact({'from': deployer_address, 'gas': deployment_gas}) # Get tx receipt to get Mixer contract address tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash, 10000) @@ -159,96 +135,6 @@ def deploy_mixer( return(mixer, initial_root) -def deploy_otschnorr_contracts( - web3: Any, - verifier: Any, - deployer_address: str, - deployment_gas: int) -> str: - """ - Deploy the verifier used with OTSCHNORR - """ - # Deploy the verifier contract with the good verification key - tx_hash = verifier.constructor().transact( - {'from': deployer_address, 'gas': deployment_gas}) - - # Get tx receipt to get Verifier contract address - tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash, 10000) - verifier_address = tx_receipt['contractAddress'] - return verifier_address - - -def deploy_contracts( - web3: Any, - mk_tree_depth: int, - proof_verifier_interface: Interface, - otsig_verifier_interface: Interface, - mixer_interface: Interface, - hasher_interface: Interface, - vk: GenericVerificationKey, - deployer_address: str, - deployment_gas: int, - token_address: str, - zksnark: IZKSnarkProvider) -> Tuple[Any, str]: - """ - Deploy the mixer contract with the given merkle tree depth and returns an - instance of the mixer along with the initial merkle tree root to use for - the first zero knowledge payments - """ - # Deploy the proof verifier contract with the good verification key - proof_verifier = web3.eth.contract( - abi=proof_verifier_interface['abi'], - bytecode=proof_verifier_interface['bin'] - ) - - verifier_constr_params = zksnark.verifier_constructor_parameters(vk) - tx_hash = proof_verifier.constructor(**verifier_constr_params) \ - .transact({'from': deployer_address, 'gas': deployment_gas}) - tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash, 10000) - proof_verifier_address = tx_receipt['contractAddress'] - - # Deploy MiMC contract - _, hasher_address = deploy_mimc_contract( - web3, hasher_interface, deployer_address) - - # Deploy the one-time signature verifier contract - otsig_verifier = web3.eth.contract( - abi=otsig_verifier_interface['abi'], - bytecode=otsig_verifier_interface['bin']) - otsig_verifier_address = deploy_otschnorr_contracts( - web3, otsig_verifier, deployer_address, deployment_gas) - - return deploy_mixer( - web3, - proof_verifier_address, - otsig_verifier_address, - mixer_interface, - mk_tree_depth, - deployer_address, - deployment_gas, - token_address, - hasher_address) - - -def deploy_mimc_contract( - web3: Any, - interface: Interface, - account: str) -> Tuple[Any, str]: - """ - Deploy mimc contract - """ - contract = web3.eth.contract(abi=interface['abi'], bytecode=interface['bin']) - tx_hash = contract.constructor().transact({'from': account}) - # Get tx receipt to get Mixer contract address - tx_receipt = web3.eth.waitForTransactionReceipt(tx_hash, 10000) - address = tx_receipt['contractAddress'] - # Get the mixer contract instance - instance = web3.eth.contract( - address=address, - abi=interface['abi'] - ) - return instance, address - - def deploy_tree_contract( web3: Any, interface: Interface, @@ -290,11 +176,12 @@ def mix( """ pk_sender_encoded = encode_encryption_public_key(pk_sender) proof_params = zksnark.mixer_proof_parameters(parsed_proof) + inputs = hex_to_int(parsed_proof["inputs"]) tx_hash = mixer_instance.functions.mix( *proof_params, - [[int(vk.ppk[0]), int(vk.ppk[1])], [int(vk.spk[0]), int(vk.spk[1])]], + [int(vk.ppk[0]), int(vk.ppk[1]), int(vk.spk[0]), int(vk.spk[1])], sigma, - hex_to_int(parsed_proof["inputs"]), + inputs, pk_sender_encoded, ciphertext1, ciphertext2, @@ -447,13 +334,6 @@ def _extract_note(log_commit: Any, log_ciph: Any) -> Tuple[int, bytes, bytes]: log_commit, log_ciph in zip(log_commitments, log_ciphertexts)] -def mimc_hash(instance: Any, m: bytes, k: bytes, seed: bytes) -> bytes: - """ - Call the hash method of MiMC contract - """ - return instance.functions.hash(m, k, seed).call() - - def get_commitments(mixer_instance: Any) -> List[bytes]: """ Query the Zeth contact to get the list of commitments diff --git a/pyClient/zeth/joinsplit.py b/pyClient/zeth/joinsplit.py index a16ffac8f..3b48da0da 100644 --- a/pyClient/zeth/joinsplit.py +++ b/pyClient/zeth/joinsplit.py @@ -447,16 +447,11 @@ def deploy( write_verification_key(vk_json) print("[INFO] 3. VK written, deploying smart contracts...") - (proof_verifier_interface, otsig_verifier_interface, mixer_interface) = \ - contracts.compile_contracts(zksnark) - hasher_interface, _ = contracts.compile_util_contracts() - (mixer_instance, initial_merkle_root) = contracts.deploy_contracts( + mixer_interface = contracts.compile_mixer(zksnark) + (mixer_instance, initial_merkle_root) = contracts.deploy_mixer( web3, mk_tree_depth, - proof_verifier_interface, - otsig_verifier_interface, mixer_interface, - hasher_interface, vk_json, deployer_eth_address, deploy_gas.wei, diff --git a/pyClient/zeth/utils.py b/pyClient/zeth/utils.py index c9bc302b2..270aa409a 100644 --- a/pyClient/zeth/utils.py +++ b/pyClient/zeth/utils.py @@ -205,7 +205,6 @@ def compute_merkle_path( # index of the root node) address = int(address/2) - 1 else: - print("append note at address: " + str(address + 1)) merkle_path.append(Web3.toHex(byte_tree[address + 1])[2:]) address = int(address/2) return merkle_path diff --git a/pyClient/zeth/zksnark.py b/pyClient/zeth/zksnark.py index c287c3aaa..188793c13 100644 --- a/pyClient/zeth/zksnark.py +++ b/pyClient/zeth/zksnark.py @@ -34,7 +34,7 @@ class IZKSnarkProvider(ABC): @staticmethod @abstractmethod - def get_contract_names() -> Tuple[str, str]: + def get_contract_name() -> str: """ Get the verifier and mixer contracts for this SNARK. """ @@ -42,7 +42,7 @@ def get_contract_names() -> Tuple[str, str]: @staticmethod @abstractmethod - def verifier_constructor_parameters( + def verification_key_parameters( vk: GenericVerificationKey) -> Dict[str, List[int]]: pass @@ -70,12 +70,11 @@ def mixer_proof_parameters(parsed_proof: GenericProof) -> List[List[int]]: class Groth16SnarkProvider(IZKSnarkProvider): @staticmethod - def get_contract_names() -> Tuple[str, str]: - return ( - constants.GROTH16_VERIFIER_CONTRACT, constants.GROTH16_MIXER_CONTRACT) + def get_contract_name() -> str: + return constants.GROTH16_MIXER_CONTRACT @staticmethod - def verifier_constructor_parameters( + def verification_key_parameters( vk: GenericVerificationKey) -> Dict[str, List[int]]: return { "Alpha": hex_to_int(vk["alpha_g1"]), @@ -111,20 +110,18 @@ def parse_proof(proof_obj: ExtendedProof) -> GenericProof: def mixer_proof_parameters(parsed_proof: GenericProof) -> List[List[Any]]: return [ hex_to_int(parsed_proof["a"]), - [hex_to_int(parsed_proof["b"][0]), - hex_to_int(parsed_proof["b"][1])], + hex_to_int(parsed_proof["b"][0] + parsed_proof["b"][1]), hex_to_int(parsed_proof["c"])] class PGHR13SnarkProvider(IZKSnarkProvider): @staticmethod - def get_contract_names() -> Tuple[str, str]: - return ( - constants.PGHR13_VERIFIER_CONTRACT, constants.PGHR13_MIXER_CONTRACT) + def get_contract_name() -> str: + return constants.PGHR13_MIXER_CONTRACT @staticmethod - def verifier_constructor_parameters( + def verification_key_parameters( vk: GenericVerificationKey) -> Dict[str, List[int]]: return { "A1": hex_to_int(vk["a"][0]), diff --git a/scripts/ci b/scripts/ci index 02b423640..09625a367 100755 --- a/scripts/ci +++ b/scripts/ci @@ -21,6 +21,14 @@ function format_check() { scripts/check_copyright } +function contracts_check() { + pushd zeth-contracts + npm config set python python2.7 + npm install + npm run check + popd +} + function pyclient_check() { pushd pyClient python3.7 -m venv env diff --git a/zeth-contracts/.soliumignore b/zeth-contracts/.soliumignore new file mode 100644 index 000000000..15c47afcc --- /dev/null +++ b/zeth-contracts/.soliumignore @@ -0,0 +1,2 @@ +node_modules +contracts/Migrations.sol diff --git a/zeth-contracts/.soliumrc.json b/zeth-contracts/.soliumrc.json new file mode 100644 index 000000000..bea041661 --- /dev/null +++ b/zeth-contracts/.soliumrc.json @@ -0,0 +1,23 @@ +{ + "extends": "solium:recommended", + "plugins": [ + "security" + ], + "rules": { + "quotes": [ + "error", + "double" + ], + "indentation": [ + "error", + 4 + ], + "linebreak-style": [ + "error", + "unix" + ], + "security/no-inline-assembly": 0, + "max-len": [ "error", 82 ], + "operator-whitespace": 0 + } +} diff --git a/zeth-contracts/contracts/BaseMerkleTree.sol b/zeth-contracts/contracts/BaseMerkleTree.sol index cab63d675..5ecd41307 100644 --- a/zeth-contracts/contracts/BaseMerkleTree.sol +++ b/zeth-contracts/contracts/BaseMerkleTree.sol @@ -7,64 +7,69 @@ pragma solidity ^0.5.0; // Adapted from: https://github.com/zcash-hackworks/babyzoe contract BaseMerkleTree { - // Index of the current node: Index to insert the next incoming commitment - uint currentNodeIndex; + // Depth of the merkle tree (should be set with the same depth set in the + // cpp prover) + uint256 constant depth = 4; - // Array containing the 2^(depth) leaves of the merkle tree - // We can switch the leaves to be of type bytes and not bytes32 - // to support digest of various size (eg: if we use different hash functions) - // That way we'd have a merkle tree for any type of hash function (that can be implemented - // as a precompiled contract for instance) - // - // Leaves is a 2D array - bytes32[] leaves; // Declared as a dynamic array, the bound is put in the constructor + // Number of leaves + uint256 constant nbLeaves = 2**depth; - // Depth of the merkle tree (should be set with the same depth set in the cpp prover) - uint depth; + // Index of the current node: Index to insert the next incoming commitment + uint256 currentNodeIndex; - // Number of leaves - uint nbLeaves; + // Array containing the 2^(depth) leaves of the merkle tree. We can switch + // the leaves to be of type bytes and not bytes32 to support digest of + // various size (eg: if we use different hash functions). That way we'd + // have a merkle tree for any type of hash function (that can be implemented + // as a precompiled contract for instance) + // + // Leaves is a 2D array + bytes32[nbLeaves] leaves; - // Debug only - event LogDebug(bytes32 message); + // Debug only + event LogDebug(bytes32 message); - // Constructor - constructor(uint treeDepth) public { - depth = treeDepth; - nbLeaves = 2**depth; + // Constructor + constructor(uint256 treeDepth) public { + require ( + treeDepth == depth, + "Invalid depth in BaseMerkleTree"); + } - bytes32[] memory leavesBuilder = new bytes32[](nbLeaves); - leaves = leavesBuilder; - } + // Appends a commitment to the tree, and returns its address + function insert(bytes32 commitment) public returns (uint) { + // If this require fails => the merkle tree is full, we can't append + // leaves anymore + require( + currentNodeIndex < nbLeaves, + "Merkle tree full: Cannot append anymore" + ); - // Appends a commitment to the tree, and returns its address - function insert(bytes32 commitment) public returns (uint) { - // If this require fails => the merkle tree is full, we can't append leaves anymore - require( - currentNodeIndex < nbLeaves, - "Merkle tree full: Cannot append anymore" - ); + leaves[currentNodeIndex] = commitment; + currentNodeIndex++; - leaves[currentNodeIndex] = commitment; - currentNodeIndex++; + // This address can be emitted to indicate the address of the commiment + // This is useful for the proof generation + return currentNodeIndex - 1; + } - // This address can be emitted to indicate the address of the commiment - // This is useful for the proof generation - return currentNodeIndex - 1; - } + // Function that is fundamental in order to enable a client to fetch the + // leaves and recompute the merkle tree to generate a proof (needs the + // merkle authentication path and the merkle tree root to be computed). + // + // Recomputing the merkle should not be necessary as it could be read + // directly from the smart contract state but we'll use this function for + // now. + // + // returns the bytes32[] array of leaves. + function getLeaves() public view returns (bytes32[] memory) { + bytes32[] memory tmpLeaves = new bytes32[](nbLeaves); + for (uint256 i = 0; i < nbLeaves; i++) { + tmpLeaves[i] = leaves[i]; + } - // Function that is fundamental in order to enable a client to fetch the leaves and - // recompute the merkle tree to generate a proof (needs the merkle authentication path and the merkle tree root to be computed) - // - // Recomputing the merkle should not be necessary as it could be read directly from the smart contract state - // but we'll use this function for now - function getLeaves() public view returns (bytes32[] memory) { // returns the bytes32[] array of leaves - bytes32[] memory tmpLeaves = new bytes32[](nbLeaves); - for(uint i; i < nbLeaves; i++) { - tmpLeaves[i] = leaves[i]; + // Returns the array of leaves of the merkle tree + return tmpLeaves; } - // Returns the array of leaves of the merkle tree - return tmpLeaves; - } } diff --git a/zeth-contracts/contracts/BaseMixer.sol b/zeth-contracts/contracts/BaseMixer.sol index c9c1b553c..921990eb6 100644 --- a/zeth-contracts/contracts/BaseMixer.sol +++ b/zeth-contracts/contracts/BaseMixer.sol @@ -6,43 +6,46 @@ pragma solidity ^0.5.0; import "./MerkleTreeMiMC7.sol"; -/* - * Declare the ERC20 interface in order to handle ERC20 tokens transfers - * to and from the Mixer. Note that we only declare the functions we are interested in, - * namely, transferFrom() (used to do a Deposit), and transfer() (used to do a withdrawal) -**/ +// Declare the ERC20 interface in order to handle ERC20 tokens transfers to and +// from the Mixer. Note that we only declare the functions we are interested in, +// namely, transferFrom() (used to do a Deposit), and transfer() (used to do a +// withdrawal) contract ERC20 { function transferFrom(address from, address to, uint256 value) public; function transfer(address to, uint256 value) public; } -/* - * ERC223 token compatible contract -**/ +// ERC223 token compatible contract contract ERC223ReceivingContract { - // See: https://github.com/Dexaran/ERC223-token-standard/blob/Recommended/Receiver_Interface.sol + // See: + // https://github.com/Dexaran/ERC223-token-standard/blob/Recommended/Receiver_Interface.sol struct Token { address sender; - uint value; + uint256 value; bytes data; bytes4 sig; } - function tokenFallback(address from, uint value, bytes memory data) public pure { + function tokenFallback(address from, uint256 value, bytes memory data) + public pure { Token memory tkn; tkn.sender = from; tkn.value = value; tkn.data = data; - // see https://solidity.readthedocs.io/en/v0.5.5/types.html#conversions-between-elementary-types - uint32 u = uint32(bytes4(data[0])) + uint32(bytes4(data[1]) >> 8) + uint32(bytes4(data[2]) >> 16) + uint32(bytes4(data[3]) >> 24); + // See: + // https://solidity.readthedocs.io/en/v0.5.5/types.html#conversions-between-elementary-types + uint32 u = + uint32(bytes4(data[0])) + + uint32(bytes4(data[1]) >> 8) + + uint32(bytes4(data[2]) >> 16) + + uint32(bytes4(data[3]) >> 24); tkn.sig = bytes4(u); } } -/* - * BaseMixer implements the functions shared across all Mixers (regardless which zkSNARK is used) -**/ +// BaseMixer implements the functions shared across all Mixers (regardless which +// zkSNARK is used) contract BaseMixer is MerkleTreeMiMC7, ERC223ReceivingContract { // The roots of the different updated trees @@ -51,35 +54,51 @@ contract BaseMixer is MerkleTreeMiMC7, ERC223ReceivingContract { // The public list of nullifiers (prevents double spend) mapping(bytes32 => bool) nullifiers; - // JoinSplit description, gives the number of inputs (nullifiers) and outputs (commitments/ciphertexts) to receive and process - // IMPORTANT NOTE: We need to employ the same JS configuration than the one used in the cpp prover - // Here we use 2 inputs and 2 outputs (it is a 2-2 JS) - uint constant jsIn = 2; // Nb of nullifiers - uint constant jsOut = 2; // Nb of commitments/ciphertexts + // JoinSplit description, gives the number of inputs (nullifiers) and + // outputs (commitments/ciphertexts) to receive and process. + // + // IMPORTANT NOTE: We need to employ the same JS configuration than the one + // used in the cpp prover. Here we use 2 inputs and 2 outputs (it is a 2-2 + // JS). + uint256 constant jsIn = 2; // Nb of nullifiers + uint256 constant jsOut = 2; // Nb of commitments/ciphertexts // Size of the public values in bits - uint constant public_value_length = 64; + uint256 constant public_value_length = 64; - // Constants regarding the hash digest length, the prime number used and its associated length in bits and the max values (v_in and v_out) + // Constants regarding the hash digest length, the prime number used and + // its associated length in bits and the max values (v_in and v_out) // uint r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; // field_capacity = floor( log_2(r) ) - uint constant digest_length = 256; - uint constant field_capacity = 253; - - // Variable representing the number of "residual" bits we can expect from converting a hash digest into a field element - // see primary input `residual_bits` in Reminder below - uint constant packing_residue_length = digest_length - field_capacity; - - // Number of hash digests in the primary inputs - uint constant nb_hash_digests = 1 + 2*jsIn + jsOut; - - // Total number of residual bits from packing of 256-bit long string into 253-bit long field elements - // to which are added the public value of size 64 bits - uint constant length_bit_residual = 2 * public_value_length + packing_residue_length * nb_hash_digests; - // uint nb_field_residual = (length_bit_residual + field_capacity - 1) / field_capacity; - - // Padding size in the residual field element (we require a single residual f.e. (c.f. constructor)) - uint constant padding_size = digest_length - length_bit_residual; + uint256 constant digest_length = 256; + uint256 constant field_capacity = 253; + + // Variable representing the number of "residual" bits we can expect from + // converting a hash digest into a field element see primary input + // `residual_bits` in Reminder below + uint256 constant packing_residue_length = digest_length - field_capacity; + + // Number of hash digests in the primary inputs: + // 1 (the root) + // 2 * jsIn (nullifier and message auth tag per JS input) + // jsOut (commitment per JS output) + uint256 constant nb_hash_digests = 1 + 2*jsIn + jsOut; + + // Total number of residual bits from packing of 256-bit long string into + // 253-bit long field elements to which are added the public value of size + // 64 bits + uint256 constant length_bit_residual = 2 * public_value_length + + packing_residue_length * nb_hash_digests; + + // Number of field elements required to hold residual bits. + // (length_bit_residual + field_capacity - 1) / field_capacity + // (Note, compiler complains if we use the above expression in the + // definition of the constant, so this must be set explicitly.) + uint256 constant nb_field_residual = 1; + + // Padding size in the residual field element (we require a single residual + // f.e. (c.f. constructor)) + uint256 constant padding_size = digest_length - length_bit_residual; // The number of public inputs is: // - 1 (the root) @@ -87,8 +106,9 @@ contract BaseMixer is MerkleTreeMiMC7, ERC223ReceivingContract { // - jsOut (the commitments) // - 1 (hsig) // - JsIn (the message auth. tags) - // - nb_field_residual (the residual bits not fitting in a single field element and the in and out public values) - // uint constant nbInputs = 1 + nb_hash_digests + nb_field_residual; + // - nb_field_residual (the residual bits not fitting in a single field + // element and the in and out public values) + uint256 constant nbInputs = 1 + nb_hash_digests + nb_field_residual; // Contract variable that indicates the address of the token contract // If token = address(0) then the mixer works with ether @@ -98,26 +118,29 @@ contract BaseMixer is MerkleTreeMiMC7, ERC223ReceivingContract { // the python wrappers. Use Szabos (10^12 Wei). uint64 constant public_unit_value_wei = 1 szabo; - // Event to emit the address of a commitment in the merke tree - // Allows for faster execution of the "Receive" functions on the receiver side. - // The ciphertext of a note is emitted along the address of insertion in the tree - // Thus, instead of checking that the decrypted note is represented somewhere in the tree, the recipient just needs - // to check that the decrypted note opens the commitment at the emitted address - event LogCommitment(uint commAddr, bytes32 commit); + // Event to emit the value and address new commitments in the merke tree. + // Clients can use this when syncing with the latest state. As they + // encounter ciphertexts which they can decrypt and parse, they can verify + // that the note data opens the commitment (that the message is valid), and + // record the location of this commitment in order to later generate a + // Merkle path for it. + event LogCommitment(uint256 commAddr, bytes32 commit); // Event to emit the root of a the merkle tree event LogMerkleRoot(bytes32 root); - // Event to emit the encryption public key of the sender and ciphertexts of the coins' data to be sent to the recipient of the payment - // This event is key to obfuscate the transaction graph while enabling on-chain storage of the coins' data - // (useful to ease backup of user's wallets) + // Event to emit the encryption public key of the sender and ciphertexts of + // the coins' data to be sent to the recipient of the payment. This event + // is key to obfuscate the transaction graph while enabling on-chain storage + // of the coins' data (useful to ease backup of user's wallets) event LogSecretCiphers(bytes32 pk_sender, bytes ciphertext); // Debug only event LogDebug(string message); // Constructor - constructor(uint depth, address token_address, address hasher_address) MerkleTreeMiMC7(hasher_address, depth) public { + constructor( + uint256 depth, address token_address) MerkleTreeMiMC7(depth) public { // We log the first root to get started bytes32 initialRoot = getRoot(); roots[initialRoot] = true; @@ -136,134 +159,201 @@ contract BaseMixer is MerkleTreeMiMC7, ERC223ReceivingContract { ); } - // ============================================================================================ // + // ====================================================================== // // Reminder: Remember that the primary inputs are ordered as follows: - // We make sure to have the primary inputs ordered as follow: - // [Root, NullifierS, CommitmentS, h_sig, h_iS, Residual Field Element(S)] - // ie, below is the index mapping of the primary input elements on the protoboard: + // + // [Root, NullifierS, CommitmentS, h_sig, h_iS, Residual Field Element(S)] + // + // ie, below is the index mapping of the primary input elements on the + // protoboard: + // // - Index of the "Root" field elements: {0} // - Index of the "NullifierS" field elements: [1, 1 + NumInputs[ - // - Index of the "CommitmentS" field elements: [1 + NumInputs, 1 + NumInputs + NumOutputs[ + // - Index of the "CommitmentS" field elements: + // [1 + NumInputs, 1 + NumInputs + NumOutputs[ // - Index of the "h_sig" field element: {1 + NumInputs + NumOutputs} - // - Index of the "Message Authentication TagS" (h_i) field elements: [1 + NumInputs + NumOutputs + 1, 1 + NumInputs + NumOuputs + 1 + NumInputs[ - // - Index of the "Residual Field Element(s)" field elements: [1 + NumInputs + NumOutputs + 1 + NumInputs, 1 + NumInputs + NumOuputs + 1 + NumInputs + nb_field_residual[ + // - Index of the "Message Authentication TagS" (h_i) field elements: + // [1 + NumInputs + NumOutputs + 1, + // 1 + NumInputs + NumOuputs + 1 + NumInputs[ + // - Index of the "Residual Field Element(s)" field elements: + // [1 + NumInputs + NumOutputs + 1 + NumInputs, + // 1 + NumInputs + NumOuputs + 1 + NumInputs + nb_field_residual[ // // The Residual field elements are structured as follows: // - v_pub_in [0, public_value_length[ // - v_pub_out [public_value_length, 2*public_value_length[ - // - h_sig remaining bits [2*public_value_length, 2*public_value_length + (digest_length-field_capacity)[ - // - nullifierS remaining bits [2*public_value_length + (digest_length-field_capacity), 2*public_value_length + (1+NumInputs)*(digest_length-field_capacity)[ - // - commitmentS remaining bits [2*public_value_length + (1+NumInputs)*(digest_length-field_capacity), 2*public_value_length + (1+NumInputs+NumOutputs)*(digest_length-field_capacity)[ - // - message authentication tagS remaining bits [2*public_value_length + (1+NumInputs+NumOutputs)*(digest_length-field_capacity), 2*public_value_length + (1+2*NumInputs+NumOutputs)*(digest_length-field_capacity)] + // - h_sig remaining bits + // [2*public_value_length, + // 2*public_value_length + (digest_length-field_capacity)[ + // - nullifierS remaining bits: + // [2*public_value_length + (digest_length-field_capacity), + // 2*public_value_length + (1+NumInputs)*(digest_length-field_capacity)[ + // - commitmentS remaining bits: + // [2*public_value_length + (1+NumInputs)*(digest_length-field_capacity), + // 2*public_value_length + (1+NumInputs+NumOutputs)*(digest_length-field_capacity)[ + // - message authentication tagS remaining bits: + // [2*public_value_length + (1+NumInputs+NumOutputs)*(digest_length-field_capacity), + // 2*public_value_length + (1+2*NumInputs+NumOutputs)*(digest_length-field_capacity)] // ============================================================================================ // - // This function is used to extract the public values (vpub_in and vpub_out) from the residual field element(S) - function assemble_public_values(uint[] memory primary_inputs) public pure returns (uint64 vpub_in, uint64 vpub_out){ - // We know vpub_in corresponds to the first 64 bits of the first residual field element after padding. - // We retrieve the public value in and remove any extra bits (due to the padding) - uint residual_hash_size = packing_residue_length*nb_hash_digests; + // This function is used to extract the public values (vpub_in and + // vpub_out) from the residual field element(S) - bytes32 vpub_bytes = bytes32(primary_inputs[1 + nb_hash_digests]) >> (residual_hash_size + public_value_length); + function assemble_public_values(uint256[nbInputs] memory primary_inputs) + public pure + returns (uint64 vpub_in, uint64 vpub_out){ + // We know vpub_in corresponds to the first 64 bits of the first + // residual field element after padding. We retrieve the public value + // in and remove any extra bits (due to the padding) + uint256 residual_hash_size = packing_residue_length*nb_hash_digests; + + bytes32 vpub_bytes = bytes32(primary_inputs[1 + nb_hash_digests]) + >> (residual_hash_size + public_value_length); vpub_in = uint64(uint(vpub_bytes)); - // We retrieve the public value out and remove any extra bits (due to the padding) - vpub_bytes = bytes32(primary_inputs[1 + nb_hash_digests]) >> residual_hash_size; + // We retrieve the public value out and remove any extra bits (due to + // the padding) + vpub_bytes = bytes32(primary_inputs[1 + nb_hash_digests]) + >> residual_hash_size; vpub_out = uint64(uint(vpub_bytes)); } - // This function is used to reassemble hsig given the the primary_inputs - // To do so, we extract the remaining bits of hsig from the residual field element(S) and combine them with the hsig field element - function assemble_hsig(uint[] memory primary_inputs) public pure returns (bytes32 hsig){ - - // We know hsig residual bits correspond to the 128th to 130st bits of the first residual field element after padding. - // We retrieve hsig's residual bits and remove any extra bits (due to the padding) - // They correspond to the (digest_length - field_capacity) least significant bits of hsig in big endian - bytes32 hsig_bytes = (bytes32(primary_inputs[1 + nb_hash_digests]) << padding_size + 2*public_value_length) >> field_capacity; - - // We retrieve the field element corresponding to the `field_capacity` most significant bits of hsig - // We remove the left padding due to casting `field_capacity` bits into a bytes32 - // We reassemble hsig by adding the values - hsig = bytes32(uint(primary_inputs[1 + jsIn + jsOut] << (digest_length - field_capacity)) + uint(hsig_bytes)); + // This function is used to reassemble hsig given the the primary_inputs To + // do so, we extract the remaining bits of hsig from the residual field + // element(S) and combine them with the hsig field element + function assemble_hsig(uint256[nbInputs] memory primary_inputs) + public pure + returns (bytes32 hsig) { + + // We know hsig residual bits correspond to the 128th to 130st bits of + // the first residual field element after padding. We retrieve hsig's + // residual bits and remove any extra bits (due to the padding) They + // correspond to the (digest_length - field_capacity) least significant + // bits of hsig in big endian + bytes32 hsig_bytes = + (bytes32(primary_inputs[1 + nb_hash_digests]) << padding_size + + 2*public_value_length) >> field_capacity; + + // We retrieve the field element corresponding to the `field_capacity` + // most significant bits of hsig We remove the left padding due to + // casting `field_capacity` bits into a bytes32 We reassemble hsig by + // adding the values + uint256 high_bits = uint( + primary_inputs[1 + jsIn + jsOut] << (digest_length - field_capacity)); + hsig = bytes32(high_bits + uint(hsig_bytes)); } - // This function is used to reassemble the nullifiers given the nullifier index [0, jsIn[ and the primary_inputs - // To do so, we extract the remaining bits of the nullifier from the residual field element(S) and combine them with the nullifier field element - function assemble_nullifier(uint index, uint[] memory primary_inputs) public pure returns (bytes32 nf){ + // This function is used to reassemble the nullifiers given the nullifier + // index [0, jsIn[ and the primary_inputs To do so, we extract the + // remaining bits of the nullifier from the residual field element(S) and + // combine them with the nullifier field element + function assemble_nullifier( + uint256 index, uint256[nbInputs] memory primary_inputs) + public pure + returns (bytes32 nf) { + // We first check that the nullifier we want to retrieve exists require( index < jsIn, "nullifier index overflow" ); - // We offset the nullifier index by the number of values preceding the nullifiers in the primary inputs: - // the root (1) - // This offset also corresponds to the offset of the commitment in the residual field elements (minus the public values) - uint nullifier_index = 1 + index; - - // We compute the nullifier's residual bits index and check the 1st f.e. indeed comprises it - // See the way the residual bits are ordered in the extended proof - uint nf_bit_index = 2*public_value_length + nullifier_index * packing_residue_length; + // We offset the nullifier index by the number of values preceding the + // nullifiers in the primary inputs: the root (1). This offset also + // corresponds to the offset of the commitment in the residual field + // elements (minus the public values) + uint256 nullifier_index = 1 + index; + + // We compute the nullifier's residual bits index and check the 1st + // f.e. indeed comprises it. See the way the residual bits are ordered + // in the extended proof + uint256 nf_bit_index = + 2*public_value_length + nullifier_index * packing_residue_length; require( field_capacity >= nf_bit_index + packing_residue_length, "nullifier written in different residual bit f.e." ); - // We retrieve nf's residual bits and remove any extra bits (due to the padding) - // They correspond to the (digest_length - field_capacity) least significant bits of nf in big endian - bytes32 nf_bytes = (bytes32(primary_inputs[1 + nb_hash_digests]) << (padding_size + nf_bit_index)) >> field_capacity; - - - // We retrieve the field element corresponding to the `field_capacity` most significant bits of nf - // We remove the left padding due to casting `field_capacity` bits into a bytes32 - // We reassemble nf by adding the values - nf = bytes32(uint(primary_inputs[nullifier_index] << (digest_length - field_capacity)) + uint(nf_bytes)); + // We retrieve nf's residual bits and remove any extra bits (due to the + // padding). They correspond to the (digest_length - field_capacity) + // least significant bits of nf in big endian + bytes32 nf_bytes = ( + bytes32(primary_inputs[1 + nb_hash_digests]) + << (padding_size + nf_bit_index)) >> field_capacity; + + // We retrieve the field element corresponding to the `field_capacity` + // most significant bits of nf. We remove the left padding due to + // casting `field_capacity` bits into a bytes32. We reassemble nf by + // adding the values. + uint256 high_bits = uint( + primary_inputs[nullifier_index] << (digest_length - field_capacity)); + nf = bytes32(high_bits + uint(nf_bytes)); } - // This function is used to reassemble the commitment given the commitment index [0, jsOut[ and the primary_inputs - // To do so, we extract the remaining bits of the commitment from the residual field element(S) and combine them with the commitment field element - function assemble_commitment(uint index, uint[] memory primary_inputs) public pure returns (bytes32 cm){ + // This function is used to reassemble the commitment given the commitment + // index [0, jsOut[ and the primary_inputs. To do so, we extract the + // remaining bits of the commitment from the residual field element(S) and + // combine them with the commitment field element. + function assemble_commitment( + uint256 index, uint256[nbInputs] memory primary_inputs) + public pure + returns (bytes32 cm) { + // We first check that the commitment we want to retrieve exists require( index < jsOut, "commitment index overflow" ); - // We offset the commitment index by the number of values preceding the commitments in the primary inputs: - // the root (1) and the nullifiers (jsIn) - // This offset also corresponds to the offset of the commitment in the residual field elements (minus the public values) - uint commitment_index = 1 + jsIn + index; - - // We compute the commitment's residual bits index and check the 1st f.e. indeed comprises it - // See the way the residual bits are ordered in the extended proof - uint commitment_bit_index = 2 * public_value_length + commitment_index * packing_residue_length; + // We offset the commitment index by the number of values preceding the + // commitments in the primary inputs: the root (1) and the nullifiers + // (jsIn). This offset also corresponds to the offset of the commitment + // in the residual field elements (minus the public values) + uint256 commitment_index = 1 + jsIn + index; + + // We compute the commitment's residual bits index and check the 1st + // f.e. indeed comprises it. See the way the residual bits are ordered + // in the extended proof. + uint256 commitment_bit_index = + 2 * public_value_length + commitment_index * packing_residue_length; require( field_capacity >= commitment_bit_index + packing_residue_length, "commitment written in different residual bit f.e." ); - // We retrieve cm's residual bits and remove any extra bits (due to the padding) - // They correspond to the (digest_length - field_capacity) least significant bits of cm in big endian - bytes32 cm_bytes = (bytes32(primary_inputs[1 + nb_hash_digests]) << (padding_size + commitment_bit_index)) >> field_capacity; - - // We retrieve the field element corresponding to the `field_capacity` most significant bits of cm - // We remove the left padding due to casting `field_capacity` bits into a bytes32 - // We reassemble cm by adding the values - cm = bytes32(uint(primary_inputs[commitment_index] << (digest_length - field_capacity)) + uint(cm_bytes)); + // We retrieve cm's residual bits and remove any extra bits (due to the + // padding). They correspond to the (digest_length - field_capacity) + // least significant bits of cm in big endian. + bytes32 cm_bytes = ( + bytes32(primary_inputs[1 + nb_hash_digests]) + << (padding_size + commitment_bit_index) + ) >> field_capacity; + + // We retrieve the field element corresponding to the `field_capacity` + // most significant bits of cm. We remove the left padding due to + // casting `field_capacity` bits into a bytes32. We reassemble cm by + // adding the values. + uint256 high_bits = uint( + primary_inputs[commitment_index] << (digest_length - field_capacity)); + cm = bytes32(high_bits + uint(cm_bytes)); } - // This function processes the primary inputs to append and check the root and nullifiers in the primary inputs (instance) - // and modifies the state of the mixer contract accordingly - // (ie: Appends the commitments to the tree, appends the nullifiers to the list and so on) + // This function processes the primary inputs to append and check the root + // and nullifiers in the primary inputs (instance) and modifies the state + // of the mixer contract accordingly. (ie: Appends the commitments to the + // tree, appends the nullifiers to the list and so on). function check_mkroot_nullifiers_hsig_append_nullifiers_state( - uint[2][2] memory vk, - uint[] memory primary_inputs) internal { + uint256[4] memory vk, + uint256[nbInputs] memory primary_inputs) + internal { // 1. We re-assemble the full root digest and check it is in the tree require( roots[bytes32(primary_inputs[0])], "Invalid root: This root doesn't exist" ); - // 2. We re-assemble the nullifiers (JSInputs) and check they were not already seen + // 2. We re-assemble the nullifiers (JSInputs) and check they were not + // already seen. bytes32[jsIn] memory nfs; - for(uint i; i < jsIn; i++) { + for (uint256 i = 0; i < jsIn; i++) { nfs[i] = assemble_nullifier(i, primary_inputs); require( !nullifiers[nfs[i]], @@ -272,8 +362,9 @@ contract BaseMixer is MerkleTreeMiMC7, ERC223ReceivingContract { nullifiers[nfs[i]] = true; } - // 3. We re-compute h_sig, re-assemble the expected h_sig and check they are equal - // (i.e. that h_sig re-assembled was correctly generated from vk) + // 3. We re-compute h_sig, re-assemble the expected h_sig and check + // they are equal (i.e. that h_sig re-assembled was correctly generated + // from vk). bytes32 expected_hsig = sha256(abi.encodePacked(nfs, vk)); bytes32 hsig = assemble_hsig(primary_inputs); require( @@ -282,24 +373,25 @@ contract BaseMixer is MerkleTreeMiMC7, ERC223ReceivingContract { ); } - - function assemble_commitments_and_append_to_state(uint[] memory primary_inputs) internal { + function assemble_commitments_and_append_to_state( + uint256[nbInputs] memory primary_inputs) + internal { // We re-assemble the commitments (JSOutputs) - for(uint i; i < jsOut; i++) { + for (uint256 i = 0; i < jsOut; i++) { bytes32 current_commitment = assemble_commitment(i, primary_inputs); - uint commitmentAddress = insert(current_commitment); + uint256 commitmentAddress = insert(current_commitment); emit LogCommitment(commitmentAddress, current_commitment); } } - function process_public_values(uint[] memory primary_inputs) internal { + function process_public_values(uint256[nbInputs] memory primary_inputs) + internal { // 0. We get vpub_in and vpub_out - uint vpub_in_zeth_units; - uint vpub_out_zeth_units; - (vpub_in_zeth_units, vpub_out_zeth_units) = assemble_public_values(primary_inputs); + (uint256 vpub_in_zeth_units, uint256 vpub_out_zeth_units) = + assemble_public_values(primary_inputs); // 1. We get the vpub_in in wei - uint vpub_in = vpub_in_zeth_units * public_unit_value_wei; + uint256 vpub_in = vpub_in_zeth_units * public_unit_value_wei; // If the vpub_in is > 0, we need to make sure the right amount is paid if (vpub_in > 0) { @@ -319,10 +411,10 @@ contract BaseMixer is MerkleTreeMiMC7, ERC223ReceivingContract { } // 2. Get vpub_out in wei - uint vpub_out = vpub_out_zeth_units * public_unit_value_wei; + uint256 vpub_out = vpub_out_zeth_units * public_unit_value_wei; - // If value_pub_out > 0 then we do a withdraw - // We retrieve the msg.sender and send him the appropriate value IF proof is valid + // If value_pub_out > 0 then we do a withdraw. We retrieve the + // msg.sender and send him the appropriate value IF proof is valid if (vpub_out > 0) { if (token != address(0)) { ERC20 erc20Token = ERC20(token); @@ -338,7 +430,11 @@ contract BaseMixer is MerkleTreeMiMC7, ERC223ReceivingContract { emit LogMerkleRoot(root); } - function emit_ciphertexts(bytes32 pk_sender, bytes memory ciphertext0, bytes memory ciphertext1) internal { + function emit_ciphertexts( + bytes32 pk_sender, + bytes memory ciphertext0, + bytes memory ciphertext1) + internal { emit LogSecretCiphers(pk_sender, ciphertext0); emit LogSecretCiphers(pk_sender, ciphertext1); } diff --git a/zeth-contracts/contracts/Groth16Mixer.sol b/zeth-contracts/contracts/Groth16Mixer.sol index 65e4c8e07..6309b7104 100644 --- a/zeth-contracts/contracts/Groth16Mixer.sol +++ b/zeth-contracts/contracts/Groth16Mixer.sol @@ -5,69 +5,358 @@ pragma solidity ^0.5.0; import "./OTSchnorrVerifier.sol"; -import "./Groth16Verifier.sol"; import "./BaseMixer.sol"; contract Groth16Mixer is BaseMixer { - // zkSNARK verifier smart contract - Groth16Verifier public zksnark_verifier; - // OT-Signature verifier smart contract - OTSchnorrVerifier public otsig_verifier; + + // The structure of the verification key differs from the reference paper. + // It doesn't contain any element of GT, but only elements of G1 and G2 (the + // source groups). This is due to the lack of precompiled contract to + // manipulate elements of the target group GT on Ethereum. + struct VerifyingKey { + Pairing.G1Point Alpha; // slots 0x00, 0x01 + Pairing.G2Point Beta; // slots 0x02, 0x03, 0x04, 0x05 + Pairing.G2Point Delta; // slots 0x06, 0x07, 0x08, 0x09 + Pairing.G1Point[] ABC; // slot 0x0a + } + + // Internal Proof structure. Avoids reusing the G1 and G2 structs, since + // these cause extra pointers in memory, and complexity passing the data to + // precompiled contracts. + struct Proof { + // Pairing.G1Point A; + uint256 A_X; + uint256 A_Y; + // Pairing.G2Point B; + uint256 B_X0; + uint256 B_X1; + uint256 B_Y0; + uint256 B_Y1; + // Pairing.G1Point C; + uint256 C_X; + uint256 C_Y; + } + + VerifyingKey verifyKey; // Constructor constructor( - address snark_ver, - address sig_ver, - uint mk_depth, + uint256 mk_depth, address token, - address hasher) BaseMixer(mk_depth, token, hasher) public { - zksnark_verifier = Groth16Verifier(snark_ver); - otsig_verifier = OTSchnorrVerifier(sig_ver); + uint256[2] memory Alpha, + uint256[2] memory Beta1, + uint256[2] memory Beta2, + uint256[2] memory Delta1, + uint256[2] memory Delta2, + uint256[] memory ABC_coords) + BaseMixer(mk_depth, token) + public { + verifyKey.Alpha = Pairing.G1Point(Alpha[0], Alpha[1]); + verifyKey.Beta = Pairing.G2Point(Beta1[0], Beta1[1], Beta2[0], Beta2[1]); + verifyKey.Delta = Pairing.G2Point( + Delta1[0], Delta1[1], Delta2[0], Delta2[1]); + + // The `ABC` are elements of G1 (and thus have 2 coordinates in the + // underlying field). Here, we reconstruct these group elements from + // field elements (ABC_coords are field elements) + uint256 i = 0; + while(verifyKey.ABC.length != ABC_coords.length/2) { + verifyKey.ABC.push(Pairing.G1Point(ABC_coords[i], ABC_coords[i+1])); + i += 2; + } } // This function allows to mix coins and execute payments in zero // knowledge. The nb of ciphertexts depends on the JS description (Here 2 // inputs) function mix( - uint[2] memory a, - uint[2][2] memory b, - uint[2] memory c, - uint[2][2] memory vk, - uint sigma, - uint[] memory input, + uint256[2] memory a, + uint256[4] memory b, + uint256[2] memory c, + uint256[4] memory vk, + uint256 sigma, + uint256[nbInputs] memory input, bytes32 pk_sender, bytes memory ciphertext0, - bytes memory ciphertext1 - ) public payable { + bytes memory ciphertext1) + public payable { // 1. Check the root and the nullifiers check_mkroot_nullifiers_hsig_append_nullifiers_state(vk, input); // 2.a Verify the signature on the hash of data_to_be_signed - bytes32 hash_to_be_signed = sha256(abi.encodePacked( - pk_sender, ciphertext0, ciphertext1, a, b, c, input)); + bytes32 hash_to_be_signed = sha256( + abi.encodePacked( + pk_sender, + ciphertext0, + ciphertext1, + a, + b, + c, + input + )); require( - otsig_verifier.verify(vk, sigma, hash_to_be_signed), + OTSchnorrVerifier.verify( + vk[0], vk[1], vk[2], vk[3], sigma, hash_to_be_signed), "Invalid signature: Unable to verify the signature correctly" ); // 2.b Verify the proof require( - zksnark_verifier.verifyTx(a, b, c, input), + verifyTx(a, b, c, input), "Invalid proof: Unable to verify the proof correctly" ); // 3. Append the commitments to the tree assemble_commitments_and_append_to_state(input); - // 4. get the public values in Wei and modify the state depending on + // 4. Get the public values in Wei and modify the state depending on // their values process_public_values(input); // 5. Add the new root to the list of existing roots and emit it add_and_emit_merkle_root(getRoot()); - // Emit the all the coins' secret data encrypted with the recipients' + // 6. Emit the all the coins' secret data encrypted with the recipients' // respective keys emit_ciphertexts(pk_sender, ciphertext0, ciphertext1); } + + function verify(uint256[] memory input, Proof memory proof) + internal + returns (uint) { + + // `input.length` = size of the instance = l (see notations in the + // reference paper). We have coefficients indexed in the range[1..l], + // where l is the instance size, and we define a_0 = 1. This is the + // reason we need to check that: input.length + 1 == vk.ABC.length (the + // +1 accounts for a_0). This equality is a strong consistency check + // (len(givenInputs) needs to equal expectedInputSize (not less)) + require( + input.length + 1 == verifyKey.ABC.length, + "Input length differs from expected"); + + // Memory scratch pad, large enough to accomodate the max used size + // (see layout diagrams below). + uint256[24] memory pad; + + // 1. Compute the linear combination + // vk_x = \sum_{i=0}^{l} a_i * vk.ABC[i], vk_x in G1. + // + // ORIGINAL CODE: + // Pairing.G1Point memory vk_x = vk.ABC[0]; // a_0 = 1 + // for (uint256 i = 0; i < input.length; i++) { + // vk_x = Pairing.add(vk_x, Pairing.mul(vk.ABC[i + 1], input[i])); + // } + // + // The linear combination loop was the biggest cost center of the mixer + // contract. The following assembly block removes a lot of unnecessary + // memory usage and data copying, but relies on the structure of storage + // data. + // + // `pad` is layed out as follows, (so that calls to precompiled + // contracts can be done with minimal data copying) + // + // OFFSET USAGE + // 0x20 accum_y + // 0x00 accum_x + + // In each iteration, copy scalar multiplicaation data to 0x40+ + // + // OFFSET USAGE + // 0x80 input_i -- + // 0x60 abc_y | compute abc[i+1] * input[i] in-place + // 0x40 abc_x -- + // 0x20 accum_y + // 0x00 accum_x + // + // ready to call bn256ScalarMul(in: 0x40, out: 0x40). This results in: + // + // OFFSET USAGE + // 0x80 + // 0x60 input_i * abc_y -- + // 0x40 input_i * abc_x | accum = accum + input[i] * abc[i+1] + // 0x20 accum_y | + // 0x00 accum_x -- + // + // ready to call bn256Add(in: 0x00, out: 0x00) to update accum_x, + // accum_y in place. + + bool success = true; + assembly { + + let g := sub(gas, 2000) + + // Compute slot of ABC[0]. Solidity memory array layout defines the + // first entry of verifyKey.ABC as the keccak256 hash of the slot + // of verifyKey.ABC. The slot of verifyKey.ABC is computed using + // Solidity implicit `_slot` notation. + mstore(pad, add(verifyKey_slot, 10)) + let abc_slot := keccak256(pad, 32) + + // Compute input array bounds (layout: ,elem_0,elem_1...) + let input_i := add(input, 0x20) + let input_end := add(input_i, mul(0x20, mload(input))) + + // Initialize pad[0] with abc[0] + mstore(pad, sload(abc_slot)) + mstore(add(pad, 0x20), sload(add(abc_slot, 1))) + abc_slot := add(abc_slot, 2) + + // Location within pad to do scalar mul operation + let mul_in := add(pad, 0x40) + + // Iterate over all inputs / ABC values + for + { } + lt(input_i, input_end) + { + abc_slot := add(abc_slot, 2) + input_i := add(input_i, 0x20) + } + { + // Copy abc[i+1] into mul_in, incrementing abc + mstore(mul_in, sload(abc_slot)) + mstore(add(mul_in, 0x20), sload(add(abc_slot, 1))) + + // Copy input[i] into mul_in + 0x40, and increment index_i + mstore(add(mul_in, 0x40), mload(input_i)) + + // bn256ScalarMul and bn256Add can be done with no copying + let s1 := call(g, 7, 0, mul_in, 0x60, mul_in, 0x40) + let s2 := call(g, 6, 0, pad, 0x80, pad, 0x40) + success := and(success, and(s1, s2)) + } + } + + require( + success, + "Call to the bn256Add or bn256ScalarMul precompiled failed"); + + // 2. The verification check: + // e(Proof.A, Proof.B) = + // e(vk.Alpha, vk.Beta) * e(vk_x, P2) * e(Proof.C, vk.Delta) + // where: + // - e: G_1 x G_2 -> G_T is a bilinear map + // - `*`: denote the group operation in G_T + + // ORIGINAL CODE: + // bool res = Pairing.pairingProd4( + // Pairing.negate(Pairing.G1Point(proof.A_X, proof.A_Y)), + // Pairing.G2Point(proof.B_X0, proof.B_X1, proof.B_Y0, proof.B_Y1), + // verifyKey.Alpha, verifyKey.Beta, + // vk_x, Pairing.P2(), + // Pairing.G1Point(proof.C_X, proof.C_Y), + // verifyKey.Delta); + // if (!res) { + // return 0; + // } + // return 1; + + // Assembly below fills out pad and calls bn256Pairing, performing a + // check of the form: + // + // e(vk_x, P2) * e(vk.Alpha, vk.Beta) * + // e(negate(Proof.A), Proof.B) * e(Proof.C, vk.Delta) == 1 + // + // See Pairing.pairing(). Note terms have been re-ordered since vk_x is + // already at offset 0x00. Memory is laid out: + // + // 0x0300 + // 0x0280 - verifyKey.Delta in G2 + // 0x0240 - proof.C in G1 + // 0x01c0 - Proof.B in G2 + // 0x0180 - negate(Proof.A) in G1 + // 0x0100 - vk.Beta in G2 + // 0x00c0 - vk.Alpha in G1 + // 0x0040 - P2 in G2 + // 0x0000 - vk_x in G1 (Already present, by the above) + + assembly { + + // Write P2, from offset 0x40. See Pairing for these values. + mstore( + add(pad, 0x040), + 11559732032986387107991004021392285783925812861821192530917403151452391805634) + mstore( + add(pad, 0x060), + 10857046999023057135944570762232829481370756359578518086990519993285655852781) + mstore( + add(pad, 0x080), + 4082367875863433681332203403145435568316851327593401208105741076214120093531) + mstore( + add(pad, 0x0a0), + 8495653923123431417604973247489272438418190587263600148770280649306958101930) + + // Write vk.Alpha, vk.Beta (first 6 uints from verifyKey) from + // offset 0x0c0. + mstore(add(pad, 0x0c0), sload(verifyKey_slot)) + mstore(add(pad, 0x0e0), sload(add(verifyKey_slot, 1))) + mstore(add(pad, 0x100), sload(add(verifyKey_slot, 2))) + mstore(add(pad, 0x120), sload(add(verifyKey_slot, 3))) + mstore(add(pad, 0x140), sload(add(verifyKey_slot, 4))) + mstore(add(pad, 0x160), sload(add(verifyKey_slot, 5))) + + // Write negate(Proof.A) and Proof.B from offset 0x180. + mstore(add(pad, 0x180), mload(proof)) + let q := 21888242871839275222246405745257275088696311157297823662689037894645226208583 + let proof_A_y := mload(add(proof, 0x20)) + mstore(add(pad, 0x1a0), sub(q, mod(proof_A_y, q))) + mstore(add(pad, 0x1c0), mload(add(proof, 0x40))) + mstore(add(pad, 0x1e0), mload(add(proof, 0x60))) + mstore(add(pad, 0x200), mload(add(proof, 0x80))) + mstore(add(pad, 0x220), mload(add(proof, 0xa0))) + + // Proof.C and verifyKey.Delta from offset 0x240. + mstore(add(pad, 0x240), mload(add(proof, 0xc0))) + mstore(add(pad, 0x260), mload(add(proof, 0xe0))) + mstore(add(pad, 0x280), sload(add(verifyKey_slot, 6))) + mstore(add(pad, 0x2a0), sload(add(verifyKey_slot, 7))) + mstore(add(pad, 0x2c0), sload(add(verifyKey_slot, 8))) + mstore(add(pad, 0x2e0), sload(add(verifyKey_slot, 9))) + + success := call(sub(gas, 2000), 8, 0, pad, 0x300, pad, 0x20) + } + + require( + success, + "Call to bn256Add, bn256ScalarMul or bn256Pairing failed"); + return pad[0]; + } + + function verifyTx( + uint256[2] memory a, + uint256[4] memory b, + uint256[2] memory c, + uint256[nbInputs] memory primaryInputs) + internal + returns (bool) { + // Scalar field characteristic + // solium-disable-next-line + uint256 r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + Proof memory proof; + proof.A_X = a[0]; + proof.A_Y = a[1]; + proof.B_X0 = b[0]; + proof.B_X1 = b[1]; + proof.B_Y0 = b[2]; + proof.B_Y1 = b[3]; + proof.C_X = c[0]; + proof.C_Y = c[1]; + + // Make sure that all primary inputs lie in the scalar field + + // TODO: For some reason, using a statically sized array (or + // primaryInputs directly) causes an out-of-gas exception, which seems + // completely counter-intuitive. Until that is tracked down, we use a + // dynamic array. + + uint256[] memory inputValues = new uint256[](nbInputs); + for (uint256 i = 0 ; i < nbInputs; i++) { + require(primaryInputs[i] < r, "Input is not in scalar field"); + inputValues[i] = primaryInputs[i]; + } + + return 1 == verify(inputValues, proof); + } } diff --git a/zeth-contracts/contracts/Groth16Verifier.sol b/zeth-contracts/contracts/Groth16Verifier.sol deleted file mode 100644 index 0ecad2a2c..000000000 --- a/zeth-contracts/contracts/Groth16Verifier.sol +++ /dev/null @@ -1,136 +0,0 @@ -// Copyright (c) 2015-2019 Clearmatics Technologies Ltd -// -// SPDX-License-Identifier: LGPL-3.0+ - -pragma solidity ^0.5.0; - -/* - * Reference paper: - * - * \[Gro16]: - * "On the Size of Pairing-based Non-interactive Arguments", - * Jens Groth, - * EUROCRYPT 2016, - * -**/ - -import "./Pairing.sol"; - -// Groth16 Verifier contract -contract Groth16Verifier { - using Pairing for *; - - // The structure of the verification key differs from the reference paper. - // It doesn't contain any element of GT, but only elements of G1 and G2 (the source groups). - // This is due to the lack of precompiled contract to manipulate elements of the target group GT on Ethereum. - struct VerifyingKey { - Pairing.G1Point Alpha; // element of G1 used to obtain Alpha in G1 - Pairing.G2Point Beta; // element of G2 used to obtain Beta in G2 - Pairing.G2Point Delta; - Pairing.G1Point[] ABC; // List of encodings of [Beta * u_i(x) + Alpha * v_i(x) + w_i(x)], for i in [0..l], in G1 - } - - struct Proof { - Pairing.G1Point A; - Pairing.G2Point B; - Pairing.G1Point C; - } - - VerifyingKey verifyKey; - - event LogVerifier(string); - - constructor( - uint[2] memory Alpha, - uint[2] memory Beta1, - uint[2] memory Beta2, - uint[2] memory Delta1, - uint[2] memory Delta2, - uint[] memory ABC_coords - ) public { - verifyKey.Alpha = Pairing.G1Point(Alpha[0], Alpha[1]); - verifyKey.Beta = Pairing.G2Point(Beta1, Beta2); - verifyKey.Delta = Pairing.G2Point(Delta1, Delta2); - - // The `ABC` are elements of G1 (and thus have 2 coordinates in the underlying field) - // Here, we reconstruct these group elements from field elements (ABC_coords are field elements) - uint i; - while(verifyKey.ABC.length != ABC_coords.length/2) { - verifyKey.ABC.push(Pairing.G1Point(ABC_coords[i], ABC_coords[i+1])); - i += 2; - } - } - - function verify(uint[] memory input, Proof memory proof) internal returns (uint) { - VerifyingKey memory vk = verifyKey; - - // `input.length` = size of the instance = l (see notations in the reference paper) - // We have coefficients indexed in the range[1..l], where l is the instance size, and we define - // a_0 = 1. This is the reason why we need to check that: - // input.length + 1 == vk.ABC.length (the +1 accounts for a_0) - // This equality is a strong consistency check (len(givenInputs) needs to equal expectedInputSize (not less)) - require( - input.length + 1 == vk.ABC.length, - "Using strong input consistency, and the input length differs from expected" - ); - - // 1. Compute the linear combination vk_x = \sum_{i=0}^{l} a_i * vk.ABC[i], vk_x in G1 - Pairing.G1Point memory vk_x = vk.ABC[0]; // a_0 = 1 - for (uint i; i < input.length; i++) { - vk_x = Pairing.add(vk_x, Pairing.mul(vk.ABC[i + 1], input[i])); - } - - // 2. The verification check: - // e(Proof.A, Proof.B) = e(vk.Alpha, vk.Beta) * e(vk_x, P2) * e(Proof.C, vk.Delta) - // where: - // - e: G_1 x G_2 -> G_T is a bilinear map - // - `*`: denote the group operation in G_T - - bool res = Pairing.pairingProd4( - proof.A, proof.B, - Pairing.negate(vk.Alpha), vk.Beta, - Pairing.negate(vk_x), Pairing.P2(), - Pairing.negate(proof.C), vk.Delta - ); - - if (!res) { - return 0; - } - - return 1; - } - - function verifyTx( - uint[2] memory a, - uint[2][2] memory b, - uint[2] memory c, - uint[] memory primaryInputs - ) public returns (bool) { - // Scalar field characteristic - uint256 r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - - Proof memory proof; - proof.A = Pairing.G1Point(a[0], a[1]); - proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); - proof.C = Pairing.G1Point(c[0], c[1]); - - uint[] memory inputValues = new uint[](primaryInputs.length); - for(uint i = 0; i < primaryInputs.length; i++){ - // Make sure that all primary inputs lie in the scalar field - require( - primaryInputs[i] < r, - "Input is not in scalar field" - ); - inputValues[i] = primaryInputs[i]; - } - - uint verification_result = verify(inputValues, proof); - if (verification_result != 1) { - emit LogVerifier("Failed to verify the transaction"); - return false; - } - - emit LogVerifier("Proof verification successfull"); - return true; - } -} diff --git a/zeth-contracts/contracts/MerkleTreeMiMC7.sol b/zeth-contracts/contracts/MerkleTreeMiMC7.sol index 1e8fdbab2..7f2bdf53f 100644 --- a/zeth-contracts/contracts/MerkleTreeMiMC7.sol +++ b/zeth-contracts/contracts/MerkleTreeMiMC7.sol @@ -8,43 +8,54 @@ import "./BaseMerkleTree.sol"; import "./MiMC7.sol"; contract MerkleTreeMiMC7 is BaseMerkleTree { - // Custom hash smart contract - MiMC7 public mimc7_hasher; - - constructor(address hasher_address, uint treeDepth) BaseMerkleTree(treeDepth) public { - mimc7_hasher = MiMC7(hasher_address); - } - - // Returns the current merkle tree - function getTree() public view returns (bytes32[] memory) { - uint nbNodes = 2**(depth + 1) - 1; - bytes32[] memory tmpTree = new bytes32[](nbNodes); - bytes32 left; - bytes32 right; - // Dump the leaves in the right indexes in the tree - for (uint i; i < nbLeaves; i++) { - tmpTree[(nbLeaves - 1) + i] = leaves[i]; - } - - // Compute the internal nodes of the merkle tree - for (uint i = nbLeaves - 2; i > 0; i--) { - left = tmpTree[2*i+1]; - right = tmpTree[2*(i+1)]; - // Seed is hardcoded and given by "clearmatics_mt_seed" - tmpTree[i] = mimc7_hasher.hash(left, right, "clearmatics_mt_seed"); + constructor(uint256 treeDepth) BaseMerkleTree(treeDepth) public { } - // Compute the merkle root - left = tmpTree[1]; - right = tmpTree[2]; - tmpTree[0] = mimc7_hasher.hash(left, right, "clearmatics_mt_seed"); - - return tmpTree; - } + // Returns the current merkle tree + function getTree() public view returns (bytes32[] memory) { + uint256 nbNodes = 2**(depth + 1) - 1; + bytes32[] memory tmpTree = new bytes32[](nbNodes); + bytes32 left; + bytes32 right; + // Dump the leaves in the right indexes in the tree + for (uint256 i = 0; i < nbLeaves; i++) { + tmpTree[(nbLeaves - 1) + i] = leaves[i]; + } + + // Compute the internal nodes of the merkle tree + for (uint256 i = nbLeaves - 2; i > 0; i--) { + left = tmpTree[2*i+1]; + right = tmpTree[2*(i+1)]; + tmpTree[i] = MiMC7.hash(left, right); + } + + // Compute the merkle root + left = tmpTree[1]; + right = tmpTree[2]; + tmpTree[0] = MiMC7.hash(left, right); + + return tmpTree; + } - // Returns the root of the merkle tree - function getRoot() public view returns(bytes32) { - return getTree()[0]; - } + // Returns the root of the merkle tree + function getRoot() public view returns(bytes32) { + uint256 layerSize = nbLeaves / 2; + bytes32[nbLeaves/2] memory pad; + + // Compute first layer from storage + for (uint256 i = 0 ; i < layerSize ; ++i) { + pad[i] = MiMC7.hash(leaves[2*i], leaves[2*i + 1]); + } + layerSize = layerSize / 2; + + // Compute successive layers from their parents, in-place. + for ( ; layerSize > 0 ; layerSize = layerSize / 2) { + for (uint256 i = 0 ; i < layerSize ; ++i) { + pad[i] = MiMC7.hash(pad[2*i], pad[2*i + 1]); + } + } + + return pad[0]; + } } diff --git a/zeth-contracts/contracts/MerkleTreeSha256.sol b/zeth-contracts/contracts/MerkleTreeSha256.sol index 15c154dd5..3e5ce34e7 100644 --- a/zeth-contracts/contracts/MerkleTreeSha256.sol +++ b/zeth-contracts/contracts/MerkleTreeSha256.sol @@ -9,33 +9,34 @@ import "./BaseMerkleTree.sol"; contract MerkleTreeSha256 is BaseMerkleTree { - constructor(uint treeDepth) BaseMerkleTree(treeDepth) public { - // Nothing - } - - // Returns the current merkle tree - function getTree() public view returns (bytes32[] memory) { - uint nbNodes = 2**(depth + 1) - 1; - bytes32[] memory tmpTree = new bytes32[](nbNodes); - - // Dump the leaves in the right indexes in the tree - for (uint i; i < nbLeaves; i++) { - tmpTree[(nbLeaves - 1) + i] = leaves[i]; + constructor(uint256 treeDepth) BaseMerkleTree(treeDepth) public { + // Nothing } - // Compute the internal nodes of the merkle tree - for (uint i = nbLeaves - 2; i > 0; i--) { - tmpTree[i] = sha256(abi.encodePacked(tmpTree[i*2+1], tmpTree[2*(i+1)])); - } + // Returns the current merkle tree + function getTree() public view returns (bytes32[] memory) { + uint256 nbNodes = 2**(depth + 1) - 1; + bytes32[] memory tmpTree = new bytes32[](nbNodes); + + // Dump the leaves in the right indexes in the tree + for (uint256 i = 0; i < nbLeaves; i++) { + tmpTree[(nbLeaves - 1) + i] = leaves[i]; + } - // Compute the merkle root - tmpTree[0] = sha256(abi.encodePacked(tmpTree[1], tmpTree[2])); + // Compute the internal nodes of the merkle tree + for (uint256 i = nbLeaves - 2; i > 0; i--) { + tmpTree[i] = sha256( + abi.encodePacked(tmpTree[i*2+1], tmpTree[2*(i+1)])); + } - return tmpTree; - } + // Compute the merkle root + tmpTree[0] = sha256(abi.encodePacked(tmpTree[1], tmpTree[2])); - // Returns the root of the merkle tree - function getRoot() public view returns(bytes32) { - return getTree()[0]; - } + return tmpTree; + } + + // Returns the root of the merkle tree + function getRoot() public view returns(bytes32) { + return getTree()[0]; + } } diff --git a/zeth-contracts/contracts/MiMC7.sol b/zeth-contracts/contracts/MiMC7.sol index 8e79ea638..0d5b043b9 100644 --- a/zeth-contracts/contracts/MiMC7.sol +++ b/zeth-contracts/contracts/MiMC7.sol @@ -4,70 +4,77 @@ pragma solidity ^0.5.0; -/* - * Reference papers: - * - * \[AGRRT16]: - * "MiMC: Efficient Encryption and Cryptographic Hashing with Minimal Multiplicative Complexity", - * Martin Albrecht, Lorenzo Grassi, Christian Rechberger, Arnab Roy, and Tyge Tiessen, - * ASIACRYPT 2016, - * - * - * "One-way compression function" - * Section: "Miyaguchi–Preneel" - * -**/ +library MiMC7 +{ + /* + * Reference papers: + * + * \[AGRRT16]: + * "MiMC: Efficient Encryption and Cryptographic Hashing with Minimal + * Multiplicative Complexity", Martin Albrecht, Lorenzo Grassi, Christian + * Rechberger, Arnab Roy, and Tyge Tiessen, ASIACRYPT 2016, + * + * + * "One-way compression function" + * Section: "Miyaguchi–Preneel" + * + **/ + function hash(bytes32 x, bytes32 y) internal pure returns (bytes32 out) { + assembly { + // Use scratch space (0x00) for roundConstant. Must use memory since + // keccak256 is iteratively applied. Start with seed = + // keccak256("clearmatics_mt_seed") + mstore(0x0, 0xdec937b7fa8db3de380427a8cc947bfab68514522c3439cfa2e9965509836814) -contract MiMC7 { - function hash(bytes32 x, bytes32 y, bytes memory enc_seed) public pure returns (bytes32 out) { - // See: https://github.com/ethereum/go-ethereum/blob/master/crypto/bn256/cloudflare/constants.go#L23 - uint r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + // See: + // https://github.com/ethereum/go-ethereum/blob/master/crypto/bn256/cloudflare/constants.go#L23 + let r := 21888242871839275222246405745257275088548364400416034343698204186575808495617 - bytes32 seed = keccak256(enc_seed); - bytes32 key = y; // y will be use used as round key of the block cipher as defined by Miyaguchi-Prenel construction + // y will be use used as round key of the block cipher as defined by + // Miyaguchi-Prenel construction + let key := y - assembly { - // Load the "free memory pointer" to point to the next free memory address - let roundConstant := mload(0x40) - // 0x40 (free memory pointer) now becomes the next memory location - mstore(0x40, add(roundConstant, 32)) - // We store the seed in the memory word/address pointed by roundConstant - mstore(roundConstant, seed) + // Round function f(message) = (message + key + roundConstant)^d + // d (= exponent) = 7; #rounds = 91 + // + // Note on the exponent: gcd(7, r - 1) = 1 which confirms that the + // monomial x^7 is a permutation in Fr. See: Proposition 1, Section + // 4 and section 5; https://eprint.iacr.org/2016/492.pdf + // + // In the first round the constant is not used + let outPermutation := x - // Round function f(message) = (message + key + roundConstant)^d - // d (= exponent) = 7; #rounds = 91 - // - // Note on the exponent: gcd(7, r - 1) = 1 which confirms that the monomial x^7 is a permutation in Fr - // See: Proposition 1, Section 4 and section 5; https://eprint.iacr.org/2016/492.pdf - // - // In the first round the constant is not used - let outPermutation := x + // a = outPermutation + roundConstant + key mod r + let a := addmod(outPermutation, key, r) + // a2 = a^2 mod r + let a2 := mulmod(a, a, r) + // outPermutation = a^7 mod r + // (x^7 is the permutation polynomial used) + outPermutation := mulmod(mulmod(a2, a2, r), mulmod(a2, a, r), r) - // a = outPermutation + roundConstant + key mod r - let a := addmod(outPermutation, key, r) - // a2 = a^2 mod r - let a2 := mulmod(a, a, r) - // outPermutation = a^7 mod r (x^7 is the permutation polynomial used) - outPermutation := mulmod(mulmod(mulmod(a2, a2, r), a2, r), a, r) + for {let j := 0} slt(j, 90) {j := add(j,1)} { + // roundConstant = H(roundConstant); + // we derive the (round) constants by iterative hash on the seed + let roundConstant := keccak256(0x0, 32) + mstore(0x0, roundConstant) + // a = outPermutation + roundConstant + key mod r + a := addmod(addmod(outPermutation, roundConstant, r), key, r) + // a2 = a^2 mod r + a2 := mulmod(a, a, r) + // outPermutation = a^7 mod r + // (x^7 is the permutation polynomial used) + outPermutation := mulmod(mulmod(mulmod(a2, a2, r), a2, r), a, r) + } - for {let j := 0} slt(j, 90) {j := add(j,1)} { - // roundConstant = H(roundConstant); we derive the (round) constants by iterative hash on the seed - mstore(roundConstant, keccak256(roundConstant, 32)) - // a = outPermutation + roundConstant + key mod r - a := addmod(addmod(outPermutation, mload(roundConstant), r), key, r) - // a2 = a^2 mod r - a2 := mulmod(a, a, r) - // outPermutation = a^7 mod r (x^7 is the permutation polynomial used) - outPermutation := mulmod(mulmod(mulmod(a2, a2, r), a2, r), a, r) - } - - // Compute H_i from H_{i-1} to generate the round key for the next entry in the input slice x - // In MiMC the output of the last round is mixed with the round key: This corresponds to the `outMiMCCipher = addmod(outPermutation, key, r)` - // And, the Myjaguchi-Prenell OWCF is ran: `addmod(addmod(outMiMCCipher, message, r), key, r)` - // Note that we have merged the key addition ( +key ) of the last round of MiMC with the Myjaguchi-Prenell step - out := addmod(addmod(addmod(outPermutation, key, r), x, r), key, r) + // Compute H_i from H_{i-1} to generate the round key for the next + // entry in the input slice x. In MiMC the output of the last round + // is mixed with the round key: This corresponds to the + // `outMiMCCipher = addmod(outPermutation, key, r)`. And, the + // Myjaguchi-Prenell OWCF is ran: `addmod(addmod(outMiMCCipher, + // message, r), key, r)`. Note that we have merged the key addition + // ( +key ) of the last round of MiMC with the Myjaguchi-Prenell + // step. + out := addmod(addmod(addmod(outPermutation, key, r), x, r), key, r) + } } - - return out; - } } diff --git a/zeth-contracts/contracts/OTSchnorrVerifier.sol b/zeth-contracts/contracts/OTSchnorrVerifier.sol index 4937c67c1..22661a857 100644 --- a/zeth-contracts/contracts/OTSchnorrVerifier.sol +++ b/zeth-contracts/contracts/OTSchnorrVerifier.sol @@ -15,33 +15,108 @@ pragma solidity ^0.5.0; **/ import "./Pairing.sol"; -contract OTSchnorrVerifier { +library OTSchnorrVerifier { using Pairing for *; - constructor() public { - // Nothing - } - function verify( - uint[2][2] memory vk, - uint sigma, - bytes32 hash_to_be_signed - ) public returns (bool) { - bytes32 h_bytes = - sha256(abi.encodePacked(vk[1][0], vk[1][1], hash_to_be_signed)); - uint h = uint(h_bytes); - - // X = g^{x}, where g represents a generator of the cyclic group G - Pairing.G1Point memory X = Pairing.G1Point(vk[0][0], vk[0][1]); - // Y = g^{y} - Pairing.G1Point memory Y = Pairing.G1Point(vk[1][0], vk[1][1]); - - // S = g^{sigma} - Pairing.G1Point memory S = Pairing.mul(Pairing.P1(), sigma); - // S_comp = g^{y + xh} - Pairing.G1Point memory S_comp = Pairing.add(Y, Pairing.mul(X, h)); - - // Check that g^{sigma} == g^{y + xh} - return (S.X == S_comp.X && S.Y == S_comp.Y); + uint256 vk0, + uint256 vk1, + uint256 vk2, + uint256 vk3, + uint256 sigma, + bytes32 hash_to_be_signed) + internal + returns (bool) + { + // Original code: + // + // bytes32 h_bytes = sha256( + // abi.encodePacked(vk[2], vk[3], hash_to_be_signed)); + // uint256 h = uint256(h_bytes); + // + // // X = g^{x}, where g represents a generator of the cyclic group G + // Pairing.G1Point memory X = Pairing.G1Point(vk[0], vk[1]); + // // Y = g^{y} + // Pairing.G1Point memory Y = Pairing.G1Point(vk[2], vk[3]); + // + // // S = g^{sigma} + // Pairing.G1Point memory S = Pairing.mul(Pairing.P1(), sigma); + // // S_comp = g^{y + xh} + // Pairing.G1Point memory S_comp = Pairing.add(Y, Pairing.mul(X, h)); + // + // // Check that g^{sigma} == g^{y + xh} + // return (S.X == S_comp.X && S.Y == S_comp.Y); + + // Pad + uint256[5] memory pad; + + assembly { + + let g := sub(gas, 2000) + + // pad: + // 0x40 hash_to_be_signed + // 0x20 Y[1] + // 0x00 Y[0] + // Compute sha256 into 0x40 + + mstore(pad, vk2) + mstore(add(pad, 0x20), vk3) + mstore(add(pad, 0x40), hash_to_be_signed) + pop(call(g, 2, 0, pad, 0x60, add(pad, 0x80), 0x20)) + + // pad: + // 0x80 h = sha256(Y || hash_to_be_signed) + // 0x60 + // 0x40 + // 0x20 Y[1] + // 0x00 Y[0] + // Write X from 0x40 and call bn256ScalarMul(in: 0x40, out: 0x40) + + let x_location := add(pad, 0x40) + mstore(x_location, vk0) + mstore(add(x_location, 0x20), vk1) + pop(call(g, 7, 0, x_location, 0x60, x_location, 0x40)) + + // pad: + // 0x60 h.X[1] + // 0x40 h.X[0] + // 0x20 Y[1] + // 0x00 Y[0] + // Call bn256Sum(in: 0x00, out: 0x00) + + pop(call(g, 6, 0, pad, 0x80, pad, 0x40)) + + // pad: + // 0x60 + // 0x40 + // 0x20 (Y + h.X)[1] + // 0x00 (Y + h.X)[0] + // copy P1 and sigma (see Pairing.sol for values) + + mstore(add(pad, 0x40), 1) + mstore(add(pad, 0x60), 2) + mstore(add(pad, 0x80), sigma) + + // pad: + // 0x80 sigma + // 0x60 P1[1] + // 0x40 P1[0] + // 0x20 (Y + h.X)[1] + // 0x00 (Y + h.X)[0] + // call bn256ScalarMul(in: 0x40, out: 0x40) + + pop(call(g, 7, 0, x_location, 0x60, x_location, 0x40)) + + // pad: + // 0x60 sigma.P1[1] + // 0x40 sigma.P1[0] + // 0x20 (Y + h.X)[1] + // 0x00 (Y + h.X)[0] + } + + // compare + + return pad[0] == pad[2] && pad[1] == pad[3]; } } diff --git a/zeth-contracts/contracts/Pairing.sol b/zeth-contracts/contracts/Pairing.sol index b2cd20a24..65c883bc5 100644 --- a/zeth-contracts/contracts/Pairing.sol +++ b/zeth-contracts/contracts/Pairing.sol @@ -4,16 +4,27 @@ pragma solidity ^0.5.0; +// Several pairing-related utility functions. +// +// Precompiled contract details (bn256Add, bn256ScalarMul, bn256Pairing) can be +// found at the following links. Implementations: +// https://github.com/ethereum/go-ethereum/blob/master/core/vm/contracts.go gas +// and costs: +// https://github.com/ethereum/go-ethereum/blob/master/params/protocol_params.go + library Pairing { + struct G1Point { - uint X; - uint Y; + uint256 X; + uint256 Y; } // Encoding of field elements is: X[0] * z + X[1] struct G2Point { - uint[2] X; - uint[2] Y; + uint256 X0; + uint256 X1; + uint256 Y0; + uint256 Y1; } // Return the generator of G1 @@ -24,34 +35,39 @@ library Pairing { // Return the generator of G2 function P2() internal pure returns (G2Point memory) { return G2Point( - [11559732032986387107991004021392285783925812861821192530917403151452391805634, - 10857046999023057135944570762232829481370756359578518086990519993285655852781], - [4082367875863433681332203403145435568316851327593401208105741076214120093531, - 8495653923123431417604973247489272438418190587263600148770280649306958101930] - ); + // solium-disable-next-line + 11559732032986387107991004021392285783925812861821192530917403151452391805634, + // solium-disable-next-line + 10857046999023057135944570762232829481370756359578518086990519993285655852781, + // solium-disable-next-line + 4082367875863433681332203403145435568316851327593401208105741076214120093531, + // solium-disable-next-line + 8495653923123431417604973247489272438418190587263600148770280649306958101930); } // Return the negation of p, i.e. p.add(p.negate()) should be zero. function negate(G1Point memory p) internal pure returns (G1Point memory) { // The prime q in the base field F_q for G1 - uint q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; + // solium-disable-next-line + uint256 q = 21888242871839275222246405745257275088696311157297823662689037894645226208583; if (p.X == 0 && p.Y == 0) return G1Point(0, 0); return G1Point(p.X, q - (p.Y % q)); } // Return the sum of two points of G1 - function add(G1Point memory p1, G1Point memory p2) internal returns (G1Point memory r) { - uint[4] memory input; + function add(G1Point memory p1, G1Point memory p2) + internal + returns (G1Point memory r) { + uint256[4] memory input; input[0] = p1.X; input[1] = p1.Y; input[2] = p2.X; input[3] = p2.Y; bool success; assembly { - // bn256Add precompiled: https://github.com/ethereum/go-ethereum/blob/master/core/vm/contracts.go#L57 - // Gas cost: 500 (see: https://github.com/ethereum/go-ethereum/blob/master/params/protocol_params.go#L84) - success := call(sub(gas, 2000), 6, 0, input, 0xc0, r, 0x60) + // Call bn256Add([p1.X, p1.Y, p2.X, p2.Y]) + success := call(sub(gas, 2000), 6, 0, input, 0x80, r, 0x40) // Use "invalid" to make gas estimation work //switch success case 0 { invalid } } @@ -63,68 +79,73 @@ library Pairing { // Return the product of a point on G1 and a scalar, i.e. // p == p.mul(1) and p.add(p) == p.mul(2) for all points p. - function mul(G1Point memory p, uint s) internal returns (G1Point memory r) { - uint[3] memory input; + function mul(G1Point memory p, uint256 s) + internal + returns (G1Point memory r) { + uint256[3] memory input; input[0] = p.X; input[1] = p.Y; input[2] = s; bool success; assembly { - // bn256ScalarMul precompiled: https://github.com/ethereum/go-ethereum/blob/master/core/vm/contracts.go#L58 - // Gas cost: 40000 (see: https://github.com/ethereum/go-ethereum/blob/master/params/protocol_params.go#L85) - success := call(sub(gas, 2000), 7, 0, input, 0x80, r, 0x60) + // Call bn256ScalarMul([p.X, p.Y, s]) + success := call(sub(gas, 2000), 7, 0, input, 0x60, r, 0x40) // Use "invalid" to make gas estimation work //switch success case 0 { invalid } } require ( success, - "Call to the bn256ScalarMul precompiled failed (probably an out of gas error?)" + "Call to bn256ScalarMul failed (probably an out of gas error?)" ); } // Return the result of computing the pairing check - function pairing(G1Point[] memory p1, G2Point[] memory p2) internal returns (bool) { + function pairing(G1Point[] memory p1, G2Point[] memory p2) + internal + returns (bool) { require( p1.length == p2.length, "Mismatch between the number of elements in G1 and elements in G2" ); // For each pairing check we have 2 coordinates for the elements in G1, // and 4 coordinates for the elements in G2 - uint elements = p1.length; - uint inputSize = elements * 6; - uint[] memory input = new uint[](inputSize); - for (uint i; i < elements; i++) + uint256 elements = p1.length; + uint256 inputSize = elements * 6; + uint256[] memory input = new uint256[](inputSize); + for (uint256 i = 0; i < elements; i++) { // Curve point (G1) - 2 coordinates of 32bytes (0x20 in hex) input[i * 6 + 0] = p1[i].X; input[i * 6 + 1] = p1[i].Y; // Twist point (G2) - 2*2 coordinates of 32bytes (0x20 in hex) - input[i * 6 + 2] = p2[i].X[0]; - input[i * 6 + 3] = p2[i].X[1]; - input[i * 6 + 4] = p2[i].Y[0]; - input[i * 6 + 5] = p2[i].Y[1]; + input[i * 6 + 2] = p2[i].X0; + input[i * 6 + 3] = p2[i].X1; + input[i * 6 + 4] = p2[i].Y0; + input[i * 6 + 5] = p2[i].Y1; } - uint[1] memory out; + uint256[1] memory out; bool success; assembly { - // bn256Pairing precompiled: https://github.com/ethereum/go-ethereum/blob/master/core/vm/contracts.go#L59 - // - // The bn256Pairing precompiled takes an input of size N * 192 (a set of pairs - // of elements (g1, g2) \in G1 x G2 has a size of 192bytes), and carries out a pairing check (not a pairing!) - // (ie: the result is a boolean, not an element in G_T) + // bn256Pairing precompiled: + // https://github.com/ethereum/go-ethereum/blob/master/core/vm/contracts.go#L59 // - // As a consequence, and looking in the Cloudflare bn256 library used in Geth, we see that the PairingCheck - // function runs a Miller loop on every given pair of elements (g1, g2) \in G1 x G2, multiplies the result - // of the miller loops and runs finalExponentiation to get a result is G_T. If the result obtained is ONE - // then the result of the pairing check is True, else False. + // The bn256Pairing precompiled takes an input of size N * 192 (a + // set of pairs of elements (g1, g2) \in G1 x G2 has a size of + // 192bytes), and carries out a pairing check (not a pairing!) (ie: + // the result is a boolean, not an element in G_T). // - // Looking at the comments above, we see we can run PairingChecks on any number of pairs (g1, g2) \in G1 x G2. - // To check something in the form: - // e(g1, g2) = e(g'1, g'2), we need to call the precompiled bn256Pairing on input - // [(g1, g2), (neg(g'1), g'2)] + // As a consequence, and looking in the Cloudflare bn256 library + // used in Geth, we see that the PairingCheck function runs a Miller + // loop on every given pair of elements (g1, g2) \in G1 x G2, + // multiplies the result of the miller loops and runs + // finalExponentiation to get a result is G_T. If the result + // obtained is ONE then the result of the pairing check is True, + // else False. // - // Gas cost: 100000 + elements * 80000 - // (see: https://github.com/ethereum/go-ethereum/blob/master/core/vm/contracts.go#L330) + // Looking at the comments above, we see we can run PairingChecks on + // any number of pairs (g1, g2) \in G1 x G2. To check something in + // the form: e(g1, g2) = e(g'1, g'2), we need to call the + // precompiled bn256Pairing on input [(g1, g2), (neg(g'1), g'2)] success := call(sub(gas, 2000), 8, 0, add(input, 0x20), mul(inputSize, 0x20), out, 0x20) // Use "invalid" to make gas estimation work //switch success case 0 { invalid } @@ -140,8 +161,9 @@ library Pairing { // Convenience method for a pairing check for two pairs. function pairingProd2( G1Point memory a1, G2Point memory a2, - G1Point memory b1, G2Point memory b2 - ) internal returns (bool) { + G1Point memory b1, G2Point memory b2) + internal + returns (bool) { G1Point[] memory p1 = new G1Point[](2); G2Point[] memory p2 = new G2Point[](2); p1[0] = a1; @@ -155,8 +177,9 @@ library Pairing { function pairingProd3( G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2, - G1Point memory c1, G2Point memory c2 - ) internal returns (bool) { + G1Point memory c1, G2Point memory c2) + internal + returns (bool) { G1Point[] memory p1 = new G1Point[](3); G2Point[] memory p2 = new G2Point[](3); p1[0] = a1; @@ -173,8 +196,9 @@ library Pairing { G1Point memory a1, G2Point memory a2, G1Point memory b1, G2Point memory b2, G1Point memory c1, G2Point memory c2, - G1Point memory d1, G2Point memory d2 - ) internal returns (bool) { + G1Point memory d1, G2Point memory d2) + internal + returns (bool) { G1Point[] memory p1 = new G1Point[](4); G2Point[] memory p2 = new G2Point[](4); p1[0] = a1; diff --git a/zeth-contracts/contracts/Pghr13Mixer.sol b/zeth-contracts/contracts/Pghr13Mixer.sol index cc0be2cbe..d13104cfb 100644 --- a/zeth-contracts/contracts/Pghr13Mixer.sol +++ b/zeth-contracts/contracts/Pghr13Mixer.sol @@ -5,38 +5,89 @@ pragma solidity ^0.5.0; import "./OTSchnorrVerifier.sol"; -import "./Pghr13Verifier.sol"; import "./BaseMixer.sol"; contract Pghr13Mixer is BaseMixer { - // zkSNARK verifier smart contract - Pghr13Verifier public zksnark_verifier; - // OT-Signature verifier smart contract - OTSchnorrVerifier public otsig_verifier; + + struct VerifyingKey { + Pairing.G2Point A; + Pairing.G1Point B; + Pairing.G2Point C; + Pairing.G2Point gamma; + Pairing.G1Point gammaBeta1; + Pairing.G2Point gammaBeta2; + Pairing.G2Point Z; + Pairing.G1Point[] IC; + } + + struct Proof { + Pairing.G1Point A; + Pairing.G1Point A_p; + Pairing.G2Point B; + Pairing.G1Point B_p; + Pairing.G1Point C; + Pairing.G1Point C_p; + Pairing.G1Point K; + Pairing.G1Point H; + } + + VerifyingKey verifyKey; // Constructor - constructor(address snark_ver, address sig_ver, uint mk_depth, address token, address hasher) BaseMixer(mk_depth, token, hasher) public { - zksnark_verifier = Pghr13Verifier(snark_ver); - otsig_verifier = OTSchnorrVerifier(sig_ver); + constructor( + uint256 mk_depth, + address token, + uint256[2] memory A1, + uint256[2] memory A2, + uint256[2] memory B, + uint256[2] memory C1, + uint256[2] memory C2, + uint256[2] memory gamma1, + uint256[2] memory gamma2, + uint256[2] memory gammaBeta1, + uint256[2] memory gammaBeta2_1, + uint256[2] memory gammaBeta2_2, + uint256[2] memory Z1, + uint256[2] memory Z2, + uint256[] memory IC_coefficients) + BaseMixer(mk_depth, token) + public { + verifyKey.A = Pairing.G2Point(A1[0], A1[1], A2[0], A2[1]); + verifyKey.B = Pairing.G1Point(B[0], B[1]); + verifyKey.C = Pairing.G2Point(C1[0], C1[1], C2[0], C2[1]); + verifyKey.gamma = Pairing.G2Point( + gamma1[0], gamma1[1], gamma2[0], gamma1[1]); + verifyKey.gammaBeta1 = Pairing.G1Point(gammaBeta1[0], gammaBeta1[1]); + verifyKey.gammaBeta2 = Pairing.G2Point( + gammaBeta2_1[0], gammaBeta2_1[1], gammaBeta2_2[0], gammaBeta2_2[1]); + verifyKey.Z = Pairing.G2Point(Z1[0], Z1[1], Z2[0], Z2[1]); + + uint256 i = 0; + while(verifyKey.IC.length != IC_coefficients.length/2) { + verifyKey.IC.push( + Pairing.G1Point(IC_coefficients[i], IC_coefficients[i+1])); + i += 2; + } } - // This function allows to mix coins and execute payments in zero knowledge + // This function allows to mix coins and execute payments in zero knowledge. + // Nb of ciphertexts depends on the JS description (Here 2 inputs) function mix ( - uint[2] memory a, - uint[2] memory a_p, - uint[2][2] memory b, - uint[2] memory b_p, - uint[2] memory c, - uint[2] memory c_p, - uint[2] memory h, - uint[2] memory k, - uint[2][2] memory vk, - uint sigma, - uint[] memory input, + uint256[2] memory a, + uint256[2] memory a_p, + uint256[2][2] memory b, + uint256[2] memory b_p, + uint256[2] memory c, + uint256[2] memory c_p, + uint256[2] memory h, + uint256[2] memory k, + uint256[4] memory vk, + uint256 sigma, + uint256[nbInputs] memory input, bytes32 pk_sender, bytes memory ciphertext0, - bytes memory ciphertext1 // Nb of ciphertexts depends on the JS description (Here 2 inputs) - ) public payable { + bytes memory ciphertext1) + public payable { // 1. Check the root and the nullifiers check_mkroot_nullifiers_hsig_append_nullifiers_state(vk, input); @@ -58,8 +109,11 @@ contract Pghr13Mixer is BaseMixer { ) ); require( - otsig_verifier.verify( - vk, + OTSchnorrVerifier.verify( + vk[0], + vk[1], + vk[2], + vk[3], sigma, hash_to_be_signed ), @@ -68,20 +122,151 @@ contract Pghr13Mixer is BaseMixer { // 2.b Verify the proof require( - zksnark_verifier.verifyTx(a, a_p, b, b_p, c, c_p, h, k, input), + verifyTx(a, a_p, b, b_p, c, c_p, h, k, input), "Invalid proof: Unable to verify the proof correctly" ); + // 3. Append the commitments to the tree assemble_commitments_and_append_to_state(input); - // 4. get the public values in Wei and modify the state depending on their values + // 4. get the public values in Wei and modify the state depending on + // their values process_public_values(input); // 5. Add the new root to the list of existing roots and emit it add_and_emit_merkle_root(getRoot()); - // Emit the all the coins' secret data encrypted with the recipients' respective keys + // Emit the all the coins' secret data encrypted with the recipients' + // respective keys emit_ciphertexts(pk_sender, ciphertext0, ciphertext1); } + + function getIC(uint256 i) public view returns (uint) { + return(verifyKey.IC[i].X); + } + + function getICLen() public view returns (uint) { + return(verifyKey.IC.length); + } + + function verify( + uint256[nbInputs] memory input, + Proof memory proof) + internal + returns (uint) { + VerifyingKey memory vk = verifyKey; + // |I_{in}| == input.length, and vk.IC also contains A_0(s). Thus + // ||vk.IC| == input.length + 1 + require( + input.length + 1 == vk.IC.length, + "Using strong input consistency, and the input length differs from expected" + ); + + // 1. Compute the linear combination + // vk_x := vk_{IC,0} + \sum_{i=1}^{n} x_i * vk_{IC,i}, vk_x ∈ G1 + // + // E(A_{in}(s)) if the encoding of + // A_{in}(s) = \sum_{k ∈ I_{in}} a_k · A_k(s), + // where I_{in} denotes the indices of the input wires. + // + // |I_{in}| = n here as we assume that we have a vector x of inputs of + // size n. + Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); + for (uint256 i = 0; i < input.length; i++) { + vk_x = Pairing.add(vk_x, Pairing.mul(vk.IC[i + 1], input[i])); + } + vk_x = Pairing.add(vk_x, vk.IC[0]); + + // 2. Check the validity of knowledge commitments for A, B, C + // e(π_A, vk_A) = e(π′A, P2), e(vk_B, π_B) + // = e(π′_B, P2), e(vk_C, π_C) + // = e(π′_C, P2), + if (!Pairing.pairingProd2( + proof.A, vk.A, + Pairing.negate(proof.A_p), Pairing.P2()) + ) { + return 1; + } + if (!Pairing.pairingProd2( + vk.B, proof.B, + Pairing.negate(proof.B_p), Pairing.P2()) + ) { + return 2; + } + if (!Pairing.pairingProd2( + proof.C, vk.C, + Pairing.negate(proof.C_p), Pairing.P2()) + ) { + return 3; + } + + // 3. Check same coefficients were used + // e(π_K, vk_γ) = e(vk_x + π_A + π_C, vk_{γβ2}) · e(vk_{γβ1}, π_B) + if (!Pairing.pairingProd3( + proof.K, vk.gamma, + Pairing.negate(Pairing.add(vk_x, Pairing.add(proof.A, proof.C))), vk.gammaBeta2, + Pairing.negate(vk.gammaBeta1), proof.B) + ) { + return 4; + } + + // 4. Check QAP divisibility + // e(vk_x + π_A, π_B) = e(π_H, vk_Z) · e(π_C, P2) + if (!Pairing.pairingProd3( + Pairing.add(vk_x, proof.A), proof.B, + Pairing.negate(proof.H), vk.Z, + Pairing.negate(proof.C), Pairing.P2()) + ) { + return 5; + } + + return 0; + } + + function verifyTx( + uint256[2] memory a, + uint256[2] memory a_p, + uint256[2][2] memory b, + uint256[2] memory b_p, + uint256[2] memory c, + uint256[2] memory c_p, + uint256[2] memory h, + uint256[2] memory k, + uint256[nbInputs] memory primaryInputs) + public + returns (bool) { + // Scalar field characteristic + // solium-disable-next-line + uint256 r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; + + Proof memory proof; + proof.A = Pairing.G1Point(a[0], a[1]); + proof.A_p = Pairing.G1Point(a_p[0], a_p[1]); + proof.B = Pairing.G2Point(b[0][0], b[0][1], b[1][0], b[1][1]); + proof.B_p = Pairing.G1Point(b_p[0], b_p[1]); + proof.C = Pairing.G1Point(c[0], c[1]); + proof.C_p = Pairing.G1Point(c_p[0], c_p[1]); + proof.H = Pairing.G1Point(h[0], h[1]); + proof.K = Pairing.G1Point(k[0], k[1]); + + // uint256[] memory inputValues = new uint256[](primaryInputs.length); + for(uint256 i = 0; i < primaryInputs.length; i++){ + // Make sure that all primary inputs lie in the scalar field + require( + primaryInputs[i] < r, + "Input is not is scalar field" + ); + /* inputValues[i] = primaryInputs[i]; */ + } + + uint256 verification_result = verify(primaryInputs, proof); + if (verification_result != 0) { + /* emit LogVerifier("Failed to verify the transaction"); */ + return false; + } + + /* emit LogVerifier("Proof verification successfull"); */ + return true; + } } diff --git a/zeth-contracts/contracts/Pghr13Verifier.sol b/zeth-contracts/contracts/Pghr13Verifier.sol deleted file mode 100644 index d69008b60..000000000 --- a/zeth-contracts/contracts/Pghr13Verifier.sol +++ /dev/null @@ -1,201 +0,0 @@ -// Copyright (c) 2015-2019 Clearmatics Technologies Ltd -// -// SPDX-License-Identifier: LGPL-3.0+ - -pragma solidity ^0.5.0; - -/* - * Reference papers: - * \[PGHR13]: - * "Pinocchio: Nearly practical verifiable computation", - * Bryan Parno, Craig Gentry, Jon Howell, Mariana Raykova, - * IEEE S&P 2013, - * - * - * [BCTV14]: - * "Succinct Non-Interactive Zero Knowledge for a von Neumann Architecture", - * Eli Ben-Sasson, Alessandro Chiesa, Eran Tromer, Madars Virza, - * USENIX Security 2014, - * - * - * [Gab19] - * "On the security of the BCTV Pinocchio zk-SNARK variant", - * Ariel Gabizon, - * -**/ - -import "./Pairing.sol"; - -// PGHR13 Verifier contract -contract Pghr13Verifier { - using Pairing for *; - - struct VerifyingKey { - Pairing.G2Point A; - Pairing.G1Point B; - Pairing.G2Point C; - Pairing.G2Point gamma; - Pairing.G1Point gammaBeta1; - Pairing.G2Point gammaBeta2; - Pairing.G2Point Z; - Pairing.G1Point[] IC; - } - - struct Proof { - Pairing.G1Point A; - Pairing.G1Point A_p; - Pairing.G2Point B; - Pairing.G1Point B_p; - Pairing.G1Point C; - Pairing.G1Point C_p; - Pairing.G1Point K; - Pairing.G1Point H; - } - - VerifyingKey verifyKey; - - event LogVerifier(string); - - constructor( - uint[2] memory A1, - uint[2] memory A2, - uint[2] memory B, - uint[2] memory C1, - uint[2] memory C2, - uint[2] memory gamma1, - uint[2] memory gamma2, - uint[2] memory gammaBeta1, - uint[2] memory gammaBeta2_1, - uint[2] memory gammaBeta2_2, - uint[2] memory Z1, - uint[2] memory Z2, - uint[] memory IC_coefficients - ) public { - verifyKey.A = Pairing.G2Point(A1,A2); - verifyKey.B = Pairing.G1Point(B[0], B[1]); - verifyKey.C = Pairing.G2Point(C1, C2); - verifyKey.gamma = Pairing.G2Point(gamma1, gamma2); - verifyKey.gammaBeta1 = Pairing.G1Point(gammaBeta1[0], gammaBeta1[1]); - verifyKey.gammaBeta2 = Pairing.G2Point(gammaBeta2_1, gammaBeta2_2); - verifyKey.Z = Pairing.G2Point(Z1,Z2); - - uint i; - while(verifyKey.IC.length != IC_coefficients.length/2) { - verifyKey.IC.push(Pairing.G1Point(IC_coefficients[i], IC_coefficients[i+1])); - i += 2; - } - } - - function getIC(uint i) public view returns (uint) { - return(verifyKey.IC[i].X); - } - - function getICLen() public view returns (uint) { - return(verifyKey.IC.length); - } - - function verify(uint[] memory input, Proof memory proof) internal returns (uint) { - VerifyingKey memory vk = verifyKey; - // |I_{in}| == input.length, and vk.IC also contains A_0(s). Thus |vk.IC| == input.length + 1 - require( - input.length + 1 == vk.IC.length, - "Using strong input consistency, and the input length differs from expected" - ); - - // 1. Compute the linear combination vk_x := vk_{IC,0} + \sum_{i=1}^{n} x_i * vk_{IC,i}, vk_x ∈ G1 - // E(A_{in}(s)) if the encoding of A_{in}(s) = \sum_{k ∈ I_{in}} a_k · A_k(s), where I_{in} denotes the indices of the input wires - // |I_{in}| = n here as we assume that we have a vector x of inputs of size n - Pairing.G1Point memory vk_x = Pairing.G1Point(0, 0); - for (uint i; i < input.length; i++) { - vk_x = Pairing.add(vk_x, Pairing.mul(vk.IC[i + 1], input[i])); - } - vk_x = Pairing.add(vk_x, vk.IC[0]); - - // 2. Check the validity of knowledge commitments for A, B, C - // e(π_A, vk_A) = e(π′A, P2), e(vk_B, π_B) = e(π′_B, P2), e(vk_C, π_C) = e(π′_C, P2), - if (!Pairing.pairingProd2( - proof.A, vk.A, - Pairing.negate(proof.A_p), Pairing.P2()) - ) { - return 1; - } - if (!Pairing.pairingProd2( - vk.B, proof.B, - Pairing.negate(proof.B_p), Pairing.P2()) - ) { - return 2; - } - if (!Pairing.pairingProd2( - proof.C, vk.C, - Pairing.negate(proof.C_p), Pairing.P2()) - ) { - return 3; - } - - // 3. Check same coefficients were used - // e(π_K, vk_γ) = e(vk_x + π_A + π_C, vk_{γβ2}) · e(vk_{γβ1}, π_B) - if (!Pairing.pairingProd3( - proof.K, vk.gamma, - Pairing.negate(Pairing.add(vk_x, Pairing.add(proof.A, proof.C))), vk.gammaBeta2, - Pairing.negate(vk.gammaBeta1), proof.B) - ) { - return 4; - } - - // 4. Check QAP divisibility - // e(vk_x + π_A, π_B) = e(π_H, vk_Z) · e(π_C, P2) - if (!Pairing.pairingProd3( - Pairing.add(vk_x, proof.A), proof.B, - Pairing.negate(proof.H), vk.Z, - Pairing.negate(proof.C), Pairing.P2()) - ) { - return 5; - } - - return 0; - } - - function verifyTx( - uint[2] memory a, - uint[2] memory a_p, - uint[2][2] memory b, - uint[2] memory b_p, - uint[2] memory c, - uint[2] memory c_p, - uint[2] memory h, - uint[2] memory k, - uint[] memory primaryInputs - ) public returns (bool) { - // Scalar field characteristic - uint256 r = 21888242871839275222246405745257275088548364400416034343698204186575808495617; - - Proof memory proof; - proof.A = Pairing.G1Point(a[0], a[1]); - proof.A_p = Pairing.G1Point(a_p[0], a_p[1]); - proof.B = Pairing.G2Point([b[0][0], b[0][1]], [b[1][0], b[1][1]]); - proof.B_p = Pairing.G1Point(b_p[0], b_p[1]); - proof.C = Pairing.G1Point(c[0], c[1]); - proof.C_p = Pairing.G1Point(c_p[0], c_p[1]); - proof.H = Pairing.G1Point(h[0], h[1]); - proof.K = Pairing.G1Point(k[0], k[1]); - - uint[] memory inputValues = new uint[](primaryInputs.length); - for(uint i = 0; i < primaryInputs.length; i++){ - // Make sure that all primary inputs lie in the scalar field - require( - primaryInputs[i] < r, - "Input is not is scalar field" - ); - inputValues[i] = primaryInputs[i]; - } - - uint verification_result = verify(inputValues, proof); - if (verification_result != 0) { - emit LogVerifier("Failed to verify the transaction"); - return false; - } - - emit LogVerifier("Proof verification successfull"); - return true; - } -} diff --git a/zeth-contracts/package.json b/zeth-contracts/package.json index 993ae7859..fdefe8415 100644 --- a/zeth-contracts/package.json +++ b/zeth-contracts/package.json @@ -4,20 +4,22 @@ "description": "zerocash on Ethereum", "main": "truffle.js", "scripts": { - "testrpc": "ganache-cli --port 8545 --gasLimit 0xFFFFFFFFFFF --gasPrice 1 --defaultBalanceEther 90000000000 --networkId 1234", + "testrpc": "ganache-cli --hardfork istanbul --port 8545 --gasLimit 0xFFFFFFFFFFF --gasPrice 1 --defaultBalanceEther 90000000000 --networkId 1234", "compile": "truffle compile", "deploy": "truffle deploy", "test": "truffle test", - "debug": "truffle debug" + "debug": "truffle debug", + "check": "node_modules/solium/bin/solium.js --dir contracts" }, "author": "Clearmatics Technologies LTD: Cryptography R&D Department", "license": "LGPL-3.0+", "dependencies": { "add": "^2.0.6", "ethereumjs-abi": "^0.6.8", - "ganache-cli": "^6.7.0", + "ganache-cli": "^6.8.0-istanbul.0", "openzeppelin-solidity": "^2.4.0", "shell-escape": "^0.2.0", + "solium": "^1.2.5", "strip-hex-prefix": "^1.0.0", "web3": "^1.2.4" }