Poseidon Hash
The Ghost Protocol uses Poseidon hashing for commitments and nullifiers. This is a ZK-friendly hash function optimized for use in circuits.
Installation
npm install circomlibjs
Basic Usage
import { buildPoseidon } from 'circomlibjs';
const poseidon = await buildPoseidon();
const F = poseidon.F;
// Hash two values
function hash2(a, b) {
return BigInt(F.toString(poseidon([BigInt(a), BigInt(b)])));
}
Commitment Computation
function computeCommitment(secret, nullifierSecret, dataHash, blinding) {
const inner1 = hash2(secret, nullifierSecret);
const inner2 = hash2(dataHash, blinding);
return hash2(inner1, inner2);
}
// Usage
const commitment = computeCommitment(
secretBn,
nullifierSecretBn,
dataHash,
blindingBn
);
Nullifier Computation
function computeNullifier(nullifierSecret, commitment, leafIndex) {
const intermediate = hash2(nullifierSecret, commitment);
return hash2(intermediate, leafIndex);
}
// Usage (after getting leafIndex from commit event)
const nullifier = computeNullifier(
nullifierSecretBn,
commitment,
leafIndex
);
Field Size
All values must be valid BN254 field elements:
const FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
// Ensure values are in field
const value = someBigInt % FIELD_SIZE;
Complete Example
import { buildPoseidon } from 'circomlibjs';
const FIELD_SIZE = 21888242871839275222246405745257275088548364400416034343698204186575808495617n;
async function createCommitment(dataHash) {
const poseidon = await buildPoseidon();
const F = poseidon.F;
// Generate random secrets
const secret = randomFieldElement();
const nullifierSecret = randomFieldElement();
const blinding = randomFieldElement();
// Compute commitment
const inner1 = F.toString(poseidon([secret, nullifierSecret]));
const inner2 = F.toString(poseidon([dataHash, blinding]));
const commitment = F.toString(poseidon([BigInt(inner1), BigInt(inner2)]));
return {
secret,
nullifierSecret,
blinding,
commitment: '0x' + BigInt(commitment).toString(16).padStart(64, '0'),
};
}
function randomFieldElement() {
const bytes = crypto.getRandomValues(new Uint8Array(32));
return BigInt('0x' + Buffer.from(bytes).toString('hex')) % FIELD_SIZE;
}
Important Notes
Order Matters
The order of inputs to Poseidon matters. Poseidon(a, b) ≠ Poseidon(b, a). Always use the exact formula from the documentation.
Commitment Formula
commitment = Poseidon(Poseidon(secret, nullifierSecret), Poseidon(dataHash, blinding))
Nullifier Formula
nullifier = Poseidon(Poseidon(nullifierSecret, commitment), leafIndex)