Skip to content

Commit

Permalink
examples: Transparent columns usage (part 1) (#8)
Browse files Browse the repository at this point in the history
* feat: Add example of Transparent (Constant) column usage

* example: Add example of Transparent (Powers) column usage

* example: Add example of Transparent (DisjointProduct) column usage

* example: Add example of Transparent (EqIndPartialEval) column usage
  • Loading branch information
storojs72 authored Feb 17, 2025
1 parent 891f844 commit 67418ff
Show file tree
Hide file tree
Showing 5 changed files with 326 additions and 0 deletions.
16 changes: 16 additions & 0 deletions examples/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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"

Expand Down
88 changes: 88 additions & 0 deletions examples/acc-constants.rs
Original file line number Diff line number Diff line change
@@ -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<U, F128>,
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::<F32>(oracle);
let values = oracle_witness.as_mut_slice::<u32>();
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::<U, F128>::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::<F1>(oracle).unwrap().as_slice::<u32>();

// 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::<F32>(oracle).unwrap().as_slice::<u32>();

// 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();
}
64 changes: 64 additions & 0 deletions examples/acc-disjoint-product.rs
Original file line number Diff line number Diff line change
@@ -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::<U, F128>::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::<F8>(disjoint_product_id);

let values = disjoint_product_witness.as_mut_slice::<F8>();

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();
}
81 changes: 81 additions & 0 deletions examples/acc-eq-ind-partial-eval.rs
Original file line number Diff line number Diff line change
@@ -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::<U, F128>::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::<F128>(id);

let column_values = eq_witness.as_mut_slice::<F128>();
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();
}
77 changes: 77 additions & 0 deletions examples/acc-powers.rs
Original file line number Diff line number Diff line change
@@ -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<U, F128>, 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::<F32>(transparent);
let transparent_values = transparent_witness.as_mut_slice::<F32>();
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<U, F128>, 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::<F16>(transparent);
let transparent_values = transparent_witness.as_mut_slice::<F16>();
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::<U, F128>::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();
}

0 comments on commit 67418ff

Please sign in to comment.