Skip to content
Open
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
aca9049
cargo.toml update
milosdjurica Feb 10, 2025
42e7341
Merge branch 'microsoft:main' into no_std_latest
milosdjurica Feb 10, 2025
9486641
Merge branch 'microsoft:main' into no_std_latest
milosdjurica Mar 3, 2025
3aaff41
halo2curves only on STD, prelude crate
milosdjurica Feb 14, 2025
04ee879
no_std pasta curves impl
milosdjurica Feb 18, 2025
0628d50
no_std pasta_curves impl + comments in cargo.toml
milosdjurica Mar 3, 2025
b7782a0
bincode and byteorder fix, added comments for later, TODO delete comm…
milosdjurica Mar 3, 2025
3cb4f0f
bincode fix
milosdjurica Mar 3, 2025
670a842
rayon paralel only for STD, sequential for NO_STD
milosdjurica Mar 4, 2025
96fb896
Prelude changes (#11)
milosdjurica Mar 4, 2025
4aa9ebd
Merge branch 'microsoft:main' into no_std_latest
milosdjurica Mar 4, 2025
c81aecb
getrandom optional for wasm32
milosdjurica Mar 4, 2025
c49fa4f
once_cell from core for no_std and allow unused imports for FloatCore
milosdjurica Mar 11, 2025
577e8d5
removed comments
milosdjurica Mar 11, 2025
30ddc84
moving core::cell:OnceCell into prelude
milosdjurica Mar 11, 2025
bff6b4d
Merge main (#12)
milosdjurica Mar 13, 2025
31b310c
Merge branch 'main' into no_std_latest
milosdjurica Mar 13, 2025
13448ee
comment formatting
milosdjurica Mar 13, 2025
b51c4cb
removed cs and vk
milosdjurica Mar 13, 2025
46866bf
removed comments
milosdjurica Mar 14, 2025
58b6783
maybe_rayon (#13)
milosdjurica Mar 14, 2025
db76abf
merge main
milosdjurica Mar 14, 2025
ec35376
no_std job
milosdjurica Mar 14, 2025
84059a1
clippy fix
milosdjurica Mar 14, 2025
90679f9
std::marker::PhantomData -> core::marker::PhantomData
milosdjurica Mar 14, 2025
5804c19
OnceCell instead of Option
milosdjurica Mar 17, 2025
339994b
clippy-no-default-features workflow & clippy fix
milosdjurica Mar 17, 2025
b108ab8
removed comments
milosdjurica Mar 17, 2025
6a91fba
write_bytes -> to_bytes
milosdjurica Mar 18, 2025
e87ed1d
result -> digest
milosdjurica Mar 18, 2025
5b26ac4
removed comments, updated imports from std to core
milosdjurica Mar 18, 2025
6d4c959
both STD and NO_STD using same msm
milosdjurica Mar 18, 2025
ae8a034
merge main
milosdjurica Mar 20, 2025
27a4de4
merge main
milosdjurica Mar 21, 2025
b45a5db
Changed from pasta_curves in no_std to halo2curves for both (#14)
milosdjurica Mar 24, 2025
402073b
removed chacha random number
milosdjurica Mar 25, 2025
3514046
merge main
milosdjurica Mar 26, 2025
72b4521
merge main
milosdjurica Apr 3, 2025
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
66 changes: 46 additions & 20 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,28 +12,52 @@ keywords = ["zkSNARKs", "cryptography", "proofs"]
rust-version = "1.79.0"

[dependencies]
ff = { version = "0.13.0", features = ["derive"] }
digest = "0.10"
sha3 = "0.10"
rayon = "1.10"
ff = { version = "0.13.0", features = [
"derive",
"bits",
], default-features = false }
digest = { version = "0.10", default-features = false }
sha3 = { version = "0.10", default-features = false }

rand_core = { version = "0.6", default-features = false }
rand_chacha = "0.3"
subtle = "2.6.1"
halo2curves = { version = "0.8.0", features = ["bits", "derive_serde"] }
generic-array = "1.2.0"
num-bigint = { version = "0.4.6", features = ["serde", "rand"] }
num-traits = "0.2.19"
num-integer = "0.1.46"
serde = { version = "1.0.217", features = ["derive"] }
bincode = "1.3"
bitvec = "1.0"
byteorder = "1.4.3"
thiserror = "2.0.11"
once_cell = "1.18.0"
itertools = "0.14.0"
rand_chacha = { version = "0.3", default-features = false }
subtle = { version = "2.6.1", default-features = false }

generic-array = { version = "1.2.0", default-features = false }
num-bigint = { version = "0.4.6", features = [
"serde",
"rand",
], default-features = false }
num-traits = { version = "0.2.19", default-features = false }
num-integer = { version = "0.1.46", default-features = false }
serde = { version = "1.0.217", features = ["derive"], default-features = false }
bincode = { version = "2.0.0-rc.3", default-features = false, features = [
"alloc",
"derive",
"serde",
] }
bitvec = { version = "1.0", default-features = false }
thiserror = { version = "2.0.11", default-features = false }
itertools = { version = "0.14.0", default-features = false }
pasta_curves = { version = "0.5", features = ["repr-c", "serde"] }
libm = { version = "0.2.11", default-features = false }
hashbrown = { version = "0.15.2" }


# ! OPTIONAL
rayon = { version = "1.10", optional = true, default-features = false }
halo2curves = { version = "0.8.0", features = [
"bits",
"derive_serde",
], optional = true }
once_cell = { version = "1.18.0", optional = true }
byteorder = { version = "1.4.3", optional = true }


[target.'cfg(target_arch = "wasm32")'.dependencies]
getrandom = { version = "0.2.15", default-features = false, features = ["js"] }
getrandom = { version = "0.2.15", default-features = false, features = [
"js",
], optional = true }

[dev-dependencies]
criterion = { version = "0.5", features = ["html_reports"] }
Expand Down Expand Up @@ -63,6 +87,8 @@ name = "ppsnark"
harness = false

[features]
default = ["halo2curves/asm"]
default = ["std"]
Comment thread
srinathsetty marked this conversation as resolved.
std = ["halo2curves", "rayon", "getrandom", "once_cell", "byteorder"]
asm = ["halo2curves/asm"]
flamegraph = ["pprof2/flamegraph", "pprof2/criterion"]
experimental = []
8 changes: 4 additions & 4 deletions examples/and.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
//! This example executes a batch of 64-bit AND operations.
//! It performs the AND operation by first decomposing the operands into bits and then performing the operation bit-by-bit.
//! We execute a configurable number of AND operations per step of Nova's recursion.
use bincode::config::legacy;
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Is there an alternative to this? May be I'm misunderstanding this. Are we at the risk of this getting deprecated soon?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

No, there is no risk of getting deprecated. legacy() is just a function to get same encoding configuration that was used in v1.x.x . I used legacy() over standard, just in order to make it backward compatible with previous version of Nova.
Here is the bincode code explaining the difference between the standard() and legacy() functions.

https://github.com/bincode-org/bincode/blob/508a00bb17fda81da1ad2b3856669c27b7fe5dbd/src/config.rs#L52

/// The default config for bincode 2.0. By default this will be:
/// - Little endian
/// - Variable int encoding
pub const fn standard() -> Configuration {
    generate()
}

/// Creates the "legacy" default config. This is the default config that was present in bincode 1.0
/// - Little endian
/// - Fixed int length encoding
pub const fn legacy() -> Configuration<LittleEndian, Fixint, NoLimit> {
    generate()
}

Copy link
Copy Markdown
Collaborator

@srinathsetty srinathsetty Mar 17, 2025

Choose a reason for hiding this comment

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

Thanks for the clarification! I'm not sure there's benefit from being backward compatible with a previous version of Nova. If we are updating to bincode 2.0, I think it makes sense to have the bincode 2.0 format.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

It is possible to have standard config too, it will require some additional changes in tests too, but it should be feasible. Let me know which one you prefer

use core::marker::PhantomData;
use ff::{Field, PrimeField, PrimeFieldBits};
use flate2::{write::ZlibEncoder, Compression};
use nova_snark::{
frontend::{
num::AllocatedNum, AllocatedBit, ConstraintSystem, LinearCombination, SynthesisError,
Expand Down Expand Up @@ -286,9 +286,9 @@ fn main() {
assert!(res.is_ok());
let compressed_snark = res.unwrap();

let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

It seems like we are removing flate2 compression. Is there a reason?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Yes, due to the changes in bincode . If we use bincode::serde::::encode_to_vec() , that function accepts only 2 arguments -> what we are encoding (compressed_snark in this case), and bincode configuration.

If we use bincode::serde::encode_into_writter() , this function accepts 3 arguments -> what we are encoding, "writer", and bincode configuration. However, writer has to implement bincode::Writer trait, and this requirement is not satisfied.

bincode::serialize_into(&mut encoder, &compressed_snark).unwrap();
let compressed_snark_encoded = encoder.finish().unwrap();
// let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
Comment thread
milosdjurica marked this conversation as resolved.
Outdated
let compressed_snark_encoded =
bincode::serde::encode_to_vec(&compressed_snark, legacy()).unwrap();
println!(
"CompressedSNARK::len {:?} bytes",
compressed_snark_encoded.len()
Expand Down
7 changes: 3 additions & 4 deletions examples/hashchain.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
//! This example proves the knowledge of preimage to a hash chain tail, with a configurable number of elements per hash chain node.
//! The output of each step tracks the current tail of the hash chain
use bincode::config::legacy;
use ff::Field;
use flate2::{write::ZlibEncoder, Compression};
use generic_array::typenum::U24;
use nova_snark::{
frontend::{
Expand Down Expand Up @@ -179,9 +179,8 @@ fn main() {
assert!(res.is_ok());
let compressed_snark = res.unwrap();

let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
bincode::serialize_into(&mut encoder, &compressed_snark).unwrap();
let compressed_snark_encoded = encoder.finish().unwrap();
let compressed_snark_encoded =
bincode::serde::encode_to_vec(&compressed_snark, legacy()).unwrap();
println!(
"CompressedSNARK::len {:?} bytes",
compressed_snark_encoded.len()
Expand Down
7 changes: 3 additions & 4 deletions examples/minroot.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
//! Demonstrates how to use Nova to produce a recursive proof of the correct execution of
//! iterations of the `MinRoot` function, thereby realizing a Nova-based verifiable delay function (VDF).
//! We execute a configurable number of iterations of the `MinRoot` function per step of Nova's recursion.
use bincode::config::legacy;
use ff::Field;
use flate2::{write::ZlibEncoder, Compression};
use nova_snark::{
frontend::{num::AllocatedNum, ConstraintSystem, SynthesisError},
nova::{CompressedSNARK, PublicParams, RecursiveSNARK},
Expand Down Expand Up @@ -248,9 +248,8 @@ fn main() {
assert!(res.is_ok());
let compressed_snark = res.unwrap();

let mut encoder = ZlibEncoder::new(Vec::new(), Compression::default());
bincode::serialize_into(&mut encoder, &compressed_snark).unwrap();
let compressed_snark_encoded = encoder.finish().unwrap();
let compressed_snark_encoded =
bincode::serde::encode_to_vec(&compressed_snark, legacy()).unwrap();
println!(
"CompressedSNARK::len {:?} bytes",
compressed_snark_encoded.len()
Expand Down
85 changes: 38 additions & 47 deletions src/digest.rs
Original file line number Diff line number Diff line change
@@ -1,29 +1,28 @@
use crate::constants::NUM_HASH_BITS;
use bincode::Options;
#[cfg(not(feature = "std"))]
use crate::prelude::*;
use crate::{constants::NUM_HASH_BITS, errors::NovaError};
use bincode::config::legacy;
use ff::PrimeField;
use serde::Serialize;
use sha3::{Digest, Sha3_256};
use std::{io, marker::PhantomData};
#[cfg(feature = "std")]
use std::marker::PhantomData;

/// Trait for components with potentially discrete digests to be included in their container's digest.
pub trait Digestible {
/// Write the byte representation of Self in a byte buffer
Comment thread
milosdjurica marked this conversation as resolved.
Outdated
fn write_bytes<W: Sized + io::Write>(&self, byte_sink: &mut W) -> Result<(), io::Error>;
fn write_bytes(&self) -> Result<Vec<u8>, NovaError>;
}

/// Marker trait to be implemented for types that implement `Digestible` and `Serialize`.
/// Their instances will be serialized to bytes then digested.
pub trait SimpleDigestible: Serialize {}

impl<T: SimpleDigestible> Digestible for T {
fn write_bytes<W: Sized + io::Write>(&self, byte_sink: &mut W) -> Result<(), io::Error> {
let config = bincode::DefaultOptions::new()
.with_little_endian()
.with_fixint_encoding();
// Note: bincode recursively length-prefixes every field!
config
.serialize_into(byte_sink, self)
.map_err(|e| io::Error::new(io::ErrorKind::InvalidData, e))
fn write_bytes(&self) -> Result<Vec<u8>, NovaError> {
bincode::serde::encode_to_vec(self, legacy()).map_err(|e| NovaError::DigestError {
reason: e.to_string(),
})
}
}

Expand All @@ -45,15 +44,15 @@ impl<'a, F: PrimeField, T: Digestible> DigestComputer<'a, F, T> {
});

// turn the bit vector into a scalar
let mut digest = F::ZERO;
Comment thread
milosdjurica marked this conversation as resolved.
let mut result = F::ZERO;
let mut coeff = F::ONE;
for bit in bv {
if bit {
digest += coeff;
result += coeff;
}
coeff += coeff;
}
digest
result
}

/// Create a new `DigestComputer`
Expand All @@ -65,13 +64,15 @@ impl<'a, F: PrimeField, T: Digestible> DigestComputer<'a, F, T> {
}

/// Compute the digest of a `Digestible` instance.
pub fn digest(&self) -> Result<F, io::Error> {
pub fn digest(&self) -> Result<F, core::fmt::Error> {
let bytes = self.inner.write_bytes().expect("Serialization error");

let mut hasher = Self::hasher();
self
.inner
.write_bytes(&mut hasher)
.expect("Serialization error");
let bytes: [u8; 32] = hasher.finalize().into();
hasher.update(&bytes);
let final_bytes = hasher.finalize();
let bytes: Vec<u8> = final_bytes.to_vec();

// Now map to the field or handle it as necessary
Ok(Self::map_to_field(&bytes))
}
}
Expand All @@ -80,47 +81,38 @@ impl<'a, F: PrimeField, T: Digestible> DigestComputer<'a, F, T> {
mod tests {
use super::{DigestComputer, SimpleDigestible};
use crate::{provider::PallasEngine, traits::Engine};
use bincode::config::legacy;
use ff::Field;
use once_cell::sync::OnceCell;
use serde::{Deserialize, Serialize};

type E = PallasEngine;

#[derive(Serialize, Deserialize)]
struct S<E: Engine> {
i: usize,
#[serde(skip, default = "OnceCell::new")]
digest: OnceCell<E::Scalar>,
#[serde(skip)]
digest: Option<E::Scalar>,
Comment thread
srinathsetty marked this conversation as resolved.
Outdated
}

impl<E: Engine> SimpleDigestible for S<E> {}

impl<E: Engine> S<E> {
fn new(i: usize) -> Self {
S {
i,
digest: OnceCell::new(),
}
S { i, digest: None }
}

fn digest(&self) -> E::Scalar {
self
.digest
.get_or_try_init(|| DigestComputer::new(self).digest())
.cloned()
.unwrap()
fn digest(&mut self) -> E::Scalar {
let digest: E::Scalar = DigestComputer::new(self).digest().unwrap();
*self.digest.get_or_insert(digest)
}
}

#[test]
fn test_digest_field_not_ingested_in_computation() {
let s1 = S::<E>::new(42);

// let's set up a struct with a weird digest field to make sure the digest computation does not depend of it
let oc = OnceCell::new();
oc.set(<E as Engine>::Scalar::ONE).unwrap();

let s2: S<E> = S { i: 42, digest: oc };
let mut s2 = S::<E>::new(42);
s2.digest = Some(<E as Engine>::Scalar::ONE); // Set manually for test

assert_eq!(
DigestComputer::<<E as Engine>::Scalar, _>::new(&s1)
Expand All @@ -143,19 +135,18 @@ mod tests {

#[test]
fn test_digest_impervious_to_serialization() {
let good_s = S::<E>::new(42);

// let's set up a struct with a weird digest field to confuse deserializers
let oc = OnceCell::new();
oc.set(<E as Engine>::Scalar::ONE).unwrap();
let mut good_s = S::<E>::new(42);
let mut bad_s = S::<E>::new(42);
bad_s.digest = Some(<E as Engine>::Scalar::ONE); // Set manually for test

let bad_s: S<E> = S { i: 42, digest: oc };
// this justifies the adjective "bad"
assert_ne!(good_s.digest(), bad_s.digest());

let naughty_bytes = bincode::serialize(&bad_s).unwrap();
let naughty_bytes = bincode::serde::encode_to_vec(&bad_s, legacy()).unwrap();
let mut retrieved_s: S<E> = bincode::serde::decode_from_slice(&naughty_bytes, legacy())
.unwrap()
.0;

let retrieved_s: S<E> = bincode::deserialize(&naughty_bytes).unwrap();
assert_eq!(good_s.digest(), retrieved_s.digest())
}
}
7 changes: 6 additions & 1 deletion src/errors.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
//! This module defines errors returned by the library.
use crate::frontend::SynthesisError;
#[cfg(not(feature = "std"))]
use crate::prelude::*;
use core::fmt::Debug;
use thiserror::Error;

Expand Down Expand Up @@ -70,7 +72,10 @@ pub enum NovaError {
},
/// returned when there is an error creating a digest
#[error("DigestError")]
DigestError,
DigestError {
/// The reason for digest creation failure
reason: String,
},
/// returned when the prover cannot prove the provided statement due to completeness error
#[error("InternalError")]
InternalError,
Expand Down
12 changes: 5 additions & 7 deletions src/frontend/constraint_system.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
use std::{io, marker::PhantomData};

use ff::PrimeField;

use super::lc::{Index, LinearCombination, Variable};
#[cfg(not(feature = "std"))]
use crate::prelude::*;
use ff::PrimeField;
#[cfg(feature = "std")]
use std::marker::PhantomData;
Comment thread
milosdjurica marked this conversation as resolved.
Outdated

/// Computations are expressed in terms of arithmetic circuits, in particular
/// rank-1 quadratic constraint systems. The `Circuit` trait represents a
Expand Down Expand Up @@ -33,9 +34,6 @@ pub enum SynthesisError {
/// During proof generation, we encountered an identity in the CRS
#[error("encountered an identity element in the CRS")]
UnexpectedIdentity,
/// During proof generation, we encountered an I/O error with the CRS
#[error("encountered an I/O error: {0}")]
IoError(#[from] io::Error),
/// During verification, our verifying key was malformed.
#[error("malformed verifying key")]
MalformedVerifyingKey,
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/gadgets/boolean.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
//! Gadgets for allocating bits in the circuit and performing boolean logic.

use ff::{PrimeField, PrimeFieldBits};

use crate::frontend::{ConstraintSystem, LinearCombination, SynthesisError, Variable};
#[cfg(not(feature = "std"))]
use crate::prelude::*;
use ff::{PrimeField, PrimeFieldBits};

/// Represents a variable in the constraint system which is guaranteed
/// to be either zero or one.
Expand Down
5 changes: 3 additions & 2 deletions src/frontend/gadgets/multieq.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use ff::PrimeField;

use crate::frontend::{ConstraintSystem, LinearCombination, SynthesisError, Variable};
#[cfg(not(feature = "std"))]
use crate::prelude::*;
use ff::PrimeField;

#[derive(Debug)]
pub struct MultiEq<Scalar: PrimeField, CS: ConstraintSystem<Scalar>> {
Expand Down
10 changes: 6 additions & 4 deletions src/frontend/gadgets/num.rs
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
//! Gadgets representing numbers in the scalar field of the underlying curve.

use crate::frontend::{
gadgets::boolean::{self, AllocatedBit, Boolean},
ConstraintSystem, LinearCombination, SynthesisError, Variable,
};
#[cfg(not(feature = "std"))]
use crate::prelude::*;
use ff::{PrimeField, PrimeFieldBits};
use serde::{Deserialize, Serialize};

use crate::frontend::{ConstraintSystem, LinearCombination, SynthesisError, Variable};

use crate::frontend::gadgets::boolean::{self, AllocatedBit, Boolean};

/// Represents an allocated number in the circuit.
#[derive(Debug, Serialize, Deserialize)]
pub struct AllocatedNum<Scalar: PrimeField> {
Expand Down
Loading