Documentation Index
Fetch the complete documentation index at: https://mintlify.com/a16z/jolt/llms.txt
Use this file to discover all available pages before exploring further.
Overview
The Grumpkin inline provides optimized elliptic curve operations for the Grumpkin curve, which is the cycle curve for BN254. This makes it particularly useful for recursive proof systems and applications built on top of BN254-based SNARKs.
API Reference
GrumpkinFq
Base field element (point coordinates).
pub struct GrumpkinFq {
e: ark_grumpkin::Fq, // Montgomery form internally
}
Methods
pub fn new(e: Fq) -> Self;
pub fn from_u64_arr(arr: &[u64; 4]) -> Result<Self, GrumpkinError>;
pub fn from_u64_arr_unchecked(arr: &[u64; 4]) -> Self;
pub fn fq(&self) -> Fq;
pub fn zero() -> Self;
pub fn negative_seventeen() -> Self; // Curve constant
pub fn is_zero(&self) -> bool;
pub fn neg(&self) -> Self;
pub fn add(&self, other: &GrumpkinFq) -> Self;
pub fn sub(&self, other: &GrumpkinFq) -> Self;
pub fn dbl(&self) -> Self;
pub fn tpl(&self) -> Self;
pub fn mul(&self, other: &GrumpkinFq) -> Self;
pub fn square(&self) -> Self;
pub fn div(&self, other: &GrumpkinFq) -> Self; // Uses inline
pub fn div_assume_nonzero(&self, other: &GrumpkinFq) -> Self; // Uses inline
GrumpkinFr
Scalar field element (private keys, scalars).
pub struct GrumpkinFr {
e: ark_grumpkin::Fr, // Montgomery form internally
}
Methods
pub fn new(e: Fr) -> Self;
pub fn from_u64_arr(arr: &[u64; 4]) -> Result<Self, GrumpkinError>;
pub fn from_u64_arr_unchecked(arr: &[u64; 4]) -> Self;
pub fn fr(&self) -> Fr;
pub fn zero() -> Self;
pub fn is_zero(&self) -> bool;
pub fn neg(&self) -> Self;
pub fn add(&self, other: &GrumpkinFr) -> Self;
pub fn sub(&self, other: &GrumpkinFr) -> Self;
pub fn mul(&self, other: &GrumpkinFr) -> Self;
pub fn square(&self) -> Self;
pub fn div(&self, other: &GrumpkinFr) -> Self; // Uses inline
pub fn div_assume_nonzero(&self, other: &GrumpkinFr) -> Self; // Uses inline
GrumpkinPoint
Affine point on the Grumpkin curve.
pub struct GrumpkinPoint {
x: GrumpkinFq,
y: GrumpkinFq,
}
Methods
pub fn new(x: GrumpkinFq, y: GrumpkinFq) -> Result<Self, GrumpkinError>;
pub fn new_unchecked(x: GrumpkinFq, y: GrumpkinFq) -> Self;
pub fn from_u64_arr(arr: &[u64; 8]) -> Result<Self, GrumpkinError>;
pub fn from_u64_arr_unchecked(arr: &[u64; 8]) -> Self;
pub fn to_u64_arr(&self) -> [u64; 8]; // Returns Montgomery form
pub fn x(&self) -> GrumpkinFq;
pub fn y(&self) -> GrumpkinFq;
pub fn generator() -> Self;
pub fn infinity() -> Self; // Represented as (0, 0)
pub fn is_infinity(&self) -> bool;
pub fn is_on_curve(&self) -> bool;
pub fn neg(&self) -> Self;
pub fn double(&self) -> Self;
pub fn add(&self, other: &GrumpkinPoint) -> Self;
pub fn double_and_add(&self, other: &GrumpkinPoint) -> Self;
Error Handling
pub trait UnwrapOrSpoilProof<T> {
fn unwrap_or_spoil_proof(self) -> T;
}
impl<T> UnwrapOrSpoilProof<T> for Result<T, GrumpkinError> { /* ... */ }
Use .unwrap_or_spoil_proof() to make proofs unsatisfiable on error.
Usage Examples
Point Addition
use grumpkin_inline::GrumpkinPoint;
#[jolt::provable]
fn add_points(p1: [u64; 8], p2: [u64; 8]) -> [u64; 8] {
let point1 = GrumpkinPoint::from_u64_arr(&p1).unwrap();
let point2 = GrumpkinPoint::from_u64_arr(&p2).unwrap();
point1.add(&point2).to_u64_arr()
}
Field Division
use grumpkin_inline::GrumpkinFq;
#[jolt::provable]
fn field_inverse(x: [u64; 4]) -> [u64; 4] {
let elem = GrumpkinFq::from_u64_arr(&x).unwrap();
let one = GrumpkinFq::from_u64_arr(&[1, 0, 0, 0]).unwrap();
let inv = one.div(&elem); // Uses inline
inv.fq().into_bigint().0
}
Pedersen Hash Commitment
use grumpkin_inline::{GrumpkinPoint, GrumpkinFr};
#[jolt::provable]
fn pedersen_commit(
message: [u64; 4],
randomness: [u64; 4],
base1: [u64; 8],
base2: [u64; 8],
) -> [u64; 8] {
let m = GrumpkinFr::from_u64_arr(&message).unwrap();
let r = GrumpkinFr::from_u64_arr(&randomness).unwrap();
let g = GrumpkinPoint::from_u64_arr(&base1).unwrap();
let h = GrumpkinPoint::from_u64_arr(&base2).unwrap();
// Compute m·G + r·H (simplified - actual scalar mul omitted)
// This is a conceptual example
g.add(&h).to_u64_arr()
}
Verify Point on Curve
use grumpkin_inline::{GrumpkinPoint, UnwrapOrSpoilProof};
#[jolt::provable]
fn verify_point_valid(point: [u64; 8]) {
// If point is not on curve, proof becomes unsatisfiable
GrumpkinPoint::from_u64_arr(&point).unwrap_or_spoil_proof();
}
Implementation Details
Curve Equation
Grumpkin: y² = x³ - 17 over the prime field:
q = BN254 scalar field modulus
= 0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593f0000001
Scalar field:
r = BN254 base field modulus
= 0x30644e72e131a029b85045b68181585d97816a916871ca8d3c208c16d87cfd47
Cycle Curve Property
Grumpkin forms a cycle with BN254:
- Grumpkin base field = BN254 scalar field
- Grumpkin scalar field = BN254 base field
This enables efficient recursive proof composition.
Custom Instructions
The Grumpkin inline provides two advice-only instructions:
GRUMPKIN_DIVQ_ADV (funct3=0x00): Base field division (pure advice, verified)
GRUMPKIN_DIVR_ADV (funct3=0x01): Scalar field division (pure advice, verified)
Both instructions are “advice-only” meaning they provide non-deterministic hints that are then verified:
// Inline provides c as advice
let c = div_inline_advice(a, b);
// Then verify: b * c == a
if b.mul(&c) != a {
hcf(); // Spoil proof if advice is incorrect
}
The implementation uses arkworks’ Montgomery form internally:
- Conversions happen at boundaries (
from_u64_arr, to_u64_arr)
- Addition/subtraction work identically in Montgomery and standard form
- Multiplication/division use arkworks on host, inline on guest
Infinity Representation
Infinity is represented as (0, 0) since this point is not on the curve y² = x³ - 17:
// For (0, 0): 0² = 0 ≠ 0³ - 17 = -17
Optimized Double-and-Add
The double_and_add() method computes 2·P + Q more efficiently than separate operations:
// Standard: 2 field divisions
let r = p.double();
let result = r.add(&q);
// Optimized: 1 field division (plus other operations)
let result = p.double_and_add(&q);
Error Types
pub enum GrumpkinError {
InvalidFqElement, // Value ≥ q
InvalidFrElement, // Value ≥ r
NotOnCurve, // Point not on y² = x³ - 17
}
Constants
pub const INLINE_OPCODE: u32 = 0x0B;
pub const GRUMPKIN_FUNCT7: u32 = 0x06;
pub const GRUMPKIN_DIVQ_ADV_FUNCT3: u32 = 0x00;
pub const GRUMPKIN_DIVQ_ADV_NAME: &str = "GRUMPKIN_DIVQ_ADV";
pub const GRUMPKIN_DIVR_ADV_FUNCT3: u32 = 0x01;
pub const GRUMPKIN_DIVR_ADV_NAME: &str = "GRUMPKIN_DIVR_ADV";
- Field division: ~10-20x faster than pure arkworks
- Point operations: Proportionally faster due to division acceleration
- Advice verification: Minimal overhead for correctness checks
Comparison with secp256k1 Inline
| Feature | secp256k1 | Grumpkin |
|---|
| Field form | Standard (non-Montgomery) | Montgomery |
| Mul inline | Full computation | N/A (uses arkworks) |
| Div inline | Full computation | Advice + verification |
| GLV | Yes | No |
| Use case | Bitcoin/Ethereum signatures | BN254 recursive proofs |
Feature Flags
host: Enables reference implementation for host-side execution
- Guest code: Compile WITHOUT this feature
- Prover code: Compile WITH this feature
Source Code Location
jolt-inlines/grumpkin/
├── src/
│ ├── lib.rs # Module definitions and constants
│ ├── sdk.rs # Public API (564 lines)
│ ├── sequence_builder.rs # Instruction sequences
│ └── host.rs # Host-side registration
└── Cargo.toml
See Also