Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
with: { components: rustfmt }
with:
components: rustfmt
- run: cargo fmt --all -- --check

# ---------- Tests (independent) ----------
Expand All @@ -29,7 +30,14 @@ jobs:
# Rust toolchain
- uses: dtolnay/rust-toolchain@stable

# Cache Cargo deps
# --- Pin legacy Noir toolchain (Poseidon2 still in stdlib) -----------
- name: Install Noir toolchain 1.0.0-beta.3
uses: noir-lang/noirup@v0.1.2
with:
toolchain: "1.0.0-beta.3"
# --------------------------------------------------------------------

# Cache Cargo dependencies
- uses: actions/cache@v3
with:
path: |
Expand All @@ -46,3 +54,11 @@ jobs:
- name: Run tests
run: cargo test --verbose

# ---------- Test alloc feature ----------
- name: Test alloc feature
run: cargo test alloc --verbose

# ---------- Test std feature ----------
- name: Test std feature
run: cargo test std --verbose

8 changes: 1 addition & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

52 changes: 36 additions & 16 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,25 +8,45 @@ description = "Rust verifier for UltraHonk proofs"
repository = "https://github.com/yugocabrio/ultrahonk-rust-verifier"

[dependencies]
sha3 = "0.10"
sha3 = { version = "0.10", default-features = false }

ark-ff = "0.5"
ark-ec = "0.5"
ark-serialize = "0.5"
ark-std = { version = "0.5", features = ["std"] }
ark-bn254 = { version = "0.5", features = ["std"] }
ark-ff = { version = "0.5", default-features = false }
ark-ec = { version = "0.5", default-features = false }
ark-serialize = { version = "0.5", default-features = false }
ark-std = { version = "0.5", default-features = false }
ark-bn254 = { version = "0.5", default-features = false, features = ["curve"] }

anyhow = "1.0"
serde = { version = "1.0", features = ["derive"], default-features = false, optional = true }
serde_json = { version = "1.0", default-features = false, features = ["alloc"], optional = true }
hex = { version = "0.4", default-features = false, features = ["alloc"] }

serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
hex = "0.4"
num-bigint = { version = "0.4", default-features = false }
num-traits = { version = "0.2", default-features = false }

num-bigint = "0.4"
num-traits = "0.2"

lazy_static = "1.4"
lazy_static = { version = "1.4", optional = true }
once_cell = { version = "1.19", default-features = false, features = ["alloc", "race"] }

[features]
default = []
trace = []
default = ["alloc"]
std = [
"sha3/std",
"ark-ff/std",
"ark-ec/std",
"ark-serialize/std",
"ark-std/std",
"ark-bn254/std",
"serde",
"serde_json",
"hex/std",
"num-bigint/std",
"num-traits/std",
"lazy_static",
"once_cell/std"
]
trace = []

alloc = [
"serde_json/alloc",
"hex/alloc",
"once_cell/alloc",
]
3 changes: 3 additions & 0 deletions src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ use crate::field::Fr;
use crate::types::G1Point;
use ark_ff::{BigInteger256, PrimeField};

#[cfg(not(feature = "std"))]
use alloc::{format, string::String};

/// trace! macro is a lightweight debug print macro that only outputs when the `trace` feature is enabled.
/// you can use it like this: cargo test --features trace -- --nocapture / cargo run --features trace
#[macro_export]
Expand Down
7 changes: 5 additions & 2 deletions src/field.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@ use ark_bn254::Fr as ArkFr;
use ark_ff::BigInteger256;
use ark_ff::{Field, PrimeField, Zero};
use ark_serialize::CanonicalSerialize;
use core::ops::{Add, Mul, Neg, Sub};
use hex;
use std::ops::{Add, Mul, Neg, Sub};

#[cfg(not(feature = "std"))]
use alloc::{borrow::ToOwned, format, string::String};

#[inline(always)]
fn normalize_hex(s: &str) -> String {
Expand Down Expand Up @@ -118,7 +121,7 @@ impl Neg for Fr {
}

impl CanonicalSerialize for Fr {
fn serialize_with_mode<W: std::io::Write>(
fn serialize_with_mode<W: ark_serialize::Write>(
&self,
mut writer: W,
_compress: ark_serialize::Compress,
Expand Down
5 changes: 4 additions & 1 deletion src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
// src/lib.rs
#![cfg_attr(not(feature = "std"), no_std)]

#[cfg(not(feature = "std"))]
extern crate alloc;

pub mod debug;
pub mod field;
Expand Down
15 changes: 14 additions & 1 deletion src/relations.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@

use crate::field::Fr;
use crate::types::{RelationParameters, Wire};
use std::ops::Neg;
use core::ops::Neg;

#[cfg(not(feature = "std"))]
use alloc::vec;

#[cfg(feature = "std")]
macro_rules! println {
($($args:tt)*) => { std::println!($($args)*) };
}

#[cfg(not(feature = "std"))]
macro_rules! println {
($($args:tt)*) => {};
}

/// Precomputed NEG_HALF = (p - 1)/2 in BN254 scalar field.
fn neg_half() -> Fr {
Expand Down
3 changes: 3 additions & 0 deletions src/shplemini.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use ark_bn254::{Bn254, Fq, Fq2, G1Affine, G1Projective, G2Affine};
use ark_ec::{pairing::Pairing, CurveGroup, PrimeGroup};
use ark_ff::{BigInteger, Field, One, PrimeField, Zero};

#[cfg(not(feature = "std"))]
use alloc::{string::String, vec, vec::Vec};

pub const NUMBER_UNSHIFTED: usize = 35; // = 40 – 5
pub const NUMBER_SHIFTED: usize = 5; // Final 5 are shifted

Expand Down
38 changes: 36 additions & 2 deletions src/sumcheck.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,17 @@ use crate::{
types::{Transcript, VerificationKey},
};

lazy_static::lazy_static! {
#[cfg(not(feature = "std"))]
use alloc::{boxed, format, string::String};

#[cfg(feature = "std")]
use lazy_static::lazy_static;

#[cfg(not(feature = "std"))]
use once_cell::race::OnceBox;

#[cfg(feature = "std")]
lazy_static! {
/// 8-point barycentric coefficients
static ref BARY: [Fr; 8] = [
"0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51",
Expand All @@ -22,6 +32,25 @@ lazy_static::lazy_static! {
].map(Fr::from_str);
}

#[cfg(not(feature = "std"))]
static BARY_BOX: OnceBox<[Fr; 8]> = OnceBox::new();

#[cfg(not(feature = "std"))]
fn get_bary() -> &'static [Fr; 8] {
BARY_BOX.get_or_init(|| {
alloc::boxed::Box::new([
Fr::from_str("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffec51"),
Fr::from_str("0x00000000000000000000000000000000000000000000000000000000000002d0"),
Fr::from_str("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffff11"),
Fr::from_str("0x0000000000000000000000000000000000000000000000000000000000000090"),
Fr::from_str("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593efffff71"),
Fr::from_str("0x00000000000000000000000000000000000000000000000000000000000000f0"),
Fr::from_str("0x30644e72e131a029b85045b68181585d2833e84879b9709143e1f593effffd31"),
Fr::from_str("0x00000000000000000000000000000000000000000000000000000000000013b0"),
])
})
}

/// Check if the sum of two univariates equals the target value
#[inline(always)]
fn check_round_sum(u: &[Fr], target: Fr) -> bool {
Expand All @@ -40,7 +69,12 @@ fn next_target(u: &[Fr], chi: Fr) -> Fr {
// Σ u_i / (BARY[i] * (χ - i))
let mut acc = Fr::zero();
for i in 0..8 {
let inv = (BARY[i] * (chi - Fr::from_u64(i as u64))).inverse();
#[cfg(feature = "std")]
let bary_val = BARY[i];
#[cfg(not(feature = "std"))]
let bary_val = get_bary()[i];

let inv = (bary_val * (chi - Fr::from_u64(i as u64))).inverse();
acc = acc + (u[i] * inv);
}

Expand Down
3 changes: 3 additions & 0 deletions src/transcript.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@ use crate::{
};
use ark_bn254::G1Affine;

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

fn push_point(buf: &mut Vec<u8>, pt: &G1Affine) {
let (x_lo, x_hi) = fq_to_halves_be(&pt.x);
let (y_lo, y_hi) = fq_to_halves_be(&pt.y);
Expand Down
5 changes: 4 additions & 1 deletion src/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@
use crate::field::Fr;
use ark_bn254::{Fq, G1Affine};

#[cfg(not(feature = "std"))]
use alloc::vec::Vec;

/// Number of subrelations in the Ultra Honk protocol.
pub const NUMBER_OF_SUBRELATIONS: usize = 26;
pub const CONST_PROOF_SIZE_LOG_N: usize = 28;
Expand Down Expand Up @@ -76,7 +79,7 @@ impl G1Point {
}
}

/// The verification key structure, matching TS's VerificationKey interface.
/// The verification key structure
#[derive(Clone, Debug)]
pub struct VerificationKey {
pub circuit_size: u64,
Expand Down
27 changes: 16 additions & 11 deletions src/utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,9 @@ use ark_bn254::Fq;
use ark_ff::{BigInteger256, PrimeField};
use num_bigint::BigUint;
use num_traits::Num;
use std::fs::File;
use std::io::Read;

#[cfg(not(feature = "std"))]
use alloc::{string::String, vec::Vec};

/// Convert 32 bytes into an Fr.
fn bytes_to_fr(bytes: &[u8; 32]) -> Fr {
Expand Down Expand Up @@ -182,15 +183,11 @@ fn combine_fields(low_str: &str, high_str: &str) -> BigUint {
(high << 136) | low
}

/// Load a VerificationKey from a JSON file containing an array of hex‐encoded field‐elements.
pub fn load_vk(path: &str) -> VerificationKey {
// Read entire file as string
let mut file = File::open(path).expect("VK JSON file not found");
let mut data = String::new();
file.read_to_string(&mut data).unwrap();

/// Load a VerificationKey from a JSON string containing an array of hex‐encoded field‐elements.
#[cfg(feature = "serde_json")]
pub fn load_vk_from_json(json_data: &str) -> VerificationKey {
// Parse JSON into Vec<String>
let vk_fields: Vec<String> = serde_json::from_str(&data).unwrap();
let vk_fields: Vec<String> = serde_json::from_str(json_data).unwrap();
// Ensure we have at least the minimal length
assert!(
vk_fields.len() > 127,
Expand All @@ -205,7 +202,15 @@ pub fn load_vk(path: &str) -> VerificationKey {
let public_inputs_size = BigUint::from_str_radix(vk_fields[1].trim_start_matches("0x"), 16)
.unwrap()
.to_u64_digits()[0];
let log_circuit_size = (circuit_size_u64 as f64).log2() as u64;
let log_circuit_size = {
let mut n = circuit_size_u64;
let mut log = 0;
while n > 1 {
n >>= 1;
log += 1;
}
log
};

// Helper to convert combined BigUint into an Fq
fn biguint_to_fq(x: BigUint) -> Fq {
Expand Down
22 changes: 15 additions & 7 deletions src/verifier.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,29 @@
//! UltraHonk verifier

use crate::{
field::Fr,
shplemini::verify_shplemini,
sumcheck::verify_sumcheck,
transcript::generate_transcript,
utils::{load_proof, load_vk},
field::Fr, shplemini::verify_shplemini, sumcheck::verify_sumcheck,
transcript::generate_transcript, utils::load_proof,
};

#[cfg(feature = "serde_json")]
use crate::utils::load_vk_from_json;

#[cfg(not(feature = "std"))]
use alloc::{format, string::String, vec::Vec};

pub struct UltraHonkVerifier {
vk: crate::types::VerificationKey,
}

impl UltraHonkVerifier {
pub fn new(vk_path: &str) -> Self {
pub fn new_with_vk(vk: crate::types::VerificationKey) -> Self {
Self { vk }
}

#[cfg(feature = "serde_json")]
pub fn new_from_json(json_data: &str) -> Self {
Self {
vk: load_vk(vk_path),
vk: load_vk_from_json(json_data),
}
}

Expand Down
4 changes: 2 additions & 2 deletions tests/verifier_test.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ fn run(dir: &str) -> Result<(), String> {
let proof_buf = fs::read(path.join("proof")).map_err(|e| e.to_string())?;
let (pub_inputs, proof_bytes) = load_proof_and_public_inputs(&proof_buf);

let vk_path = path.join("vk_fields.json");
let verifier = UltraHonkVerifier::new(vk_path.to_str().unwrap());
let vk_json = fs::read_to_string(path.join("vk_fields.json")).map_err(|e| e.to_string())?;
let verifier = UltraHonkVerifier::new_from_json(&vk_json);

let pub_inputs_bytes: Vec<Vec<u8>> =
pub_inputs.iter().map(|fr| fr.to_bytes().to_vec()).collect();
Expand Down