Circuits

Poseidon2 hash

All hashing in Stealth Pay uses Poseidon2 over BN254 — commitments, nullifiers, and Merkle nodes. It is ZK-friendly (cheap to prove in a circuit) and algebraically native to the BN254 field used by UltraHonk.

Sponge construction

typescript
// hash2: 2-input domain-separated hash
function hash2(a: bigint, b: bigint): bigint {
  const iv = mod(2n * TWO_POW_64);  // capacity = 2·2^64
  let state = [a, b, 0n, iv];
  state = permute(state);
  return state[0];
}

// hash4: used for commitments
function hash4(a: bigint, b: bigint, c: bigint, d: bigint): bigint {
  const iv = mod(4n * TWO_POW_64);  // capacity = 4·2^64
  let state = [a, b, c, iv];
  state = permute(state);
  state[0] = mod(state[0] + d);
  state = permute(state);
  return state[0];
}

Usage in Stealth Pay

PurposeCall
Commitmenthash4(pubkey, token, amount, salt)
Nullifierhash2(privkey, commitment)
Merkle nodehash2(left, right)
Spending pubkeyhash2(privkey, privkey)
The TypeScript SDK uses @zkpassport/poseidon2 which passes the official Barretenberg test vector: permute([0,1,2,3])[0] === 0x01bd538c...01737.