Pure-Go implementation of the Aztec UltraHonk proof verification protocol over the BN254 curve. Compatible with proofs generated by Barretenberg.
- Full UltraHonk proof verification (sumcheck + Shplemini/KZG pairing)
- Fiat-Shamir transcript generation (Keccak256-based)
- 8 relation accumulators: arithmetic, permutation, lookup, delta range, elliptic curve, auxiliary, Poseidon2 external/internal
- Load verification keys from Barretenberg output (
bb write_vkbinary and JSON formats) - Verification key serialization/deserialization
- Zero external dependencies beyond gnark-crypto and
golang.org/x/crypto
go get github.com/nixprotocol/ultrahonk-goRequires Go 1.23+.
package main
import (
"fmt"
"os"
honk "github.com/nixprotocol/ultrahonk-go"
)
func main() {
// Load verification key from Barretenberg binary output
vkData, _ := os.ReadFile("path/to/vk")
vk, err := honk.DeserializeVKFromBarretenberg(vkData)
if err != nil {
panic(err)
}
// Or from Barretenberg JSON output
// vkJSON, _ := os.ReadFile("path/to/vk.json")
// vk, err := honk.DeserializeVKFromJSON(vkJSON)
// Parse proof bytes (ProofSize * 32 = 14592 bytes)
proofBytes := []byte{...} // your proof
// Parse public inputs from hex strings
inputs, err := honk.ParsePublicInputs([]string{
"0x2a711793ab8f5e5050f59b872365ad644e115ad50a23796e6bca4edfa98ac4cd",
"0x0000000000000000000000000000000000000000000000000000000005f5e100",
// ... more inputs ...
})
if err != nil {
panic(err)
}
verified, err := honk.Verify(vk, proofBytes, inputs)
if err != nil {
fmt.Printf("verification error: %v\n", err)
return
}
fmt.Printf("proof valid: %v\n", verified)
}Three ways to load a VK:
| Method | Use case |
|---|---|
DeserializeVKFromBarretenberg(data) |
Binary output from bb write_vk (1760 bytes) |
DeserializeVKFromJSON(data) |
JSON output from bb write_vk --output_format json |
DeserializeVK(data) |
Library's own compact binary format (1752 bytes) |
You can also construct a VerificationKey struct directly and serialize it with SerializeVK().
| Function | Description |
|---|---|
Verify(vk, proofBytes, publicInputs) |
Verify an UltraHonk proof against a verification key |
ParsePublicInputs(hexStrings) |
Parse hex-encoded public input strings into field elements |
LoadProof(proofBytes) |
Deserialize raw bytes into a structured Proof |
DeserializeVKFromBarretenberg(data) |
Deserialize a VK from Barretenberg's binary format |
DeserializeVKFromJSON(data) |
Deserialize a VK from Barretenberg's JSON format |
SerializeVK(vk) |
Serialize a VerificationKey to the library's binary format |
DeserializeVK(data) |
Deserialize a VK from the library's binary format |
Verify expects proof-only bytes (14,592 bytes = 456 field elements x 32 bytes).
Important: bb prove output includes public inputs prepended to the proof. You must strip them before passing to Verify:
bb prove output layout:
[public_inputs (N * 32 bytes)] [proof (14592 bytes)]
where N = vk.PublicInputsSize (from the VK header)
To split them:
vk, _ := honk.DeserializeVKFromBarretenberg(vkData)
bbOutput, _ := os.ReadFile("path/to/proof")
pubInputBytes := int(vk.PublicInputsSize) * 32
proofBytes := bbOutput[pubInputBytes:]
// Public inputs are typically passed separately (e.g., from your application logic).The proof body contains:
- Pairing point object (16 field elements)
- Wire commitments (W1-W4, ZPerm, lookup helpers) as split-limb G1 points
- Sumcheck univariates (28 rounds x 8 evaluations)
- Sumcheck evaluations (40 entities)
- Gemini fold commitments and evaluations
- Shplonk and KZG quotient commitments
| Parameter | Value |
|---|---|
| Max proof size log N | 28 |
| Number of entities | 40 |
| Subrelations | 26 |
| Batched relation partial length | 8 |
| Curve | BN254 |
Apple M1 Pro:
BenchmarkVerify 705 1.65 ms/op 78 KB/op 539 allocs/op
BenchmarkLoadProof 99094 12.3 us/op 16 KB/op 1 allocs/op
BenchmarkSerializeVK 1000000 1.0 us/op 1.8 KB/op 1 allocs/op
Compatible with UltraHonk proofs generated by Barretenberg v0.87. The Fiat-Shamir transcript, relation evaluations, and pairing check match the Solidity verifier specification.