Skip to content
Draft
Show file tree
Hide file tree
Changes from 49 commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
cd91986
added machinery to flamegraph/profile the multi-record program
Antonio95 Apr 16, 2026
e954ee7
working on multi-transaction flow
Antonio95 Apr 20, 2026
89e90bd
splitting commit
Antonio95 Apr 30, 2026
340fb72
added benchmarking machinery, polished
Antonio95 Apr 28, 2026
0d8b5d3
wip, dirty branch
Antonio95 Apr 15, 2026
76525ff
streamlined criterion and flamegraph machinery
Antonio95 Apr 16, 2026
6812d61
minor rewording, changed param
Antonio95 Apr 28, 2026
148451b
switched to V2 to anchor the pre-changes comparison point
Antonio95 Apr 29, 2026
3ea5cf1
added missing file
Antonio95 Apr 16, 2026
53d33bb
fixed stale merge conflict; measuring machinery ready
Antonio95 Apr 30, 2026
89cb2fc
bhp work
Antonio95 Apr 30, 2026
3ecf17e
poseidon speed-up thanks to custom s-box implementation; improved pow…
Antonio95 Apr 16, 2026
34143bb
cleanup
Antonio95 Apr 16, 2026
3d08c04
added function to get VarunaVersion from ConsensusVersion; switched t…
Antonio95 Apr 17, 2026
c55217d
parallelised output verification
Antonio95 Apr 17, 2026
7e898fd
replaced two internal states in Poseidon by their concatenation
Antonio95 Apr 20, 2026
bda4ac9
Revert "replaced two internal states in Poseidon by their concatenation"
Antonio95 Apr 20, 2026
fd28ca3
introduced preprocessed paired BHP bases
Antonio95 Apr 20, 2026
239cd4c
removed prints
Antonio95 Apr 20, 2026
efa368b
BHP wip
Antonio95 Apr 28, 2026
dd09be7
added variable window size to BHP; added BHP setup and size metrics
Antonio95 Apr 29, 2026
6306031
feat: varuna performance optimisations
Apr 14, 2026
1fd300b
feat: PR comments
Apr 16, 2026
ff55075
feat: cargo fmt
Apr 16, 2026
cdca9e1
Introduce ANCHOR_TIMES
raychu86 Mar 18, 2026
8ec0ab3
Use ConsensusVersion in target construction
raychu86 Mar 18, 2026
4570bec
Remove ANCHOR_TIME, derive ANCHOR_HEIGHT from V1 literal
raychu86 Mar 18, 2026
2644872
Add consensus tests for ANCHOR_TIMES
raychu86 Mar 18, 2026
7e127b9
Add tests
raychu86 Mar 18, 2026
29e10a8
Update anchor time height to V15
raychu86 Apr 3, 2026
f1096ad
Be explicit about anchor time used for reward calculations
raychu86 Apr 3, 2026
9f566f3
nit
raychu86 Apr 3, 2026
dab37dd
feat: fail on unwrap
Apr 17, 2026
74343e0
Run failing test on current branch
vicsn Apr 15, 2026
dbb06d0
Fix test_target_doubling_changes_with_anchor_time expectation
vicsn Apr 15, 2026
9617735
fix(ledger): check subDAG atomicity correctly for a chain of pending …
kaimast Apr 17, 2026
e808c03
ci: increase machine size for ledger-narwhal-data job
kaimast Apr 17, 2026
aadb2dc
fix(ledger): ensure identical private keys are generated by TestChain…
kaimast Apr 17, 2026
a321e28
Revert "feat: fail on unwrap"
davencyw Apr 21, 2026
07109ad
misc(ledger): log solution ID when post-ratify fails
kaimast Apr 10, 2026
a3f15bf
fix: restore the old random range algorithm for MerklePuzzle::num_leaves
ljedrz Apr 16, 2026
d6aaa54
fix: restore the old choose_weighted algorithm for sample_instructions
ljedrz Apr 17, 2026
bee5021
tweak: apply review comments
ljedrz Apr 17, 2026
495209f
tweak: make the reimplementation of legacy rand functions more 1:1
ljedrz Apr 17, 2026
25b50ab
logs: extra block tree caching info
ljedrz Apr 27, 2026
fdd65ac
introduced constensus version V16, cleanup
Antonio95 Apr 29, 2026
7575a57
updated two benches to varuna version v3 for comparison
Antonio95 Apr 29, 2026
35f63a6
Merge branch 'staging' into profile_verifier_multirecord
Antonio95 Apr 30, 2026
7f1400b
temporarily added the branch to merge-workflow triggers
Antonio95 Apr 30, 2026
1164c45
addressed PR feedback
Antonio95 May 4, 2026
317611a
Merge branch 'staging' into profile_verifier_multirecord
Antonio95 May 4, 2026
02cdf49
reduced BHP_NUM_COMBINED_CHUNKS to 4 for CLI flows
Antonio95 May 5, 2026
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
1 change: 1 addition & 0 deletions .circleci/config.yml
Original file line number Diff line number Diff line change
Expand Up @@ -1285,6 +1285,7 @@ workflows:
or pipeline.git.branch == "canary"
or pipeline.git.branch == "testnet"
or pipeline.git.branch == "mainnet"
or pipeline.git.branch == "profile_verifier_multirecord"

Copilot AI Apr 30, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The merge-workflow branch filter now includes profile_verifier_multirecord, which will trigger a large set of expensive CI jobs on that branch. This looks like a temporary profiling branch name and should not be merged into main CI config; please remove it (or replace with a generic mechanism such as tags / parameters if you need opt-in heavy checks).

Suggested change
or pipeline.git.branch == "profile_verifier_multirecord"

Copilot uses AI. Check for mistakes.
jobs:
- check-unused-dependencies # This can be cleaned up before releases
- check-cargo-semver-checks # This can be cleaned up before releases
Expand Down
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,9 @@
**storage_db
**/*.prover.*
**/*.verifier.*
**/varuna_verifier_artifacts/
*.usrs
flamegraph.svg
!**/powers-of-beta-15.usrs*
!**/shifted-powers-of-beta-15.usrs*
!**/powers-of-beta-16.usrs*
Expand Down
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ dev-print = [
"snarkvm-utilities/dev-print",
"snarkvm-algorithms/dev-print",
"snarkvm-circuit/dev-print",
"snarkvm-console/dev-print",
"snarkvm-curves/dev-print",
"snarkvm-fields/dev-print",
"snarkvm-parameters/dev-print",
Expand Down
6 changes: 6 additions & 0 deletions algorithms/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,12 @@ path = "benches/snark/varuna.rs"
harness = false
required-features = [ "test" ]

[[bench]]
name = "varuna_verifier"
path = "benches/snark/varuna_verifier.rs"
harness = false
required-features = [ "test" ]

[dependencies.snarkvm-curves]
workspace = true

Expand Down
82 changes: 81 additions & 1 deletion algorithms/benches/snark/varuna.rs
Original file line number Diff line number Diff line change
Expand Up @@ -229,6 +229,86 @@ fn snark_batch_verify(c: &mut Criterion) {
});
}

fn snark_batch_verify_scaling(c: &mut Criterion) {
let rng = &mut TestRng::default();

let batch_specs = vec![
// [many x (batch_size, num_constraints, num_variables, num_public_inputs)])
vec![(1, 10_000, 5_000, 16)],
vec![(1, 50_000, 25_000, 64)],
vec![(30, 50_000, 25_000, 64)],
vec![(30, 50_000, 25_000, 64), (1, 5_000_000, 5_000_000, 1024)],
];

let max_vars = *batch_specs
.iter()
.map(|batches| batches.iter().map(|(_, _, num_variables, _)| num_variables).max().unwrap())
.max()
.unwrap();
let max_constraints = *batch_specs
.iter()
.map(|batches| batches.iter().map(|(_, num_constraints, _, _)| num_constraints).max().unwrap())
.max()
.unwrap();
let max_density = 2 * max_constraints;

let max_degree = AHPForR1CS::<Fr, VarunaHidingMode>::max_degree(max_constraints, max_vars, max_density).unwrap();
let universal_srs = VarunaInst::universal_setup(max_degree).unwrap();
let universal_prover = &universal_srs.to_universal_prover().unwrap();
let universal_verifier = &universal_srs.to_universal_verifier().unwrap();
let fs_parameters = FS::sample_parameters();

let varuna_version = VarunaVersion::V3;

for batch_spec in batch_specs {
let batch_str = batch_spec
.iter()
.map(|(batch_size, num_constraints, _, num_public_inputs)| {
format!("({batch_size} x [{num_public_inputs}, {num_constraints}])")
})
.collect::<Vec<_>>()
.join(" + ");

let circuits_and_inputs = batch_spec
.iter()
.map(|&batch| {
let (batch_size, num_constraints, num_variables, num_public_inputs) = batch;
let (circuit, public_inputs) =
TestCircuit::gen_rand(num_public_inputs, num_constraints, num_variables, rng);
(
VarunaInst::circuit_setup(&universal_srs, &circuit).unwrap(),
vec![circuit; batch_size],
vec![public_inputs; batch_size],
)
})
.collect::<Vec<_>>();

let pks_to_circuits = circuits_and_inputs
.iter()
.map(|((pk, _), circuits, _)| (pk, circuits.as_slice()))
.collect::<BTreeMap<_, _>>();
let vks_to_inputs =
circuits_and_inputs.iter().map(|((_, vk), _, inputs)| (vk, inputs.as_slice())).collect::<BTreeMap<_, _>>();

let proof =
VarunaInst::prove_batch(universal_prover, &fs_parameters, varuna_version, &pks_to_circuits, rng).unwrap();

c.bench_function(&format!("snark_batch_verify_scaling/{batch_str}"), |b| {
b.iter(|| {
let verification = VarunaInst::verify_batch(
universal_verifier,
&fs_parameters,
varuna_version,
&vks_to_inputs,
&proof,
)
.unwrap();
assert!(verification);
});
});
}
}

fn snark_vk_serialize(c: &mut Criterion) {
use snarkvm_utilities::serialize::Compress;
let mut group = c.benchmark_group("snark_vk_serialize");
Expand Down Expand Up @@ -344,7 +424,7 @@ fn snark_certificate_verify(c: &mut Criterion) {
criterion_group! {
name = varuna_snark;
config = Criterion::default().measurement_time(Duration::from_secs(10));
targets = snark_universal_setup, snark_circuit_setup, snark_prove, snark_verify, snark_batch_prove, snark_batch_verify, snark_vk_serialize, snark_vk_deserialize, snark_certificate_prove, snark_certificate_verify,
targets = snark_universal_setup, snark_circuit_setup, snark_prove, snark_verify, snark_batch_prove, snark_batch_verify, snark_batch_verify_scaling, snark_vk_serialize, snark_vk_deserialize, snark_certificate_prove, snark_certificate_verify,
}

criterion_main!(varuna_snark);
187 changes: 187 additions & 0 deletions algorithms/benches/snark/varuna_verifier.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,187 @@
// Copyright (c) 2019-2026 Provable Inc.
// This file is part of the snarkVM library.

// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at:

// http://www.apache.org/licenses/LICENSE-2.0

// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

/*
Performs time measurements on the verification of test circuits with selected batch sizes and parameters.
- Generate artifacts with:
cargo bench --bench varuna_verifier --features test -- --generate
- Artifacts are ignored by git. To clean them, run:
cargo bench --bench varuna_verifier --features test -- --clean
- Obtain time measurements (using previously generated artifacts) with:
cargo bench --bench varuna_verifier --features test
The --serial feature can be added to deactivate parallelism.
- Flamegraph (on previously generated artifacts) with:
cargo flamegraph --bench varuna_verifier --features="test, serial"
*/

use snarkvm_algorithms::{
AlgebraicSponge,
SNARK,
crypto_hash::PoseidonSponge,
snark::varuna::{
CircuitVerifyingKey,
Proof,
TestCircuit,
VarunaHidingMode,
VarunaSNARK,
VarunaVersion,
ahp::AHPForR1CS,
},
};
use snarkvm_curves::bls12_377::{Bls12_377, Fq, Fr};
use snarkvm_utilities::{CanonicalDeserialize, CanonicalSerialize, FromBytes, TestRng, ToBytes};

use std::{collections::BTreeMap, env, path::Path, time::Instant};

type VarunaInst = VarunaSNARK<Bls12_377, FS, VarunaHidingMode>;
type FS = PoseidonSponge<Fq, 2, 1>;

fn main() {
/////////////////////////// User defined

// How many times `verify_batch` runs when not using `--generate`. Larger values
// help flamegraph / timing stability.
let n_samples = 10;

// Each tuple is: (batch_size, num_constraints, num_variables,
// num_public_inputs)
let batches = [(30, 50_000, 25_000, 64), (1, 5_000_000, 5_000_000, 1024)];

///////////////////////////

let generate = env::args().any(|arg| arg == "--generate");
let clean = env::args().any(|arg| arg == "--clean");
let artifact_path = Path::new(env!("CARGO_MANIFEST_DIR")).join("benches/snark/varuna_verifier_artifacts");

if clean {
if generate {
panic!(
"--clean and --generate cannot be used together. Use --generate to generate\\
the artifacts, --clean to delete them (and end), and neither to use the existing artifacts."
);
}
std::fs::remove_dir_all(&artifact_path).unwrap();
println!("Artifacts deleted.");
return;
}

if !artifact_path.exists() {
if !generate {
panic!("--generate was not passed, but artifacts were not found.");
}
std::fs::create_dir(&artifact_path).unwrap();
}

let rng = &mut TestRng::default();

let max_vars = *batches.iter().map(|(_, _, num_variables, _)| num_variables).max().unwrap();
let max_constraints = *batches.iter().map(|(_, num_constraints, _, _)| num_constraints).max().unwrap();
let max_density = 2 * max_constraints;

let max_degree = AHPForR1CS::<Fr, VarunaHidingMode>::max_degree(max_constraints, max_vars, max_density).unwrap();
let universal_srs = VarunaInst::universal_setup(max_degree).unwrap();
let universal_prover = &universal_srs.to_universal_prover().unwrap();
let universal_verifier = &universal_srs.to_universal_verifier().unwrap();
let fs_parameters = FS::sample_parameters();

let varuna_version = VarunaVersion::V3;

let batch_str = batches
.iter()
.map(|(batch_size, num_constraints, _, num_public_inputs)| {
format!("({batch_size} x [{num_public_inputs}, {num_constraints}])")
})
.collect::<Vec<_>>()
.join(" + ");

println!("Batches: {batch_str}");

let sanitized_batch_str = batch_str.replace(' ', "_");

let vk_path = artifact_path.join(format!("vk_{sanitized_batch_str}.bin"));
let inputs_path = artifact_path.join(format!("inputs_{sanitized_batch_str}.bin"));
let proof_path = artifact_path.join(format!("proof_{sanitized_batch_str}.bin"));

if generate {
println!("Generating artifacts for {batch_str}...");

let circuits_and_inputs: Vec<_> = batches
.iter()
.map(|&batch| {
let (batch_size, num_constraints, num_variables, num_public_inputs) = batch;
let (circuit, public_inputs) =
TestCircuit::gen_rand(num_public_inputs, num_constraints, num_variables, rng);
(
VarunaInst::circuit_setup(&universal_srs, &circuit).unwrap(),
vec![circuit; batch_size],
vec![public_inputs; batch_size],
)
})
.collect();

let pks_to_circuits = circuits_and_inputs
.iter()
.map(|((pk, _), circuits, _)| (pk, circuits.as_slice()))
.collect::<BTreeMap<_, _>>();
let vks_to_inputs =
circuits_and_inputs.iter().map(|((_, vk), _, inputs)| (vk, inputs.as_slice())).collect::<BTreeMap<_, _>>();

let proof =
VarunaInst::prove_batch(universal_prover, &fs_parameters, varuna_version, &pks_to_circuits, rng).unwrap();

let vks = vks_to_inputs.keys().map(|vk| (*vk).clone()).collect::<Vec<_>>();
let inputs = vks_to_inputs.values().cloned().collect::<Vec<_>>();

let mut vk_buf = Vec::new();
CanonicalSerialize::serialize_uncompressed(&vks, &mut vk_buf).unwrap();
std::fs::write(&vk_path, vk_buf).expect("Failed to write verifying keys");
let mut inputs_buf = Vec::new();
CanonicalSerialize::serialize_uncompressed(&inputs, &mut inputs_buf).unwrap();
std::fs::write(&inputs_path, inputs_buf).expect("Failed to write inputs");
std::fs::write(&proof_path, proof.to_bytes_le().unwrap()).expect("Failed to write proof");
}

// Reload from disk so serialization and verification paths are exercised.
let vks: Vec<CircuitVerifyingKey<Bls12_377>> = CanonicalDeserialize::deserialize_uncompressed(
&*std::fs::read(&vk_path).expect("Failed to read verifying keys"),
)
.unwrap();
let inputs: Vec<Vec<Vec<Fr>>> =
CanonicalDeserialize::deserialize_uncompressed(&*std::fs::read(&inputs_path).expect("Failed to read inputs"))
.unwrap();
let proof = Proof::<Bls12_377>::read_le(&*std::fs::read(&proof_path).expect("Failed to read proof")).unwrap();
let vks_to_inputs: BTreeMap<_, _> = vks.iter().zip(inputs.iter()).map(|(vk, inp)| (vk, inp.as_slice())).collect();

if generate {
println!("Verifying generated proof for {batch_str}...");
assert!(
VarunaInst::verify_batch(universal_verifier, &fs_parameters, varuna_version, &vks_to_inputs, &proof)
.unwrap()
);
println!("Verification successful");
} else {
println!("Verifying proof for {batch_str} {n_samples} times...");
let timer = Instant::now();
for _ in 0..n_samples {
assert!(
VarunaInst::verify_batch(universal_verifier, &fs_parameters, varuna_version, &vks_to_inputs, &proof)
.unwrap()
);
}
let elapsed = timer.elapsed().as_micros() as f64 / 1000.0;
let elapsed_avg = elapsed / n_samples as f64;
println!("Verification successful in {elapsed:.2} ms ({elapsed_avg:.2} ms per sample)");
}
}
43 changes: 37 additions & 6 deletions algorithms/src/crypto_hash/poseidon.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,14 +219,17 @@ impl<F: PrimeField, const RATE: usize> PoseidonSponge<F, RATE, 1> {

#[inline]
fn apply_s_box(&mut self, is_full_round: bool) {
let pow_alpha_in_place: fn(&mut F) = match self.parameters.alpha {
3 => pow_3_in_place,
5 => pow_5_in_place,
17 => pow_17_in_place,
alpha => panic!("No optimized S-box for alpha = {alpha}"),
};

if is_full_round {
// Full rounds apply the S Box (x^alpha) to every element of state
for elem in self.state.iter_mut() {
*elem = elem.pow([self.parameters.alpha]);
}
self.state.iter_mut().for_each(pow_alpha_in_place);
} else {
// Partial rounds apply the S Box (x^alpha) to just the first element of state
self.state[0] = self.state[0].pow([self.parameters.alpha]);
pow_alpha_in_place(&mut self.state[0]);
}
}

Expand Down Expand Up @@ -501,3 +504,31 @@ impl<F: PrimeField, const RATE: usize> PoseidonSponge<F, RATE, 1> {
dest_elements
}
}

// S-box functions to raise state elements to the values of alpha present in the
// codebase: 3, 5 and 17. These are more performant than the general pow
// function.
#[inline]
fn pow_3_in_place<F: PrimeField>(val: &mut F) {
let val_copy = *val;
val.square_in_place();
val.mul_assign(val_copy);
}

#[inline]
fn pow_5_in_place<F: PrimeField>(val: &mut F) {
let val_copy = *val;
val.square_in_place();
val.square_in_place();
val.mul_assign(val_copy);
}

#[inline]
fn pow_17_in_place<F: PrimeField>(val: &mut F) {
let val_copy = *val;
val.square_in_place();
val.square_in_place();
val.square_in_place();
val.square_in_place();
val.mul_assign(val_copy);
}
Loading