Skip to main content

Cryptography Reference

All cryptographic primitives and parameters used in the system.

Curves

Grumpkin is the only curve used across all layers. It is an embedded curve over BN254's scalar field, which makes it efficient to operate on inside circuits that use BN254 as the proof system's native field.

ParameterValue
Equationy² = x³ − 17
Base fieldBN254 scalar field (Fr): 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
Curve orderBN254 base field (Fq): 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47
GeneratorG = (1, 0x02cf135e7506a45d632d270d45f1181294833fc48d823f272c)
Cofactor1

The identity element is encoded as (0,0) throughout the codebase — this is not a point on the curve, but is special-cased as the identity in all Solidity, Noir, and TypeScript code.

Proof System

UltraHonk via Noir and Barretenberg (bb.js). All circuits compile to constraint systems of size 2^15 (32,768 gates).

CircuitPublic inputs
Transfer23
Unshield12
Stake18
Unstake14

Proof verification happens on-chain in Solidity via hardcoded verification keys.

Encryption

ElGamal in exponential form on the Grumpkin curve. This variant is additively homomorphic, which allows the contract to update recipient balances via point addition without decrypting.

EncryptEnc(m, pk, r) = (r·G, m·G + r·pk)
Decryptm·G = C − sk·R, then solve discrete log for m
Homomorphic additionEnc(a) + Enc(b) = Enc(a+b) via component-wise point addition

The trade-off is that decryption requires solving a discrete log on the Grumpkin curve. See discrete-log solver.

Hash Functions

Poseidon2 is the primary hash function, used everywhere inside circuits:

  • Balance commitments
  • Merkle tree nodes (3-input hash for ternary trees)
  • Nullifier derivation: Poseidon2(blinding, leafIndex, 0)
  • Stake commitment: Poseidon2(amount, blinding)
  • Compliance transfer IDs
  • IMT leaf structure: Poseidon2(value, nextValue, nextIndex)

The sponge construction uses rate 3, capacity 1 (4-element state). The implementation uses Noir's built-in poseidon2_permutation blackbox.

SHA-256 is used only in TypeScript for key derivation.

Keccak-256 is used on-chain (Solidity) and as an option in the proof backend.

Merkle Trees

Both trees are ternary (3 children per node) with depth 16 (~43 million leaves), using Poseidon2 3-input hash for internal nodes.

Stake tree — append-only, stores stake commitments. The contract keeps historical roots, so proofs against older roots remain valid. This allows concurrent stakes without serialization.

Nullifier IMT (Indexed Merkle Tree) — an ordered linked-list Merkle tree. Each leaf stores {value, nextValue, nextIndex}. Insertion requires updating two paths (the existing low-leaf and the new leaf). The contract does not keep historical roots — only the current root is valid. This serializes all unstake operations. See flaw #6.

Nullifier Derivation

nullifier = Poseidon2(blinding, leafIndex, 0)

blinding is a random secret chosen at stake time. leafIndex is the stake tree position. The nullifier is inserted into the IMT on unstake to prevent double-spending.

On-Chain Arithmetic

Grumpkin point operations in Solidity use Jacobian coordinates internally to avoid per-step modular inverses, converting to affine only at the end. Modular inverse uses the modexp precompile (address 0x05) with Fermat's little theorem.

For shield operations, a precomputed table of 2^i · G (i = 0..63) enables scalar multiplication via binary decomposition, avoiding the full double-and-add loop.