Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

examples: Transparent columns usage (part 1) #8

Merged
merged 5 commits into from
Feb 17, 2025
Merged
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
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();
}