Skip to main content

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:
  1. GRUMPKIN_DIVQ_ADV (funct3=0x00): Base field division (pure advice, verified)
  2. 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
}

Montgomery Form

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";

Performance Characteristics

  • 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

Featuresecp256k1Grumpkin
Field formStandard (non-Montgomery)Montgomery
Mul inlineFull computationN/A (uses arkworks)
Div inlineFull computationAdvice + verification
GLVYesNo
Use caseBitcoin/Ethereum signaturesBN254 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

Build docs developers (and LLMs) love