Barretenberg on the browser
bb.js is the TypeScript/JavaScript prover and verifier library for Barretenberg. It provides both a command-line interface and a programmatic API for generating and verifying zero-knowledge proofs in Node.js and browser environments.
Overview
bb.js supports multiple proof systems:
- UltraHonk: The current recommended proof system with various hash function options
- MegaHonk: Alternative Honk implementation
- ClientIVC: For Aztec-specific client-side proving
Installation
As a Library
Install bb.js as a dependency in your project:
npm install @aztec/bb.js
or with yarn:
yarn add @aztec/bb.js
Proving and Verifying with UltraHonk
Using the UltraHonkBackend Class
The UltraHonkBackend
class provides a high-level interface for proof generation and verification. You can import any specific backend (i.e. UltraHonk):
import { UltraHonkBackend} from '@aztec/bb.js';
Using a precompiled program and a witness from nargo execute
, you can directly import it and initialize the backend:
// Load circuit bytecode (from Noir compiler output)
const circuitPath = path.join(__dirname, 'fixtures/main/target/program.json');
const circuitJson = JSON.parse(readFileSync(circuitPath, 'utf8'));
const bytecode = circuitJson.bytecode;
// Load witness data
const witnessPath = path.join(__dirname, 'fixtures/main/target/program.gz');
const witnessBuffer = readFileSync(witnessPath);
// Initialize backend
const backend = new UltraHonkBackend(bytecode);
And just prove it using the witness:
// Generate proof with Keccak for EVM verification
const proofData: ProofData = await backend.generateProof(witnessBuffer, {
keccak: true
});
const provingTime = Date.now() - startTime;
console.log(`Proof generated in ${provingTime}ms`);
console.log(`Proof size: ${proofData.proof.length} bytes`);
console.log(`Public inputs: ${proofData.publicInputs.length}`);
Verification is similarly simple:
// Verify the proof
console.log('Verifying proof...');
const isValid = await backend.verifyProof(proofData, { keccak: true });
console.log(`Proof verification: ${isValid ? 'SUCCESS' : 'FAILED'}`);
Working with Different Hash Functions
UltraHonk supports different hash functions for different target verification environments:
// Standard UltraHonk (uses Poseidon)
const proof = await backend.generateProof(witnessBuffer);
expect(proof.proof).to.have.length.greaterThan(0);
// Keccak variant (for EVM verification)
const proofKeccak = await backend.generateProof(witnessBuffer, { keccak: true });
expect(proofKeccak.proof).to.have.length.greaterThan(0);
// ZK variants for recursive proofs
const proofKeccakZK = await backend.generateProof(witnessBuffer, { keccakZK: true });
expect(proofKeccakZK.proof).to.have.length.greaterThan(0);
Getting Verification Keys (VK)
// Get verification key
const vk = await backend.getVerificationKey();
// For a solidity verifier:
const vkKeccak = await backend.getVerificationKey({ keccak: true });
Getting Solidity Verifier
The solidity verifier is the VK, but with some logic that allows for non-interactive verification:
// Needs the keccak hash variant of the VK
const solidityContract = await backend.getSolidityVerifier(vkKeccak);
Using the Low-Level API
For more control, you can use the Barretenberg API directly:
const api = await Barretenberg.new({ threads: 1 });
// Blake2s hashing
const input = Buffer.from('hello world!');
const hash = await api.blake2s(input);
// Pedersen commitment
const left = Fr.random();
const right = Fr.random();
const commitment = await api.pedersenCommit([left, right], 0);
await api.destroy();
Browser Environment Considerations
Multithreading Support
To enable multithreading in browsers using some frameworks (ex. Next.js), you may need to set COOP and COEP headers:
// Next.js example configuration
{
async headers() {
return [
{
source: '/:path*',
headers: [
{ key: 'Cross-Origin-Embedder-Policy', value: 'require-corp' },
{ key: 'Cross-Origin-Opener-Policy', value: 'same-origin' },
],
},
];
},
}
Performance Optimization
Thread Configuration
You can define specific thread counts in case you need the cores for other things in your app:
// Auto-detect optimal thread count (default)
const api = await Barretenberg.new();
// Manual thread configuration
const api = await Barretenberg.new({
threads: Math.min(navigator.hardwareConcurrency || 1, 8)
});
// Single-threaded for compatibility
const api = await Barretenberg.new({ threads: 1 });
Memory Management
It can be useful to manage memory manually, specially if targeting specific memory-constrained environments (ex. Safari):
// Configure initial and maximum memory
const api = await Barretenberg.new({
threads: 4,
memory: {
initial: 128 * 1024 * 1024, // 128MB
maximum: 512 * 1024 * 1024 // 512MB
}
});