diff --git a/examples/Cargo.toml b/examples/Cargo.toml index 3b309cb93..93fe2bdcb 100644 --- a/examples/Cargo.toml +++ b/examples/Cargo.toml @@ -104,6 +104,22 @@ path = "acc-repeated.rs" name = "acc-zeropadded" path = "acc-zeropadded.rs" +[[example]] +name = "acc-powers" +path = "acc-powers.rs" + +[[example]] +name = "acc-constants" +path = "acc-constants.rs" + +[[example]] +name = "acc-disjoint-product" +path = "acc-disjoint-product.rs" + +[[example]] +name = "acc-eq-ind-partial-eval" +path = "acc-eq-ind-partial-eval.rs" + [lints.clippy] needless_range_loop = "allow" diff --git a/examples/acc-constants.rs b/examples/acc-constants.rs new file mode 100644 index 000000000..0947ed107 --- /dev/null +++ b/examples/acc-constants.rs @@ -0,0 +1,88 @@ +use binius_circuits::{builder::ConstraintSystemBuilder, sha256::u32const_repeating}; +use binius_core::{ + constraint_system::validate::validate_witness, oracle::OracleId, + transparent::constant::Constant, +}; +use binius_field::{arch::OptimalUnderlier, BinaryField128b, BinaryField1b, BinaryField32b}; + +type U = OptimalUnderlier; +type F128 = BinaryField128b; +type F32 = BinaryField32b; +type F1 = BinaryField1b; + +const LOG_SIZE: usize = 4; + +// FIXME: Following gadgets are unconstrained. Only for demonstrative purpose, don't use in production + +fn constants_gadget( + name: impl ToString, + log_size: usize, + builder: &mut ConstraintSystemBuilder, + constant_value: u32, +) -> OracleId { + builder.push_namespace(name); + + let c = Constant::new(log_size, F32::new(constant_value)); + + let oracle = builder.add_transparent("constant", c).unwrap(); + + if let Some(witness) = builder.witness() { + let mut oracle_witness = witness.new_column::(oracle); + let values = oracle_witness.as_mut_slice::(); + for v in values { + *v = constant_value; + } + } + + builder.pop_namespace(); + + oracle +} + +// Transparent column can also naturally be used for storing some constants (also available for verifier). +// For example there is a 'u32const_repeating' function (in sha256 gadget) that does exactly this +// using Transparent + Repeated columns. Alternatively one can use Constant abstraction to create equivalent +// Transparent column. +fn main() { + let allocator = bumpalo::Bump::new(); + let mut builder = ConstraintSystemBuilder::::new_with_witness(&allocator); + + pub const SHA256_INIT: [u32; 8] = [ + 0x6a09e667, 0xbb67ae85, 0x3c6ef372, 0xa54ff53a, 0x510e527f, 0x9b05688c, 0x1f83d9ab, + 0x5be0cd19, + ]; + + let oracles: [OracleId; 8] = + SHA256_INIT.map(|c| u32const_repeating(LOG_SIZE, &mut builder, c, "INIT").unwrap()); + if let Some(witness) = builder.witness() { + for (index, oracle) in oracles.into_iter().enumerate() { + let values = witness.get::(oracle).unwrap().as_slice::(); + + // every value in the column should match the expected one + for value in values { + assert_eq!(*value, SHA256_INIT[index]); + } + } + } + + let oracles: [OracleId; 8] = + SHA256_INIT.map(|c| constants_gadget("constants_gadget", LOG_SIZE, &mut builder, c)); + if let Some(witness) = builder.witness() { + for (index, oracle) in oracles.into_iter().enumerate() { + // The difference is here. With Constant we have to operate over F32, while + // with Transparent + Repeated approach as in 'u32const_repeating' we operate over F1, + // which can be more convenient in the bit-oriented computations + let values = witness.get::(oracle).unwrap().as_slice::(); + + // every value in the column should match the expected one + for value in values { + assert_eq!(*value, SHA256_INIT[index]); + } + } + } + + let witness = builder.take_witness().unwrap(); + let constraints_system = builder.build().unwrap(); + + validate_witness(&constraints_system, &[], &witness).unwrap(); +} diff --git a/examples/acc-disjoint-product.rs b/examples/acc-disjoint-product.rs new file mode 100644 index 000000000..05f4b59d9 --- /dev/null +++ b/examples/acc-disjoint-product.rs @@ -0,0 +1,64 @@ +use binius_circuits::builder::ConstraintSystemBuilder; +use binius_core::{ + constraint_system::validate::validate_witness, + transparent::{constant::Constant, disjoint_product::DisjointProduct, powers::Powers}, +}; +use binius_field::{ + arch::OptimalUnderlier, BinaryField, BinaryField128b, BinaryField8b, PackedField, +}; + +type U = OptimalUnderlier; +type F128 = BinaryField128b; +type F8 = BinaryField8b; + +const LOG_SIZE: usize = 4; + +// FIXME: Following gadgets are unconstrained. Only for demonstrative purpose, don't use in production + +// DisjointProduct can be used for creating some more elaborated regularities over public data. +// In the following example we have a Transparent column with DisjointProduct instantiated over Powers +// and Constant. In this regularity, the DisjointProduct would be represented as a following expression: +// +// [ c * F8(x)^0, c * F8(x)^1, c * F8(x)^2, ... c * F8(x)^(2^LOG_SIZE) ], +// +// where +// 'x' is a multiplicative generator - a public value that exists for every BinaryField, +// 'c' is some (F8) constant. +// +// Also note, that DisjointProduct makes eventual Transparent column to have height (n_vars) which is sum +// of heights (n_vars) of Powers and Constant, so actual data could be repeated multiple times +fn main() { + let allocator = bumpalo::Bump::new(); + let mut builder = ConstraintSystemBuilder::::new_with_witness(&allocator); + + let generator = F8::MULTIPLICATIVE_GENERATOR; + let powers = Powers::new(LOG_SIZE, generator.into()); + + let constant_value = F8::new(0xf0); + let constant = Constant::new(LOG_SIZE, constant_value); + + let disjoint_product = DisjointProduct(powers, constant); + let disjoint_product_id = builder + .add_transparent("disjoint_product", disjoint_product) + .unwrap(); + + if let Some(witness) = builder.witness() { + let mut disjoint_product_witness = witness.new_column::(disjoint_product_id); + + let values = disjoint_product_witness.as_mut_slice::(); + + let mut exponent = 0u64; + for val in values.iter_mut() { + if exponent == 2u64.pow(LOG_SIZE as u32) { + exponent = 0; + } + *val = generator.pow(exponent) * constant_value; + exponent += 1; + } + } + + let witness = builder.take_witness().unwrap(); + let constraints_system = builder.build().unwrap(); + + validate_witness(&constraints_system, &[], &witness).unwrap(); +} diff --git a/examples/acc-eq-ind-partial-eval.rs b/examples/acc-eq-ind-partial-eval.rs new file mode 100644 index 000000000..7fcdc4b54 --- /dev/null +++ b/examples/acc-eq-ind-partial-eval.rs @@ -0,0 +1,81 @@ +use binius_circuits::builder::ConstraintSystemBuilder; +use binius_core::{ + constraint_system::validate::validate_witness, transparent::eq_ind::EqIndPartialEval, +}; +use binius_field::{arch::OptimalUnderlier, BinaryField128b, PackedField}; + +type U = OptimalUnderlier; +type F128 = BinaryField128b; + +const LOG_SIZE: usize = 3; + +// FIXME: Following gadgets are unconstrained. Only for demonstrative purpose, don't use in production + +// Currently, it is hard for me to imagine some real world use-cases where Transparent column specified by +// EqIndPartialEval could be useful. The program can use some of its data as challenges and the Transparent +// column with EqIndPartialEval will expect witness values defined as following: +// +// x_i * y_i + (1 - x_i) * (1 - y_i) +// +// where 'x_i' is an element from a particular row of basis matrix, and y_i is a given challenge. +// +fn main() { + let allocator = bumpalo::Bump::new(); + let mut builder = ConstraintSystemBuilder::::new_with_witness(&allocator); + + // A truth table [000, 001, 010, 011 ... 111] where each row is in reversed order + let rev_basis = [ + vec![0, 0, 0], + vec![1, 0, 0], + vec![0, 1, 0], + vec![1, 1, 0], + vec![0, 0, 1], + vec![1, 0, 1], + vec![0, 1, 1], + vec![1, 1, 1], + ]; + + // rev_basis size correlates with LOG_SIZE + assert_eq!(1 << LOG_SIZE, rev_basis.len()); + + // let's choose some random challenges (each not greater than 1 << LOG_SIZE bits for this example) + let challenges = vec![F128::from(110), F128::from(190), F128::from(200)]; + + // challenges size correlates with LOG_SIZE + assert_eq!(challenges.len(), LOG_SIZE); + + let eq_ind_partial_eval = EqIndPartialEval::new(LOG_SIZE, challenges.clone()).unwrap(); + + let id = builder + .add_transparent("eq_ind_partial_eval", eq_ind_partial_eval) + .unwrap(); + + if let Some(witness) = builder.witness() { + let mut eq_witness = witness.new_column::(id); + + let column_values = eq_witness.as_mut_slice::(); + assert_eq!(column_values.len(), 1 << LOG_SIZE); + + let one = F128::one(); + + for (inv_basis_item, val) in rev_basis.iter().zip(column_values.iter_mut()) { + let mut value = F128::one(); + inv_basis_item + .iter() + .zip(challenges.iter()) + .for_each(|(x, y)| { + let x = F128::new(*x); + let y = *y; + + // following expression is defined in the EqIndPartialEval implementation + value *= x * y + (one - x) * (one - y); + }); + *val = value; + } + } + + let witness = builder.take_witness().unwrap(); + let constraints_system = builder.build().unwrap(); + + validate_witness(&constraints_system, &[], &witness).unwrap(); +} diff --git a/examples/acc-powers.rs b/examples/acc-powers.rs new file mode 100644 index 000000000..159aa1136 --- /dev/null +++ b/examples/acc-powers.rs @@ -0,0 +1,77 @@ +use binius_circuits::builder::ConstraintSystemBuilder; +use binius_core::constraint_system::validate::validate_witness; +use binius_field::{ + arch::OptimalUnderlier, BinaryField, BinaryField128b, BinaryField16b, BinaryField32b, + PackedField, +}; + +type U = OptimalUnderlier; +type F128 = BinaryField128b; +type F32 = BinaryField32b; +type F16 = BinaryField16b; + +const LOG_SIZE: usize = 3; + +// FIXME: Following gadgets are unconstrained. Only for demonstrative purpose, don't use in production + +// Values for the Transparent columns are known to verifier, so they can be used for storing non-private data +// (like constants for example). The following gadget demonstrates how to use Powers abstraction to build a +// Transparent column that keeps following values (we write them during witness population): +// +// [ F32(x)^0, F32(x)^1 , F32(x)^2, ... F32(x)^(2^LOG_SIZE) ], + +// where 'x' is a multiplicative generator - a public value that exists for every BinaryField +// +fn powers_gadget_f32(builder: &mut ConstraintSystemBuilder, name: impl ToString) { + builder.push_namespace(name); + + let generator = F32::MULTIPLICATIVE_GENERATOR; + let powers = binius_core::transparent::powers::Powers::new(LOG_SIZE, generator.into()); + let transparent = builder + .add_transparent("Powers of F32 gen", powers) + .unwrap(); + + if let Some(witness) = builder.witness() { + let mut transparent_witness = witness.new_column::(transparent); + let transparent_values = transparent_witness.as_mut_slice::(); + for (exp, val) in transparent_values.iter_mut().enumerate() { + *val = generator.pow(exp as u64); + } + } + + builder.pop_namespace(); +} + +// Only Field is being changed +fn powers_gadget_f16(builder: &mut ConstraintSystemBuilder, name: impl ToString) { + builder.push_namespace(name); + + let generator = F16::MULTIPLICATIVE_GENERATOR; + let powers = binius_core::transparent::powers::Powers::new(LOG_SIZE, generator.into()); + let transparent = builder + .add_transparent("Powers of F16 gen", powers) + .unwrap(); + + if let Some(witness) = builder.witness() { + let mut transparent_witness = witness.new_column::(transparent); + let transparent_values = transparent_witness.as_mut_slice::(); + for (exp, val) in transparent_values.iter_mut().enumerate() { + *val = generator.pow(exp as u64); + } + } + + builder.pop_namespace(); +} + +fn main() { + let allocator = bumpalo::Bump::new(); + let mut builder = ConstraintSystemBuilder::::new_with_witness(&allocator); + + powers_gadget_f16(&mut builder, "f16"); + powers_gadget_f32(&mut builder, "f32"); + + let witness = builder.take_witness().unwrap(); + let constraints_system = builder.build().unwrap(); + + validate_witness(&constraints_system, &[], &witness).unwrap(); +}