BattleZips
  • Awesome Circom
  • 🔬Theory
    • Prerequisite Knowledge
    • Resources
      • White Papers & PDF's
      • Blogs and Writeups
      • Videos
      • Important Entities
      • Communities
    • Proving Schemes
    • Primitives
      • Hash Functions
      • Public Key Cryptosystems
        • Note on L1 key registry → L2 hot key + callback to circuit-optimized hash functions
        • ECDSA & secp256k1
        • EdDSA
      • Merkle Trees
        • What is a Merkle Tree?
        • What is a merkle proof of inclusion?
        • zk-kit
        • Incremental Merkle Trees
        • Sparse Merkle Trees
        • Tree Arity (Binary, Quinary)
      • Semaphore
      • Arithmetic Circuits
  • 🏗️Development
    • Circom Language
      • Installation
      • IDE
      • Signals and Variables
      • Signal Assignment and Constraint Generation
      • Conditional Statements
      • Components and Templates
      • Circuit Compilation
      • Syntax
    • SnarkJS
      • Proving Schemes
      • Powers of Tau
      • ZK Keys
      • Zero Knowledge Proofs
      • On-Chain ZKP
      • Page 2
    • circomlib
      • Basic Math Constraints
      • Multiplexing
      • Hashing
      • EdDSA
      • circomlibjs
    • circom-tester
    • hardhat-circom
    • SHIELD
    • Circomspect
  • 🌆Ecosystem
    • Circom vs Other Solutions
      • Domain-Specific Languages
      • ZK Virtual Machines
      • ZK Ethereum Virtual Machines
    • Communities to Join
    • Recorded Content
    • Projects
  • 🛳️Examples
    • BattleZips V1
      • On the BattleZips Project
      • Docs holder
        • Join Game UML Sequence Diagram
        • Play Game UML Sequence Diagram
        • End Game UML Sequence Diagram
      • ZK Privacy Stack
      • Deploying Artifacts to Prod
      • Browser Client
    • RollupNC
      • Smart Contracts
      • Account/ State Tree
      • Transaction Tree
      • Layer 1 Deposits to Layer 2
      • Layer 2 Transacting
      • Layer 2 Withdrawals to Layer 1
Powered by GitBook
On this page
  • Introduction to Circom Hashing
  • Example of Application
  • MiMC
  • Poseidon
  • SHA-256
  1. Development
  2. circomlib

Hashing

Using 1st party implementations of circuit friendly (and unfriendly) hash functions in Circom circuits

PreviousMultiplexingNextEdDSA

Last updated 2 years ago

Introduction to Circom Hashing

See the Theory > Primitives > Hashing section for more of an explanation on hash functions in general, as well as the specifics of hash functions friendly to the arithmetic circuit.

As a preface, in general you can assume more inputs to make the computation of your hash more expensive both in Zero Knowledge constraints and in EVM gas. There is also an increased cost to multiple rounds of hash function/ template calls compared to a one-shot hash with multiple inputs.

You most likely have seen the equivalent of :

pragma circom 2.0.0;

/*This circuit template checks that c is the multiplication of a and b.*/  

template Multiplier2 () {  

   // Declaration of signals.  
   signal input a;  
   signal input b;  
   signal output c;  

   // Constraints.  
   c <== a * b;  
}

This circuit may seem absolutely pointless without any experience. However, as a black box hashing is little more than a irreversible function to deterministically obscure data using math.

Put even simpler, pretend that the operation a * b is our hash function.

  • c represents the hash

Functionally, the "Hello World" circuit is meant to quickly convey that with the Circom DSL, individuals can prove knowledge of underlying data and prove that an encrypted value is the hash of that underlying data. From this point on, we can work with the hash in public or further in Zero Knowledge as a verifiable receipt of the underlying data contained within.

Example of Application

For instance, part of the decentralization in BattleZips V1 is derived from the contract storage of a hash proven to be a MiMC sponge hash of a valid board configuration. Consider the flow of (see Examples > BattleZips for all pseudo UML sequence diagrams for BattleZips):

...
/// PROVE THE HASH IS OF A VALID BOARD CONFIGURATION ///
require(
    bv.verifyProof(a, [b_0, b_1], c, [_boardHash]),
    "Invalid Board Config!"
);
/// STORE THE BOARD HASH IN THE CONTRACT ///
// _game = game lobby
// .participants[1] means bob/ joinGame; [0] would be alice/ newGame
games[_game].participants[1] = _msgSender();
games[_game].boards[1] = _boardHash;
...

Logically, this can be broken down to a sort of informational escrow:

  1. store _boardHash for the player for the duration of that game

MiMC

Full support from the auxiliary first party libraries is included for both MiMC and MiMC Sponge:

@todo: Figure out wtf the difference between MiMC and MiMC Sponge. For this reason, we will explain the rest of the MiMC section using the MiMC Sponge circomlib construct. Know that while the exact API for the MiMC template may differ slightly from the MiMC Sponge, the actual input output functionality is exactly the same and reworking the following code for determined users of mimc.circom should be quick.

Zero Knowledge

JavaScript

Solidity

Poseidon

SHA-256

> ./zkcompile battleship_pedersen.circom 
# Wires: 3540
# Constraints: 708
# Private Inputs: 15
# Public Inputs: 4
# Outputs: 1
> ./zkcompile battleship_sha256.circom 
# Wires: 31366
# Constraints: 29785
# Private Inputs: 15
# Public Inputs: 4
# Outputs: 1

There is an immense cost to verifying a SHA-256 hash in Zero Knowledge. Unless your circuit's existence is predicated on the need to verify existing ECDSA signatures or SHA-256 hashes, you should not create new SHA-256 hashes to verify by starting out with SHA-256. As demonstrated above with the BattleZips board placement, if you only need the data authentication provided by SHA-256 and not SHA-256 itself you should implement a different hash function.

a and b represent a , the data being hidden or 'd for integrity

When Alice runs or Bob runs , the following code runs, given inputs of a proof and the board hash:

using the () on-chain zkSNARK verifier, use the proof(a, b_0, b_1, c)to convince the smart contract that _boardHashis the MiMC7 hash of a valid battleship board, as encoded by board.circom

Once the contract has been convinced of the authenticity of the hash, we can transfer the right to input the hash for from the end user to the smart contract. Notice that in , the boardHash value is recalled from EVM storage and directly assigned. The caller has no ability to assign the 0th (first) public proof input parameter, beyond their initial commitment to the board hash when creating or starting the game.

templates: [, ]

drivers: [, ]

EdDSA templates: [, ]

contract generators: [, ]

template Poseidon(nInputs) {
    signal input inputs[nInputs];
    signal output out;

    component pEx = PoseidonEx(nInputs, 1);
    pEx.initialState <== 0;
    for (var i=0; i<nInputs; i++) {
        pEx.inputs[i] <== inputs[i];
    }
    out <== pEx.out[0];
}

Let's individually note the properties before demonstrating the component use:

  • Supplying the nInputs template parameter determines the number of signals that will be hashed together

    • Note that this is not limited to 6 like the Poseidon solidity contract

  • The component is used simply by assigning nInputs number of signals in the inputs signal array

  • Once all inputs have been assigned, the Poseidon hash of the values can be accessed with the output signal out

pragma circom 2.0.3;

include "../../../node_modules/circomlib/circuits/poseidon.circom";

template BalanceLeaf() {

    /// SIGNAL IO ///
    signal input pubkey[2]; // Account EdDSA pubkey [x, y]
    signal input balance; // Account token balance
    signal input nonce; // Number of transactions made by the account
    signal input tokenType; // Token registry index for token type
    signal output out; // Poseidon hash of account (account leaf)
    
    /// HASH ACCOUNT DATA INTO ACCOUNT LEAF ///
    component balanceLeaf = Poseidon(5);
    balanceLeaf.inputs[0] <== pubkey[0];
    balanceLeaf.inputs[1] <== pubkey[1];
    balanceLeaf.inputs[2] <== balance;
    balanceLeaf.inputs[3] <== nonce; 
    balanceLeaf.inputs[4] <== tokenType;

    out <== balanceLeaf.out;
}

In this case, we assign the Poseidon component's nInputs parameter to 5 to ingest the two pubkey points, the balance, the nonce, and the tokenType. These items must be assigned to the array in proper order. Once nInputs assignments are made to each index of the of the balanceLeaf.inputs[] array, we can access the Poseidon hash with balanceLeaf.out.

The circomlibjs library includes a JS component for Poseidon. Your application will very frequently use the JavaScript hasher to work with this data on the client side, including use in both Solidity and the Circom circuits (see more on browser inclusion of circomlibjs assets in Development > SnarkJS > Browser).

const { buildPoseidon } = require('circomlibjs')

...
it('Deposit #1 (COORDINATOR ADDRESS)', async () => {
    // instantiate poseidon hasher from circomlibjs
    const poseidon = await buildPoseidon();
    ...
    // generate account leaf
    // A.K.A. Poseidon hash of the eddsa key, balance, nonce, tokenType
    const data = [...l2Pubkey, 0, 0, 0];
    const leaf = F.toObject(poseidon(data));
    ...
})
...

Note: Poseidon is colloquially and a superior upgrade of MiMC. When dealing with 6 or less inputs to your hash function, you will most likely experience all around superior performance to MiMC. However, in the , we found that the max input size for the circomlibjs auto-generated contract was 6, meaning a transaction leaf with 8 different fields was most efficiently hashes in three rounds - hash each half of the transaction leaf separately with 4 inputs, then hash the two intermediate hashes together for the transaction leaf. This provided inferior performance to the .

BattleZips V1 was forked (almost entirely transformed at that) from written by an unaffiliated GitHub user . In that README.md, they explained:

There have been different discussions on whether/how hashing can be made more efficient in arithmetic circuits for zkSNARKs use; see and .

For this battleship use case, taking as an example,Pedersen Hash is proved to be more efficent in terms of the resulting number of wires (>88% reduction) and constraints (>97% reduction), compared with the SHA-256 counterpart:

Nonetheless, there is first party support for SHA-256 in the form of the template . Being SHA-256, you can use standard solidity and javascript libraries. If you really wanted to see ZK Battleship, and can stomach translating you can check out theClever use of GitHub search should yield you additional resources to replicate in your own work.

The use of the Poseidon hash function in Circom is quite simple. The template is shown below:

In practice, we can look to the BattleZips updated RollupNC repository. Specifically, lets take a look at the process of hashing an account leaf for the state tree in (see Examples > RollupNC > Account/ State Tree for context), where each account leaf is the Poseidon hash of 5 values:

Lastly, you can find the .

To employ the hash function, let's use the (edited slightly here):

Poseidon is a popular hash function and you should not have trouble finding its use within the open-source code contained in Ecosystem > Projects. You can start with the for the first party demonstration of use.

🏗️
Hashing
Circom's "Hello World" on their official docs
preimage
checksum
newGame()
joinGame()
board.circom
bv
deployed
shot.circom
turn()
mimc.circom
mimcsponge.circom
mimc7.js
mimcsponge.js
eddsamimc.circom
eddsamimcsponge.circom
mimc7_gencontract.js
mimcsponge_gencontract.js
Zero Knowledge
Poseidon
balance_leaf.circom
first party unit test for the Poseidon circuit in the circomlib repo
JavaScript
BattleZips updated RollupNC unit tests
circomlibjs Poseidon unit tests
Solidity
theoretically
BattleZips update to RollupNC
older MiMC version of the RollupNC repository
zkbattleship-circuit
tommymsz006
Zcash forum
ethresear.ch
circom's hash
sha256compression.circom
There are third party circom templates for keccak256 as well.