Skip to content
Open
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
2 changes: 2 additions & 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 @@ -36,6 +36,7 @@ divan = "0.1.21"
flock-core = { git = "https://github.com/succinctlabs/flock.git", rev = "879072249e52b8b9054bf0c6a034cec20f8f6fc7" }
num-bigint = "0.4"
num-traits = "0.2"
pastey = "0.2.3"
proptest = "1.11.0"
rand = "0.10"
rand_chacha = "0.10"
Expand Down
40 changes: 36 additions & 4 deletions crates/circuit/src/constraints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ impl<C> SparseRow<C> {
pub fn entries(&self) -> &[(usize, C)] {
&self.entries
}

/// The entries, consuming the row.
pub fn into_entries(self) -> Vec<(usize, C)> {
self.entries
}
}

/// A row-major sparse matrix.
Expand Down Expand Up @@ -84,6 +89,11 @@ impl<C> SparseMatrix<C> {
&self.rows
}

/// The rows, consuming the matrix.
pub fn into_rows(self) -> Vec<SparseRow<C>> {
self.rows
}

/// Number of rows.
pub fn row_count(&self) -> usize {
self.rows.len()
Expand All @@ -96,7 +106,7 @@ impl<C> SparseMatrix<C> {
}

impl<C: Send + Sync> SparseMatrix<C> {
fn map_values_with<D, M>(self, map: M) -> SparseMatrix<D>
fn map_values<D, M>(self, map: M) -> SparseMatrix<D>
where
D: Send + Sync,
M: Fn(C) -> D + Send + Sync,
Expand All @@ -116,6 +126,28 @@ impl<C: Send + Sync> SparseMatrix<C> {
columns: self.columns,
}
}

/// Maps every coefficient, keeping this matrix.
pub fn map_values_ref<D, M>(&self, map: M) -> SparseMatrix<D>
where
D: Send + Sync,
M: Fn(&C) -> D + Send + Sync,
{
SparseMatrix {
rows: self
.rows
.par_iter()
.map(|row| SparseRow {
entries: row
.entries
.iter()
.map(|(column, coefficient)| (*column, map(coefficient)))
.collect(),
})
.collect(),
columns: self.columns,
}
}
}

/// Why a sparse row representation is malformed.
Expand Down Expand Up @@ -279,9 +311,9 @@ impl<R: Send + Sync> ConstraintMatrices<R> {
{
ConstraintMatrices {
m: self.m,
a: self.a.map_values_with(&map),
b: self.b.map_values_with(&map),
c: self.c.map_values_with(&map),
a: self.a.map_values(&map),
b: self.b.map_values(&map),
c: self.c.map_values(&map),
}
}
}
Expand Down
94 changes: 85 additions & 9 deletions crates/circuit/src/matrix_products.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

use crate::witgen::Z as Integer;
use crate::{BitWidth, IntoWords};
use common::BitzConstraintRing;
use num_traits::{One, Zero};
use rayon::prelude::*;
use std::cmp::Ordering;
Expand Down Expand Up @@ -242,6 +243,7 @@ fn add_mod_words<const LIMBS: usize>(
/// A dense vector of canonical runtime-field elements.
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ModularVector<const PRIME_LIMBS: usize> {
/// Vector of canonical field element in little-endian limbs form, in row order.
values: Vec<[u64; PRIME_LIMBS]>,
}

Expand All @@ -256,7 +258,7 @@ impl<const PRIME_LIMBS: usize> ModularVector<PRIME_LIMBS> {
self.values.is_empty()
}

/// Dense canonical field elements in row order.
/// Slice of canonical field element in little-endian limbs form, in row order.
pub fn values(&self) -> &[[u64; PRIME_LIMBS]] {
&self.values
}
Expand All @@ -267,12 +269,13 @@ impl<const PRIME_LIMBS: usize> ModularVector<PRIME_LIMBS> {
}
}

impl From<&ModularVector<2>> for Vec<field::FqDefault> {
/// Converts two little-endian limbs, treating them as one `u128`.
impl<T: From<u128>> From<&ModularVector<2>> for Vec<T> {
fn from(values: &ModularVector<2>) -> Self {
values
.values()
.iter()
.map(|&[low, high]| field::FqDefault::from_limbs(low, high))
.map(|&[low, high]| T::from(u128::from(low) | (u128::from(high) << 64)))
.collect()
}
}
Expand Down Expand Up @@ -354,6 +357,23 @@ impl From<&num_bigint::BigInt> for StoredInteger {
}
}

impl From<&StoredInteger> for num_bigint::BigInt {
fn from(value: &StoredInteger) -> Self {
let bytes = value
.words()
.iter()
.flat_map(|word| word.to_le_bytes())
.collect::<Vec<_>>();
Self::from_signed_bytes_le(&bytes)
}
}

impl From<&i128> for StoredInteger {
fn from(value: &i128) -> Self {
Self::from(&Integer::<2>::from(*value))
}
}

impl<const LIMBS: usize> From<&Integer<LIMBS>> for StoredInteger {
/// Stores a gadget-local fixed integer without changing its value.
fn from(value: &Integer<LIMBS>) -> Self {
Expand Down Expand Up @@ -401,6 +421,22 @@ impl IntegerProducts {
self.c_mw.push(StoredInteger::from(&c));
}

/// Whether `A(Mw) * B(Mw) = C(Mw)` holds row by row over the integers
/// (hence modulo every prime).
pub fn is_satisfied<R>(&self) -> bool
where
R: BitzConstraintRing + for<'a> From<&'a StoredInteger>,
{
self.a_mw.len() == self.b_mw.len()
&& self.a_mw.len() == self.c_mw.len()
&& self
.a_mw
.par_iter()
.zip(&self.b_mw)
.zip(&self.c_mw)
.all(|((a, b), c)| R::from(a) * R::from(b) == R::from(c))
}

/// Reduces every materialized element modulo `modulus`.
///
/// Vectors with at least 32,768 entries use Rayon. Smaller vectors remain
Expand Down Expand Up @@ -446,12 +482,7 @@ mod tests {
type R = num_bigint::BigInt;

fn stored_ring(value: &StoredInteger) -> R {
let bytes = value
.words()
.iter()
.flat_map(|word| word.to_le_bytes())
.collect::<Vec<_>>();
R::from_signed_bytes_le(&bytes)
R::from(value)
}

fn direct_row(row: &SparseRow<R>, integer_witness: &PackedWitness) -> R {
Expand Down Expand Up @@ -540,6 +571,51 @@ mod tests {
}
}

#[test]
fn products_are_satisfied_over_the_integers_only_when_exact() {
let mut products = IntegerProducts::default();
assert!(products.is_satisfied::<R>());
let z = |value: i128| Integer::<2>::from(value);
products.push(z(-3), z(5), z(-15));
products.push(
z(i128::from(i64::MIN)),
z(i128::from(i64::MIN)),
z(1 << 126),
);
assert!(products.is_satisfied::<R>());
products.push(z(2), z(2), z(5));
assert!(!products.is_satisfied::<R>());
products.c_mw.pop();
assert!(
!products.is_satisfied::<R>(),
"a missing row is not satisfied"
);
}

#[test]
fn every_source_stores_an_integer_alike() {
for value in [
0_i128,
1,
-1,
7,
-7,
i128::from(i64::MAX),
i128::from(i64::MIN),
1 << 100,
-(1 << 100),
] {
let from_bigint = StoredInteger::from(&R::from(value));
assert_eq!(StoredInteger::from(&value), from_bigint, "{value}");
assert_eq!(
StoredInteger::from(&Integer::<2>::from(value)),
from_bigint,
"{value}"
);
assert_eq!(stored_ring(&from_bigint), R::from(value));
}
}

#[test]
fn arbitrary_bigints_round_trip_through_stored_integers() {
let boundary = R::one() << 128_usize;
Expand Down
4 changes: 2 additions & 2 deletions crates/circuit/src/witgen.rs
Original file line number Diff line number Diff line change
Expand Up @@ -612,8 +612,8 @@ impl ProductWitgen {

/// Consumes the runner into `w`, `M * w`, and exact matrix products.
pub fn into_parts(self) -> (PackedWitness, PackedWitness, IntegerProducts) {
let (witness, integer_witness) = self.witgen.into_witnesses();
(witness, integer_witness, self.products)
let (bool_witness, integer_witness) = self.witgen.into_witnesses();
(bool_witness, integer_witness, self.products)
}
}

Expand Down
7 changes: 6 additions & 1 deletion crates/common/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ pub use fold::{
Fold, FoldError, column_images, fold_column, fold_columns, reconstruct, row_images,
};
pub use opening::OpeningQuery;
pub use params::{BitZParams, ParamsError, VirtualParams, VirtualParamsError};
pub use params::{
BitZParams, MIN_PRIME_BITS, ParamsError, VirtualParams, VirtualParamsError, prime_bits,
};
pub use shape::{Shape, ShapeError};
pub use table::{BitTable, TableError, TransposeError, TransposedBitTable};
pub use virtual_map::{
Expand Down Expand Up @@ -89,6 +91,7 @@ define_blanket_trait! {
#[cfg(test)]
mod tests {
use super::*;
use field::dynamic::DynField;
use field::{F128, FqDefault};

#[test]
Expand All @@ -105,8 +108,10 @@ mod tests {
fn assert_impl_field<T: BitzField>() {}
assert_impl_field::<FqDefault>();
assert_impl_field::<F128>();
assert_impl_field::<DynField>();

fn assert_impl_claim_field<T: BitzClaimField>() {}
assert_impl_claim_field::<FqDefault>();
assert_impl_claim_field::<DynField>();
}
}
Loading
Loading