Merge pull request #598 from zcash/circuit-value-type

Introduce `halo2_proofs::circuit::Value`
This commit is contained in:
str4d
2022-06-13 16:41:26 +01:00
committed by GitHub
60 changed files with 2067 additions and 1431 deletions

View File

@@ -6,6 +6,48 @@ and this project adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `halo2_gadgets::utilities::RangeConstrained<F, Value<F>>::bitrange_of`
### Changed
All APIs that represented witnessed values as `Option<V>` now represent them as
`halo2_proofs::circuit::Value<V>`. The core API changes are listed below.
- The following APIs now take `Value<_>` instead of `Option<_>`:
- `halo2_gadgets::ecc`:
- `EccInstructions::{witness_point, witness_point_non_id}`
- `EccInstructions::{witness_scalar_var, witness_scalar_fixed}`
- `ScalarVar::new`
- `ScalarFixed::new`
- `NonIdentityPoint::new`
- `Point::new`
- `halo2_gadgets::sinsemilla`:
- `SinsemillaInstructions::witness_message_piece`
- `MessagePiece::{from_field_elem, from_subpieces}`
- `halo2_gadgets::sinsemilla::merkle`:
- `MerklePath::construct`
- `halo2_gadgets::utilities`:
- `UtilitiesInstructions::load_private`
- `RangeConstrained::witness_short`
- `halo2_gadgets::utilities::cond_swap`:
- `CondSwapInstructions::swap`
- `halo2_gadgets::utilities::decompose_running_sum`:
- `RunningSumConfig::witness_decompose`
- `halo2_gadgets::utilities::lookup_range_check`:
- `LookupRangeCheckConfig::{witness_check, witness_short_check}`
- The following APIs now return `Value<_>` instead of `Option<_>`:
- `halo2_gadgets::ecc::chip`:
- `EccPoint::{point, is_identity}`
- `NonIdentityEccPoint::point`
- `halo2_gadgets::utilities`:
- `FieldValue::value`
- `Var::value`
- `RangeConstrained::value`
- `halo2_gadgets::sha256::BlockWord` is now a newtype wrapper around
`Value<u32>` instead of `Option<u32>`.
### Removed
- `halo2_gadgets::utilities::RangeConstrained<F, Option<F>>::bitrange_of`
## [0.1.0] - 2022-05-10
### Added

View File

@@ -1,6 +1,6 @@
use ff::Field;
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
pasta::Fp,
plonk::{
create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Circuit, Column,
@@ -26,10 +26,10 @@ struct HashCircuit<S, const WIDTH: usize, const RATE: usize, const L: usize>
where
S: Spec<Fp, WIDTH, RATE> + Clone + Copy,
{
message: Option<[Fp; L]>,
message: Value<[Fp; L]>,
// For the purpose of this test, witness the result.
// TODO: Move this into an instance column.
output: Option<Fp>,
output: Value<Fp>,
_spec: PhantomData<S>,
}
@@ -49,8 +49,8 @@ where
fn without_witnesses(&self) -> Self {
Self {
message: None,
output: None,
message: Value::unknown(),
output: Value::unknown(),
_spec: PhantomData,
}
}
@@ -92,7 +92,7 @@ where
|| format!("load message_{}", i),
config.input[i],
0,
|| value.ok_or(Error::Synthesis),
|| value,
)
};
@@ -110,12 +110,8 @@ where
layouter.assign_region(
|| "constrain output",
|mut region| {
let expected_var = region.assign_advice(
|| "load output",
config.input[0],
0,
|| self.output.ok_or(Error::Synthesis),
)?;
let expected_var =
region.assign_advice(|| "load output", config.input[0], 0, || self.output)?;
region.constrain_equal(output.cell(), expected_var.cell())
},
)
@@ -191,8 +187,8 @@ fn bench_poseidon<S, const WIDTH: usize, const RATE: usize, const L: usize>(
let params: Params<vesta::Affine> = Params::new(K);
let empty_circuit = HashCircuit::<S, WIDTH, RATE, L> {
message: None,
output: None,
message: Value::unknown(),
output: Value::unknown(),
_spec: PhantomData,
};
@@ -212,8 +208,8 @@ fn bench_poseidon<S, const WIDTH: usize, const RATE: usize, const L: usize>(
let output = poseidon::Hash::<_, S, ConstantLength<L>, WIDTH, RATE>::init().hash(message);
let circuit = HashCircuit::<S, WIDTH, RATE, L> {
message: Some(message),
output: Some(output),
message: Value::known(message),
output: Value::known(output),
_spec: PhantomData,
};

View File

@@ -1,5 +1,5 @@
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
pasta::{pallas, EqAffine},
plonk::{
create_proof, keygen_pk, keygen_vk, verify_proof, Circuit, ConstraintSystem, Error,
@@ -47,22 +47,22 @@ fn bench(name: &str, k: u32, c: &mut Criterion) {
// Test vector: "abc"
let test_input = [
BlockWord(Some(0b01100001011000100110001110000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000011000)),
BlockWord(Value::known(0b01100001011000100110001110000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000011000)),
];
// Create a message of length 31 blocks

View File

@@ -4,7 +4,7 @@ use std::fmt::Debug;
use halo2_proofs::{
arithmetic::CurveAffine,
circuit::{Chip, Layouter},
circuit::{Chip, Layouter, Value},
plonk::Error,
};
@@ -57,7 +57,7 @@ pub trait EccInstructions<C: CurveAffine>:
fn witness_point(
&self,
layouter: &mut impl Layouter<C::Base>,
value: Option<C>,
value: Value<C>,
) -> Result<Self::Point, Error>;
/// Witnesses the given point as a private input to the circuit.
@@ -65,21 +65,21 @@ pub trait EccInstructions<C: CurveAffine>:
fn witness_point_non_id(
&self,
layouter: &mut impl Layouter<C::Base>,
value: Option<C>,
value: Value<C>,
) -> Result<Self::NonIdentityPoint, Error>;
/// Witnesses a full-width scalar to be used in variable-base multiplication.
fn witness_scalar_var(
&self,
layouter: &mut impl Layouter<C::Base>,
value: Option<C::Scalar>,
value: Value<C::Scalar>,
) -> Result<Self::ScalarVar, Error>;
/// Witnesses a full-width scalar to be used in fixed-base multiplication.
fn witness_scalar_fixed(
&self,
layouter: &mut impl Layouter<C::Base>,
value: Option<C::Scalar>,
value: Value<C::Scalar>,
) -> Result<Self::ScalarFixed, Error>;
/// Converts a magnitude and sign that exists as variables in the circuit into a
@@ -184,7 +184,7 @@ impl<C: CurveAffine, EccChip: EccInstructions<C>> ScalarVar<C, EccChip> {
pub fn new(
chip: EccChip,
mut layouter: impl Layouter<C::Base>,
value: Option<C::Scalar>,
value: Value<C::Scalar>,
) -> Result<Self, Error> {
let scalar = chip.witness_scalar_var(&mut layouter, value);
scalar.map(|inner| ScalarVar { chip, inner })
@@ -219,7 +219,7 @@ impl<C: CurveAffine, EccChip: EccInstructions<C>> ScalarFixed<C, EccChip> {
pub fn new(
chip: EccChip,
mut layouter: impl Layouter<C::Base>,
value: Option<C::Scalar>,
value: Value<C::Scalar>,
) -> Result<Self, Error> {
let scalar = chip.witness_scalar_fixed(&mut layouter, value);
scalar.map(|inner| ScalarFixed { chip, inner })
@@ -266,7 +266,7 @@ impl<C: CurveAffine, EccChip: EccInstructions<C>> NonIdentityPoint<C, EccChip> {
pub fn new(
chip: EccChip,
mut layouter: impl Layouter<C::Base>,
value: Option<C>,
value: Value<C>,
) -> Result<Self, Error> {
let point = chip.witness_point_non_id(&mut layouter, value);
point.map(|inner| NonIdentityPoint { chip, inner })
@@ -384,7 +384,7 @@ impl<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug + Eq> Point<C,
pub fn new(
chip: EccChip,
mut layouter: impl Layouter<C::Base>,
value: Option<C>,
value: Value<C>,
) -> Result<Self, Error> {
let point = chip.witness_point(&mut layouter, value);
point.map(|inner| Point { chip, inner })
@@ -581,7 +581,7 @@ pub(crate) mod tests {
use group::{prime::PrimeCurveAffine, Curve, Group};
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
@@ -784,13 +784,13 @@ pub(crate) mod tests {
let p = super::NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "P"),
Some(p_val),
Value::known(p_val),
)?;
let p_neg = -p_val;
let p_neg = super::NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "-P"),
Some(p_neg),
Value::known(p_neg),
)?;
// Generate a random non-identity point Q
@@ -798,7 +798,7 @@ pub(crate) mod tests {
let q = super::NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "Q"),
Some(q_val),
Value::known(q_val),
)?;
// Make sure P and Q are not the same point.
@@ -809,13 +809,13 @@ pub(crate) mod tests {
let _ = super::Point::new(
chip.clone(),
layouter.namespace(|| "identity"),
Some(pallas::Affine::identity()),
Value::known(pallas::Affine::identity()),
)?;
super::NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "identity"),
Some(pallas::Affine::identity()),
Value::known(pallas::Affine::identity()),
)
.expect_err("Trying to witness the identity should return an error");
}

View File

@@ -10,7 +10,7 @@ use arrayvec::ArrayVec;
use ff::PrimeField;
use group::prime::PrimeCurveAffine;
use halo2_proofs::{
circuit::{AssignedCell, Chip, Layouter},
circuit::{AssignedCell, Chip, Layouter, Value},
plonk::{Advice, Assigned, Column, ConstraintSystem, Error, Fixed},
};
use pasta_curves::{arithmetic::CurveAffine, pallas};
@@ -57,17 +57,14 @@ impl EccPoint {
}
/// Returns the value of this curve point, if known.
pub fn point(&self) -> Option<pallas::Affine> {
match (self.x.value(), self.y.value()) {
(Some(x), Some(y)) => {
if x.is_zero_vartime() && y.is_zero_vartime() {
Some(pallas::Affine::identity())
} else {
Some(pallas::Affine::from_xy(x.evaluate(), y.evaluate()).unwrap())
}
pub fn point(&self) -> Value<pallas::Affine> {
self.x.value().zip(self.y.value()).map(|(x, y)| {
if x.is_zero_vartime() && y.is_zero_vartime() {
pallas::Affine::identity()
} else {
pallas::Affine::from_xy(x.evaluate(), y.evaluate()).unwrap()
}
_ => None,
}
})
}
/// The cell containing the affine short-Weierstrass x-coordinate,
/// or 0 for the zero point.
@@ -81,7 +78,7 @@ impl EccPoint {
}
#[cfg(test)]
fn is_identity(&self) -> Option<bool> {
fn is_identity(&self) -> Value<bool> {
self.x.value().map(|x| x.is_zero_vartime())
}
}
@@ -113,14 +110,11 @@ impl NonIdentityEccPoint {
}
/// Returns the value of this curve point, if known.
pub fn point(&self) -> Option<pallas::Affine> {
match (self.x.value(), self.y.value()) {
(Some(x), Some(y)) => {
assert!(!x.is_zero_vartime() && !y.is_zero_vartime());
Some(pallas::Affine::from_xy(x.evaluate(), y.evaluate()).unwrap())
}
_ => None,
}
pub fn point(&self) -> Value<pallas::Affine> {
self.x.value().zip(self.y.value()).map(|(x, y)| {
assert!(!x.is_zero_vartime() && !y.is_zero_vartime());
pallas::Affine::from_xy(x.evaluate(), y.evaluate()).unwrap()
})
}
/// The cell containing the affine short-Weierstrass x-coordinate.
pub fn x(&self) -> AssignedCell<pallas::Base, pallas::Base> {
@@ -336,7 +330,7 @@ impl<FixedPoints: super::FixedPoints<pallas::Affine>> EccChip<FixedPoints> {
/// each `k_i` is in the range [0..2^3).
#[derive(Clone, Debug)]
pub struct EccScalarFixed {
value: Option<pallas::Scalar>,
value: Value<pallas::Scalar>,
/// The circuit-assigned windows representing this scalar, or `None` if the scalar has
/// not been used yet.
windows: Option<ArrayVec<AssignedCell<pallas::Base, pallas::Base>, { NUM_WINDOWS }>>,
@@ -450,7 +444,7 @@ where
fn witness_point(
&self,
layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Affine>,
value: Value<pallas::Affine>,
) -> Result<Self::Point, Error> {
let config = self.config().witness_point;
layouter.assign_region(
@@ -462,7 +456,7 @@ where
fn witness_point_non_id(
&self,
layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Affine>,
value: Value<pallas::Affine>,
) -> Result<Self::NonIdentityPoint, Error> {
let config = self.config().witness_point;
layouter.assign_region(
@@ -474,7 +468,7 @@ where
fn witness_scalar_var(
&self,
_layouter: &mut impl Layouter<pallas::Base>,
_value: Option<pallas::Scalar>,
_value: Value<pallas::Scalar>,
) -> Result<Self::ScalarVar, Error> {
// This is unimplemented for halo2_gadgets v0.1.0.
todo!()
@@ -483,7 +477,7 @@ where
fn witness_scalar_fixed(
&self,
_layouter: &mut impl Layouter<pallas::Base>,
value: Option<pallas::Scalar>,
value: Value<pallas::Scalar>,
) -> Result<Self::ScalarFixed, Error> {
Ok(EccScalarFixed {
value,

View File

@@ -227,16 +227,16 @@ impl Config {
let (x_q, y_q) = (q.x.value(), q.y.value());
// Assign α = inv0(x_q - x_p)
let alpha = x_p.zip(x_q).map(|(x_p, x_q)| (x_q - x_p).invert());
region.assign_advice(|| "α", self.alpha, offset, || alpha.ok_or(Error::Synthesis))?;
let alpha = (x_q - x_p).invert();
region.assign_advice(|| "α", self.alpha, offset, || alpha)?;
// Assign β = inv0(x_p)
let beta = x_p.map(|x_p| x_p.invert());
region.assign_advice(|| "β", self.beta, offset, || beta.ok_or(Error::Synthesis))?;
let beta = x_p.invert();
region.assign_advice(|| "β", self.beta, offset, || beta)?;
// Assign γ = inv0(x_q)
let gamma = x_q.map(|x_q| x_q.invert());
region.assign_advice(|| "γ", self.gamma, offset, || gamma.ok_or(Error::Synthesis))?;
let gamma = x_q.invert();
region.assign_advice(|| "γ", self.gamma, offset, || gamma)?;
// Assign δ = inv0(y_q + y_p) if x_q = x_p, 0 otherwise
let delta = x_p
@@ -250,7 +250,7 @@ impl Config {
Assigned::Zero
}
});
region.assign_advice(|| "δ", self.delta, offset, || delta.ok_or(Error::Synthesis))?;
region.assign_advice(|| "δ", self.delta, offset, || delta)?;
#[allow(clippy::collapsible_else_if)]
// Assign lambda
@@ -278,12 +278,7 @@ impl Config {
}
}
});
region.assign_advice(
|| "λ",
self.lambda,
offset,
|| lambda.ok_or(Error::Synthesis),
)?;
region.assign_advice(|| "λ", self.lambda, offset, || lambda)?;
// Calculate (x_r, y_r)
let r =
@@ -314,21 +309,11 @@ impl Config {
// Assign x_r
let x_r = r.map(|r| r.0);
let x_r_cell = region.assign_advice(
|| "x_r",
self.x_qr,
offset + 1,
|| x_r.ok_or(Error::Synthesis),
)?;
let x_r_cell = region.assign_advice(|| "x_r", self.x_qr, offset + 1, || x_r)?;
// Assign y_r
let y_r = r.map(|r| r.1);
let y_r_cell = region.assign_advice(
|| "y_r",
self.y_qr,
offset + 1,
|| y_r.ok_or(Error::Synthesis),
)?;
let y_r_cell = region.assign_advice(|| "y_r", self.y_qr, offset + 1, || y_r)?;
let result = EccPoint {
x: x_r_cell,
@@ -345,9 +330,9 @@ impl Config {
let real_sum = p.zip(q).map(|(p, q)| p + q);
let result = result.point();
if let (Some(real_sum), Some(result)) = (real_sum, result) {
assert_eq!(real_sum.to_affine(), result);
}
real_sum
.zip(result)
.assert_if_known(|(real_sum, result)| &real_sum.to_affine() == result);
}
Ok(result)
@@ -357,7 +342,10 @@ impl Config {
#[cfg(test)]
pub mod tests {
use group::{prime::PrimeCurveAffine, Curve};
use halo2_proofs::{circuit::Layouter, plonk::Error};
use halo2_proofs::{
circuit::{Layouter, Value},
plonk::Error,
};
use pasta_curves::{arithmetic::CurveExt, pallas};
use crate::ecc::{chip::EccPoint, EccInstructions, NonIdentityPoint};
@@ -380,9 +368,10 @@ pub mod tests {
// Check complete addition P + (-P)
let zero = {
let result = p.add(layouter.namespace(|| "P + (-P)"), p_neg)?;
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
result
.inner()
.is_identity()
.assert_if_known(|is_identity| *is_identity);
result
};
@@ -398,7 +387,7 @@ pub mod tests {
let witnessed_result = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "witnessed P + Q"),
Some((p_val + q_val).to_affine()),
Value::known((p_val + q_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain P + Q"), &witnessed_result)?;
}
@@ -409,7 +398,7 @@ pub mod tests {
let witnessed_result = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "witnessed P + P"),
Some((p_val + p_val).to_affine()),
Value::known((p_val + p_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain P + P"), &witnessed_result)?;
}
@@ -431,7 +420,7 @@ pub mod tests {
let endo_p = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "endo(P)"),
Some(endo_p.to_affine()),
Value::known(endo_p.to_affine()),
)?;
p.add(layouter.namespace(|| "P + endo(P)"), &endo_p)?;
@@ -440,7 +429,7 @@ pub mod tests {
let endo_p_neg = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "endo(-P)"),
Some(endo_p_neg.to_affine()),
Value::known(endo_p_neg.to_affine()),
)?;
p.add(layouter.namespace(|| "P + endo(-P)"), &endo_p_neg)?;
@@ -449,7 +438,7 @@ pub mod tests {
let endo_2_p = NonIdentityPoint::new(
chip.clone(),
layouter.namespace(|| "endo^2(P)"),
Some(endo_2_p.to_affine()),
Value::known(endo_2_p.to_affine()),
)?;
p.add(layouter.namespace(|| "P + endo^2(P)"), &endo_2_p)?;
@@ -458,7 +447,7 @@ pub mod tests {
let endo_2_p_neg = NonIdentityPoint::new(
chip,
layouter.namespace(|| "endo^2(-P)"),
Some(endo_2_p_neg.to_affine()),
Value::known(endo_2_p_neg.to_affine()),
)?;
p.add(layouter.namespace(|| "P + endo^2(-P)"), &endo_2_p_neg)?;

View File

@@ -95,20 +95,14 @@ impl Config {
x_p.zip(y_p)
.zip(x_q)
.zip(y_q)
.map(|(((x_p, y_p), x_q), y_q)| {
.error_if_known_and(|(((x_p, y_p), x_q), y_q)| {
// P is point at infinity
if (x_p.is_zero_vartime() && y_p.is_zero_vartime())
(x_p.is_zero_vartime() && y_p.is_zero_vartime())
// Q is point at infinity
|| (x_q.is_zero_vartime() && y_q.is_zero_vartime())
// x_p = x_q
|| (x_p == x_q)
{
Err(Error::Synthesis)
} else {
Ok(())
}
})
.transpose()?;
})?;
// Copy point `p` into `x_p`, `y_p` columns
p.x.copy_advice(|| "x_p", region, self.x_p, offset)?;
@@ -137,20 +131,10 @@ impl Config {
// Assign the sum to `x_qr`, `y_qr` columns in the next row
let x_r = r.map(|r| r.0);
let x_r_var = region.assign_advice(
|| "x_r",
self.x_qr,
offset + 1,
|| x_r.ok_or(Error::Synthesis),
)?;
let x_r_var = region.assign_advice(|| "x_r", self.x_qr, offset + 1, || x_r)?;
let y_r = r.map(|r| r.1);
let y_r_var = region.assign_advice(
|| "y_r",
self.y_qr,
offset + 1,
|| y_r.ok_or(Error::Synthesis),
)?;
let y_r_var = region.assign_advice(|| "y_r", self.y_qr, offset + 1, || y_r)?;
let result = NonIdentityEccPoint {
x: x_r_var,
@@ -164,7 +148,10 @@ impl Config {
#[cfg(test)]
pub mod tests {
use group::Curve;
use halo2_proofs::{circuit::Layouter, plonk::Error};
use halo2_proofs::{
circuit::{Layouter, Value},
plonk::Error,
};
use pasta_curves::pallas;
use crate::ecc::{EccInstructions, NonIdentityPoint};
@@ -188,7 +175,7 @@ pub mod tests {
let witnessed_result = NonIdentityPoint::new(
chip,
layouter.namespace(|| "witnessed P + Q"),
Some((p_val + q_val).to_affine()),
Value::known((p_val + q_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain P + Q"), &witnessed_result)?;
}

View File

@@ -11,7 +11,7 @@ use std::{
use ff::PrimeField;
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{AssignedCell, Layouter, Region},
circuit::{AssignedCell, Layouter, Region, Value},
plonk::{Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Selector},
poly::Rotation,
};
@@ -270,9 +270,9 @@ impl Config {
let real_mul = base.zip(alpha).map(|(base, alpha)| base * alpha);
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
assert_eq!(real_mul.to_affine(), result);
}
real_mul
.zip(result)
.assert_if_known(|(real_mul, result)| &real_mul.to_affine() == result);
}
let zs = {
@@ -327,7 +327,7 @@ impl Config {
base: &NonIdentityEccPoint,
acc: EccPoint,
z_1: Z<pallas::Base>,
lsb: Option<bool>,
lsb: Value<bool>,
) -> Result<(EccPoint, Z<pallas::Base>), Error> {
// Enforce switching logic on LSB using a custom gate
self.q_mul_lsb.enable(region, offset)?;
@@ -343,7 +343,7 @@ impl Config {
|| "z_0",
self.complete_config.z_complete,
offset + 1,
|| z_0_val.ok_or(Error::Synthesis),
|| z_0_val,
)?;
Z(z_0_cell)
@@ -356,39 +356,24 @@ impl Config {
.copy_advice(|| "copy base_y", region, self.add_config.y_p, offset + 1)?;
// If `lsb` is 0, return `Acc + (-P)`. If `lsb` is 1, simply return `Acc + 0`.
let x = if let Some(lsb) = lsb {
let x = lsb.and_then(|lsb| {
if !lsb {
base.x.value().cloned()
} else {
Some(Assigned::Zero)
Value::known(Assigned::Zero)
}
} else {
None
};
});
let y = if let Some(lsb) = lsb {
let y = lsb.and_then(|lsb| {
if !lsb {
base.y.value().map(|y_p| -y_p)
-base.y.value()
} else {
Some(Assigned::Zero)
Value::known(Assigned::Zero)
}
} else {
None
};
});
let x_cell = region.assign_advice(
|| "x",
self.add_config.x_p,
offset,
|| x.ok_or(Error::Synthesis),
)?;
let y_cell = region.assign_advice(
|| "y",
self.add_config.y_p,
offset,
|| y.ok_or(Error::Synthesis),
)?;
let x_cell = region.assign_advice(|| "x", self.add_config.x_p, offset, || x)?;
let y_cell = region.assign_advice(|| "y", self.add_config.y_p, offset, || y)?;
let p = EccPoint {
x: x_cell,
@@ -436,7 +421,7 @@ impl<F: FieldExt> Deref for Z<F> {
}
// https://p.z.cash/halo2-0.1:ecc-var-mul-witness-scalar?partial
fn decompose_for_scalar_mul(scalar: Option<&pallas::Base>) -> Vec<Option<bool>> {
fn decompose_for_scalar_mul(scalar: Value<&pallas::Base>) -> Vec<Value<bool>> {
construct_uint! {
struct U256(4);
}
@@ -460,18 +445,16 @@ fn decompose_for_scalar_mul(scalar: Option<&pallas::Base>) -> Vec<Option<bool>>
};
// Take the first 255 bits.
bitstring.take(pallas::Scalar::NUM_BITS as usize)
bitstring
.take(pallas::Scalar::NUM_BITS as usize)
.collect::<Vec<_>>()
});
if let Some(bitstring) = bitstring {
// Transpose.
let mut bitstring: Vec<_> = bitstring.map(Some).collect();
// Reverse to get the big-endian bit representation.
bitstring.reverse();
bitstring
} else {
vec![None; pallas::Scalar::NUM_BITS as usize]
}
// Transpose.
let mut bitstring = bitstring.transpose_vec(pallas::Scalar::NUM_BITS as usize);
// Reverse to get the big-endian bit representation.
bitstring.reverse();
bitstring
}
#[cfg(test)]
@@ -481,7 +464,7 @@ pub mod tests {
Curve,
};
use halo2_proofs::{
circuit::{Chip, Layouter},
circuit::{Chip, Layouter, Value},
plonk::Error,
};
use pasta_curves::pallas;
@@ -519,7 +502,7 @@ pub mod tests {
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
Some((base_val * scalar).to_affine()),
Value::known((base_val * scalar).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
}
@@ -531,7 +514,7 @@ pub mod tests {
let scalar = chip.load_private(
layouter.namespace(|| "random scalar"),
column,
Some(scalar_val),
Value::known(scalar_val),
)?;
let scalar = ScalarVar::from_base(
chip.clone(),
@@ -554,8 +537,11 @@ pub mod tests {
{
let scalar_val = pallas::Base::zero();
let (result, _) = {
let scalar =
chip.load_private(layouter.namespace(|| "zero"), column, Some(scalar_val))?;
let scalar = chip.load_private(
layouter.namespace(|| "zero"),
column,
Value::known(scalar_val),
)?;
let scalar = ScalarVar::from_base(
chip.clone(),
layouter.namespace(|| "ScalarVar from_base"),
@@ -563,17 +549,21 @@ pub mod tests {
)?;
p.mul(layouter.namespace(|| "[0]B"), scalar)?
};
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
result
.inner()
.is_identity()
.assert_if_known(|is_identity| *is_identity);
}
// [-1]B (the largest possible base field element)
{
let scalar_val = -pallas::Base::one();
let (result, _) = {
let scalar =
chip.load_private(layouter.namespace(|| "-1"), column, Some(scalar_val))?;
let scalar = chip.load_private(
layouter.namespace(|| "-1"),
column,
Value::known(scalar_val),
)?;
let scalar = ScalarVar::from_base(
chip.clone(),
layouter.namespace(|| "ScalarVar from_base"),

View File

@@ -3,7 +3,7 @@ use super::{COMPLETE_RANGE, X, Y, Z};
use crate::utilities::{bool_check, ternary};
use halo2_proofs::{
circuit::Region,
circuit::{Region, Value},
plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector},
poly::Rotation,
};
@@ -88,7 +88,7 @@ impl Config {
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
bits: &[Option<bool>],
bits: &[Value<bool>],
base: &EccPoint,
x_a: X<pallas::Base>,
y_a: Y<pallas::Base>,
@@ -139,15 +139,10 @@ impl Config {
// Update `z`.
z = {
// z_next = z_cur * 2 + k_next
let z_val = z.value().zip(k.as_ref()).map(|(z_val, k)| {
pallas::Base::from(2) * z_val + pallas::Base::from(*k as u64)
});
let z_cell = region.assign_advice(
|| "z",
self.z_complete,
row + offset + 2,
|| z_val.ok_or(Error::Synthesis),
)?;
let z_val = z.value() * Value::known(pallas::Base::from(2))
+ k.map(|k| pallas::Base::from(k as u64));
let z_cell =
region.assign_advice(|| "z", self.z_complete, row + offset + 2, || z_val)?;
Z(z_cell)
};
zs.push(z.clone());
@@ -177,12 +172,7 @@ impl Config {
// fine because we are just assigning the same value to the same cell
// twice, and then applying an equality constraint between the cell and
// itself (which the permutation argument treats as a no-op).
region.assign_advice(
|| "y_p",
self.add_config.y_p,
row + offset,
|| y_p.ok_or(Error::Synthesis),
)?
region.assign_advice(|| "y_p", self.add_config.y_p, row + offset, || y_p)?
};
// U = P if the bit is set; U = -P is the bit is not set.

View File

@@ -2,7 +2,7 @@ use super::super::NonIdentityEccPoint;
use super::{X, Y, Z};
use crate::utilities::bool_check;
use halo2_proofs::{
circuit::Region,
circuit::{Region, Value},
plonk::{
Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector, VirtualCells,
},
@@ -229,7 +229,7 @@ impl<const NUM_BITS: usize> Config<NUM_BITS> {
region: &mut Region<'_, pallas::Base>,
offset: usize,
base: &NonIdentityEccPoint,
bits: &[Option<bool>],
bits: &[Value<bool>],
acc: (X<pallas::Base>, Y<pallas::Base>, Z<pallas::Base>),
) -> Result<(X<pallas::Base>, Y<pallas::Base>, Vec<Z<pallas::Base>>), Error> {
// Check that we have the correct number of bits for this double-and-add.
@@ -239,17 +239,16 @@ impl<const NUM_BITS: usize> Config<NUM_BITS> {
let (x_p, y_p) = (base.x.value().cloned(), base.y.value().cloned());
let (x_a, y_a) = (acc.0.value().cloned(), acc.1.value().cloned());
if let (Some(x_a), Some(y_a), Some(x_p), Some(y_p)) = (x_a, y_a, x_p, y_p) {
// A is point at infinity
if (x_p.is_zero_vartime() && y_p.is_zero_vartime())
// Q is point at infinity
|| (x_a.is_zero_vartime() && y_a.is_zero_vartime())
// x_p = x_a
|| (x_p == x_a)
{
return Err(Error::Synthesis);
}
}
x_a.zip(y_a)
.zip(x_p.zip(y_p))
.error_if_known_and(|((x_a, y_a), (x_p, y_p))| {
// A is point at infinity
(x_p.is_zero_vartime() && y_p.is_zero_vartime())
// Q is point at infinity
|| (x_a.is_zero_vartime() && y_a.is_zero_vartime())
// x_p = x_a
|| (x_p == x_a)
})?;
// Set q_mul values
{
@@ -302,27 +301,12 @@ impl<const NUM_BITS: usize> Config<NUM_BITS> {
.value()
.zip(k.as_ref())
.map(|(z_val, k)| pallas::Base::from(2) * z_val + pallas::Base::from(*k as u64));
z = region.assign_advice(
|| "z",
self.z,
row + offset,
|| z_val.ok_or(Error::Synthesis),
)?;
z = region.assign_advice(|| "z", self.z, row + offset, || z_val)?;
zs.push(Z(z.clone()));
// Assign `x_p`, `y_p`
region.assign_advice(
|| "x_p",
self.double_and_add.x_p,
row + offset,
|| x_p.ok_or(Error::Synthesis),
)?;
region.assign_advice(
|| "y_p",
self.y_p,
row + offset,
|| y_p.ok_or(Error::Synthesis),
)?;
region.assign_advice(|| "x_p", self.double_and_add.x_p, row + offset, || x_p)?;
region.assign_advice(|| "y_p", self.y_p, row + offset, || y_p)?;
// If the bit is set, use `y`; if the bit is not set, use `-y`
let y_p = y_p
@@ -339,7 +323,7 @@ impl<const NUM_BITS: usize> Config<NUM_BITS> {
|| "lambda1",
self.double_and_add.lambda_1,
row + offset,
|| lambda1.ok_or(Error::Synthesis),
|| lambda1,
)?;
// x_R = λ1^2 - x_A - x_P
@@ -361,25 +345,18 @@ impl<const NUM_BITS: usize> Config<NUM_BITS> {
|| "lambda2",
self.double_and_add.lambda_2,
row + offset,
|| lambda2.ok_or(Error::Synthesis),
|| lambda2,
)?;
// Compute and assign `x_a` for the next row
let x_a_new = lambda2
.zip(x_a.value())
.zip(x_r)
.map(|((lambda2, x_a), x_r)| lambda2.square() - x_a - x_r);
y_a = lambda2
.zip(x_a.value().cloned())
.zip(x_a_new)
.zip(y_a)
.map(|(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a);
let x_a_new = lambda2.square() - x_a.value() - x_r;
y_a = lambda2 * (x_a.value() - x_a_new) - y_a;
let x_a_val = x_a_new;
x_a = region.assign_advice(
|| "x_a",
self.double_and_add.x_a,
row + offset + 1,
|| x_a_val.ok_or(Error::Synthesis),
|| x_a_val,
)?;
}
@@ -388,7 +365,7 @@ impl<const NUM_BITS: usize> Config<NUM_BITS> {
|| "y_a",
self.double_and_add.lambda_1,
offset + NUM_BITS,
|| y_a.ok_or(Error::Synthesis),
|| y_a,
)?;
Ok((X(x_a), Y(y_a), zs))

View File

@@ -121,7 +121,7 @@ impl Config {
|| "s = alpha + k_254 ⋅ 2^130",
self.advices[0],
0,
|| s_val.ok_or(Error::Synthesis),
|| s_val,
)
},
)?
@@ -153,7 +153,7 @@ impl Config {
|| "η = inv0(z_130)",
self.advices[0],
offset + 2,
|| eta.ok_or(Error::Synthesis),
|| eta,
)?;
}

View File

@@ -11,7 +11,7 @@ use group::{
Curve,
};
use halo2_proofs::{
circuit::{AssignedCell, Region},
circuit::{AssignedCell, Region, Value},
plonk::{
Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector,
VirtualCells,
@@ -236,7 +236,7 @@ impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
constants = Some(build_constants());
}
let lagrange_coeffs = &constants.as_ref().unwrap().0;
Ok(lagrange_coeffs[window][k])
Value::known(lagrange_coeffs[window][k])
},
)?;
}
@@ -248,7 +248,7 @@ impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
window + offset,
|| {
let z = &constants.as_ref().unwrap().1;
Ok(pallas::Base::from(z[window]))
Value::known(pallas::Base::from(z[window]))
},
)?;
}
@@ -262,8 +262,8 @@ impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
region: &mut Region<'_, pallas::Base>,
offset: usize,
w: usize,
k_usize: Option<usize>,
window_scalar: Option<pallas::Scalar>,
k_usize: Value<usize>,
window_scalar: Value<pallas::Scalar>,
base: &F,
) -> Result<NonIdentityEccPoint, Error> {
let base_value = base.generator();
@@ -284,7 +284,7 @@ impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
|| format!("mul_b_x, window {}", w),
self.add_config.x_p,
offset + w,
|| x.ok_or(Error::Synthesis),
|| x,
)?;
let y = mul_b.map(|mul_b| {
@@ -296,7 +296,7 @@ impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
|| format!("mul_b_y, window {}", w),
self.add_config.y_p,
offset + w,
|| y.ok_or(Error::Synthesis),
|| y,
)?;
NonIdentityEccPoint { x, y }
@@ -304,7 +304,7 @@ impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
// Assign u = (y_p + z_w).sqrt()
let u_val = k_usize.map(|k| pallas::Base::from_repr(base_u[w][k]).unwrap());
region.assign_advice(|| "u", self.u, offset + w, || u_val.ok_or(Error::Synthesis))?;
region.assign_advice(|| "u", self.u, offset + w, || u_val)?;
Ok(mul_b)
}
@@ -370,8 +370,8 @@ impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
region: &mut Region<'_, pallas::Base>,
offset: usize,
w: usize,
k: Option<pallas::Scalar>,
k_usize: Option<usize>,
k: Value<pallas::Scalar>,
k_usize: Value<usize>,
base: &F,
) -> Result<NonIdentityEccPoint, Error> {
// `scalar = [(k_w + 2) ⋅ 8^w]
@@ -440,15 +440,13 @@ impl ScalarFixed {
///
/// This function does not require that the base field fits inside the scalar field,
/// because the window size fits into either field.
fn windows_field(&self) -> Vec<Option<pallas::Scalar>> {
fn windows_field(&self) -> Vec<Value<pallas::Scalar>> {
let running_sum_to_windows = |zs: Vec<AssignedCell<pallas::Base, pallas::Base>>| {
(0..(zs.len() - 1))
.map(|idx| {
let z_cur = zs[idx].value();
let z_next = zs[idx + 1].value();
let word = z_cur
.zip(z_next)
.map(|(z_cur, z_next)| z_cur - z_next * *H_BASE);
let word = z_cur - z_next * Value::known(*H_BASE);
// This assumes that the endianness of the encodings of pallas::Base
// and pallas::Scalar are the same. They happen to be, but we need to
// be careful if this is generalised.
@@ -484,7 +482,7 @@ impl ScalarFixed {
/// The scalar decomposition is guaranteed to be in three-bit windows, so we construct
/// `usize` indices from the lowest three bits of each window field element for
/// convenient indexing into `u`-values.
fn windows_usize(&self) -> Vec<Option<usize>> {
fn windows_usize(&self) -> Vec<Value<usize>> {
self.windows_field()
.iter()
.map(|window| {

View File

@@ -227,9 +227,9 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
let real_mul = scalar.map(|scalar| base.generator() * scalar);
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
assert_eq!(real_mul.to_affine(), result);
}
real_mul
.zip(result)
.assert_if_known(|(real_mul, result)| &real_mul.to_affine() == result);
}
// We want to enforce canonicity of a 255-bit base field element, α.
@@ -326,7 +326,7 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
|| "α_1 = α[252..=253]",
self.canon_advices[1],
offset,
|| alpha_1.ok_or(Error::Synthesis),
|| alpha_1,
)?;
// Witness the MSB α_2 = α[254]
@@ -335,7 +335,7 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
|| "α_2 = α[254]",
self.canon_advices[2],
offset,
|| alpha_2.ok_or(Error::Synthesis),
|| alpha_2,
)?;
}
@@ -382,7 +382,7 @@ pub mod tests {
Curve,
};
use halo2_proofs::{
circuit::{Chip, Layouter},
circuit::{Chip, Layouter, Value},
plonk::Error,
};
use pasta_curves::pallas;
@@ -432,7 +432,7 @@ pub mod tests {
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
Some((base_val * scalar).to_affine()),
Value::known((base_val * scalar).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
}
@@ -444,7 +444,7 @@ pub mod tests {
let scalar_fixed = chip.load_private(
layouter.namespace(|| "random base field element"),
column,
Some(scalar_fixed),
Value::known(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "random [a]B"), scalar_fixed)?
};
@@ -472,7 +472,7 @@ pub mod tests {
let scalar_fixed = chip.load_private(
layouter.namespace(|| "mul with double"),
column,
Some(scalar_fixed),
Value::known(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul with double"), scalar_fixed)?
};
@@ -490,21 +490,28 @@ pub mod tests {
{
let scalar_fixed = pallas::Base::zero();
let result = {
let scalar_fixed =
chip.load_private(layouter.namespace(|| "zero"), column, Some(scalar_fixed))?;
let scalar_fixed = chip.load_private(
layouter.namespace(|| "zero"),
column,
Value::known(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul by zero"), scalar_fixed)?
};
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
result
.inner()
.is_identity()
.assert_if_known(|is_identity| *is_identity);
}
// [-1]B is the largest base field element
{
let scalar_fixed = -pallas::Base::one();
let result = {
let scalar_fixed =
chip.load_private(layouter.namespace(|| "-1"), column, Some(scalar_fixed))?;
let scalar_fixed = chip.load_private(
layouter.namespace(|| "-1"),
column,
Value::known(scalar_fixed),
)?;
base.mul(layouter.namespace(|| "mul by -1"), scalar_fixed)?
};
constrain_equal_non_id(

View File

@@ -4,7 +4,7 @@ use crate::utilities::{decompose_word, range_check};
use arrayvec::ArrayVec;
use ff::PrimeField;
use halo2_proofs::{
circuit::{AssignedCell, Layouter, Region},
circuit::{AssignedCell, Layouter, Region, Value},
plonk::{ConstraintSystem, Constraints, Error, Selector},
poly::Rotation,
};
@@ -57,7 +57,7 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
scalar: Option<pallas::Scalar>,
scalar: Value<pallas::Scalar>,
) -> Result<EccScalarFixed, Error> {
let windows = self.decompose_scalar_fixed::<{ pallas::Scalar::NUM_BITS as usize }>(
scalar, offset, region,
@@ -74,7 +74,7 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
/// The scalar is allowed to be non-canonical.
fn decompose_scalar_fixed<const SCALAR_NUM_BITS: usize>(
&self,
scalar: Option<pallas::Scalar>,
scalar: Value<pallas::Scalar>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<ArrayVec<AssignedCell<pallas::Base, pallas::Base>, NUM_WINDOWS>, Error> {
@@ -84,20 +84,18 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
}
// Decompose scalar into `k-bit` windows
let scalar_windows: Option<Vec<u8>> = scalar.map(|scalar| {
let scalar_windows: Value<Vec<u8>> = scalar.map(|scalar| {
decompose_word::<pallas::Scalar>(&scalar, SCALAR_NUM_BITS, FIXED_BASE_WINDOW_SIZE)
});
// Transpose `Option<Vec<u8>>` into `Vec<Option<pallas::Base>>`.
let scalar_windows: Vec<Option<pallas::Base>> = if let Some(windows) = scalar_windows {
assert_eq!(windows.len(), NUM_WINDOWS);
windows
.into_iter()
.map(|window| Some(pallas::Base::from(window as u64)))
.collect()
} else {
vec![None; NUM_WINDOWS]
};
// Transpose `Value<Vec<u8>>` into `Vec<Value<pallas::Base>>`.
let scalar_windows = scalar_windows
.map(|windows| {
windows
.into_iter()
.map(|window| pallas::Base::from(window as u64))
})
.transpose_vec(NUM_WINDOWS);
// Store the scalar decomposition
let mut windows: ArrayVec<AssignedCell<pallas::Base, pallas::Base>, NUM_WINDOWS> =
@@ -107,7 +105,7 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
|| format!("k[{:?}]", offset + idx),
self.super_config.window,
offset + idx,
|| window.ok_or(Error::Synthesis),
|| window,
)?;
windows.push(window_cell);
}
@@ -170,9 +168,9 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
let real_mul = scalar.value.map(|scalar| base.generator() * scalar);
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
assert_eq!(real_mul.to_affine(), result);
}
real_mul
.zip(result)
.assert_if_known(|(real_mul, result)| &real_mul.to_affine() == result);
}
Ok((result, scalar))
@@ -182,7 +180,10 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
#[cfg(test)]
pub mod tests {
use group::{ff::Field, Curve};
use halo2_proofs::{circuit::Layouter, plonk::Error};
use halo2_proofs::{
circuit::{Layouter, Value},
plonk::Error,
};
use pasta_curves::pallas;
use rand::rngs::OsRng;
@@ -224,7 +225,7 @@ pub mod tests {
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
Some((base_val * scalar_val).to_affine()),
Value::known((base_val * scalar_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
}
@@ -235,7 +236,7 @@ pub mod tests {
let by = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "random a"),
Some(scalar_fixed),
Value::known(scalar_fixed),
)?;
let (result, _) = base.mul(layouter.namespace(|| "random [a]B"), by)?;
@@ -263,7 +264,7 @@ pub mod tests {
let by = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| LAST_DOUBLING),
Some(scalar_fixed),
Value::known(scalar_fixed),
)?;
let (result, _) = base.mul(layouter.namespace(|| "mul with double"), by)?;
@@ -280,12 +281,16 @@ pub mod tests {
// on the last step.
{
let scalar_fixed = pallas::Scalar::zero();
let zero =
ScalarFixed::new(chip.clone(), layouter.namespace(|| "0"), Some(scalar_fixed))?;
let zero = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "0"),
Value::known(scalar_fixed),
)?;
let (result, _) = base.mul(layouter.namespace(|| "mul by zero"), zero)?;
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
result
.inner()
.is_identity()
.assert_if_known(|is_identity| *is_identity);
}
// [-1]B is the largest scalar field element.
@@ -294,7 +299,7 @@ pub mod tests {
let neg_1 = ScalarFixed::new(
chip.clone(),
layouter.namespace(|| "-1"),
Some(scalar_fixed),
Value::known(scalar_fixed),
)?;
let (result, _) = base.mul(layouter.namespace(|| "mul by -1"), neg_1)?;
constrain_equal_non_id(

View File

@@ -175,15 +175,13 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
z_21.copy_advice(|| "last_window", &mut region, self.super_config.u, offset)?;
// Conditionally negate `y`-coordinate
let y_val = if let Some(sign) = sign.value() {
let y_val = sign.value().and_then(|sign| {
if sign == &-pallas::Base::one() {
magnitude_mul.y.value().cloned().map(|y| -y)
-magnitude_mul.y.value()
} else {
magnitude_mul.y.value().cloned()
}
} else {
None
};
});
// Enable mul_fixed_short selector on final row
self.q_mul_fixed_short.enable(&mut region, offset)?;
@@ -193,7 +191,7 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
|| "y_var",
self.super_config.add_config.y_p,
offset,
|| y_val.ok_or(Error::Synthesis),
|| y_val,
)?;
Ok(EccPoint {
@@ -213,34 +211,35 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
use super::super::FixedPoint;
use group::{ff::PrimeField, Curve};
if let (Some(magnitude), Some(sign)) = (scalar.magnitude.value(), scalar.sign.value()) {
let magnitude_is_valid = magnitude <= &pallas::Base::from(0xFFFF_FFFF_FFFF_FFFFu64);
let sign_is_valid = sign * sign == pallas::Base::one();
if magnitude_is_valid && sign_is_valid {
let scalar = scalar.magnitude.value().zip(scalar.sign.value()).map(
|(magnitude, sign)| {
scalar
.magnitude
.value()
.zip(scalar.sign.value())
.zip(result.point())
.assert_if_known(|((magnitude, sign), result)| {
let magnitude_is_valid =
magnitude <= &&pallas::Base::from(0xFFFF_FFFF_FFFF_FFFFu64);
let sign_is_valid = sign.square() == pallas::Base::one();
// Only check the result if the magnitude and sign are valid.
!(magnitude_is_valid && sign_is_valid) || {
let scalar = {
// Move magnitude from base field into scalar field (which always fits
// for Pallas).
let magnitude = pallas::Scalar::from_repr(magnitude.to_repr()).unwrap();
let sign = if sign == &pallas::Base::one() {
let sign = if sign == &&pallas::Base::one() {
pallas::Scalar::one()
} else {
-pallas::Scalar::one()
};
magnitude * sign
},
);
let real_mul = scalar.map(|scalar| base.generator() * scalar);
};
let real_mul = base.generator() * scalar;
let result = result.point();
if let (Some(real_mul), Some(result)) = (real_mul, result) {
assert_eq!(real_mul.to_affine(), result);
&real_mul.to_affine() == result
}
}
}
});
}
Ok((result, scalar))
@@ -252,7 +251,7 @@ pub mod tests {
use group::{ff::PrimeField, Curve};
use halo2_proofs::{
arithmetic::CurveAffine,
circuit::{AssignedCell, Chip, Layouter},
circuit::{AssignedCell, Chip, Layouter, Value},
plonk::{Any, Error},
};
use pasta_curves::{arithmetic::FieldExt, pallas};
@@ -282,9 +281,13 @@ pub mod tests {
sign: pallas::Base,
) -> Result<MagnitudeSign, Error> {
let column = chip.config().advices[0];
let magnitude =
chip.load_private(layouter.namespace(|| "magnitude"), column, Some(magnitude))?;
let sign = chip.load_private(layouter.namespace(|| "sign"), column, Some(sign))?;
let magnitude = chip.load_private(
layouter.namespace(|| "magnitude"),
column,
Value::known(magnitude),
)?;
let sign =
chip.load_private(layouter.namespace(|| "sign"), column, Value::known(sign))?;
Ok((magnitude, sign))
}
@@ -299,7 +302,7 @@ pub mod tests {
let expected = NonIdentityPoint::new(
chip,
layouter.namespace(|| "expected point"),
Some((base_val * scalar_val).to_affine()),
Value::known((base_val * scalar_val).to_affine()),
)?;
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
}
@@ -391,9 +394,10 @@ pub mod tests {
)?;
test_short.mul(layouter.namespace(|| *name), by)?
};
if let Some(is_identity) = result.inner().is_identity() {
assert!(is_identity);
}
result
.inner()
.is_identity()
.assert_if_known(|is_identity| *is_identity);
}
Ok(())
@@ -413,10 +417,10 @@ pub mod tests {
#[derive(Default)]
struct MyCircuit {
magnitude: Option<pallas::Base>,
sign: Option<pallas::Base>,
magnitude: Value<pallas::Base>,
sign: Value<pallas::Base>,
// For test checking
magnitude_error: Option<pallas::Base>,
magnitude_error: Value<pallas::Base>,
}
impl UtilitiesInstructions<pallas::Base> for MyCircuit {
@@ -517,41 +521,41 @@ pub mod tests {
let circuits = [
// 2^64
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 64)),
sign: Some(pallas::Base::one()),
magnitude_error: Some(pallas::Base::from(1 << 1)),
magnitude: Value::known(pallas::Base::from_u128(1 << 64)),
sign: Value::known(pallas::Base::one()),
magnitude_error: Value::known(pallas::Base::from(1 << 1)),
},
// -2^64
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 64)),
sign: Some(-pallas::Base::one()),
magnitude_error: Some(pallas::Base::from(1 << 1)),
magnitude: Value::known(pallas::Base::from_u128(1 << 64)),
sign: Value::known(-pallas::Base::one()),
magnitude_error: Value::known(pallas::Base::from(1 << 1)),
},
// 2^66
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 66)),
sign: Some(pallas::Base::one()),
magnitude_error: Some(pallas::Base::from(1 << 3)),
magnitude: Value::known(pallas::Base::from_u128(1 << 66)),
sign: Value::known(pallas::Base::one()),
magnitude_error: Value::known(pallas::Base::from(1 << 3)),
},
// -2^66
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 66)),
sign: Some(-pallas::Base::one()),
magnitude_error: Some(pallas::Base::from(1 << 3)),
magnitude: Value::known(pallas::Base::from_u128(1 << 66)),
sign: Value::known(-pallas::Base::one()),
magnitude_error: Value::known(pallas::Base::from(1 << 3)),
},
// 2^254
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 127).square()),
sign: Some(pallas::Base::one()),
magnitude_error: Some(
magnitude: Value::known(pallas::Base::from_u128(1 << 127).square()),
sign: Value::known(pallas::Base::one()),
magnitude_error: Value::known(
pallas::Base::from_u128(1 << 95).square() * pallas::Base::from(2),
),
},
// -2^254
MyCircuit {
magnitude: Some(pallas::Base::from_u128(1 << 127).square()),
sign: Some(-pallas::Base::one()),
magnitude_error: Some(
magnitude: Value::known(pallas::Base::from_u128(1 << 127).square()),
sign: Value::known(-pallas::Base::one()),
magnitude_error: Value::known(
pallas::Base::from_u128(1 << 95).square() * pallas::Base::from(2),
),
},
@@ -559,38 +563,43 @@ pub mod tests {
for circuit in circuits.iter() {
let prover = MockProver::<pallas::Base>::run(11, circuit, vec![]).unwrap();
assert_eq!(
prover.verify(),
Err(vec![
VerifyFailure::ConstraintNotSatisfied {
constraint: (
(17, "Short fixed-base mul gate").into(),
0,
"last_window_check"
)
.into(),
location: FailureLocation::InRegion {
region: (3, "Short fixed-base mul (most significant word)").into(),
offset: 1,
circuit.magnitude_error.assert_if_known(|magnitude_error| {
assert_eq!(
prover.verify(),
Err(vec![
VerifyFailure::ConstraintNotSatisfied {
constraint: (
(17, "Short fixed-base mul gate").into(),
0,
"last_window_check",
)
.into(),
location: FailureLocation::InRegion {
region: (3, "Short fixed-base mul (most significant word)")
.into(),
offset: 1,
},
cell_values: vec![(
((Any::Advice, 5).into(), 0).into(),
format_value(*magnitude_error),
)],
},
cell_values: vec![(
((Any::Advice, 5).into(), 0).into(),
format_value(circuit.magnitude_error.unwrap()),
)],
},
VerifyFailure::Permutation {
column: (Any::Fixed, 9).into(),
location: FailureLocation::OutsideRegion { row: 0 },
},
VerifyFailure::Permutation {
column: (Any::Advice, 4).into(),
location: FailureLocation::InRegion {
region: (2, "Short fixed-base mul (incomplete addition)").into(),
offset: 22,
VerifyFailure::Permutation {
column: (Any::Fixed, 9).into(),
location: FailureLocation::OutsideRegion { row: 0 },
},
}
])
);
VerifyFailure::Permutation {
column: (Any::Advice, 4).into(),
location: FailureLocation::InRegion {
region: (2, "Short fixed-base mul (incomplete addition)")
.into(),
offset: 22,
},
},
])
);
true
});
}
}
@@ -598,9 +607,9 @@ pub mod tests {
{
let magnitude_u64 = rand::random::<u64>();
let circuit = MyCircuit {
magnitude: Some(pallas::Base::from(magnitude_u64)),
sign: Some(pallas::Base::zero()),
magnitude_error: None,
magnitude: Value::known(pallas::Base::from(magnitude_u64)),
sign: Value::known(pallas::Base::zero()),
magnitude_error: Value::unknown(),
};
let negation_check_y = {

View File

@@ -3,7 +3,7 @@ use super::{EccPoint, NonIdentityEccPoint};
use group::prime::PrimeCurveAffine;
use halo2_proofs::{
circuit::{AssignedCell, Region},
circuit::{AssignedCell, Region, Value},
plonk::{
Advice, Assigned, Column, ConstraintSystem, Constraints, Error, Expression, Selector,
VirtualCells,
@@ -87,19 +87,17 @@ impl Config {
fn assign_xy(
&self,
value: Option<(Assigned<pallas::Base>, Assigned<pallas::Base>)>,
value: Value<(Assigned<pallas::Base>, Assigned<pallas::Base>)>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<Coordinates, Error> {
// Assign `x` value
let x_val = value.map(|value| value.0);
let x_var =
region.assign_advice(|| "x", self.x, offset, || x_val.ok_or(Error::Synthesis))?;
let x_var = region.assign_advice(|| "x", self.x, offset, || x_val)?;
// Assign `y` value
let y_val = value.map(|value| value.1);
let y_var =
region.assign_advice(|| "y", self.y, offset, || y_val.ok_or(Error::Synthesis))?;
let y_var = region.assign_advice(|| "y", self.y, offset, || y_val)?;
Ok((x_var, y_var))
}
@@ -107,7 +105,7 @@ impl Config {
/// Assigns a point that can be the identity.
pub(super) fn point(
&self,
value: Option<pallas::Affine>,
value: Value<pallas::Affine>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<EccPoint, Error> {
@@ -131,19 +129,15 @@ impl Config {
/// Assigns a non-identity point.
pub(super) fn point_non_id(
&self,
value: Option<pallas::Affine>,
value: Value<pallas::Affine>,
offset: usize,
region: &mut Region<'_, pallas::Base>,
) -> Result<NonIdentityEccPoint, Error> {
// Enable `q_point_non_id` selector
self.q_point_non_id.enable(region, offset)?;
if let Some(value) = value {
// Return an error if the point is the identity.
if value == pallas::Affine::identity() {
return Err(Error::Synthesis);
}
};
// Return an error if the point is the identity.
value.error_if_known_and(|value| value == &pallas::Affine::identity())?;
let value = value.map(|value| {
let value = value.coordinates().unwrap();
@@ -173,7 +167,7 @@ pub mod tests {
NonIdentityPoint::new(
chip,
layouter.namespace(|| "witness identity"),
Some(pallas::Affine::identity()),
Value::known(pallas::Affine::identity()),
)
.expect_err("witnessing 𝒪 should return an error");
}

View File

@@ -3,7 +3,7 @@ use std::iter;
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{AssignedCell, Cell, Chip, Layouter, Region},
circuit::{AssignedCell, Cell, Chip, Layouter, Region, Value},
plonk::{
Advice, Any, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector,
},
@@ -349,7 +349,7 @@ impl<
|| format!("load pad_{}", i),
config.rc_b[i],
1,
|| Ok(padding_value),
|| Value::known(padding_value),
)?,
_ => panic!("Input is not padded"),
};
@@ -367,20 +367,18 @@ impl<
// Constrain the output.
let constrain_output_word = |i: usize| {
let value = initial_state[i].0.value().and_then(|initial_word| {
input
let value = initial_state[i].0.value().copied()
+ input
.get(i)
.map(|word| word.0.value().cloned())
// The capacity element is never altered by the input.
.unwrap_or_else(|| Some(F::zero()))
.map(|input_word| *initial_word + input_word)
});
.unwrap_or_else(|| Value::known(F::zero()));
region
.assign_advice(
|| format!("load output_{}", i),
config.state[i],
2,
|| value.ok_or(Error::Synthesis),
|| value,
)
.map(StateWord)
};
@@ -424,7 +422,7 @@ impl<F: FieldExt> Var<F> for StateWord<F> {
self.0.cell()
}
fn value(&self) -> Option<F> {
fn value(&self) -> Value<F> {
self.0.value().cloned()
}
}
@@ -446,7 +444,7 @@ impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
.value()
.map(|v| *v + config.round_constants[round][idx])
});
let r: Option<Vec<F>> = q.map(|q| q.map(|q| q.pow(&config.alpha))).collect();
let r: Value<Vec<F>> = q.map(|q| q.map(|q| q.pow(&config.alpha))).collect();
let m = &config.m_reg;
let state = m.iter().map(|m_i| {
r.as_ref().map(|r| {
@@ -469,9 +467,9 @@ impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
) -> Result<Self, Error> {
Self::round(region, config, round, offset, config.s_partial, |region| {
let m = &config.m_reg;
let p: Option<Vec<_>> = self.0.iter().map(|word| word.0.value().cloned()).collect();
let p: Value<Vec<_>> = self.0.iter().map(|word| word.0.value().cloned()).collect();
let r: Option<Vec<_>> = p.map(|p| {
let r: Value<Vec<_>> = p.map(|p| {
let r_0 = (p[0] + config.round_constants[round][0]).pow(&config.alpha);
let r_i = p[1..]
.iter()
@@ -484,10 +482,10 @@ impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
|| format!("round_{} partial_sbox", round),
config.partial_sbox,
offset,
|| r.as_ref().map(|r| r[0]).ok_or(Error::Synthesis),
|| r.as_ref().map(|r| r[0]),
)?;
let p_mid: Option<Vec<_>> = m
let p_mid: Value<Vec<_>> = m
.iter()
.map(|m_i| {
r.as_ref().map(|r| {
@@ -504,14 +502,14 @@ impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
|| format!("round_{} rc_{}", round + 1, i),
config.rc_b[i],
offset,
|| Ok(config.round_constants[round + 1][i]),
|| Value::known(config.round_constants[round + 1][i]),
)
};
for i in 0..WIDTH {
load_round_constant(i)?;
}
let r_mid: Option<Vec<_>> = p_mid.map(|p| {
let r_mid: Value<Vec<_>> = p_mid.map(|p| {
let r_0 = (p[0] + config.round_constants[round + 1][0]).pow(&config.alpha);
let r_i = p[1..]
.iter()
@@ -520,7 +518,7 @@ impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
std::iter::empty().chain(Some(r_0)).chain(r_i).collect()
});
let state: Vec<Option<_>> = m
let state: Vec<Value<_>> = m
.iter()
.map(|m_i| {
r_mid.as_ref().map(|r| {
@@ -557,7 +555,7 @@ impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
round: usize,
offset: usize,
round_gate: Selector,
round_fn: impl FnOnce(&mut Region<F>) -> Result<(usize, [Option<F>; WIDTH]), Error>,
round_fn: impl FnOnce(&mut Region<F>) -> Result<(usize, [Value<F>; WIDTH]), Error>,
) -> Result<Self, Error> {
// Enable the required gate.
round_gate.enable(region, offset)?;
@@ -568,7 +566,7 @@ impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
|| format!("round_{} rc_{}", round, i),
config.rc_a[i],
offset,
|| Ok(config.round_constants[round][i]),
|| Value::known(config.round_constants[round][i]),
)
};
for i in 0..WIDTH {
@@ -584,7 +582,7 @@ impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
|| format!("round_{} state_{}", next_round, i),
config.state[i],
offset + 1,
|| value.ok_or(Error::Synthesis),
|| value,
)?;
Ok(StateWord(var))
};
@@ -598,7 +596,7 @@ impl<F: FieldExt, const WIDTH: usize> Pow5State<F, WIDTH> {
mod tests {
use group::ff::{Field, PrimeField};
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
pasta::Fp,
plonk::{Circuit, ConstraintSystem, Error},
@@ -653,12 +651,12 @@ mod tests {
|| "prepare initial state",
|mut region| {
let state_word = |i: usize| {
let value = Some(Fp::from(i as u64));
let value = Value::known(Fp::from(i as u64));
let var = region.assign_advice(
|| format!("load state_{}", i),
config.state[i],
0,
|| value.ok_or(Error::Synthesis),
|| value,
)?;
Ok(StateWord(var))
};
@@ -697,7 +695,7 @@ mod tests {
|| format!("load final_state_{}", i),
config.state[i],
0,
|| Ok(expected_final_state[i]),
|| Value::known(expected_final_state[i]),
)?;
region.constrain_equal(final_state[i].0.cell(), var.cell())
};
@@ -726,10 +724,10 @@ mod tests {
const RATE: usize,
const L: usize,
> {
message: Option<[Fp; L]>,
message: Value<[Fp; L]>,
// For the purpose of this test, witness the result.
// TODO: Move this into an instance column.
output: Option<Fp>,
output: Value<Fp>,
_spec: PhantomData<S>,
}
@@ -741,8 +739,8 @@ mod tests {
fn without_witnesses(&self) -> Self {
Self {
message: None,
output: None,
message: Value::unknown(),
output: Value::unknown(),
_spec: PhantomData,
}
}
@@ -781,7 +779,7 @@ mod tests {
|| format!("load message_{}", i),
config.state[i],
0,
|| value.ok_or(Error::Synthesis),
|| value,
)
};
@@ -803,7 +801,7 @@ mod tests {
|| "load output",
config.state[0],
0,
|| self.output.ok_or(Error::Synthesis),
|| self.output,
)?;
region.constrain_equal(output.cell(), expected_var.cell())
},
@@ -821,8 +819,8 @@ mod tests {
let k = 6;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {
message: Some(message),
output: Some(output),
message: Value::known(message),
output: Value::known(output),
_spec: PhantomData,
};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
@@ -839,8 +837,8 @@ mod tests {
let k = 7;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 3> {
message: Some(message),
output: Some(output),
message: Value::known(message),
output: Value::known(output),
_spec: PhantomData,
};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
@@ -859,8 +857,8 @@ mod tests {
let k = 6;
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {
message: Some(message),
output: Some(output),
message: Value::known(message),
output: Value::known(output),
_spec: PhantomData,
};
let prover = MockProver::run(k, &circuit, vec![]).unwrap();
@@ -880,8 +878,8 @@ mod tests {
.unwrap();
let circuit = HashCircuit::<OrchardNullifier, 3, 2, 2> {
message: None,
output: None,
message: Value::unknown(),
output: Value::unknown(),
_spec: PhantomData,
};
halo2_proofs::dev::CircuitLayout::default()

View File

@@ -3,7 +3,7 @@ use std::marker::PhantomData;
use super::Sha256Instructions;
use halo2_proofs::{
circuit::{AssignedCell, Chip, Layouter, Region},
circuit::{AssignedCell, Chip, Layouter, Region, Value},
pasta::pallas,
plonk::{Advice, Any, Assigned, Column, ConstraintSystem, Error},
};
@@ -49,7 +49,7 @@ const IV: [u32; STATE] = [
#[derive(Clone, Copy, Debug, Default)]
/// A word in a `Table16` message block.
// TODO: Make the internals of this struct private.
pub struct BlockWord(pub Option<u32>);
pub struct BlockWord(pub Value<u32>);
#[derive(Clone, Debug)]
/// Little-endian bits (up to 64 bits)
@@ -129,26 +129,26 @@ impl<const LEN: usize> AssignedBits<LEN> {
annotation: A,
column: impl Into<Column<Any>>,
offset: usize,
value: Option<T>,
value: Value<T>,
) -> Result<Self, Error>
where
A: Fn() -> AR,
AR: Into<String>,
<T as TryInto<[bool; LEN]>>::Error: std::fmt::Debug,
{
let value: Option<[bool; LEN]> = value.map(|v| v.try_into().unwrap());
let value: Option<Bits<LEN>> = value.map(|v| v.into());
let value: Value<[bool; LEN]> = value.map(|v| v.try_into().unwrap());
let value: Value<Bits<LEN>> = value.map(|v| v.into());
let column: Column<Any> = column.into();
match column.column_type() {
Any::Advice => {
region.assign_advice(annotation, column.try_into().unwrap(), offset, || {
value.clone().ok_or(Error::Synthesis)
value.clone()
})
}
Any::Fixed => {
region.assign_fixed(annotation, column.try_into().unwrap(), offset, || {
value.clone().ok_or(Error::Synthesis)
value.clone()
})
}
_ => panic!("Cannot assign to instance column"),
@@ -158,7 +158,7 @@ impl<const LEN: usize> AssignedBits<LEN> {
}
impl AssignedBits<16> {
fn value_u16(&self) -> Option<u16> {
fn value_u16(&self) -> Value<u16> {
self.value().map(|v| v.into())
}
@@ -167,23 +167,23 @@ impl AssignedBits<16> {
annotation: A,
column: impl Into<Column<Any>>,
offset: usize,
value: Option<u16>,
value: Value<u16>,
) -> Result<Self, Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
let column: Column<Any> = column.into();
let value: Option<Bits<16>> = value.map(|v| v.into());
let value: Value<Bits<16>> = value.map(|v| v.into());
match column.column_type() {
Any::Advice => {
region.assign_advice(annotation, column.try_into().unwrap(), offset, || {
value.clone().ok_or(Error::Synthesis)
value.clone()
})
}
Any::Fixed => {
region.assign_fixed(annotation, column.try_into().unwrap(), offset, || {
value.clone().ok_or(Error::Synthesis)
value.clone()
})
}
_ => panic!("Cannot assign to instance column"),
@@ -193,7 +193,7 @@ impl AssignedBits<16> {
}
impl AssignedBits<32> {
fn value_u32(&self) -> Option<u32> {
fn value_u32(&self) -> Value<u32> {
self.value().map(|v| v.into())
}
@@ -202,23 +202,23 @@ impl AssignedBits<32> {
annotation: A,
column: impl Into<Column<Any>>,
offset: usize,
value: Option<u32>,
value: Value<u32>,
) -> Result<Self, Error>
where
A: Fn() -> AR,
AR: Into<String>,
{
let column: Column<Any> = column.into();
let value: Option<Bits<32>> = value.map(|v| v.into());
let value: Value<Bits<32>> = value.map(|v| v.into());
match column.column_type() {
Any::Advice => {
region.assign_advice(annotation, column.try_into().unwrap(), offset, || {
value.clone().ok_or(Error::Synthesis)
value.clone()
})
}
Any::Fixed => {
region.assign_fixed(annotation, column.try_into().unwrap(), offset, || {
value.clone().ok_or(Error::Synthesis)
value.clone()
})
}
_ => panic!("Cannot assign to instance column"),
@@ -384,10 +384,10 @@ trait Table16Assignment {
lookup: &SpreadInputs,
a_3: Column<Advice>,
row: usize,
r_0_even: Option<[bool; 16]>,
r_0_odd: Option<[bool; 16]>,
r_1_even: Option<[bool; 16]>,
r_1_odd: Option<[bool; 16]>,
r_0_even: Value<[bool; 16]>,
r_0_odd: Value<[bool; 16]>,
r_1_even: Value<[bool; 16]>,
r_1_odd: Value<[bool; 16]>,
) -> Result<
(
(AssignedBits<16>, AssignedBits<16>),
@@ -436,10 +436,10 @@ trait Table16Assignment {
lookup: &SpreadInputs,
a_3: Column<Advice>,
row: usize,
r_0_even: Option<[bool; 16]>,
r_0_odd: Option<[bool; 16]>,
r_1_even: Option<[bool; 16]>,
r_1_odd: Option<[bool; 16]>,
r_0_even: Value<[bool; 16]>,
r_0_odd: Value<[bool; 16]>,
r_1_even: Value<[bool; 16]>,
r_1_odd: Value<[bool; 16]>,
) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> {
let (even, _odd) = self.assign_spread_outputs(
region, lookup, a_3, row, r_0_even, r_0_odd, r_1_even, r_1_odd,

View File

@@ -4,7 +4,7 @@ use super::{
AssignedBits, BlockWord, SpreadInputs, SpreadVar, Table16Assignment, ROUNDS, STATE,
};
use halo2_proofs::{
circuit::Layouter,
circuit::{Layouter, Value},
pasta::pallas,
plonk::{Advice, Column, ConstraintSystem, Error, Selector},
poly::Rotation,
@@ -27,12 +27,12 @@ pub trait UpperSigmaVar<
const D_LEN: usize,
>
{
fn spread_a(&self) -> Option<[bool; A_LEN]>;
fn spread_b(&self) -> Option<[bool; B_LEN]>;
fn spread_c(&self) -> Option<[bool; C_LEN]>;
fn spread_d(&self) -> Option<[bool; D_LEN]>;
fn spread_a(&self) -> Value<[bool; A_LEN]>;
fn spread_b(&self) -> Value<[bool; B_LEN]>;
fn spread_c(&self) -> Value<[bool; C_LEN]>;
fn spread_d(&self) -> Value<[bool; D_LEN]>;
fn xor_upper_sigma(&self) -> Option<[bool; 64]> {
fn xor_upper_sigma(&self) -> Value<[bool; 64]> {
self.spread_a()
.zip(self.spread_b())
.zip(self.spread_c())
@@ -128,15 +128,15 @@ impl AbcdVar {
}
impl UpperSigmaVar<4, 22, 18, 20> for AbcdVar {
fn spread_a(&self) -> Option<[bool; 4]> {
fn spread_a(&self) -> Value<[bool; 4]> {
self.a.spread.value().map(|v| v.0)
}
fn spread_b(&self) -> Option<[bool; 22]> {
fn spread_b(&self) -> Value<[bool; 22]> {
self.b.spread.value().map(|v| v.0)
}
fn spread_c(&self) -> Option<[bool; 18]> {
fn spread_c(&self) -> Value<[bool; 18]> {
self.c_lo
.spread
.value()
@@ -153,7 +153,7 @@ impl UpperSigmaVar<4, 22, 18, 20> for AbcdVar {
})
}
fn spread_d(&self) -> Option<[bool; 20]> {
fn spread_d(&self) -> Value<[bool; 20]> {
self.d.spread.value().map(|v| v.0)
}
}
@@ -217,7 +217,7 @@ impl EfghVar {
}
impl UpperSigmaVar<12, 10, 28, 14> for EfghVar {
fn spread_a(&self) -> Option<[bool; 12]> {
fn spread_a(&self) -> Value<[bool; 12]> {
self.a_lo
.spread
.value()
@@ -232,7 +232,7 @@ impl UpperSigmaVar<12, 10, 28, 14> for EfghVar {
})
}
fn spread_b(&self) -> Option<[bool; 10]> {
fn spread_b(&self) -> Value<[bool; 10]> {
self.b_lo
.spread
.value()
@@ -247,11 +247,11 @@ impl UpperSigmaVar<12, 10, 28, 14> for EfghVar {
})
}
fn spread_c(&self) -> Option<[bool; 28]> {
fn spread_c(&self) -> Value<[bool; 28]> {
self.c.spread.value().map(|v| v.0)
}
fn spread_d(&self) -> Option<[bool; 14]> {
fn spread_d(&self) -> Value<[bool; 14]> {
self.d.spread.value().map(|v| v.0)
}
}
@@ -266,7 +266,7 @@ impl From<(AssignedBits<16>, AssignedBits<16>)> for RoundWordDense {
}
impl RoundWordDense {
pub fn value(&self) -> Option<u32> {
pub fn value(&self) -> Value<u32> {
self.0
.value_u16()
.zip(self.1.value_u16())
@@ -284,7 +284,7 @@ impl From<(AssignedBits<32>, AssignedBits<32>)> for RoundWordSpread {
}
impl RoundWordSpread {
pub fn value(&self) -> Option<u64> {
pub fn value(&self) -> Value<u64> {
self.0
.value_u32()
.zip(self.1.value_u32())
@@ -922,7 +922,7 @@ impl CompressionConfig {
layouter: &mut impl Layouter<pallas::Base>,
state: State,
) -> Result<[BlockWord; DIGEST_SIZE], Error> {
let mut digest = [BlockWord(Some(0)); DIGEST_SIZE];
let mut digest = [BlockWord(Value::known(0)); DIGEST_SIZE];
layouter.assign_region(
|| "digest",
|mut region| {
@@ -984,10 +984,10 @@ mod tests {
let digest = config.compression.digest(&mut layouter, state)?;
for (idx, digest_word) in digest.iter().enumerate() {
assert_eq!(
(digest_word.0.unwrap() as u64 + IV[idx] as u64) as u32,
super::compression_util::COMPRESSION_OUTPUT[idx]
);
digest_word.0.assert_if_known(|digest_word| {
(*digest_word as u64 + IV[idx] as u64) as u32
== super::compression_util::COMPRESSION_OUTPUT[idx]
});
}
Ok(())

View File

@@ -6,7 +6,7 @@ use crate::sha256::table16::{
util::*, AssignedBits, SpreadVar, SpreadWord, StateWord, Table16Assignment,
};
use halo2_proofs::{
circuit::Region,
circuit::{Region, Value},
pasta::pallas,
plonk::{Advice, Column, Error},
};
@@ -203,7 +203,7 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
row: usize,
val: Option<u32>,
val: Value<u32>,
) -> Result<AbcdVar, Error> {
self.s_decompose_abcd.enable(region, row)?;
@@ -213,7 +213,7 @@ impl CompressionConfig {
let a_6 = self.extras[2];
let spread_pieces = val.map(AbcdVar::pieces);
let spread_pieces = transpose_option_vec(spread_pieces, 6);
let spread_pieces = spread_pieces.transpose_vec(6);
let a = SpreadVar::without_lookup(
region,
@@ -274,7 +274,7 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
row: usize,
val: Option<u32>,
val: Value<u32>,
) -> Result<EfghVar, Error> {
self.s_decompose_efgh.enable(region, row)?;
@@ -284,7 +284,7 @@ impl CompressionConfig {
let a_6 = self.extras[2];
let spread_pieces = val.map(EfghVar::pieces);
let spread_pieces = transpose_option_vec(spread_pieces, 6);
let spread_pieces = spread_pieces.transpose_vec(6);
let a_lo = SpreadVar::without_lookup(
region,
@@ -345,7 +345,7 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
round_idx: RoundIdx,
a_val: Option<u32>,
a_val: Value<u32>,
) -> Result<RoundWordA, Error> {
let row = get_decompose_a_row(round_idx);
@@ -358,7 +358,7 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
round_idx: RoundIdx,
e_val: Option<u32>,
e_val: Value<u32>,
) -> Result<RoundWordE, Error> {
let row = get_decompose_e_row(round_idx);
@@ -405,11 +405,11 @@ impl CompressionConfig {
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let r = word.xor_upper_sigma();
let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0_even = r_0.map(even_bits);
let r_0_odd = r_0.map(odd_bits);
let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1_even = r_1.map(even_bits);
let r_1_odd = r_1.map(odd_bits);
@@ -464,11 +464,11 @@ impl CompressionConfig {
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let r = word.xor_upper_sigma();
let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0_even = r_0.map(even_bits);
let r_0_odd = r_0.map(odd_bits);
let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1_even = r_1.map(even_bits);
let r_1_odd = r_1.map(odd_bits);
@@ -488,10 +488,10 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
row: usize,
r_0_even: Option<[bool; 16]>,
r_0_odd: Option<[bool; 16]>,
r_1_even: Option<[bool; 16]>,
r_1_odd: Option<[bool; 16]>,
r_0_even: Value<[bool; 16]>,
r_0_odd: Value<[bool; 16]>,
r_1_even: Value<[bool; 16]>,
r_1_odd: Value<[bool; 16]>,
) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> {
let a_3 = self.extras[0];
@@ -539,16 +539,16 @@ impl CompressionConfig {
.1
.copy_advice(|| "spread_f_hi", region, a_4, row + 1)?;
let p: Option<[bool; 64]> = spread_halves_e
let p: Value<[bool; 64]> = spread_halves_e
.value()
.zip(spread_halves_f.value())
.map(|(e, f)| i2lebsp(e + f));
let p_0: Option<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap());
let p_0: Value<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap());
let p_0_even = p_0.map(even_bits);
let p_0_odd = p_0.map(odd_bits);
let p_1: Option<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap());
let p_1: Value<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap());
let p_1_even = p_1.map(even_bits);
let p_1_odd = p_1.map(odd_bits);
@@ -614,7 +614,7 @@ impl CompressionConfig {
spread_neg_e_hi,
)?;
let p: Option<[bool; 64]> = {
let p: Value<[bool; 64]> = {
let spread_neg_e = spread_neg_e_lo
.zip(spread_neg_e_hi)
.map(|(lo, hi)| lebs2ip(&lo) + (1 << 32) * lebs2ip(&hi));
@@ -623,11 +623,11 @@ impl CompressionConfig {
.map(|(neg_e, g)| i2lebsp(neg_e + g))
};
let p_0: Option<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap());
let p_0: Value<[bool; 32]> = p.map(|p| p[..32].try_into().unwrap());
let p_0_even = p_0.map(even_bits);
let p_0_odd = p_0.map(odd_bits);
let p_1: Option<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap());
let p_1: Value<[bool; 32]> = p.map(|p| p[32..].try_into().unwrap());
let p_1_even = p_1.map(even_bits);
let p_1_odd = p_1.map(odd_bits);
@@ -638,10 +638,10 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
row: usize,
r_0_even: Option<[bool; 16]>,
r_0_odd: Option<[bool; 16]>,
r_1_even: Option<[bool; 16]>,
r_1_odd: Option<[bool; 16]>,
r_0_even: Value<[bool; 16]>,
r_0_odd: Value<[bool; 16]>,
r_1_even: Value<[bool; 16]>,
r_1_odd: Value<[bool; 16]>,
) -> Result<(AssignedBits<16>, AssignedBits<16>), Error> {
let a_3 = self.extras[0];
let (_even, odd) = self.assign_spread_outputs(
@@ -697,17 +697,17 @@ impl CompressionConfig {
.1
.copy_advice(|| "spread_c_hi", region, a_5, row + 1)?;
let m: Option<[bool; 64]> = spread_halves_a
let m: Value<[bool; 64]> = spread_halves_a
.value()
.zip(spread_halves_b.value())
.zip(spread_halves_c.value())
.map(|((a, b), c)| i2lebsp(a + b + c));
let m_0: Option<[bool; 32]> = m.map(|m| m[..32].try_into().unwrap());
let m_0: Value<[bool; 32]> = m.map(|m| m[..32].try_into().unwrap());
let m_0_even = m_0.map(even_bits);
let m_0_odd = m_0.map(odd_bits);
let m_1: Option<[bool; 32]> = m.map(|m| m[32..].try_into().unwrap());
let m_1: Value<[bool; 32]> = m.map(|m| m[32..].try_into().unwrap());
let m_1_even = m_1.map(even_bits);
let m_1_odd = m_1.map(odd_bits);
@@ -750,8 +750,8 @@ impl CompressionConfig {
let k_lo: [bool; 16] = k[..16].try_into().unwrap();
let k_hi: [bool; 16] = k[16..].try_into().unwrap();
{
AssignedBits::<16>::assign_bits(region, || "k_lo", a_6, row - 1, Some(k_lo))?;
AssignedBits::<16>::assign_bits(region, || "k_hi", a_6, row, Some(k_hi))?;
AssignedBits::<16>::assign_bits(region, || "k_lo", a_6, row - 1, Value::known(k_lo))?;
AssignedBits::<16>::assign_bits(region, || "k_hi", a_6, row, Value::known(k_hi))?;
}
// Assign and copy w
@@ -772,7 +772,10 @@ impl CompressionConfig {
(ch.0.value_u16(), ch.1.value_u16()),
(ch_neg.0.value_u16(), ch_neg.1.value_u16()),
(sigma_1.0.value_u16(), sigma_1.1.value_u16()),
(Some(lebs2ip(&k_lo) as u16), Some(lebs2ip(&k_hi) as u16)),
(
Value::known(lebs2ip(&k_lo) as u16),
Value::known(lebs2ip(&k_hi) as u16),
),
(w.0.value_u16(), w.1.value_u16()),
]);
@@ -780,16 +783,12 @@ impl CompressionConfig {
|| "h_prime_carry",
a_9,
row + 1,
|| {
h_prime_carry
.map(|value| pallas::Base::from(value as u64))
.ok_or(Error::Synthesis)
},
|| h_prime_carry.map(|value| pallas::Base::from(value as u64)),
)?;
let h_prime: Option<[bool; 32]> = h_prime.map(|w| i2lebsp(w.into()));
let h_prime_lo: Option<[bool; 16]> = h_prime.map(|w| w[..16].try_into().unwrap());
let h_prime_hi: Option<[bool; 16]> = h_prime.map(|w| w[16..].try_into().unwrap());
let h_prime: Value<[bool; 32]> = h_prime.map(|w| i2lebsp(w.into()));
let h_prime_lo: Value<[bool; 16]> = h_prime.map(|w| w[..16].try_into().unwrap());
let h_prime_hi: Value<[bool; 16]> = h_prime.map(|w| w[16..].try_into().unwrap());
let h_prime_lo =
AssignedBits::<16>::assign_bits(region, || "h_prime_lo", a_7, row + 1, h_prime_lo)?;
@@ -831,7 +830,7 @@ impl CompressionConfig {
|| "e_new_carry",
a_9,
row + 1,
|| e_new_carry.map(pallas::Base::from).ok_or(Error::Synthesis),
|| e_new_carry.map(pallas::Base::from),
)?;
Ok(e_new_dense)
@@ -885,7 +884,7 @@ impl CompressionConfig {
|| "a_new_carry",
a_9,
row,
|| a_new_carry.map(pallas::Base::from).ok_or(Error::Synthesis),
|| a_new_carry.map(pallas::Base::from),
)?;
Ok(a_new_dense)
@@ -898,17 +897,17 @@ impl CompressionConfig {
lo_col: Column<Advice>,
hi_row: usize,
hi_col: Column<Advice>,
word: Option<u32>,
word: Value<u32>,
) -> Result<RoundWordDense, Error> {
let word: Option<[bool; 32]> = word.map(|w| i2lebsp(w.into()));
let word: Value<[bool; 32]> = word.map(|w| i2lebsp(w.into()));
let lo = {
let lo: Option<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap());
let lo: Value<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap());
AssignedBits::<16>::assign_bits(region, || "lo", lo_col, lo_row, lo)?
};
let hi = {
let hi: Option<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap());
let hi: Value<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap());
AssignedBits::<16>::assign_bits(region, || "hi", hi_col, hi_row, hi)?
};
@@ -921,15 +920,15 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
row: usize,
word: Option<u32>,
word: Value<u32>,
) -> Result<(RoundWordDense, RoundWordSpread), Error> {
// Rename these here for ease of matching the gates to the specification.
let a_7 = self.extras[3];
let a_8 = self.extras[4];
let word: Option<[bool; 32]> = word.map(|w| i2lebsp(w.into()));
let lo: Option<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap());
let hi: Option<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap());
let word: Value<[bool; 32]> = word.map(|w| i2lebsp(w.into()));
let lo: Value<[bool; 16]> = word.map(|w| w[..16].try_into().unwrap());
let hi: Value<[bool; 16]> = word.map(|w| w[16..].try_into().unwrap());
let w_lo = SpreadVar::without_lookup(region, a_7, row, a_8, row, lo.map(SpreadWord::new))?;
let w_hi =

View File

@@ -1,7 +1,7 @@
use super::super::{super::DIGEST_SIZE, BlockWord, RoundWordDense};
use super::{compression_util::*, CompressionConfig, State};
use halo2_proofs::{
circuit::Region,
circuit::{Region, Value},
pasta::pallas,
plonk::{Advice, Column, Error},
};
@@ -39,10 +39,7 @@ impl CompressionConfig {
|| "a",
a_5,
abcd_row,
|| {
a.map(|a| pallas::Base::from(a as u64))
.ok_or(Error::Synthesis)
},
|| a.map(|a| pallas::Base::from(a as u64)),
)?;
let b = self.assign_digest_word(region, abcd_row, a_6, a_7, a_8, b.dense_halves)?;
@@ -61,10 +58,7 @@ impl CompressionConfig {
|| "e",
a_5,
efgh_row,
|| {
e.map(|e| pallas::Base::from(e as u64))
.ok_or(Error::Synthesis)
},
|| e.map(|e| pallas::Base::from(e as u64)),
)?;
let f = self.assign_digest_word(region, efgh_row, a_6, a_7, a_8, f.dense_halves)?;
@@ -91,7 +85,7 @@ impl CompressionConfig {
hi_col: Column<Advice>,
word_col: Column<Advice>,
dense_halves: RoundWordDense,
) -> Result<Option<u32>, Error> {
) -> Result<Value<u32>, Error> {
dense_halves.0.copy_advice(|| "lo", region, lo_col, row)?;
dense_halves.1.copy_advice(|| "hi", region, hi_col, row)?;
@@ -100,10 +94,7 @@ impl CompressionConfig {
|| "word",
word_col,
row,
|| {
val.map(|val| pallas::Base::from(val as u64))
.ok_or(Error::Synthesis)
},
|| val.map(|val| pallas::Base::from(val as u64)),
)?;
Ok(val)

View File

@@ -1,6 +1,10 @@
use super::super::{RoundWord, StateWord, STATE};
use super::{compression_util::*, CompressionConfig, State};
use halo2_proofs::{circuit::Region, pasta::pallas, plonk::Error};
use halo2_proofs::{
circuit::{Region, Value},
pasta::pallas,
plonk::Error,
};
impl CompressionConfig {
#[allow(clippy::many_single_char_names)]
@@ -12,26 +16,28 @@ impl CompressionConfig {
let a_7 = self.extras[3];
// Decompose E into (6, 5, 14, 7)-bit chunks
let e = self.decompose_e(region, RoundIdx::Init, Some(iv[4]))?;
let e = self.decompose_e(region, RoundIdx::Init, Value::known(iv[4]))?;
// Decompose F, G
let f = self.decompose_f(region, InitialRound, Some(iv[5]))?;
let g = self.decompose_g(region, InitialRound, Some(iv[6]))?;
let f = self.decompose_f(region, InitialRound, Value::known(iv[5]))?;
let g = self.decompose_g(region, InitialRound, Value::known(iv[6]))?;
// Assign H
let h_row = get_h_row(RoundIdx::Init);
let h = self.assign_word_halves_dense(region, h_row, a_7, h_row + 1, a_7, Some(iv[7]))?;
let h =
self.assign_word_halves_dense(region, h_row, a_7, h_row + 1, a_7, Value::known(iv[7]))?;
// Decompose A into (2, 11, 9, 10)-bit chunks
let a = self.decompose_a(region, RoundIdx::Init, Some(iv[0]))?;
let a = self.decompose_a(region, RoundIdx::Init, Value::known(iv[0]))?;
// Decompose B, C
let b = self.decompose_b(region, InitialRound, Some(iv[1]))?;
let c = self.decompose_c(region, InitialRound, Some(iv[2]))?;
let b = self.decompose_b(region, InitialRound, Value::known(iv[1]))?;
let c = self.decompose_c(region, InitialRound, Value::known(iv[2]))?;
// Assign D
let d_row = get_d_row(RoundIdx::Init);
let d = self.assign_word_halves_dense(region, d_row, a_7, d_row + 1, a_7, Some(iv[3]))?;
let d =
self.assign_word_halves_dense(region, d_row, a_7, d_row + 1, a_7, Value::known(iv[3]))?;
Ok(State::new(
StateWord::A(a),
@@ -100,7 +106,7 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
round_idx: InitialRound,
b_val: Option<u32>,
b_val: Value<u32>,
) -> Result<RoundWord, Error> {
let row = get_decompose_b_row(round_idx);
@@ -113,7 +119,7 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
round_idx: InitialRound,
c_val: Option<u32>,
c_val: Value<u32>,
) -> Result<RoundWord, Error> {
let row = get_decompose_c_row(round_idx);
@@ -126,7 +132,7 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
round_idx: InitialRound,
f_val: Option<u32>,
f_val: Value<u32>,
) -> Result<RoundWord, Error> {
let row = get_decompose_f_row(round_idx);
@@ -139,7 +145,7 @@ impl CompressionConfig {
&self,
region: &mut Region<'_, pallas::Base>,
round_idx: InitialRound,
g_val: Option<u32>,
g_val: Value<u32>,
) -> Result<RoundWord, Error> {
let row = get_decompose_g_row(round_idx);

View File

@@ -435,8 +435,10 @@ mod tests {
// Run message_scheduler to get W_[0..64]
let (w, _) = config.message_schedule.process(&mut layouter, inputs)?;
for (word, test_word) in w.iter().zip(MSG_SCHEDULE_TEST_OUTPUT.iter()) {
let word: u32 = lebs2ip(word.value().unwrap()) as u32;
assert_eq!(word, *test_word);
word.value().assert_if_known(|bits| {
let word: u32 = lebs2ip(bits) as u32;
word == *test_word
});
}
Ok(())
}

View File

@@ -1,6 +1,10 @@
use super::super::AssignedBits;
use super::MessageScheduleConfig;
use halo2_proofs::{circuit::Region, pasta::pallas, plonk::Error};
use halo2_proofs::{
circuit::{Region, Value},
pasta::pallas,
plonk::Error,
};
#[cfg(test)]
use super::super::{super::BLOCK_SIZE, BlockWord, ROUNDS};
@@ -57,22 +61,22 @@ pub fn get_word_row(word_idx: usize) -> usize {
#[cfg(test)]
pub fn msg_schedule_test_input() -> [BlockWord; BLOCK_SIZE] {
[
BlockWord(Some(0b01100001011000100110001110000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000000000)),
BlockWord(Some(0b00000000000000000000000000011000)),
BlockWord(Value::known(0b01100001011000100110001110000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000000000)),
BlockWord(Value::known(0b00000000000000000000000000011000)),
]
}
@@ -149,7 +153,7 @@ impl MessageScheduleConfig {
pub fn assign_word_and_halves(
&self,
region: &mut Region<'_, pallas::Base>,
word: Option<u32>,
word: Value<u32>,
word_idx: usize,
) -> Result<(AssignedBits<32>, (AssignedBits<16>, AssignedBits<16>)), Error> {
// Rename these here for ease of matching the gates to the specification.

View File

@@ -1,6 +1,10 @@
use super::super::{util::*, AssignedBits, BlockWord, SpreadVar, SpreadWord, Table16Assignment};
use super::{schedule_util::*, MessageScheduleConfig};
use halo2_proofs::{circuit::Region, pasta::pallas, plonk::Error};
use halo2_proofs::{
circuit::{Region, Value},
pasta::pallas,
plonk::Error,
};
use std::convert::TryInto;
// A word in subregion 1
@@ -17,23 +21,23 @@ pub struct Subregion1Word {
}
impl Subregion1Word {
fn spread_a(&self) -> Option<[bool; 6]> {
fn spread_a(&self) -> Value<[bool; 6]> {
self.a.value().map(|v| v.spread())
}
fn spread_b(&self) -> Option<[bool; 8]> {
fn spread_b(&self) -> Value<[bool; 8]> {
self.b.value().map(|v| v.spread())
}
fn spread_c(&self) -> Option<[bool; 22]> {
fn spread_c(&self) -> Value<[bool; 22]> {
self.spread_c.value().map(|v| v.0)
}
fn spread_d(&self) -> Option<[bool; 28]> {
fn spread_d(&self) -> Value<[bool; 28]> {
self.spread_d.value().map(|v| v.0)
}
fn xor_lower_sigma_0(&self) -> Option<[bool; 64]> {
fn xor_lower_sigma_0(&self) -> Value<[bool; 64]> {
self.spread_a()
.zip(self.spread_b())
.zip(self.spread_c())
@@ -100,7 +104,7 @@ impl MessageScheduleConfig {
fn decompose_subregion1_word(
&self,
region: &mut Region<'_, pallas::Base>,
word: Option<[bool; 32]>,
word: Value<[bool; 32]>,
index: usize,
) -> Result<Subregion1Word, Error> {
let row = get_word_row(index);
@@ -117,7 +121,7 @@ impl MessageScheduleConfig {
word[18..32].to_vec(),
]
});
let pieces = transpose_option_vec(pieces, 4);
let pieces = pieces.transpose_vec(4);
// Assign `a` (3-bit piece)
let a =
@@ -168,7 +172,7 @@ impl MessageScheduleConfig {
// Split `b` (4-bit chunk) into `b_hi` and `b_lo`
// Assign `b_lo`, `spread_b_lo`
let b_lo: Option<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap());
let b_lo: Value<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap());
let spread_b_lo = b_lo.map(spread_bits);
{
AssignedBits::<2>::assign_bits(region, || "b_lo", a_3, row - 1, b_lo)?;
@@ -178,7 +182,7 @@ impl MessageScheduleConfig {
// Split `b` (2-bit chunk) into `b_hi` and `b_lo`
// Assign `b_hi`, `spread_b_hi`
let b_hi: Option<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap());
let b_hi: Value<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap());
let spread_b_hi = b_hi.map(spread_bits);
{
AssignedBits::<2>::assign_bits(region, || "b_hi", a_5, row - 1, b_hi)?;
@@ -197,11 +201,11 @@ impl MessageScheduleConfig {
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let r = word.xor_lower_sigma_0();
let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0_even = r_0.map(even_bits);
let r_0_odd = r_0.map(odd_bits);
let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1_even = r_1.map(even_bits);
let r_1_odd = r_1.map(odd_bits);

View File

@@ -1,6 +1,10 @@
use super::super::{util::*, AssignedBits, Bits, SpreadVar, SpreadWord, Table16Assignment};
use super::{schedule_util::*, MessageScheduleConfig, MessageWord};
use halo2_proofs::{circuit::Region, pasta::pallas, plonk::Error};
use halo2_proofs::{
circuit::{Region, Value},
pasta::pallas,
plonk::Error,
};
use std::convert::TryInto;
/// A word in subregion 2
@@ -20,35 +24,35 @@ pub struct Subregion2Word {
}
impl Subregion2Word {
fn spread_a(&self) -> Option<[bool; 6]> {
fn spread_a(&self) -> Value<[bool; 6]> {
self.a.value().map(|v| v.spread())
}
fn spread_b(&self) -> Option<[bool; 8]> {
fn spread_b(&self) -> Value<[bool; 8]> {
self.b.value().map(|v| v.spread())
}
fn spread_c(&self) -> Option<[bool; 6]> {
fn spread_c(&self) -> Value<[bool; 6]> {
self.c.value().map(|v| v.spread())
}
fn spread_d(&self) -> Option<[bool; 14]> {
fn spread_d(&self) -> Value<[bool; 14]> {
self.spread_d.value().map(|v| v.0)
}
fn spread_e(&self) -> Option<[bool; 2]> {
fn spread_e(&self) -> Value<[bool; 2]> {
self.e.value().map(|v| v.spread())
}
fn spread_f(&self) -> Option<[bool; 2]> {
fn spread_f(&self) -> Value<[bool; 2]> {
self.f.value().map(|v| v.spread())
}
fn spread_g(&self) -> Option<[bool; 26]> {
fn spread_g(&self) -> Value<[bool; 26]> {
self.spread_g.value().map(|v| v.0)
}
fn xor_sigma_0(&self) -> Option<[bool; 64]> {
fn xor_sigma_0(&self) -> Value<[bool; 64]> {
self.spread_a()
.zip(self.spread_b())
.zip(self.spread_c())
@@ -98,7 +102,7 @@ impl Subregion2Word {
})
}
fn xor_sigma_1(&self) -> Option<[bool; 64]> {
fn xor_sigma_1(&self) -> Value<[bool; 64]> {
self.spread_a()
.zip(self.spread_b())
.zip(self.spread_c())
@@ -251,20 +255,13 @@ impl MessageScheduleConfig {
|| format!("W_{}", new_word_idx),
a_5,
get_word_row(new_word_idx - 16) + 1,
|| {
word.map(|word| pallas::Base::from(word as u64))
.ok_or(Error::Synthesis)
},
|| word.map(|word| pallas::Base::from(word as u64)),
)?;
region.assign_advice(
|| format!("carry_{}", new_word_idx),
a_9,
get_word_row(new_word_idx - 16) + 1,
|| {
carry
.map(|carry| pallas::Base::from(carry as u64))
.ok_or(Error::Synthesis)
},
|| carry.map(|carry| pallas::Base::from(carry as u64)),
)?;
let (word, halves) = self.assign_word_and_halves(region, word, new_word_idx)?;
w.push(MessageWord(word));
@@ -294,7 +291,7 @@ impl MessageScheduleConfig {
fn decompose_word(
&self,
region: &mut Region<'_, pallas::Base>,
word: Option<&Bits<32>>,
word: Value<&Bits<32>>,
index: usize,
) -> Result<Subregion2Word, Error> {
let row = get_word_row(index);
@@ -310,7 +307,7 @@ impl MessageScheduleConfig {
word[19..32].to_vec(),
]
});
let pieces = transpose_option_vec(pieces, 7);
let pieces = pieces.transpose_vec(7);
// Rename these here for ease of matching the gates to the specification.
let a_3 = self.extras[0];
@@ -320,14 +317,14 @@ impl MessageScheduleConfig {
let a = AssignedBits::<3>::assign_bits(region, || "a", a_3, row - 1, pieces[0].clone())?;
// Assign `b` (4-bit piece) lookup
let spread_b: Option<SpreadWord<4, 8>> = pieces[1].clone().map(SpreadWord::try_new);
let spread_b: Value<SpreadWord<4, 8>> = pieces[1].clone().map(SpreadWord::try_new);
let spread_b = SpreadVar::with_lookup(region, &self.lookup, row + 1, spread_b)?;
// Assign `c` (3-bit piece)
let c = AssignedBits::<3>::assign_bits(region, || "c", a_4, row - 1, pieces[2].clone())?;
// Assign `d` (7-bit piece) lookup
let spread_d: Option<SpreadWord<7, 14>> = pieces[3].clone().map(SpreadWord::try_new);
let spread_d: Value<SpreadWord<7, 14>> = pieces[3].clone().map(SpreadWord::try_new);
let spread_d = SpreadVar::with_lookup(region, &self.lookup, row, spread_d)?;
// Assign `e` (1-bit piece)
@@ -378,7 +375,7 @@ impl MessageScheduleConfig {
// Split `b` (4-bit chunk) into `b_hi` and `b_lo`
// Assign `b_lo`, `spread_b_lo`
let b_lo: Option<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap());
let b_lo: Value<[bool; 2]> = word.b.value().map(|b| b.0[..2].try_into().unwrap());
let spread_b_lo = b_lo.map(spread_bits);
{
AssignedBits::<2>::assign_bits(region, || "b_lo", a_3, row - 1, b_lo)?;
@@ -388,7 +385,7 @@ impl MessageScheduleConfig {
// Split `b` (2-bit chunk) into `b_hi` and `b_lo`
// Assign `b_hi`, `spread_b_hi`
let b_hi: Option<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap());
let b_hi: Value<[bool; 2]> = word.b.value().map(|b| b.0[2..].try_into().unwrap());
let spread_b_hi = b_hi.map(spread_bits);
{
AssignedBits::<2>::assign_bits(region, || "b_hi", a_5, row - 1, b_hi)?;
@@ -433,11 +430,11 @@ impl MessageScheduleConfig {
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let r = word.xor_sigma_0();
let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0_even = r_0.map(even_bits);
let r_0_odd = r_0.map(odd_bits);
let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1_even = r_1.map(even_bits);
let r_1_odd = r_1.map(odd_bits);
@@ -468,11 +465,11 @@ impl MessageScheduleConfig {
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let r = word.xor_sigma_1();
let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0_even = r_0.map(even_bits);
let r_0_odd = r_0.map(odd_bits);
let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1_even = r_1.map(even_bits);
let r_1_odd = r_1.map(odd_bits);

View File

@@ -1,6 +1,10 @@
use super::super::{util::*, AssignedBits, Bits, SpreadVar, SpreadWord, Table16Assignment};
use super::{schedule_util::*, MessageScheduleConfig, MessageWord};
use halo2_proofs::{circuit::Region, pasta::pallas, plonk::Error};
use halo2_proofs::{
circuit::{Region, Value},
pasta::pallas,
plonk::Error,
};
use std::convert::TryInto;
// A word in subregion 3
@@ -18,23 +22,23 @@ pub struct Subregion3Word {
}
impl Subregion3Word {
fn spread_a(&self) -> Option<[bool; 20]> {
fn spread_a(&self) -> Value<[bool; 20]> {
self.spread_a.value().map(|v| v.0)
}
fn spread_b(&self) -> Option<[bool; 14]> {
fn spread_b(&self) -> Value<[bool; 14]> {
self.b.value().map(|v| v.spread())
}
fn spread_c(&self) -> Option<[bool; 4]> {
fn spread_c(&self) -> Value<[bool; 4]> {
self.c.value().map(|v| v.spread())
}
fn spread_d(&self) -> Option<[bool; 26]> {
fn spread_d(&self) -> Value<[bool; 26]> {
self.spread_d.value().map(|v| v.0)
}
fn xor_lower_sigma_1(&self) -> Option<[bool; 64]> {
fn xor_lower_sigma_1(&self) -> Value<[bool; 64]> {
self.spread_a()
.zip(self.spread_b())
.zip(self.spread_c())
@@ -167,20 +171,13 @@ impl MessageScheduleConfig {
|| format!("W_{}", new_word_idx),
a_5,
get_word_row(new_word_idx - 16) + 1,
|| {
word.map(|word| pallas::Base::from(word as u64))
.ok_or(Error::Synthesis)
},
|| word.map(|word| pallas::Base::from(word as u64)),
)?;
region.assign_advice(
|| format!("carry_{}", new_word_idx),
a_9,
get_word_row(new_word_idx - 16) + 1,
|| {
carry
.map(|carry| pallas::Base::from(carry as u64))
.ok_or(Error::Synthesis)
},
|| carry.map(|carry| pallas::Base::from(carry as u64)),
)?;
let (word, halves) = self.assign_word_and_halves(region, word, new_word_idx)?;
w.push(MessageWord(word));
@@ -200,7 +197,7 @@ impl MessageScheduleConfig {
fn decompose_subregion3_word(
&self,
region: &mut Region<'_, pallas::Base>,
word: Option<&Bits<32>>,
word: Value<&Bits<32>>,
index: usize,
) -> Result<Subregion3Word, Error> {
let row = get_word_row(index);
@@ -217,7 +214,7 @@ impl MessageScheduleConfig {
word[19..32].to_vec(),
]
});
let pieces = transpose_option_vec(pieces, 4);
let pieces = pieces.transpose_vec(4);
// Assign `a` (10-bit piece)
let spread_a = pieces[0].clone().map(SpreadWord::try_new);
@@ -264,21 +261,21 @@ impl MessageScheduleConfig {
// b_lo (2-bit chunk)
{
let b_lo: Option<[bool; 2]> = word.b.value().map(|v| v[0..2].try_into().unwrap());
let b_lo: Value<[bool; 2]> = word.b.value().map(|v| v[0..2].try_into().unwrap());
let b_lo = b_lo.map(SpreadWord::<2, 4>::new);
SpreadVar::without_lookup(region, a_3, row - 1, a_4, row - 1, b_lo)?;
}
// b_mid (2-bit chunk)
{
let b_mid: Option<[bool; 2]> = word.b.value().map(|v| v[2..4].try_into().unwrap());
let b_mid: Value<[bool; 2]> = word.b.value().map(|v| v[2..4].try_into().unwrap());
let b_mid = b_mid.map(SpreadWord::<2, 4>::new);
SpreadVar::without_lookup(region, a_5, row - 1, a_6, row - 1, b_mid)?;
}
// b_hi (3-bit chunk)
{
let b_hi: Option<[bool; 3]> = word.b.value().map(|v| v[4..7].try_into().unwrap());
let b_hi: Value<[bool; 3]> = word.b.value().map(|v| v[4..7].try_into().unwrap());
let b_hi = b_hi.map(SpreadWord::<3, 6>::new);
SpreadVar::without_lookup(region, a_5, row + 1, a_6, row + 1, b_hi)?;
}
@@ -301,11 +298,11 @@ impl MessageScheduleConfig {
// (10, 7, 2, 13)
// Calculate R_0^{even}, R_0^{odd}, R_1^{even}, R_1^{odd}
let r = word.xor_lower_sigma_1();
let r_0: Option<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0: Value<[bool; 32]> = r.map(|r| r[..32].try_into().unwrap());
let r_0_even = r_0.map(even_bits);
let r_0_odd = r_0.map(odd_bits);
let r_1: Option<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1: Value<[bool; 32]> = r.map(|r| r[32..].try_into().unwrap());
let r_1_even = r_1.map(even_bits);
let r_1_odd = r_1.map(odd_bits);

View File

@@ -1,7 +1,7 @@
use super::{util::*, AssignedBits};
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{Chip, Layouter, Region},
circuit::{Chip, Layouter, Region, Value},
pasta::pallas,
plonk::{Advice, Column, ConstraintSystem, Error, TableColumn},
poly::Rotation,
@@ -68,7 +68,7 @@ impl<const DENSE: usize, const SPREAD: usize> SpreadWord<DENSE, SPREAD> {
/// A variable stored in advice columns corresponding to a row of [`SpreadTableConfig`].
#[derive(Clone, Debug)]
pub(super) struct SpreadVar<const DENSE: usize, const SPREAD: usize> {
pub tag: Option<u8>,
pub tag: Value<u8>,
pub dense: AssignedBits<DENSE>,
pub spread: AssignedBits<SPREAD>,
}
@@ -78,7 +78,7 @@ impl<const DENSE: usize, const SPREAD: usize> SpreadVar<DENSE, SPREAD> {
region: &mut Region<'_, pallas::Base>,
cols: &SpreadInputs,
row: usize,
word: Option<SpreadWord<DENSE, SPREAD>>,
word: Value<SpreadWord<DENSE, SPREAD>>,
) -> Result<Self, Error> {
let tag = word.map(|word| word.tag);
let dense_val = word.map(|word| word.dense);
@@ -88,10 +88,7 @@ impl<const DENSE: usize, const SPREAD: usize> SpreadVar<DENSE, SPREAD> {
|| "tag",
cols.tag,
row,
|| {
tag.map(|tag| pallas::Base::from(tag as u64))
.ok_or(Error::Synthesis)
},
|| tag.map(|tag| pallas::Base::from(tag as u64)),
)?;
let dense =
@@ -109,7 +106,7 @@ impl<const DENSE: usize, const SPREAD: usize> SpreadVar<DENSE, SPREAD> {
dense_row: usize,
spread_col: Column<Advice>,
spread_row: usize,
word: Option<SpreadWord<DENSE, SPREAD>>,
word: Value<SpreadWord<DENSE, SPREAD>>,
) -> Result<Self, Error> {
let tag = word.map(|word| word.tag);
let dense_val = word.map(|word| word.dense);
@@ -229,20 +226,20 @@ impl<F: FieldExt> SpreadTableChip<F> {
index,
|| {
row = rows.next();
row.map(|(tag, _, _)| tag).ok_or(Error::Synthesis)
Value::known(row.map(|(tag, _, _)| tag).unwrap())
},
)?;
table.assign_cell(
|| "dense",
config.table.dense,
index,
|| row.map(|(_, dense, _)| dense).ok_or(Error::Synthesis),
|| Value::known(row.map(|(_, dense, _)| dense).unwrap()),
)?;
table.assign_cell(
|| "spread",
config.table.spread,
index,
|| row.map(|(_, _, spread)| spread).ok_or(Error::Synthesis),
|| Value::known(row.map(|(_, _, spread)| spread).unwrap()),
)?;
}
@@ -292,7 +289,7 @@ mod tests {
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
pasta::Fp,
plonk::{Advice, Circuit, Column, ConstraintSystem, Error},
@@ -334,13 +331,23 @@ mod tests {
|mut gate| {
let mut row = 0;
let mut add_row = |tag, dense, spread| -> Result<(), Error> {
gate.assign_advice(|| "tag", config.input.tag, row, || Ok(tag))?;
gate.assign_advice(|| "dense", config.input.dense, row, || Ok(dense))?;
gate.assign_advice(
|| "tag",
config.input.tag,
row,
|| Value::known(tag),
)?;
gate.assign_advice(
|| "dense",
config.input.dense,
row,
|| Value::known(dense),
)?;
gate.assign_advice(
|| "spread",
config.input.spread,
row,
|| Ok(spread),
|| Value::known(spread),
)?;
row += 1;
Ok(())

View File

@@ -1,3 +1,5 @@
use halo2_proofs::circuit::Value;
pub const MASK_EVEN_32: u32 = 0x55555555;
/// The sequence of bits representing a u64 in little-endian order.
@@ -96,25 +98,15 @@ pub fn odd_bits<const LEN: usize, const HALF: usize>(bits: [bool; LEN]) -> [bool
odd_bits
}
/// Helper function to transpose an Option<Vec<F>> to a Vec<Option<F>>.
/// The length of the vector must be `len`.
pub fn transpose_option_vec<T: Clone>(vec: Option<Vec<T>>, len: usize) -> Vec<Option<T>> {
if let Some(vec) = vec {
vec.into_iter().map(Some).collect()
} else {
vec![None; len]
}
}
/// Given a vector of words as vec![(lo: u16, hi: u16)], returns their sum: u32, along
/// with a carry bit.
pub fn sum_with_carry(words: Vec<(Option<u16>, Option<u16>)>) -> (Option<u32>, Option<u64>) {
let words_lo: Option<Vec<u64>> = words.iter().map(|(lo, _)| lo.map(|lo| lo as u64)).collect();
let words_hi: Option<Vec<u64>> = words.iter().map(|(_, hi)| hi.map(|hi| hi as u64)).collect();
pub fn sum_with_carry(words: Vec<(Value<u16>, Value<u16>)>) -> (Value<u32>, Value<u64>) {
let words_lo: Value<Vec<u64>> = words.iter().map(|(lo, _)| lo.map(|lo| lo as u64)).collect();
let words_hi: Value<Vec<u64>> = words.iter().map(|(_, hi)| hi.map(|hi| hi as u64)).collect();
let sum: Option<u64> = {
let sum_lo: Option<u64> = words_lo.map(|vec| vec.iter().sum());
let sum_hi: Option<u64> = words_hi.map(|vec| vec.iter().sum());
let sum: Value<u64> = {
let sum_lo: Value<u64> = words_lo.map(|vec| vec.iter().sum());
let sum_hi: Value<u64> = words_hi.map(|vec| vec.iter().sum());
sum_lo.zip(sum_hi).map(|(lo, hi)| lo + (1 << 16) * hi)
};

View File

@@ -6,7 +6,10 @@ use crate::{
utilities::{FieldValue, RangeConstrained, Var},
};
use group::ff::{Field, PrimeField};
use halo2_proofs::{circuit::Layouter, plonk::Error};
use halo2_proofs::{
circuit::{Layouter, Value},
plonk::Error,
};
use pasta_curves::arithmetic::CurveAffine;
use std::fmt::Debug;
@@ -67,7 +70,7 @@ pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS
fn witness_message_piece(
&self,
layouter: impl Layouter<C::Base>,
value: Option<C::Base>,
value: Value<C::Base>,
num_words: usize,
) -> Result<Self::MessagePiece, Error>;
@@ -111,7 +114,7 @@ where
fn from_bitstring(
chip: SinsemillaChip,
mut layouter: impl Layouter<C::Base>,
bitstring: Vec<Option<bool>>,
bitstring: Vec<Value<bool>>,
) -> Result<Self, Error> {
// Message must be composed of `K`-bit words.
assert_eq!(bitstring.len() % K, 0);
@@ -185,7 +188,7 @@ where
fn from_bitstring(
chip: SinsemillaChip,
layouter: impl Layouter<C::Base>,
bitstring: &[Option<bool>],
bitstring: &[Value<bool>],
) -> Result<Self, Error> {
// Message must be composed of `K`-bit words.
assert_eq!(bitstring.len() % K, 0);
@@ -197,8 +200,8 @@ where
assert!(num_words <= piece_max_num_words as usize);
// Closure to parse a bitstring (little-endian) into a base field element.
let to_base_field = |bits: &[Option<bool>]| -> Option<C::Base> {
let bits: Option<Vec<bool>> = bits.iter().cloned().collect();
let to_base_field = |bits: &[Value<bool>]| -> Value<C::Base> {
let bits: Value<Vec<bool>> = bits.iter().cloned().collect();
bits.map(|bits| {
bits.into_iter().rev().fold(C::Base::zero(), |acc, bit| {
if bit {
@@ -218,7 +221,7 @@ where
pub fn from_field_elem(
chip: SinsemillaChip,
layouter: impl Layouter<C::Base>,
field_elem: Option<C::Base>,
field_elem: Value<C::Base>,
num_words: usize,
) -> Result<Self, Error> {
let inner = chip.witness_message_piece(layouter, field_elem, num_words)?;
@@ -237,22 +240,19 @@ where
pub fn from_subpieces(
chip: SinsemillaChip,
layouter: impl Layouter<C::Base>,
subpieces: impl IntoIterator<Item = RangeConstrained<C::Base, Option<C::Base>>>,
subpieces: impl IntoIterator<Item = RangeConstrained<C::Base, Value<C::Base>>>,
) -> Result<Self, Error> {
let (field_elem, total_bits) =
subpieces
.into_iter()
.fold((Some(C::Base::zero()), 0), |(acc, bits), subpiece| {
assert!(bits < 64);
let subpiece_shifted = subpiece
.inner()
.value()
.map(|v| C::Base::from(1 << bits) * v);
(
acc.zip(subpiece_shifted).map(|(a, b)| a + b),
bits + subpiece.num_bits(),
)
});
let (field_elem, total_bits) = subpieces.into_iter().fold(
(Value::known(C::Base::zero()), 0),
|(acc, bits), subpiece| {
assert!(bits < 64);
let subpiece_shifted = subpiece
.inner()
.value()
.map(|v| C::Base::from(1 << bits) * v);
(acc + subpiece_shifted, bits + subpiece.num_bits())
},
);
// Message must be composed of `K`-bit words.
assert_eq!(total_bits % K, 0);
@@ -453,7 +453,7 @@ where
#[cfg(test)]
pub(crate) mod tests {
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
@@ -615,7 +615,7 @@ pub(crate) mod tests {
let merkle_crh = HashDomain::new(chip1.clone(), ecc_chip.clone(), &TestHashDomain);
// Layer 31, l = MERKLE_DEPTH - 1 - layer = 0
let l_bitstring = vec![Some(false); K];
let l_bitstring = vec![Value::known(false); K];
let l = MessagePiece::from_bitstring(
chip1.clone(),
layouter.namespace(|| "l"),
@@ -623,8 +623,9 @@ pub(crate) mod tests {
)?;
// Left leaf
let left_bitstring: Vec<Option<bool>> =
(0..250).map(|_| Some(rand::random::<bool>())).collect();
let left_bitstring: Vec<Value<bool>> = (0..250)
.map(|_| Value::known(rand::random::<bool>()))
.collect();
let left = MessagePiece::from_bitstring(
chip1.clone(),
layouter.namespace(|| "left"),
@@ -632,35 +633,34 @@ pub(crate) mod tests {
)?;
// Right leaf
let right_bitstring: Vec<Option<bool>> =
(0..250).map(|_| Some(rand::random::<bool>())).collect();
let right_bitstring: Vec<Value<bool>> = (0..250)
.map(|_| Value::known(rand::random::<bool>()))
.collect();
let right = MessagePiece::from_bitstring(
chip1.clone(),
layouter.namespace(|| "right"),
&right_bitstring,
)?;
let l_bitstring: Option<Vec<bool>> = l_bitstring.into_iter().collect();
let left_bitstring: Option<Vec<bool>> = left_bitstring.into_iter().collect();
let right_bitstring: Option<Vec<bool>> = right_bitstring.into_iter().collect();
let l_bitstring: Value<Vec<bool>> = l_bitstring.into_iter().collect();
let left_bitstring: Value<Vec<bool>> = left_bitstring.into_iter().collect();
let right_bitstring: Value<Vec<bool>> = right_bitstring.into_iter().collect();
// Witness expected parent
let expected_parent = {
let expected_parent = if let (Some(l), Some(left), Some(right)) =
(l_bitstring, left_bitstring, right_bitstring)
{
let merkle_crh = sinsemilla::HashDomain::from_Q((*Q).into());
let point = merkle_crh
.hash_to_point(
l.into_iter()
.chain(left.into_iter())
.chain(right.into_iter()),
)
.unwrap();
Some(point.to_affine())
} else {
None
};
let expected_parent = l_bitstring.zip(left_bitstring.zip(right_bitstring)).map(
|(l, (left, right))| {
let merkle_crh = sinsemilla::HashDomain::from_Q((*Q).into());
let point = merkle_crh
.hash_to_point(
l.into_iter()
.chain(left.into_iter())
.chain(right.into_iter()),
)
.unwrap();
point.to_affine()
},
);
NonIdentityPoint::new(
ecc_chip.clone(),
@@ -687,14 +687,15 @@ pub(crate) mod tests {
let test_commit =
CommitDomain::new(chip2.clone(), ecc_chip.clone(), &TestCommitDomain);
let r_val = pallas::Scalar::random(rng);
let message: Vec<Option<bool>> =
(0..500).map(|_| Some(rand::random::<bool>())).collect();
let message: Vec<Value<bool>> = (0..500)
.map(|_| Value::known(rand::random::<bool>()))
.collect();
let (result, _) = {
let r = ScalarFixed::new(
ecc_chip.clone(),
layouter.namespace(|| "r"),
Some(r_val),
Value::known(r_val),
)?;
let message = Message::from_bitstring(
chip2,
@@ -706,14 +707,12 @@ pub(crate) mod tests {
// Witness expected result.
let expected_result = {
let message: Option<Vec<bool>> = message.into_iter().collect();
let expected_result = if let Some(message) = message {
let message: Value<Vec<bool>> = message.into_iter().collect();
let expected_result = message.map(|message| {
let domain = sinsemilla::CommitDomain::new(PERSONALIZATION);
let point = domain.commit(message.into_iter(), &r_val).unwrap();
Some(point.to_affine())
} else {
None
};
point.to_affine()
});
NonIdentityPoint::new(
ecc_chip,

View File

@@ -14,7 +14,7 @@ use crate::{
use std::marker::PhantomData;
use halo2_proofs::{
circuit::{AssignedCell, Chip, Layouter},
circuit::{AssignedCell, Chip, Layouter, Value},
plonk::{
Advice, Column, ConstraintSystem, Constraints, Error, Expression, Fixed, Selector,
TableColumn, VirtualCells,
@@ -288,7 +288,7 @@ where
fn witness_message_piece(
&self,
mut layouter: impl Layouter<pallas::Base>,
field_elem: Option<pallas::Base>,
field_elem: Value<pallas::Base>,
num_words: usize,
) -> Result<Self::MessagePiece, Error> {
let config = self.config().clone();
@@ -300,7 +300,7 @@ where
|| "witness message piece",
config.witness_pieces,
0,
|| field_elem.ok_or(Error::Synthesis),
|| field_elem,
)
},
)?;

View File

@@ -1,5 +1,5 @@
use halo2_proofs::{
circuit::Layouter,
circuit::{Layouter, Value},
plonk::{ConstraintSystem, Error, Expression, TableColumn},
poly::Rotation,
};
@@ -85,10 +85,10 @@ impl GeneratorTableConfig {
|| "table_idx",
self.table_idx,
index,
|| Ok(pallas::Base::from(index as u64)),
|| Value::known(pallas::Base::from(index as u64)),
)?;
table.assign_cell(|| "table_x", self.table_x, index, || Ok(*x))?;
table.assign_cell(|| "table_y", self.table_y, index, || Ok(*y))?;
table.assign_cell(|| "table_x", self.table_x, index, || Value::known(*x))?;
table.assign_cell(|| "table_y", self.table_y, index, || Value::known(*y))?;
}
Ok(())
},

View File

@@ -5,9 +5,8 @@ use crate::{
sinsemilla::primitives::{self as sinsemilla, lebs2ip_k, INV_TWO_POW_K, SINSEMILLA_S},
};
use halo2_proofs::circuit::AssignedCell;
use halo2_proofs::{
circuit::{Chip, Region},
circuit::{AssignedCell, Chip, Region, Value},
plonk::{Assigned, Error},
};
@@ -56,9 +55,14 @@ where
let mut y_a: Y<pallas::Base> = {
// Enable `q_sinsemilla4` on the first row.
config.q_sinsemilla4.enable(region, offset)?;
region.assign_fixed(|| "fixed y_q", config.fixed_y_q, offset, || Ok(y_q))?;
region.assign_fixed(
|| "fixed y_q",
config.fixed_y_q,
offset,
|| Value::known(y_q),
)?;
(Some(y_q.into())).into()
Value::known(y_q.into()).into()
};
// Constrain the initial x_q to equal the x-coordinate of the domain's `Q`.
@@ -95,12 +99,8 @@ where
// Assign the final y_a.
let y_a = {
// Assign the final y_a.
let y_a_cell = region.assign_advice(
|| "y_a",
config.double_and_add.lambda_1,
offset,
|| y_a.ok_or(Error::Synthesis),
)?;
let y_a_cell =
region.assign_advice(|| "y_a", config.double_and_add.lambda_1, offset, || y_a.0)?;
// Assign lambda_2 and x_p zero values since they are queried
// in the gate. (The actual values do not matter since they are
@@ -110,13 +110,13 @@ where
|| "dummy lambda2",
config.double_and_add.lambda_2,
offset,
|| Ok(pallas::Base::zero()),
|| Value::known(pallas::Base::zero()),
)?;
region.assign_advice(
|| "dummy x_p",
config.double_and_add.x_p,
offset,
|| Ok(pallas::Base::zero()),
|| Value::known(pallas::Base::zero()),
)?;
}
@@ -127,55 +127,44 @@ where
#[allow(non_snake_case)]
// Check equivalence to result from primitives::sinsemilla::hash_to_point
{
use crate::sinsemilla::{
message::MessagePiece,
primitives::{K, S_PERSONALIZATION},
};
use crate::sinsemilla::primitives::{K, S_PERSONALIZATION};
use group::{prime::PrimeCurveAffine, Curve};
use pasta_curves::arithmetic::CurveExt;
let field_elems: Option<Vec<pallas::Base>> =
message.iter().map(|piece| piece.field_elem()).collect();
let field_elems: Value<Vec<_>> = message
.iter()
.map(|piece| piece.field_elem().map(|elem| (elem, piece.num_words())))
.collect();
if field_elems.is_some() && x_a.value().is_some() && y_a.value().is_some() {
// Get message as a bitstring.
let bitstring: Vec<bool> = message
.iter()
.flat_map(|piece: &MessagePiece<pallas::Base, K>| {
piece
.field_elem()
.unwrap()
.to_le_bits()
.into_iter()
.take(K * piece.num_words())
})
.collect();
field_elems
.zip(x_a.value().zip(y_a.value()))
.assert_if_known(|(field_elems, (x_a, y_a))| {
// Get message as a bitstring.
let bitstring: Vec<bool> = field_elems
.iter()
.flat_map(|(elem, num_words)| {
elem.to_le_bits().into_iter().take(K * num_words)
})
.collect();
let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION);
let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes());
let hasher_S = pallas::Point::hash_to_curve(S_PERSONALIZATION);
let S = |chunk: &[bool]| hasher_S(&lebs2ip_k(chunk).to_le_bytes());
// We can use complete addition here because it differs from
// incomplete addition with negligible probability.
let expected_point = bitstring
.chunks(K)
.fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc);
let actual_point = pallas::Affine::from_xy(
x_a.value().unwrap().evaluate(),
y_a.value().unwrap().evaluate(),
)
.unwrap();
assert_eq!(expected_point.to_affine(), actual_point);
}
// We can use complete addition here because it differs from
// incomplete addition with negligible probability.
let expected_point = bitstring
.chunks(K)
.fold(Q.to_curve(), |acc, chunk| (acc + S(chunk)) + acc);
let actual_point =
pallas::Affine::from_xy(x_a.evaluate(), y_a.evaluate()).unwrap();
expected_point.to_affine() == actual_point
});
}
if let Some(x_a) = x_a.value() {
if let Some(y_a) = y_a.value() {
if x_a.is_zero_vartime() || y_a.is_zero_vartime() {
return Err(Error::Synthesis);
}
}
}
x_a.value()
.zip(y_a.value())
.error_if_known_and(|(x_a, y_a)| x_a.is_zero_vartime() || y_a.is_zero_vartime())?;
Ok((
NonIdentityEccPoint::from_coordinates_unchecked(x_a.0, y_a),
zs_sum,
@@ -224,7 +213,7 @@ where
|| "q_s2 = 1",
config.q_sinsemilla2,
offset + row,
|| Ok(pallas::Base::one()),
|| Value::known(pallas::Base::one()),
)?;
}
@@ -241,7 +230,7 @@ where
config.q_sinsemilla2,
offset + piece.num_words() - 1,
|| {
Ok(if final_piece {
Value::known(if final_piece {
pallas::Base::from(2)
} else {
pallas::Base::zero()
@@ -251,7 +240,7 @@ where
}
// Message piece as K * piece.length bitstring
let bitstring: Option<Vec<bool>> = piece.field_elem().map(|value| {
let bitstring: Value<Vec<bool>> = piece.field_elem().map(|value| {
value
.to_le_bits()
.into_iter()
@@ -259,7 +248,7 @@ where
.collect()
});
let words: Option<Vec<u32>> = bitstring.map(|bitstring| {
let words: Value<Vec<u32>> = bitstring.map(|bitstring| {
bitstring
.chunks_exact(sinsemilla::K)
.map(lebs2ip_k)
@@ -267,19 +256,15 @@ where
});
// Get (x_p, y_p) for each word.
let generators: Option<Vec<(pallas::Base, pallas::Base)>> = words.clone().map(|words| {
let generators: Value<Vec<(pallas::Base, pallas::Base)>> = words.clone().map(|words| {
words
.iter()
.map(|word| SINSEMILLA_S[*word as usize])
.collect()
});
// Convert `words` from `Option<Vec<u32>>` to `Vec<Option<u32>>`
let words: Vec<Option<u32>> = if let Some(words) = words {
words.into_iter().map(Some).collect()
} else {
vec![None; piece.num_words()]
};
// Convert `words` from `Value<Vec<u32>>` to `Vec<Value<u32>>`
let words = words.transpose_vec(piece.num_words());
// Decompose message piece into `K`-bit pieces with a running sum `z`.
let zs = {
@@ -302,19 +287,18 @@ where
// We end up with z_n = 0. (z_n is not directly encoded as a cell value;
// it is implicitly taken as 0 by adjusting the definition of m_{i+1}.)
let mut z = piece.field_elem();
let inv_2_k = pallas::Base::from_repr(INV_TWO_POW_K).unwrap();
let inv_2_k = Value::known(pallas::Base::from_repr(INV_TWO_POW_K).unwrap());
// We do not assign the final z_n as it is constrained to be zero.
for (idx, word) in words[0..(words.len() - 1)].iter().enumerate() {
let word = word.map(|word| pallas::Base::from(word as u64));
// z_{i + 1} = (z_i - m_{i + 1}) / 2^K
z = z
.zip(*word)
.map(|(z, word)| (z - pallas::Base::from(word as u64)) * inv_2_k);
z = (z - word) * inv_2_k;
let cell = region.assign_advice(
|| format!("z_{:?}", idx + 1),
config.bits,
offset + idx + 1,
|| z.ok_or(Error::Synthesis),
|| z,
)?;
zs.push(cell)
}
@@ -325,64 +309,43 @@ where
// The accumulator x-coordinate provided by the caller MUST have been assigned
// within this region.
let generators: Vec<Option<(pallas::Base, pallas::Base)>> =
if let Some(generators) = generators {
generators.into_iter().map(Some).collect()
} else {
vec![None; piece.num_words()]
};
let generators = generators.transpose_vec(piece.num_words());
for (row, gen) in generators.iter().enumerate() {
let x_p = gen.map(|gen| gen.0);
let y_p = gen.map(|gen| gen.1);
// Assign `x_p`
region.assign_advice(
|| "x_p",
config.double_and_add.x_p,
offset + row,
|| x_p.ok_or(Error::Synthesis),
)?;
region.assign_advice(|| "x_p", config.double_and_add.x_p, offset + row, || x_p)?;
// Compute and assign `lambda_1`
let lambda_1 = {
let lambda_1 = x_a
.value()
.zip(y_a.0)
.zip(x_p)
.zip(y_p)
.map(|(((x_a, y_a), x_p), y_p)| (y_a - y_p) * (x_a - x_p).invert());
let lambda_1 = (y_a.0 - y_p) * (x_a.value() - x_p).invert();
// Assign lambda_1
region.assign_advice(
|| "lambda_1",
config.double_and_add.lambda_1,
offset + row,
|| lambda_1.ok_or(Error::Synthesis),
|| lambda_1,
)?;
lambda_1
};
// Compute `x_r`
let x_r = lambda_1
.zip(x_a.value())
.zip(x_p)
.map(|((lambda_1, x_a), x_p)| lambda_1.square() - x_a - x_p);
let x_r = lambda_1.square() - x_a.value() - x_p;
// Compute and assign `lambda_2`
let lambda_2 = {
let lambda_2 = x_a.value().zip(y_a.0).zip(x_r).zip(lambda_1).map(
|(((x_a, y_a), x_r), lambda_1)| {
y_a * pallas::Base::from(2) * (x_a - x_r).invert() - lambda_1
},
);
let lambda_2 =
y_a.0 * pallas::Base::from(2) * (x_a.value() - x_r).invert() - lambda_1;
region.assign_advice(
|| "lambda_2",
config.double_and_add.lambda_2,
offset + row,
|| lambda_2.ok_or(Error::Synthesis),
|| lambda_2,
)?;
lambda_2
@@ -390,28 +353,21 @@ where
// Compute and assign `x_a` for the next row.
let x_a_new: X<pallas::Base> = {
let x_a_new = lambda_2
.zip(x_a.value())
.zip(x_r)
.map(|((lambda_2, x_a), x_r)| lambda_2.square() - x_a - x_r);
let x_a_new = lambda_2.square() - x_a.value() - x_r;
let x_a_cell = region.assign_advice(
|| "x_a",
config.double_and_add.x_a,
offset + row + 1,
|| x_a_new.ok_or(Error::Synthesis),
|| x_a_new,
)?;
x_a_cell.into()
};
// Compute y_a for the next row.
let y_a_new: Y<pallas::Base> = lambda_2
.zip(x_a.value())
.zip(x_a_new.value())
.zip(y_a.0)
.map(|(((lambda_2, x_a), x_a_new), y_a)| lambda_2 * (x_a - x_a_new) - y_a)
.into();
let y_a_new: Y<pallas::Base> =
(lambda_2 * (x_a.value() - x_a_new.value()) - y_a.0).into();
// Update the mutable `x_a`, `y_a` variables.
x_a = x_a_new;
@@ -444,18 +400,18 @@ impl<F: FieldExt> Deref for X<F> {
/// This is never actually witnessed until the last round, since it
/// can be derived from other variables. Thus it only exists as a field
/// element, not a `CellValue`.
struct Y<F: FieldExt>(Option<Assigned<F>>);
struct Y<F: FieldExt>(Value<Assigned<F>>);
impl<F: FieldExt> From<Option<Assigned<F>>> for Y<F> {
fn from(value: Option<Assigned<F>>) -> Self {
impl<F: FieldExt> From<Value<Assigned<F>>> for Y<F> {
fn from(value: Value<Assigned<F>>) -> Self {
Y(value)
}
}
impl<F: FieldExt> Deref for Y<F> {
type Target = Option<Assigned<F>>;
type Target = Value<Assigned<F>>;
fn deref(&self) -> &Option<Assigned<F>> {
fn deref(&self) -> &Value<Assigned<F>> {
&self.0
}
}

View File

@@ -1,16 +1,14 @@
//! Gadgets for implementing a Merkle tree with Sinsemilla.
use halo2_proofs::{
circuit::{Chip, Layouter},
circuit::{Chip, Layouter, Value},
plonk::Error,
};
use pasta_curves::arithmetic::CurveAffine;
use super::{HashDomains, SinsemillaInstructions};
use crate::utilities::{
cond_swap::CondSwapInstructions, i2lebsp, transpose_option_array, UtilitiesInstructions,
};
use crate::utilities::{cond_swap::CondSwapInstructions, i2lebsp, UtilitiesInstructions};
pub mod chip;
@@ -60,9 +58,9 @@ pub struct MerklePath<
{
chips: [MerkleChip; PAR],
domain: MerkleChip::HashDomains,
leaf_pos: Option<u32>,
leaf_pos: Value<u32>,
// The Merkle path is ordered from leaves to root.
path: Option<[C::Base; PATH_LENGTH]>,
path: Value<[C::Base; PATH_LENGTH]>,
}
impl<
@@ -86,8 +84,8 @@ where
pub fn construct(
chips: [MerkleChip; PAR],
domain: MerkleChip::HashDomains,
leaf_pos: Option<u32>,
path: Option<[C::Base; PATH_LENGTH]>,
leaf_pos: Value<u32>,
path: Value<[C::Base; PATH_LENGTH]>,
) -> Self {
assert_ne!(PAR, 0);
Self {
@@ -129,12 +127,12 @@ where
// The Merkle path is ordered from leaves to root, which is consistent with the
// little-endian representation of `pos` below.
let path = transpose_option_array(self.path);
let path = self.path.transpose_array();
// Get position as a PATH_LENGTH-bit bitstring (little-endian bit order).
let pos: [Option<bool>; PATH_LENGTH] = {
let pos: Option<[bool; PATH_LENGTH]> = self.leaf_pos.map(|pos| i2lebsp(pos as u64));
transpose_option_array(pos)
let pos: [Value<bool>; PATH_LENGTH] = {
let pos: Value<[bool; PATH_LENGTH]> = self.leaf_pos.map(|pos| i2lebsp(pos as u64));
pos.transpose_array()
};
let Q = self.domain.Q();
@@ -191,7 +189,7 @@ pub mod tests {
use group::ff::{Field, PrimeField, PrimeFieldBits};
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
pasta::pallas,
plonk::{Circuit, ConstraintSystem, Error},
@@ -204,9 +202,9 @@ pub mod tests {
#[derive(Default)]
struct MyCircuit {
leaf: Option<pallas::Base>,
leaf_pos: Option<u32>,
merkle_path: Option<[pallas::Base; MERKLE_DEPTH]>,
leaf: Value<pallas::Base>,
leaf_pos: Value<u32>,
merkle_path: Value<[pallas::Base; MERKLE_DEPTH]>,
}
impl Circuit<pallas::Base> for MyCircuit {
@@ -306,46 +304,52 @@ pub mod tests {
let computed_final_root =
path.calculate_root(layouter.namespace(|| "calculate root"), leaf)?;
if let Some(leaf_pos) = self.leaf_pos {
// The expected final root
let final_root = self.merkle_path.unwrap().iter().enumerate().fold(
self.leaf.unwrap(),
|node, (l, sibling)| {
let l = l as u8;
let (left, right) = if leaf_pos & (1 << l) == 0 {
(&node, sibling)
} else {
(sibling, &node)
};
self.leaf
.zip(self.leaf_pos)
.zip(self.merkle_path)
.zip(computed_final_root.value())
.assert_if_known(|(((leaf, leaf_pos), merkle_path), computed_final_root)| {
// The expected final root
let final_root =
merkle_path
.iter()
.enumerate()
.fold(*leaf, |node, (l, sibling)| {
let l = l as u8;
let (left, right) = if leaf_pos & (1 << l) == 0 {
(&node, sibling)
} else {
(sibling, &node)
};
use crate::sinsemilla::primitives as sinsemilla;
let merkle_crh = sinsemilla::HashDomain::from_Q(TestHashDomain.Q().into());
use crate::sinsemilla::primitives as sinsemilla;
let merkle_crh =
sinsemilla::HashDomain::from_Q(TestHashDomain.Q().into());
merkle_crh
.hash(
iter::empty()
.chain(i2lebsp::<10>(l as u64).iter().copied())
.chain(
left.to_le_bits()
.iter()
.by_vals()
.take(pallas::Base::NUM_BITS as usize),
merkle_crh
.hash(
iter::empty()
.chain(i2lebsp::<10>(l as u64).iter().copied())
.chain(
left.to_le_bits()
.iter()
.by_vals()
.take(pallas::Base::NUM_BITS as usize),
)
.chain(
right
.to_le_bits()
.iter()
.by_vals()
.take(pallas::Base::NUM_BITS as usize),
),
)
.chain(
right
.to_le_bits()
.iter()
.by_vals()
.take(pallas::Base::NUM_BITS as usize),
),
)
.unwrap_or(pallas::Base::zero())
},
);
.unwrap_or(pallas::Base::zero())
});
// Check the computed final root against the expected final root.
assert_eq!(computed_final_root.value().unwrap(), &final_root);
}
// Check the computed final root against the expected final root.
computed_final_root == &&final_root
});
Ok(())
}
@@ -367,9 +371,9 @@ pub mod tests {
// The root is provided as a public input in the Orchard circuit.
let circuit = MyCircuit {
leaf: Some(leaf),
leaf_pos: Some(pos),
merkle_path: Some(path.try_into().unwrap()),
leaf: Value::known(leaf),
leaf_pos: Value::known(pos),
merkle_path: Value::known(path.try_into().unwrap()),
};
let prover = MockProver::run(11, &circuit, vec![]).unwrap();

View File

@@ -1,7 +1,7 @@
//! Chip implementing a Merkle hash using Sinsemilla as the hash function.
use halo2_proofs::{
circuit::{AssignedCell, Chip, Layouter},
circuit::{AssignedCell, Chip, Layouter, Value},
plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector},
poly::Rotation,
};
@@ -234,7 +234,7 @@ where
self.clone(),
layouter.namespace(|| "Witness a = a_0 || a_1"),
[
RangeConstrained::bitrange_of(Some(&pallas::Base::from(l as u64)), 0..10),
RangeConstrained::bitrange_of(Value::known(&pallas::Base::from(l as u64)), 0..10),
RangeConstrained::bitrange_of(left.value(), 0..240),
],
)?;
@@ -382,30 +382,33 @@ where
use group::ff::PrimeFieldBits;
if let (Some(left), Some(right)) = (left.value(), right.value()) {
let l = i2lebsp::<10>(l as u64);
let left: Vec<_> = left
.to_le_bits()
.iter()
.by_vals()
.take(pallas::Base::NUM_BITS as usize)
.collect();
let right: Vec<_> = right
.to_le_bits()
.iter()
.by_vals()
.take(pallas::Base::NUM_BITS as usize)
.collect();
let merkle_crh = HashDomain::from_Q(Q.into());
left.value()
.zip(right.value())
.zip(hash.value())
.assert_if_known(|((left, right), hash)| {
let l = i2lebsp::<10>(l as u64);
let left: Vec<_> = left
.to_le_bits()
.iter()
.by_vals()
.take(pallas::Base::NUM_BITS as usize)
.collect();
let right: Vec<_> = right
.to_le_bits()
.iter()
.by_vals()
.take(pallas::Base::NUM_BITS as usize)
.collect();
let merkle_crh = HashDomain::from_Q(Q.into());
let mut message = l.to_vec();
message.extend_from_slice(&left);
message.extend_from_slice(&right);
let mut message = l.to_vec();
message.extend_from_slice(&left);
message.extend_from_slice(&right);
let expected = merkle_crh.hash(message.into_iter()).unwrap();
let expected = merkle_crh.hash(message.into_iter()).unwrap();
assert_eq!(expected.to_repr(), hash.value().unwrap().to_repr());
}
expected.to_repr() == hash.to_repr()
});
}
Ok(hash)
@@ -431,8 +434,8 @@ where
fn swap(
&self,
layouter: impl Layouter<pallas::Base>,
pair: (Self::Var, Option<pallas::Base>),
swap: Option<bool>,
pair: (Self::Var, Value<pallas::Base>),
swap: Value<bool>,
) -> Result<(Self::Var, Self::Var), Error> {
let config = self.config().cond_swap_config.clone();
let chip = CondSwapChip::<pallas::Base>::construct(config);
@@ -499,7 +502,7 @@ where
fn witness_message_piece(
&self,
layouter: impl Layouter<pallas::Base>,
value: Option<pallas::Base>,
value: Value<pallas::Base>,
num_words: usize,
) -> Result<Self::MessagePiece, Error> {
let config = self.config().sinsemilla_config.clone();

View File

@@ -2,7 +2,7 @@
use ff::PrimeFieldBits;
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{AssignedCell, Cell},
circuit::{AssignedCell, Cell, Value},
};
use std::fmt::Debug;
@@ -58,7 +58,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> MessagePiece<F, K> {
self.cell_value.cell()
}
pub fn field_elem(&self) -> Option<F> {
pub fn field_elem(&self) -> Value<F> {
self.cell_value.value().cloned()
}

View File

@@ -2,7 +2,7 @@
use ff::{Field, PrimeFieldBits};
use halo2_proofs::{
circuit::{AssignedCell, Cell, Layouter},
circuit::{AssignedCell, Cell, Layouter, Value},
plonk::{Advice, Column, Error, Expression},
};
use pasta_curves::arithmetic::FieldExt;
@@ -16,17 +16,17 @@ pub mod lookup_range_check;
/// A type that has a value at either keygen or proving time.
pub trait FieldValue<F: Field> {
/// Returns the value of this type.
fn value(&self) -> Option<&F>;
fn value(&self) -> Value<&F>;
}
impl<F: Field> FieldValue<F> for Option<F> {
fn value(&self) -> Option<&F> {
impl<F: Field> FieldValue<F> for Value<F> {
fn value(&self) -> Value<&F> {
self.as_ref()
}
}
impl<F: Field> FieldValue<F> for AssignedCell<F, F> {
fn value(&self) -> Option<&F> {
fn value(&self) -> Value<&F> {
self.value()
}
}
@@ -37,7 +37,7 @@ pub trait Var<F: FieldExt>: Clone + std::fmt::Debug + From<AssignedCell<F, F>> {
fn cell(&self) -> Cell;
/// The value allocated to this variable.
fn value(&self) -> Option<F>;
fn value(&self) -> Value<F>;
}
impl<F: FieldExt> Var<F> for AssignedCell<F, F> {
@@ -45,7 +45,7 @@ impl<F: FieldExt> Var<F> for AssignedCell<F, F> {
self.cell()
}
fn value(&self) -> Option<F> {
fn value(&self) -> Value<F> {
self.value().cloned()
}
}
@@ -60,18 +60,13 @@ pub trait UtilitiesInstructions<F: FieldExt> {
&self,
mut layouter: impl Layouter<F>,
column: Column<Advice>,
value: Option<F>,
value: Value<F>,
) -> Result<Self::Var, Error> {
layouter.assign_region(
|| "load private",
|mut region| {
region
.assign_advice(
|| "load private",
column,
0,
|| value.ok_or(Error::Synthesis),
)
.assign_advice(|| "load private", column, 0, || value)
.map(Self::Var::from)
},
)
@@ -98,9 +93,9 @@ impl<F: Field, T: FieldValue<F>> RangeConstrained<F, T> {
}
}
impl<F: PrimeFieldBits> RangeConstrained<F, Option<F>> {
/// Constructs a `RangeConstrained<Option<F>>` as a bitrange of the given value.
pub fn bitrange_of(value: Option<&F>, bitrange: Range<usize>) -> Self {
impl<F: PrimeFieldBits> RangeConstrained<F, Value<F>> {
/// Constructs a `RangeConstrained<Value<F>>` as a bitrange of the given value.
pub fn bitrange_of(value: Value<&F>, bitrange: Range<usize>) -> Self {
let num_bits = bitrange.len();
Self {
inner: value.map(|value| bitrange_subset(value, bitrange)),
@@ -125,7 +120,7 @@ impl<F: Field> RangeConstrained<F, AssignedCell<F, F>> {
}
/// Extracts the range-constrained value from this range-constrained cell.
pub fn value(&self) -> RangeConstrained<F, Option<F>> {
pub fn value(&self) -> RangeConstrained<F, Value<F>> {
RangeConstrained {
inner: self.inner.value().copied(),
num_bits: self.num_bits,
@@ -134,18 +129,6 @@ impl<F: Field> RangeConstrained<F, AssignedCell<F, F>> {
}
}
pub(crate) fn transpose_option_array<T: Copy + std::fmt::Debug, const LEN: usize>(
option_array: Option<[T; LEN]>,
) -> [Option<T>; LEN] {
let mut ret = [None; LEN];
if let Some(arr) = option_array {
for (entry, value) in ret.iter_mut().zip(arr) {
*entry = Some(value);
}
}
ret
}
/// Checks that an expression is either 1 or 0.
pub fn bool_check<F: FieldExt>(value: Expression<F>) -> Expression<F> {
range_check(value, 2)
@@ -320,7 +303,7 @@ mod tests {
|| format!("witness {}", self.0),
config.advice,
0,
|| Ok(pallas::Base::from(self.0 as u64)),
|| Value::known(pallas::Base::from(self.0 as u64)),
)?;
Ok(())

View File

@@ -2,7 +2,7 @@
use super::{bool_check, ternary, UtilitiesInstructions};
use halo2_proofs::{
circuit::{AssignedCell, Chip, Layouter},
circuit::{AssignedCell, Chip, Layouter, Value},
plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector},
poly::Rotation,
};
@@ -20,8 +20,8 @@ pub trait CondSwapInstructions<F: FieldExt>: UtilitiesInstructions<F> {
fn swap(
&self,
layouter: impl Layouter<F>,
pair: (Self::Var, Option<F>),
swap: Option<bool>,
pair: (Self::Var, Value<F>),
swap: Value<bool>,
) -> Result<(Self::Var, Self::Var), Error>;
}
@@ -72,8 +72,8 @@ impl<F: FieldExt> CondSwapInstructions<F> for CondSwapChip<F> {
fn swap(
&self,
mut layouter: impl Layouter<F>,
pair: (Self::Var, Option<F>),
swap: Option<bool>,
pair: (Self::Var, Value<F>),
swap: Value<bool>,
) -> Result<(Self::Var, Self::Var), Error> {
let config = self.config();
@@ -87,21 +87,11 @@ impl<F: FieldExt> CondSwapInstructions<F> for CondSwapChip<F> {
let a = pair.0.copy_advice(|| "copy a", &mut region, config.a, 0)?;
// Witness `b` value
let b = region.assign_advice(
|| "witness b",
config.b,
0,
|| pair.1.ok_or(Error::Synthesis),
)?;
let b = region.assign_advice(|| "witness b", config.b, 0, || pair.1)?;
// Witness `swap` value
let swap_val = swap.map(|swap| F::from(swap as u64));
region.assign_advice(
|| "swap",
config.swap,
0,
|| swap_val.ok_or(Error::Synthesis),
)?;
region.assign_advice(|| "swap", config.swap, 0, || swap_val)?;
// Conditionally swap a
let a_swapped = {
@@ -111,12 +101,7 @@ impl<F: FieldExt> CondSwapInstructions<F> for CondSwapChip<F> {
.zip(swap)
.map(|((a, b), swap)| if swap { b } else { a })
.cloned();
region.assign_advice(
|| "a_swapped",
config.a_swapped,
0,
|| a_swapped.ok_or(Error::Synthesis),
)?
region.assign_advice(|| "a_swapped", config.a_swapped, 0, || a_swapped)?
};
// Conditionally swap b
@@ -127,12 +112,7 @@ impl<F: FieldExt> CondSwapInstructions<F> for CondSwapChip<F> {
.zip(swap)
.map(|((a, b), swap)| if swap { a } else { b })
.cloned();
region.assign_advice(
|| "b_swapped",
config.b_swapped,
0,
|| b_swapped.ok_or(Error::Synthesis),
)?
region.assign_advice(|| "b_swapped", config.b_swapped, 0, || b_swapped)?
};
// Return swapped pair
@@ -217,7 +197,7 @@ mod tests {
use super::{CondSwapChip, CondSwapConfig, CondSwapInstructions};
use group::ff::Field;
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::MockProver,
plonk::{Circuit, ConstraintSystem, Error},
};
@@ -228,9 +208,9 @@ mod tests {
fn cond_swap() {
#[derive(Default)]
struct MyCircuit<F: FieldExt> {
a: Option<F>,
b: Option<F>,
swap: Option<bool>,
a: Value<F>,
b: Value<F>,
swap: Value<bool>,
}
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
@@ -269,17 +249,18 @@ mod tests {
self.swap,
)?;
if let Some(swap) = self.swap {
if swap {
// Check that `a` and `b` have been swapped
assert_eq!(swapped_pair.0.value().unwrap(), &self.b.unwrap());
assert_eq!(swapped_pair.1.value().unwrap(), a.value().unwrap());
} else {
// Check that `a` and `b` have not been swapped
assert_eq!(swapped_pair.0.value().unwrap(), a.value().unwrap());
assert_eq!(swapped_pair.1.value().unwrap(), &self.b.unwrap());
}
}
self.swap
.zip(a.value().zip(self.b.as_ref()))
.zip(swapped_pair.0.value().zip(swapped_pair.1.value()))
.assert_if_known(|((swap, (a, b)), (a_swapped, b_swapped))| {
if *swap {
// Check that `a` and `b` have been swapped
(a_swapped == b) && (b_swapped == a)
} else {
// Check that `a` and `b` have not been swapped
(a_swapped == a) && (b_swapped == b)
}
});
Ok(())
}
@@ -290,9 +271,9 @@ mod tests {
// Test swap case
{
let circuit: MyCircuit<Base> = MyCircuit {
a: Some(Base::random(rng)),
b: Some(Base::random(rng)),
swap: Some(true),
a: Value::known(Base::random(rng)),
b: Value::known(Base::random(rng)),
swap: Value::known(true),
};
let prover = MockProver::<Base>::run(3, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));
@@ -301,9 +282,9 @@ mod tests {
// Test non-swap case
{
let circuit: MyCircuit<Base> = MyCircuit {
a: Some(Base::random(rng)),
b: Some(Base::random(rng)),
swap: Some(false),
a: Value::known(Base::random(rng)),
b: Value::known(Base::random(rng)),
swap: Value::known(false),
};
let prover = MockProver::<Base>::run(3, &circuit, vec![]).unwrap();
assert_eq!(prover.verify(), Ok(()));

View File

@@ -24,7 +24,7 @@
use ff::PrimeFieldBits;
use halo2_proofs::{
circuit::{AssignedCell, Region},
circuit::{AssignedCell, Region, Value},
plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector},
poly::Rotation,
};
@@ -107,17 +107,12 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
&self,
region: &mut Region<'_, F>,
offset: usize,
alpha: Option<F>,
alpha: Value<F>,
strict: bool,
word_num_bits: usize,
num_windows: usize,
) -> Result<RunningSum<F>, Error> {
let z_0 = region.assign_advice(
|| "z_0 = alpha",
self.z,
offset,
|| alpha.ok_or(Error::Synthesis),
)?;
let z_0 = region.assign_advice(|| "z_0 = alpha", self.z, offset, || alpha)?;
self.decompose(region, offset, z_0, strict, word_num_bits, num_windows)
}
@@ -170,17 +165,10 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
}
// Decompose base field element into K-bit words.
let words: Vec<Option<u8>> = {
let words = z_0
.value()
.map(|word| super::decompose_word::<F>(word, word_num_bits, WINDOW_NUM_BITS));
if let Some(words) = words {
words.into_iter().map(Some).collect()
} else {
vec![None; num_windows]
}
};
let words = z_0
.value()
.map(|word| super::decompose_word::<F>(word, word_num_bits, WINDOW_NUM_BITS))
.transpose_vec(num_windows);
// Initialize empty vector to store running sum values [z_0, ..., z_W].
let mut zs: Vec<AssignedCell<F, F>> = vec![z_0.clone()];
@@ -189,20 +177,18 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
// Assign running sum `z_{i+1}` = (z_i - k_i) / (2^K) for i = 0..=n-1.
// Outside of this helper, z_0 = alpha must have already been loaded into the
// `z` column at `offset`.
let two_pow_k_inv = F::from(1 << WINDOW_NUM_BITS as u64).invert().unwrap();
let two_pow_k_inv = Value::known(F::from(1 << WINDOW_NUM_BITS as u64).invert().unwrap());
for (i, word) in words.iter().enumerate() {
// z_next = (z_cur - word) / (2^K)
let z_next = {
let z_cur_val = z.value().copied();
let word = word.map(|word| F::from(word as u64));
let z_next_val = z
.value()
.zip(word)
.map(|(z_cur_val, word)| (*z_cur_val - word) * two_pow_k_inv);
let z_next_val = (z_cur_val - word) * two_pow_k_inv;
region.assign_advice(
|| format!("z_{:?}", i + 1),
self.z,
offset + i + 1,
|| z_next_val.ok_or(Error::Synthesis),
|| z_next_val,
)?
};
@@ -247,7 +233,7 @@ mod tests {
const WINDOW_NUM_BITS: usize,
const NUM_WINDOWS: usize,
> {
alpha: Option<F>,
alpha: Value<F>,
strict: bool,
}
@@ -263,7 +249,7 @@ mod tests {
fn without_witnesses(&self) -> Self {
Self {
alpha: None,
alpha: Value::unknown(),
strict: self.strict,
}
}
@@ -320,7 +306,7 @@ mod tests {
// Strict full decomposition should pass.
let circuit: MyCircuit<pallas::Base, L_BASE, FIXED_BASE_WINDOW_SIZE, { NUM_WINDOWS }> =
MyCircuit {
alpha: Some(alpha),
alpha: Value::known(alpha),
strict: true,
};
let prover = MockProver::<pallas::Base>::run(8, &circuit, vec![]).unwrap();
@@ -338,7 +324,7 @@ mod tests {
FIXED_BASE_WINDOW_SIZE,
{ NUM_WINDOWS_SHORT },
> = MyCircuit {
alpha: Some(alpha),
alpha: Value::known(alpha),
strict: true,
};
let prover = MockProver::<pallas::Base>::run(8, &circuit, vec![]).unwrap();
@@ -356,7 +342,7 @@ mod tests {
FIXED_BASE_WINDOW_SIZE,
{ NUM_WINDOWS_SHORT },
> = MyCircuit {
alpha: Some(alpha),
alpha: Value::known(alpha),
strict: true,
};
let prover = MockProver::<pallas::Base>::run(8, &circuit, vec![]).unwrap();
@@ -395,7 +381,7 @@ mod tests {
FIXED_BASE_WINDOW_SIZE,
{ NUM_WINDOWS_SHORT },
> = MyCircuit {
alpha: Some(alpha),
alpha: Value::known(alpha),
strict: false,
};
let prover = MockProver::<pallas::Base>::run(8, &circuit, vec![]).unwrap();

View File

@@ -33,7 +33,7 @@ impl<F: FieldExt + PrimeFieldBits> RangeConstrained<F, AssignedCell<F, F>> {
pub fn witness_short<const K: usize>(
lookup_config: &LookupRangeCheckConfig<F, K>,
layouter: impl Layouter<F>,
value: Option<&F>,
value: Value<&F>,
bitrange: Range<usize>,
) -> Result<Self, Error> {
let num_bits = bitrange.len();
@@ -165,7 +165,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
|| "table_idx",
self.table_idx,
index,
|| Ok(F::from(index as u64)),
|| Value::known(F::from(index as u64)),
)?;
}
Ok(())
@@ -198,19 +198,15 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
pub fn witness_check(
&self,
mut layouter: impl Layouter<F>,
value: Option<F>,
value: Value<F>,
num_words: usize,
strict: bool,
) -> Result<RunningSum<F>, Error> {
layouter.assign_region(
|| "Witness element",
|mut region| {
let z_0 = region.assign_advice(
|| "Witness element",
self.running_sum,
0,
|| value.ok_or(Error::Synthesis),
)?;
let z_0 =
region.assign_advice(|| "Witness element", self.running_sum, 0, || value)?;
self.range_check(&mut region, z_0, num_words, strict)
},
)
@@ -245,16 +241,12 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
.collect::<Vec<_>>()
});
let words: Option<Vec<F>> = bits.map(|bits| {
bits.map(|bits| {
bits.chunks_exact(K)
.map(|word| F::from(lebs2ip::<K>(&(word.try_into().unwrap()))))
.collect::<Vec<_>>()
});
if let Some(words) = words {
words.into_iter().map(Some).collect()
} else {
vec![None; num_words]
}
})
.transpose_vec(num_words)
};
let mut zs = vec![element.clone()];
@@ -285,7 +277,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
|| format!("z_{:?}", idx + 1),
self.running_sum,
idx + 1,
|| z_val.ok_or(Error::Synthesis),
|| z_val,
)?
};
zs.push(z.clone());
@@ -331,7 +323,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
pub fn witness_short_check(
&self,
mut layouter: impl Layouter<F>,
element: Option<F>,
element: Value<F>,
num_bits: usize,
) -> Result<AssignedCell<F, F>, Error> {
assert!(num_bits <= K);
@@ -339,12 +331,8 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
|| format!("Range check {:?} bits", num_bits),
|mut region| {
// Witness `element` to use in the k-bit lookup.
let element = region.assign_advice(
|| "Witness element",
self.running_sum,
0,
|| element.ok_or(Error::Synthesis),
)?;
let element =
region.assign_advice(|| "Witness element", self.running_sum, 0, || element)?;
self.short_range_check(&mut region, element.clone(), num_bits)?;
@@ -372,16 +360,13 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
self.q_bitshift.enable(region, 1)?;
// Assign shifted `element * 2^{K - num_bits}`
let shifted = element.value().map(|element| {
let shift = F::from(1 << (K - num_bits));
*element * shift
});
let shifted = element.value().into_field() * F::from(1 << (K - num_bits));
region.assign_advice(
|| format!("element * 2^({}-{})", K, num_bits),
self.running_sum,
1,
|| shifted.ok_or(Error::Synthesis),
|| shifted,
)?;
// Assign 2^{-num_bits} from a fixed column.
@@ -406,7 +391,7 @@ mod tests {
use ff::{Field, PrimeFieldBits};
use halo2_proofs::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
dev::{FailureLocation, MockProver, VerifyFailure},
plonk::{Circuit, ConstraintSystem, Error},
};
@@ -485,7 +470,7 @@ mod tests {
let zs = config.witness_check(
layouter.namespace(|| format!("Lookup {:?}", self.num_words)),
Some(*element),
Value::known(*element),
self.num_words,
*strict,
)?;
@@ -493,9 +478,7 @@ mod tests {
assert_eq!(*expected_zs.last().unwrap(), *expected_final_z);
for (expected_z, z) in expected_zs.into_iter().zip(zs.iter()) {
if let Some(z) = z.value() {
assert_eq!(&expected_z, z);
}
z.value().assert_if_known(|z| &&expected_z == z);
}
}
Ok(())
@@ -516,7 +499,7 @@ mod tests {
#[test]
fn short_range_check() {
struct MyCircuit<F: FieldExt + PrimeFieldBits> {
element: Option<F>,
element: Value<F>,
num_bits: usize,
}
@@ -526,7 +509,7 @@ mod tests {
fn without_witnesses(&self) -> Self {
MyCircuit {
element: None,
element: Value::unknown(),
num_bits: self.num_bits,
}
}
@@ -562,7 +545,7 @@ mod tests {
// Edge case: zero bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::zero()),
element: Value::known(pallas::Base::zero()),
num_bits: 0,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@@ -572,7 +555,7 @@ mod tests {
// Edge case: K bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from((1 << K) - 1)),
element: Value::known(pallas::Base::from((1 << K) - 1)),
num_bits: K,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@@ -582,7 +565,7 @@ mod tests {
// Element within `num_bits`
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from((1 << 6) - 1)),
element: Value::known(pallas::Base::from((1 << 6) - 1)),
num_bits: 6,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@@ -592,7 +575,7 @@ mod tests {
// Element larger than `num_bits` but within K bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from(1 << 6)),
element: Value::known(pallas::Base::from(1 << 6)),
num_bits: 6,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@@ -611,7 +594,7 @@ mod tests {
// Element larger than K bits
{
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(pallas::Base::from(1 << K)),
element: Value::known(pallas::Base::from(1 << K)),
num_bits: 6,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
@@ -648,7 +631,7 @@ mod tests {
.invert()
.unwrap();
let circuit: MyCircuit<pallas::Base> = MyCircuit {
element: Some(element),
element: Value::known(element),
num_bits: num_bits as usize,
};
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();

View File

@@ -6,6 +6,39 @@ and this project adheres to Rust's notion of
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
### Added
- `halo2_proofs::circuit::Value`, a more usable and type-safe replacement for
`Option<V>` in circuit synthesis.
- `impl Mul<F: Field> for &Assigned<F>`
### Changed
All APIs that represented witnessed values as `Option<V>` now represent them as
`halo2_proofs::circuit::Value<V>`. The core API changes are listed below.
- The following APIs now take `Value<_>` instead of `Option<_>`:
- `halo2_proofs::plonk`:
- `Assignment::fill_from_row`
- The following APIs now take value closures that return `Value<V>` instead of
`Result<V, Error>`:
- `halo2_proofs::circuit`:
- `Region::{assign_advice, assign_fixed}`
- `Table::assign_cell`
- `halo2_proofs::circuit::layouter`:
- `RegionLayouter::{assign_advice, assign_fixed}`
- `TableLayouter::assign_cell`
- `halo2_proofs::plonk`:
- `Assignment::{assign_advice, assign_fixed}`
- The following APIs now return `Value<_>` instead of `Option<_>`:
- `halo2_proofs::circuit`:
- `AssignedCell::{value, value_field}`
- The following APIs now return `Result<Value<F>, Error>` instead of
`Result<Option<F>, Error>`:
- `halo2_proofs::plonk`:
- `Assignment::query_instance`
- The following APIs now return `Result<(Cell, Value<F>), Error>` instead of
`Result<(Cell, Option<F>), Error>`:
- `halo2_proofs::circuit::layouter`:
- `RegionLayouter::assign_advice_from_instance`
## [0.1.0] - 2022-05-10
### Added

View File

@@ -2,7 +2,7 @@
extern crate criterion;
use halo2_proofs::arithmetic::FieldExt;
use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner};
use halo2_proofs::circuit::{Layouter, SimpleFloorPlanner, Value};
use halo2_proofs::dev::MockProver;
use halo2_proofs::plonk::*;
use halo2_proofs::poly::Rotation;
@@ -63,7 +63,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|| format!("row {}", row),
config.table,
row as usize,
|| Ok(F::from(row + 1)),
|| Value::known(F::from(row + 1)),
)?;
}
@@ -80,7 +80,7 @@ fn criterion_benchmark(c: &mut Criterion) {
|| format!("offset {}", offset),
config.advice,
offset as usize,
|| Ok(F::from((offset % 256) + 1)),
|| Value::known(F::from((offset % 256) + 1)),
)?;
}

View File

@@ -3,7 +3,7 @@ extern crate criterion;
use group::ff::Field;
use halo2_proofs::arithmetic::FieldExt;
use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner};
use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value};
use halo2_proofs::pasta::{EqAffine, Fp};
use halo2_proofs::plonk::*;
use halo2_proofs::poly::{commitment::Params, Rotation};
@@ -38,20 +38,20 @@ fn criterion_benchmark(c: &mut Criterion) {
f: F,
) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>;
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn raw_add<F>(
&self,
layouter: &mut impl Layouter<FF>,
f: F,
) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>;
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn copy(&self, layouter: &mut impl Layouter<FF>, a: Cell, b: Cell) -> Result<(), Error>;
}
#[derive(Clone)]
struct MyCircuit<F: FieldExt> {
a: Option<F>,
a: Value<F>,
k: u32,
}
@@ -76,7 +76,7 @@ fn criterion_benchmark(c: &mut Criterion) {
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>,
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
layouter.assign_region(
|| "raw_multiply",
@@ -87,27 +87,32 @@ fn criterion_benchmark(c: &mut Criterion) {
self.config.a,
0,
|| {
value = Some(f()?);
Ok(value.ok_or(Error::Synthesis)?.0)
value = Some(f());
value.unwrap().map(|v| v.0)
},
)?;
let rhs = region.assign_advice(
|| "rhs",
self.config.b,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1),
|| value.unwrap().map(|v| v.1),
)?;
let out = region.assign_advice(
|| "out",
self.config.c,
0,
|| Ok(value.ok_or(Error::Synthesis)?.2),
|| value.unwrap().map(|v| v.2),
)?;
region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::zero()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::zero()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::zero()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::zero()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?;
region.assign_fixed(
|| "a * b",
self.config.sm,
0,
|| Value::known(FF::one()),
)?;
Ok((lhs.cell(), rhs.cell(), out.cell()))
},
)
@@ -118,7 +123,7 @@ fn criterion_benchmark(c: &mut Criterion) {
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>,
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
layouter.assign_region(
|| "raw_add",
@@ -129,27 +134,32 @@ fn criterion_benchmark(c: &mut Criterion) {
self.config.a,
0,
|| {
value = Some(f()?);
Ok(value.ok_or(Error::Synthesis)?.0)
value = Some(f());
value.unwrap().map(|v| v.0)
},
)?;
let rhs = region.assign_advice(
|| "rhs",
self.config.b,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1),
|| value.unwrap().map(|v| v.1),
)?;
let out = region.assign_advice(
|| "out",
self.config.c,
0,
|| Ok(value.ok_or(Error::Synthesis)?.2),
|| value.unwrap().map(|v| v.2),
)?;
region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::zero()))?;
region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::one()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::one()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?;
region.assign_fixed(
|| "a * b",
self.config.sm,
0,
|| Value::known(FF::zero()),
)?;
Ok((lhs.cell(), rhs.cell(), out.cell()))
},
)
@@ -169,7 +179,10 @@ fn criterion_benchmark(c: &mut Criterion) {
type FloorPlanner = SimpleFloorPlanner;
fn without_witnesses(&self) -> Self {
Self { a: None, k: self.k }
Self {
a: Value::unknown(),
k: self.k,
}
}
fn configure(meta: &mut ConstraintSystem<F>) -> PlonkConfig {
@@ -220,22 +233,17 @@ fn criterion_benchmark(c: &mut Criterion) {
let cs = StandardPlonk::new(config);
for _ in 0..((1 << (self.k - 1)) - 3) {
let mut a_squared = None;
let a: Value<Assigned<_>> = self.a.into();
let mut a_squared = Value::unknown();
let (a0, _, c0) = cs.raw_multiply(&mut layouter, || {
a_squared = self.a.map(|a| a.square());
Ok((
self.a.ok_or(Error::Synthesis)?,
self.a.ok_or(Error::Synthesis)?,
a_squared.ok_or(Error::Synthesis)?,
))
a_squared = a.square();
a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared))
})?;
let (a1, b1, _) = cs.raw_add(&mut layouter, || {
let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2));
Ok((
self.a.ok_or(Error::Synthesis)?,
a_squared.ok_or(Error::Synthesis)?,
fin.ok_or(Error::Synthesis)?,
))
let fin = a_squared + a;
a.zip(a_squared)
.zip(fin)
.map(|((a, a_squared), fin)| (a, a_squared, fin))
})?;
cs.copy(&mut layouter, a0, a1)?;
cs.copy(&mut layouter, b1, c0)?;
@@ -247,7 +255,10 @@ fn criterion_benchmark(c: &mut Criterion) {
fn keygen(k: u32) -> (Params<EqAffine>, ProvingKey<EqAffine>) {
let params: Params<EqAffine> = Params::new(k);
let empty_circuit: MyCircuit<Fp> = MyCircuit { a: None, k };
let empty_circuit: MyCircuit<Fp> = MyCircuit {
a: Value::unknown(),
k,
};
let vk = keygen_vk(&params, &empty_circuit).expect("keygen_vk should not fail");
let pk = keygen_pk(&params, vk, &empty_circuit).expect("keygen_pk should not fail");
(params, pk)
@@ -257,7 +268,7 @@ fn criterion_benchmark(c: &mut Criterion) {
let rng = OsRng;
let circuit: MyCircuit<Fp> = MyCircuit {
a: Some(Fp::random(rng)),
a: Value::known(Fp::random(rng)),
k,
};

View File

@@ -1,9 +1,9 @@
use ff::Field;
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{Cell, Layouter, Region, SimpleFloorPlanner},
circuit::{Cell, Layouter, Region, SimpleFloorPlanner, Value},
pasta::Fp,
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn},
plonk::{Advice, Assigned, Circuit, Column, ConstraintSystem, Error, Fixed, TableColumn},
poly::Rotation,
};
use rand_core::OsRng;
@@ -31,16 +31,16 @@ struct PlonkConfig {
trait StandardCs<FF: FieldExt> {
fn raw_multiply<F>(&self, region: &mut Region<FF>, f: F) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>;
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn raw_add<F>(&self, region: &mut Region<FF>, f: F) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>;
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn copy(&self, region: &mut Region<FF>, a: Cell, b: Cell) -> Result<(), Error>;
fn lookup_table(&self, layouter: &mut impl Layouter<FF>, values: &[FF]) -> Result<(), Error>;
}
struct MyCircuit<F: FieldExt> {
a: Option<F>,
a: Value<F>,
lookup_table: Vec<F>,
}
@@ -65,7 +65,7 @@ impl<FF: FieldExt> StandardCs<FF> for StandardPlonk<FF> {
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>,
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
let mut value = None;
let lhs = region.assign_advice(
@@ -73,44 +73,36 @@ impl<FF: FieldExt> StandardCs<FF> for StandardPlonk<FF> {
self.config.a,
0,
|| {
value = Some(f()?);
Ok(value.ok_or(Error::Synthesis)?.0)
value = Some(f());
value.unwrap().map(|v| v.0)
},
)?;
region.assign_advice(
|| "lhs^4",
self.config.d,
0,
|| Ok(value.ok_or(Error::Synthesis)?.0.square().square()),
)?;
let rhs = region.assign_advice(
|| "rhs",
self.config.b,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1),
|| value.unwrap().map(|v| v.0).square().square(),
)?;
let rhs =
region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?;
region.assign_advice(
|| "rhs^4",
self.config.e,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1.square().square()),
)?;
let out = region.assign_advice(
|| "out",
self.config.c,
0,
|| Ok(value.ok_or(Error::Synthesis)?.2),
|| value.unwrap().map(|v| v.1).square().square(),
)?;
let out =
region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?;
region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::zero()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::zero()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::zero()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::zero()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?;
region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::one()))?;
Ok((lhs.cell(), rhs.cell(), out.cell()))
}
fn raw_add<F>(&self, region: &mut Region<FF>, mut f: F) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>,
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
let mut value = None;
let lhs = region.assign_advice(
@@ -118,39 +110,31 @@ impl<FF: FieldExt> StandardCs<FF> for StandardPlonk<FF> {
self.config.a,
0,
|| {
value = Some(f()?);
Ok(value.ok_or(Error::Synthesis)?.0)
value = Some(f());
value.unwrap().map(|v| v.0)
},
)?;
region.assign_advice(
|| "lhs^4",
self.config.d,
0,
|| Ok(value.ok_or(Error::Synthesis)?.0.square().square()),
)?;
let rhs = region.assign_advice(
|| "rhs",
self.config.b,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1),
|| value.unwrap().map(|v| v.0.square().square()),
)?;
let rhs =
region.assign_advice(|| "rhs", self.config.b, 0, || value.unwrap().map(|v| v.1))?;
region.assign_advice(
|| "rhs^4",
self.config.e,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1.square().square()),
)?;
let out = region.assign_advice(
|| "out",
self.config.c,
0,
|| Ok(value.ok_or(Error::Synthesis)?.2),
|| value.unwrap().map(|v| v.1.square().square()),
)?;
let out =
region.assign_advice(|| "out", self.config.c, 0, || value.unwrap().map(|v| v.2))?;
region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::zero()))?;
region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::one()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::one()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?;
region.assign_fixed(|| "a * b", self.config.sm, 0, || Value::known(FF::zero()))?;
Ok((lhs.cell(), rhs.cell(), out.cell()))
}
fn copy(&self, region: &mut Region<FF>, left: Cell, right: Cell) -> Result<(), Error> {
@@ -161,7 +145,12 @@ impl<FF: FieldExt> StandardCs<FF> for StandardPlonk<FF> {
|| "",
|mut table| {
for (index, &value) in values.iter().enumerate() {
table.assign_cell(|| "table col", self.config.sl, index, || Ok(value))?;
table.assign_cell(
|| "table col",
self.config.sl,
index,
|| Value::known(value),
)?;
}
Ok(())
},
@@ -176,7 +165,7 @@ impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
fn without_witnesses(&self) -> Self {
Self {
a: None,
a: Value::unknown(),
lookup_table: self.lookup_table.clone(),
}
}
@@ -257,22 +246,17 @@ impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
layouter.assign_region(
|| format!("region_{}", i),
|mut region| {
let mut a_squared = None;
let a: Value<Assigned<_>> = self.a.into();
let mut a_squared = Value::unknown();
let (a0, _, c0) = cs.raw_multiply(&mut region, || {
a_squared = self.a.map(|a| a.square());
Ok((
self.a.ok_or(Error::Synthesis)?,
self.a.ok_or(Error::Synthesis)?,
a_squared.ok_or(Error::Synthesis)?,
))
a_squared = a.square();
a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared))
})?;
let (a1, b1, _) = cs.raw_add(&mut region, || {
let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2));
Ok((
self.a.ok_or(Error::Synthesis)?,
a_squared.ok_or(Error::Synthesis)?,
fin.ok_or(Error::Synthesis)?,
))
let fin = a_squared + a;
a.zip(a_squared)
.zip(fin)
.map(|((a, a_squared), fin)| (a, a_squared, fin))
})?;
cs.copy(&mut region, a0, a1)?;
cs.copy(&mut region, b1, c0)
@@ -294,7 +278,7 @@ fn main() {
let instance = Fp::one() + Fp::one();
let lookup_table = vec![instance, a, a, Fp::zero()];
let circuit: MyCircuit<Fp> = MyCircuit {
a: None,
a: Value::unknown(),
lookup_table,
};

View File

@@ -2,7 +2,7 @@ use std::marker::PhantomData;
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner},
circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value},
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Fixed, Instance, Selector},
poly::Rotation,
};
@@ -13,7 +13,7 @@ trait NumericInstructions<F: FieldExt>: Chip<F> {
type Num;
/// Loads a number into the circuit as a private input.
fn load_private(&self, layouter: impl Layouter<F>, a: Option<F>) -> Result<Self::Num, Error>;
fn load_private(&self, layouter: impl Layouter<F>, a: Value<F>) -> Result<Self::Num, Error>;
/// Loads a number into the circuit as a fixed constant.
fn load_constant(&self, layouter: impl Layouter<F>, constant: F) -> Result<Self::Num, Error>;
@@ -151,7 +151,7 @@ impl<F: FieldExt> NumericInstructions<F> for FieldChip<F> {
fn load_private(
&self,
mut layouter: impl Layouter<F>,
value: Option<F>,
value: Value<F>,
) -> Result<Self::Num, Error> {
let config = self.config();
@@ -159,12 +159,7 @@ impl<F: FieldExt> NumericInstructions<F> for FieldChip<F> {
|| "load private",
|mut region| {
region
.assign_advice(
|| "private input",
config.advice[0],
0,
|| value.ok_or(Error::Synthesis),
)
.assign_advice(|| "private input", config.advice[0], 0, || value)
.map(Number)
},
)
@@ -212,17 +207,12 @@ impl<F: FieldExt> NumericInstructions<F> for FieldChip<F> {
// Now we can assign the multiplication result, which is to be assigned
// into the output position.
let value = a.0.value().and_then(|a| b.0.value().map(|b| *a * *b));
let value = a.0.value().copied() * b.0.value();
// Finally, we do the assignment to the output, returning a
// variable to be used in another part of the circuit.
region
.assign_advice(
|| "lhs * rhs",
config.advice[0],
1,
|| value.ok_or(Error::Synthesis),
)
.assign_advice(|| "lhs * rhs", config.advice[0], 1, || value)
.map(Number)
},
)
@@ -250,8 +240,8 @@ impl<F: FieldExt> NumericInstructions<F> for FieldChip<F> {
#[derive(Default)]
struct MyCircuit<F: FieldExt> {
constant: F,
a: Option<F>,
b: Option<F>,
a: Value<F>,
b: Value<F>,
}
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
@@ -329,8 +319,8 @@ fn main() {
// Instantiate the circuit with the private inputs.
let circuit = MyCircuit {
constant,
a: Some(a),
b: Some(b),
a: Value::known(a),
b: Value::known(b),
};
// Arrange the public input. We expose the multiplication result in row 0

View File

@@ -2,7 +2,7 @@ use std::marker::PhantomData;
use halo2_proofs::{
arithmetic::FieldExt,
circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner},
circuit::{AssignedCell, Chip, Layouter, Region, SimpleFloorPlanner, Value},
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance, Selector},
poly::Rotation,
};
@@ -20,7 +20,7 @@ trait FieldInstructions<F: FieldExt>: AddInstructions<F> + MulInstructions<F> {
fn load_private(
&self,
layouter: impl Layouter<F>,
a: Option<F>,
a: Value<F>,
) -> Result<<Self as FieldInstructions<F>>::Num, Error>;
/// Returns `d = (a + b) * c`.
@@ -217,17 +217,12 @@ impl<F: FieldExt> AddInstructions<F> for AddChip<F> {
// Now we can compute the addition result, which is to be assigned
// into the output position.
let value = a.0.value().and_then(|a| b.0.value().map(|b| *a + *b));
let value = a.0.value().copied() + b.0.value();
// Finally, we do the assignment to the output, returning a
// variable to be used in another part of the circuit.
region
.assign_advice(
|| "lhs + rhs",
config.advice[0],
1,
|| value.ok_or(Error::Synthesis),
)
.assign_advice(|| "lhs + rhs", config.advice[0], 1, || value)
.map(Number)
},
)
@@ -343,17 +338,12 @@ impl<F: FieldExt> MulInstructions<F> for MulChip<F> {
// Now we can compute the multiplication result, which is to be assigned
// into the output position.
let value = a.0.value().and_then(|a| b.0.value().map(|b| *a * *b));
let value = a.0.value().copied() * b.0.value();
// Finally, we do the assignment to the output, returning a
// variable to be used in another part of the circuit.
region
.assign_advice(
|| "lhs * rhs",
config.advice[0],
1,
|| value.ok_or(Error::Synthesis),
)
.assign_advice(|| "lhs * rhs", config.advice[0], 1, || value)
.map(Number)
},
)
@@ -412,7 +402,7 @@ impl<F: FieldExt> FieldInstructions<F> for FieldChip<F> {
fn load_private(
&self,
mut layouter: impl Layouter<F>,
value: Option<F>,
value: Value<F>,
) -> Result<<Self as FieldInstructions<F>>::Num, Error> {
let config = self.config();
@@ -420,12 +410,7 @@ impl<F: FieldExt> FieldInstructions<F> for FieldChip<F> {
|| "load private",
|mut region| {
region
.assign_advice(
|| "private input",
config.advice[0],
0,
|| value.ok_or(Error::Synthesis),
)
.assign_advice(|| "private input", config.advice[0], 0, || value)
.map(Number)
},
)
@@ -459,14 +444,14 @@ impl<F: FieldExt> FieldInstructions<F> for FieldChip<F> {
// ANCHOR: circuit
/// The full circuit implementation.
///
/// In this struct we store the private input variables. We use `Option<F>` because
/// In this struct we store the private input variables. We use `Value<F>` because
/// they won't have any value during key generation. During proving, if any of these
/// were `None` we would get an error.
/// were `Value::unknown()` we would get an error.
#[derive(Default)]
struct MyCircuit<F: FieldExt> {
a: Option<F>,
b: Option<F>,
c: Option<F>,
a: Value<F>,
b: Value<F>,
c: Value<F>,
}
impl<F: FieldExt> Circuit<F> for MyCircuit<F> {
@@ -529,9 +514,9 @@ fn main() {
// Instantiate the circuit with the private inputs.
let circuit = MyCircuit {
a: Some(a),
b: Some(b),
c: Some(c),
a: Value::known(a),
b: Value::known(b),
c: Value::known(c),
};
// Arrange the public input. We expose the multiplication result in row 0

View File

@@ -9,6 +9,9 @@ use crate::{
plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Selector, TableColumn},
};
mod value;
pub use value::Value;
pub mod floor_planner;
pub use floor_planner::single_pass::SimpleFloorPlanner;
@@ -95,14 +98,14 @@ pub struct Cell {
/// An assigned cell.
#[derive(Clone, Debug)]
pub struct AssignedCell<V, F: Field> {
value: Option<V>,
value: Value<V>,
cell: Cell,
_marker: PhantomData<F>,
}
impl<V, F: Field> AssignedCell<V, F> {
/// Returns the value of the [`AssignedCell`].
pub fn value(&self) -> Option<&V> {
pub fn value(&self) -> Value<&V> {
self.value.as_ref()
}
@@ -117,8 +120,8 @@ where
for<'v> Assigned<F>: From<&'v V>,
{
/// Returns the field element value of the [`AssignedCell`].
pub fn value_field(&self) -> Option<Assigned<F>> {
self.value().map(|v| v.into())
pub fn value_field(&self) -> Value<Assigned<F>> {
self.value.to_field()
}
}
@@ -129,7 +132,7 @@ impl<F: Field> AssignedCell<Assigned<F>, F> {
/// If the denominator is zero, the returned cell's value is zero.
pub fn evaluate(self) -> AssignedCell<F, F> {
AssignedCell {
value: self.value.map(|v| v.evaluate()),
value: self.value.evaluate(),
cell: self.cell,
_marker: Default::default(),
}
@@ -155,9 +158,8 @@ where
A: Fn() -> AR,
AR: Into<String>,
{
let assigned_cell = region.assign_advice(annotation, column, offset, || {
self.value.clone().ok_or(Error::Synthesis)
})?;
let assigned_cell =
region.assign_advice(annotation, column, offset, || self.value.clone())?;
region.constrain_equal(assigned_cell.cell(), self.cell())?;
Ok(assigned_cell)
@@ -213,19 +215,19 @@ impl<'r, F: Field> Region<'r, F> {
mut to: V,
) -> Result<AssignedCell<VR, F>, Error>
where
V: FnMut() -> Result<VR, Error> + 'v,
V: FnMut() -> Value<VR> + 'v,
for<'vr> Assigned<F>: From<&'vr VR>,
A: Fn() -> AR,
AR: Into<String>,
{
let mut value = None;
let mut value = Value::unknown();
let cell =
self.region
.assign_advice(&|| annotation().into(), column, offset, &mut || {
let v = to()?;
let value_f = (&v).into();
value = Some(v);
Ok(value_f)
let v = to();
let value_f = v.to_field();
value = v;
value_f
})?;
Ok(AssignedCell {
@@ -261,7 +263,7 @@ impl<'r, F: Field> Region<'r, F> {
)?;
Ok(AssignedCell {
value: Some(constant),
value: Value::known(constant),
cell,
_marker: PhantomData,
})
@@ -309,19 +311,19 @@ impl<'r, F: Field> Region<'r, F> {
mut to: V,
) -> Result<AssignedCell<VR, F>, Error>
where
V: FnMut() -> Result<VR, Error> + 'v,
V: FnMut() -> Value<VR> + 'v,
for<'vr> Assigned<F>: From<&'vr VR>,
A: Fn() -> AR,
AR: Into<String>,
{
let mut value = None;
let mut value = Value::unknown();
let cell =
self.region
.assign_fixed(&|| annotation().into(), column, offset, &mut || {
let v = to()?;
let value_f = (&v).into();
value = Some(v);
Ok(value_f)
let v = to();
let value_f = v.to_field();
value = v;
value_f
})?;
Ok(AssignedCell {
@@ -376,14 +378,14 @@ impl<'r, F: Field> Table<'r, F> {
mut to: V,
) -> Result<(), Error>
where
V: FnMut() -> Result<VR, Error> + 'v,
V: FnMut() -> Value<VR> + 'v,
VR: Into<Assigned<F>>,
A: Fn() -> AR,
AR: Into<String>,
{
self.table
.assign_cell(&|| annotation().into(), column, offset, &mut || {
to().map(|v| v.into())
to().into_field()
})
}
}

View File

@@ -8,7 +8,7 @@ use ff::Field;
use crate::{
circuit::{
layouter::{RegionColumn, RegionLayouter, RegionShape, TableLayouter},
Cell, Layouter, Region, RegionIndex, RegionStart, Table,
Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value,
},
plonk::{
Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance,
@@ -131,7 +131,7 @@ impl<'a, F: Field, CS: Assignment<F> + 'a> Layouter<F> for SingleChipLayouter<'a
|| format!("Constant({:?})", constant.evaluate()),
constants_column,
*next_constant_row,
|| Ok(constant),
|| Value::known(constant),
)?;
self.cs.copy(
constants_column.into(),
@@ -280,7 +280,7 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Advice>,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
self.layouter.cs.assign_advice(
annotation,
@@ -303,7 +303,8 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
offset: usize,
constant: Assigned<F>,
) -> Result<Cell, Error> {
let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?;
let advice =
self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?;
self.constrain_constant(advice, constant)?;
Ok(advice)
@@ -316,12 +317,10 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
row: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<(Cell, Option<F>), Error> {
) -> Result<(Cell, Value<F>), Error> {
let value = self.layouter.cs.query_instance(instance, row)?;
let cell = self.assign_advice(annotation, advice, offset, &mut || {
value.ok_or(Error::Synthesis).map(|v| v.into())
})?;
let cell = self.assign_advice(annotation, advice, offset, &mut || value.to_field())?;
self.layouter.cs.copy(
cell.column,
@@ -338,7 +337,7 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Fixed>,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
self.layouter.cs.assign_fixed(
annotation,
@@ -376,9 +375,9 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F>
/// - The outer `Option` tracks whether the value in row 0 of the table column has been
/// assigned yet. This will always be `Some` once a valid table has been completely
/// assigned.
/// - The inner `Option` tracks whether the underlying `Assignment` is evaluating
/// - The inner `Value` tracks whether the underlying `Assignment` is evaluating
/// witnesses or not.
type DefaultTableValue<F> = Option<Option<Assigned<F>>>;
type DefaultTableValue<F> = Option<Value<Assigned<F>>>;
pub(crate) struct SimpleTableLayouter<'r, 'a, F: Field, CS: Assignment<F> + 'a> {
cs: &'a mut CS,
@@ -414,7 +413,7 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> TableLayouter<F>
annotation: &'v (dyn Fn() -> String + 'v),
column: TableColumn,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<(), Error> {
if self.used_columns.contains(&column) {
return Err(Error::Synthesis); // TODO better error
@@ -422,14 +421,14 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> TableLayouter<F>
let entry = self.default_and_assigned.entry(column).or_default();
let mut value = None;
let mut value = Value::unknown();
self.cs.assign_fixed(
annotation,
column.inner(),
offset, // tables are always assigned starting at row 0
|| {
let res = to();
value = res.as_ref().ok().cloned();
value = res;
res
},
)?;

View File

@@ -6,7 +6,7 @@ use crate::{
circuit::{
floor_planner::single_pass::SimpleTableLayouter,
layouter::{RegionColumn, RegionLayouter, RegionShape, TableLayouter},
Cell, Layouter, Region, RegionIndex, RegionStart, Table,
Cell, Layouter, Region, RegionIndex, RegionStart, Table, Value,
},
plonk::{
Advice, Any, Assigned, Assignment, Circuit, Column, Error, Fixed, FloorPlanner, Instance,
@@ -126,7 +126,7 @@ impl FloorPlanner for V1 {
|| format!("Constant({:?})", value.evaluate()),
fixed_column,
fixed_row,
|| Ok(value),
|| Value::known(value),
)?;
plan.cs.copy(
fixed_column.into(),
@@ -396,7 +396,7 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F> for V1Region<'r
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Advice>,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
self.plan.cs.assign_advice(
annotation,
@@ -419,7 +419,8 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F> for V1Region<'r
offset: usize,
constant: Assigned<F>,
) -> Result<Cell, Error> {
let advice = self.assign_advice(annotation, column, offset, &mut || Ok(constant))?;
let advice =
self.assign_advice(annotation, column, offset, &mut || Value::known(constant))?;
self.constrain_constant(advice, constant)?;
Ok(advice)
@@ -432,12 +433,10 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F> for V1Region<'r
row: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<(Cell, Option<F>), Error> {
) -> Result<(Cell, Value<F>), Error> {
let value = self.plan.cs.query_instance(instance, row)?;
let cell = self.assign_advice(annotation, advice, offset, &mut || {
value.ok_or(Error::Synthesis).map(|v| v.into())
})?;
let cell = self.assign_advice(annotation, advice, offset, &mut || value.to_field())?;
self.plan.cs.copy(
cell.column,
@@ -454,7 +453,7 @@ impl<'r, 'a, F: Field, CS: Assignment<F> + 'a> RegionLayouter<F> for V1Region<'r
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Fixed>,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
self.plan.cs.assign_fixed(
annotation,

View File

@@ -6,7 +6,7 @@ use std::fmt;
use ff::Field;
use super::{Cell, RegionIndex};
use super::{Cell, RegionIndex, Value};
use crate::plonk::{Advice, Any, Assigned, Column, Error, Fixed, Instance, Selector, TableColumn};
/// Helper trait for implementing a custom [`Layouter`].
@@ -54,7 +54,7 @@ pub trait RegionLayouter<F: Field>: fmt::Debug {
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Advice>,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error>;
/// Assigns a constant value to the column `advice` at `offset` within this region.
@@ -82,7 +82,7 @@ pub trait RegionLayouter<F: Field>: fmt::Debug {
row: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<(Cell, Option<F>), Error>;
) -> Result<(Cell, Value<F>), Error>;
/// Assign a fixed value
fn assign_fixed<'v>(
@@ -90,7 +90,7 @@ pub trait RegionLayouter<F: Field>: fmt::Debug {
annotation: &'v (dyn Fn() -> String + 'v),
column: Column<Fixed>,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error>;
/// Constrains a cell to have a constant value.
@@ -118,7 +118,7 @@ pub trait TableLayouter<F: Field>: fmt::Debug {
annotation: &'v (dyn Fn() -> String + 'v),
column: TableColumn,
offset: usize,
to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<(), Error>;
}
@@ -214,7 +214,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
_: &'v (dyn Fn() -> String + 'v),
column: Column<Advice>,
offset: usize,
_to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
_to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
self.columns.insert(Column::<Any>::from(column).into());
self.row_count = cmp::max(self.row_count, offset + 1);
@@ -234,7 +234,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
constant: Assigned<F>,
) -> Result<Cell, Error> {
// The rest is identical to witnessing an advice cell.
self.assign_advice(annotation, column, offset, &mut || Ok(constant))
self.assign_advice(annotation, column, offset, &mut || Value::known(constant))
}
fn assign_advice_from_instance<'v>(
@@ -244,7 +244,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
_: usize,
advice: Column<Advice>,
offset: usize,
) -> Result<(Cell, Option<F>), Error> {
) -> Result<(Cell, Value<F>), Error> {
self.columns.insert(Column::<Any>::from(advice).into());
self.row_count = cmp::max(self.row_count, offset + 1);
@@ -254,7 +254,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
row_offset: offset,
column: advice.into(),
},
None,
Value::unknown(),
))
}
@@ -263,7 +263,7 @@ impl<F: Field> RegionLayouter<F> for RegionShape {
_: &'v (dyn Fn() -> String + 'v),
column: Column<Fixed>,
offset: usize,
_to: &'v mut (dyn FnMut() -> Result<Assigned<F>, Error> + 'v),
_to: &'v mut (dyn FnMut() -> Value<Assigned<F>> + 'v),
) -> Result<Cell, Error> {
self.columns.insert(Column::<Any>::from(column).into());
self.row_count = cmp::max(self.row_count, offset + 1);

View File

@@ -0,0 +1,698 @@
use std::borrow::Borrow;
use std::ops::{Add, Mul, Neg, Sub};
use group::ff::Field;
use crate::plonk::{Assigned, Error};
/// A value that might exist within a circuit.
///
/// This behaves like `Option<V>` but differs in two key ways:
/// - It does not expose the enum cases, or provide an `Option::unwrap` equivalent. This
/// helps to ensure that unwitnessed values correctly propagate.
/// - It provides pass-through implementations of common traits such as `Add` and `Mul`,
/// for improved usability.
#[derive(Clone, Copy, Debug)]
pub struct Value<V> {
inner: Option<V>,
}
impl<V> Default for Value<V> {
fn default() -> Self {
Self::unknown()
}
}
impl<V> Value<V> {
/// Constructs an unwitnessed value.
pub const fn unknown() -> Self {
Self { inner: None }
}
/// Constructs a known value.
///
/// # Examples
///
/// ```
/// use halo2_proofs::circuit::Value;
///
/// let v = Value::known(37);
/// ```
pub const fn known(value: V) -> Self {
Self { inner: Some(value) }
}
/// Obtains the inner value for assigning into the circuit.
///
/// Returns `Error::Synthesis` if this is [`Value::unknown()`].
pub(crate) fn assign(self) -> Result<V, Error> {
self.inner.ok_or(Error::Synthesis)
}
/// Converts from `&Value<V>` to `Value<&V>`.
pub fn as_ref(&self) -> Value<&V> {
Value {
inner: self.inner.as_ref(),
}
}
/// Converts from `&mut Value<V>` to `Value<&mut V>`.
pub fn as_mut(&mut self) -> Value<&mut V> {
Value {
inner: self.inner.as_mut(),
}
}
/// Enforces an assertion on the contained value, if known.
///
/// The assertion is ignored if `self` is [`Value::unknown()`]. Do not try to enforce
/// circuit constraints with this method!
///
/// # Panics
///
/// Panics if `f` returns `false`.
pub fn assert_if_known<F: FnOnce(&V) -> bool>(&self, f: F) {
if let Some(value) = self.inner.as_ref() {
assert!(f(value));
}
}
/// Checks the contained value for an error condition, if known.
///
/// The error check is ignored if `self` is [`Value::unknown()`]. Do not try to
/// enforce circuit constraints with this method!
pub fn error_if_known_and<F: FnOnce(&V) -> bool>(&self, f: F) -> Result<(), Error> {
match self.inner.as_ref() {
Some(value) if f(value) => Err(Error::Synthesis),
_ => Ok(()),
}
}
/// Maps a `Value<V>` to `Value<W>` by applying a function to the contained value.
pub fn map<W, F: FnOnce(V) -> W>(self, f: F) -> Value<W> {
Value {
inner: self.inner.map(f),
}
}
/// Returns [`Value::unknown()`] if the value is [`Value::unknown()`], otherwise calls
/// `f` with the wrapped value and returns the result.
pub fn and_then<W, F: FnOnce(V) -> Value<W>>(self, f: F) -> Value<W> {
match self.inner {
Some(v) => f(v),
None => Value::unknown(),
}
}
/// Zips `self` with another `Value`.
///
/// If `self` is `Value::known(s)` and `other` is `Value::known(o)`, this method
/// returns `Value::known((s, o))`. Otherwise, [`Value::unknown()`] is returned.
pub fn zip<W>(self, other: Value<W>) -> Value<(V, W)> {
Value {
inner: self.inner.zip(other.inner),
}
}
}
impl<V, W> Value<(V, W)> {
/// Unzips a value containing a tuple of two values.
///
/// If `self` is `Value::known((a, b)), this method returns
/// `(Value::known(a), Value::known(b))`. Otherwise,
/// `(Value::unknown(), Value::unknown())` is returned.
pub fn unzip(self) -> (Value<V>, Value<W>) {
match self.inner {
Some((a, b)) => (Value::known(a), Value::known(b)),
None => (Value::unknown(), Value::unknown()),
}
}
}
impl<V> Value<&V> {
/// Maps a `Value<&V>` to a `Value<V>` by copying the contents of the value.
#[must_use = "`self` will be dropped if the result is not used"]
pub fn copied(self) -> Value<V>
where
V: Copy,
{
Value {
inner: self.inner.copied(),
}
}
/// Maps a `Value<&V>` to a `Value<V>` by cloning the contents of the value.
#[must_use = "`self` will be dropped if the result is not used"]
pub fn cloned(self) -> Value<V>
where
V: Clone,
{
Value {
inner: self.inner.cloned(),
}
}
}
impl<V> Value<&mut V> {
/// Maps a `Value<&mut V>` to a `Value<V>` by copying the contents of the value.
#[must_use = "`self` will be dropped if the result is not used"]
pub fn copied(self) -> Value<V>
where
V: Copy,
{
Value {
inner: self.inner.copied(),
}
}
/// Maps a `Value<&mut V>` to a `Value<V>` by cloning the contents of the value.
#[must_use = "`self` will be dropped if the result is not used"]
pub fn cloned(self) -> Value<V>
where
V: Clone,
{
Value {
inner: self.inner.cloned(),
}
}
}
impl<V: Copy, const LEN: usize> Value<[V; LEN]> {
/// Transposes a `Value<[V; LEN]>` into a `[Value<V>; LEN]`.
///
/// [`Value::unknown()`] will be mapped to `[Value::unknown(); LEN]`.
pub fn transpose_array(self) -> [Value<V>; LEN] {
let mut ret = [Value::unknown(); LEN];
if let Some(arr) = self.inner {
for (entry, value) in ret.iter_mut().zip(arr) {
*entry = Value::known(value);
}
}
ret
}
}
impl<V, I> Value<I>
where
I: IntoIterator<Item = V>,
I::IntoIter: ExactSizeIterator,
{
/// Transposes a `Value<impl IntoIterator<Item = V>>` into a `Vec<Value<V>>`.
///
/// [`Value::unknown()`] will be mapped to `vec![Value::unknown(); length]`.
///
/// # Panics
///
/// Panics if `self` is `Value::known(values)` and `values.len() != length`.
pub fn transpose_vec(self, length: usize) -> Vec<Value<V>> {
match self.inner {
Some(values) => {
let values = values.into_iter();
assert_eq!(values.len(), length);
values.map(Value::known).collect()
}
None => (0..length).map(|_| Value::unknown()).collect(),
}
}
}
//
// FromIterator
//
impl<A, V: FromIterator<A>> FromIterator<Value<A>> for Value<V> {
/// Takes each element in the [`Iterator`]: if it is [`Value::unknown()`], no further
/// elements are taken, and the [`Value::unknown()`] is returned. Should no
/// [`Value::unknown()`] occur, a container of type `V` containing the values of each
/// [`Value`] is returned.
fn from_iter<I: IntoIterator<Item = Value<A>>>(iter: I) -> Self {
Self {
inner: iter.into_iter().map(|v| v.inner).collect(),
}
}
}
//
// Neg
//
impl<V: Neg> Neg for Value<V> {
type Output = Value<V::Output>;
fn neg(self) -> Self::Output {
Value {
inner: self.inner.map(|v| -v),
}
}
}
//
// Add
//
impl<V, O> Add for Value<V>
where
V: Add<Output = O>,
{
type Output = Value<O>;
fn add(self, rhs: Self) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b),
}
}
}
impl<V, O> Add for &Value<V>
where
for<'v> &'v V: Add<Output = O>,
{
type Output = Value<O>;
fn add(self, rhs: Self) -> Self::Output {
Value {
inner: self
.inner
.as_ref()
.zip(rhs.inner.as_ref())
.map(|(a, b)| a + b),
}
}
}
impl<V, O> Add<Value<&V>> for Value<V>
where
for<'v> V: Add<&'v V, Output = O>,
{
type Output = Value<O>;
fn add(self, rhs: Value<&V>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b),
}
}
}
impl<V, O> Add<Value<V>> for Value<&V>
where
for<'v> &'v V: Add<V, Output = O>,
{
type Output = Value<O>;
fn add(self, rhs: Value<V>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b),
}
}
}
impl<V, O> Add<&Value<V>> for Value<V>
where
for<'v> V: Add<&'v V, Output = O>,
{
type Output = Value<O>;
fn add(self, rhs: &Self) -> Self::Output {
self + rhs.as_ref()
}
}
impl<V, O> Add<Value<V>> for &Value<V>
where
for<'v> &'v V: Add<V, Output = O>,
{
type Output = Value<O>;
fn add(self, rhs: Value<V>) -> Self::Output {
self.as_ref() + rhs
}
}
//
// Sub
//
impl<V, O> Sub for Value<V>
where
V: Sub<Output = O>,
{
type Output = Value<O>;
fn sub(self, rhs: Self) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b),
}
}
}
impl<V, O> Sub for &Value<V>
where
for<'v> &'v V: Sub<Output = O>,
{
type Output = Value<O>;
fn sub(self, rhs: Self) -> Self::Output {
Value {
inner: self
.inner
.as_ref()
.zip(rhs.inner.as_ref())
.map(|(a, b)| a - b),
}
}
}
impl<V, O> Sub<Value<&V>> for Value<V>
where
for<'v> V: Sub<&'v V, Output = O>,
{
type Output = Value<O>;
fn sub(self, rhs: Value<&V>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b),
}
}
}
impl<V, O> Sub<Value<V>> for Value<&V>
where
for<'v> &'v V: Sub<V, Output = O>,
{
type Output = Value<O>;
fn sub(self, rhs: Value<V>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b),
}
}
}
impl<V, O> Sub<&Value<V>> for Value<V>
where
for<'v> V: Sub<&'v V, Output = O>,
{
type Output = Value<O>;
fn sub(self, rhs: &Self) -> Self::Output {
self - rhs.as_ref()
}
}
impl<V, O> Sub<Value<V>> for &Value<V>
where
for<'v> &'v V: Sub<V, Output = O>,
{
type Output = Value<O>;
fn sub(self, rhs: Value<V>) -> Self::Output {
self.as_ref() - rhs
}
}
//
// Mul
//
impl<V, O> Mul for Value<V>
where
V: Mul<Output = O>,
{
type Output = Value<O>;
fn mul(self, rhs: Self) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b),
}
}
}
impl<V, O> Mul for &Value<V>
where
for<'v> &'v V: Mul<Output = O>,
{
type Output = Value<O>;
fn mul(self, rhs: Self) -> Self::Output {
Value {
inner: self
.inner
.as_ref()
.zip(rhs.inner.as_ref())
.map(|(a, b)| a * b),
}
}
}
impl<V, O> Mul<Value<&V>> for Value<V>
where
for<'v> V: Mul<&'v V, Output = O>,
{
type Output = Value<O>;
fn mul(self, rhs: Value<&V>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b),
}
}
}
impl<V, O> Mul<Value<V>> for Value<&V>
where
for<'v> &'v V: Mul<V, Output = O>,
{
type Output = Value<O>;
fn mul(self, rhs: Value<V>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b),
}
}
}
impl<V, O> Mul<&Value<V>> for Value<V>
where
for<'v> V: Mul<&'v V, Output = O>,
{
type Output = Value<O>;
fn mul(self, rhs: &Self) -> Self::Output {
self * rhs.as_ref()
}
}
impl<V, O> Mul<Value<V>> for &Value<V>
where
for<'v> &'v V: Mul<V, Output = O>,
{
type Output = Value<O>;
fn mul(self, rhs: Value<V>) -> Self::Output {
self.as_ref() * rhs
}
}
//
// Assigned<Field>
//
impl<F: Field> From<Value<F>> for Value<Assigned<F>> {
fn from(value: Value<F>) -> Self {
Self {
inner: value.inner.map(Assigned::from),
}
}
}
impl<F: Field> Add<Value<F>> for Value<Assigned<F>> {
type Output = Value<Assigned<F>>;
fn add(self, rhs: Value<F>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b),
}
}
}
impl<F: Field> Add<F> for Value<Assigned<F>> {
type Output = Value<Assigned<F>>;
fn add(self, rhs: F) -> Self::Output {
self + Value::known(rhs)
}
}
impl<F: Field> Add<Value<F>> for Value<&Assigned<F>> {
type Output = Value<Assigned<F>>;
fn add(self, rhs: Value<F>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a + b),
}
}
}
impl<F: Field> Add<F> for Value<&Assigned<F>> {
type Output = Value<Assigned<F>>;
fn add(self, rhs: F) -> Self::Output {
self + Value::known(rhs)
}
}
impl<F: Field> Sub<Value<F>> for Value<Assigned<F>> {
type Output = Value<Assigned<F>>;
fn sub(self, rhs: Value<F>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b),
}
}
}
impl<F: Field> Sub<F> for Value<Assigned<F>> {
type Output = Value<Assigned<F>>;
fn sub(self, rhs: F) -> Self::Output {
self - Value::known(rhs)
}
}
impl<F: Field> Sub<Value<F>> for Value<&Assigned<F>> {
type Output = Value<Assigned<F>>;
fn sub(self, rhs: Value<F>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a - b),
}
}
}
impl<F: Field> Sub<F> for Value<&Assigned<F>> {
type Output = Value<Assigned<F>>;
fn sub(self, rhs: F) -> Self::Output {
self - Value::known(rhs)
}
}
impl<F: Field> Mul<Value<F>> for Value<Assigned<F>> {
type Output = Value<Assigned<F>>;
fn mul(self, rhs: Value<F>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b),
}
}
}
impl<F: Field> Mul<F> for Value<Assigned<F>> {
type Output = Value<Assigned<F>>;
fn mul(self, rhs: F) -> Self::Output {
self * Value::known(rhs)
}
}
impl<F: Field> Mul<Value<F>> for Value<&Assigned<F>> {
type Output = Value<Assigned<F>>;
fn mul(self, rhs: Value<F>) -> Self::Output {
Value {
inner: self.inner.zip(rhs.inner).map(|(a, b)| a * b),
}
}
}
impl<F: Field> Mul<F> for Value<&Assigned<F>> {
type Output = Value<Assigned<F>>;
fn mul(self, rhs: F) -> Self::Output {
self * Value::known(rhs)
}
}
impl<V> Value<V> {
/// Returns the field element corresponding to this value.
pub fn to_field<F: Field>(&self) -> Value<Assigned<F>>
where
for<'v> Assigned<F>: From<&'v V>,
{
Value {
inner: self.inner.as_ref().map(|v| v.into()),
}
}
/// Returns the field element corresponding to this value.
pub fn into_field<F: Field>(self) -> Value<Assigned<F>>
where
V: Into<Assigned<F>>,
{
Value {
inner: self.inner.map(|v| v.into()),
}
}
/// Doubles this field element.
///
/// # Examples
///
/// If you have a `Value<F: Field>`, convert it to `Value<Assigned<F>>` first:
/// ```
/// # use pasta_curves::pallas::Base as F;
/// use halo2_proofs::{circuit::Value, plonk::Assigned};
///
/// let v = Value::known(F::from(2));
/// let v: Value<Assigned<F>> = v.into();
/// v.double();
/// ```
pub fn double<F: Field>(&self) -> Value<Assigned<F>>
where
V: Borrow<Assigned<F>>,
{
Value {
inner: self.inner.as_ref().map(|v| v.borrow().double()),
}
}
/// Squares this field element.
pub fn square<F: Field>(&self) -> Value<Assigned<F>>
where
V: Borrow<Assigned<F>>,
{
Value {
inner: self.inner.as_ref().map(|v| v.borrow().square()),
}
}
/// Cubes this field element.
pub fn cube<F: Field>(&self) -> Value<Assigned<F>>
where
V: Borrow<Assigned<F>>,
{
Value {
inner: self.inner.as_ref().map(|v| v.borrow().cube()),
}
}
/// Inverts this assigned value (taking the inverse of zero to be zero).
pub fn invert<F: Field>(&self) -> Value<Assigned<F>>
where
V: Borrow<Assigned<F>>,
{
Value {
inner: self.inner.as_ref().map(|v| v.borrow().invert()),
}
}
}
impl<F: Field> Value<Assigned<F>> {
/// Evaluates this value directly, performing an unbatched inversion if necessary.
///
/// If the denominator is zero, the returned value is zero.
pub fn evaluate(self) -> Value<F> {
Value {
inner: self.inner.map(|v| v.evaluate()),
}
}
}

View File

@@ -11,6 +11,7 @@ use ff::Field;
use crate::plonk::Assigned;
use crate::{
arithmetic::{FieldExt, Group},
circuit,
plonk::{
permutation, Advice, Any, Assignment, Circuit, Column, ColumnType, ConstraintSystem, Error,
Expression, Fixed, FloorPlanner, Instance, Selector, VirtualCell,
@@ -167,7 +168,7 @@ impl<F: Group + Field> Mul<F> for Value<F> {
/// ```
/// use halo2_proofs::{
/// arithmetic::FieldExt,
/// circuit::{Layouter, SimpleFloorPlanner},
/// circuit::{Layouter, SimpleFloorPlanner, Value},
/// dev::{FailureLocation, MockProver, VerifyFailure},
/// pasta::Fp,
/// plonk::{Advice, Any, Circuit, Column, ConstraintSystem, Error, Selector},
@@ -185,8 +186,8 @@ impl<F: Group + Field> Mul<F> for Value<F> {
///
/// #[derive(Clone, Default)]
/// struct MyCircuit {
/// a: Option<u64>,
/// b: Option<u64>,
/// a: Value<u64>,
/// b: Value<u64>,
/// }
///
/// impl<F: FieldExt> Circuit<F> for MyCircuit {
@@ -220,15 +221,13 @@ impl<F: Group + Field> Mul<F> for Value<F> {
/// layouter.assign_region(|| "Example region", |mut region| {
/// config.s.enable(&mut region, 0)?;
/// region.assign_advice(|| "a", config.a, 0, || {
/// self.a.map(|v| F::from(v)).ok_or(Error::Synthesis)
/// self.a.map(F::from)
/// })?;
/// region.assign_advice(|| "b", config.b, 0, || {
/// self.b.map(|v| F::from(v)).ok_or(Error::Synthesis)
/// self.b.map(F::from)
/// })?;
/// region.assign_advice(|| "c", config.c, 0, || {
/// self.a
/// .and_then(|a| self.b.map(|b| F::from(a * b)))
/// .ok_or(Error::Synthesis)
/// (self.a * self.b).map(F::from)
/// })?;
/// Ok(())
/// })
@@ -237,8 +236,8 @@ impl<F: Group + Field> Mul<F> for Value<F> {
///
/// // Assemble the private inputs to the circuit.
/// let circuit = MyCircuit {
/// a: Some(2),
/// b: Some(4),
/// a: Value::known(2),
/// b: Value::known(4),
/// };
///
/// // This circuit has no public inputs.
@@ -340,7 +339,11 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
Ok(())
}
fn query_instance(&self, column: Column<Instance>, row: usize) -> Result<Option<F>, Error> {
fn query_instance(
&self,
column: Column<Instance>,
row: usize,
) -> Result<circuit::Value<F>, Error> {
if !self.usable_rows.contains(&row) {
return Err(Error::not_enough_rows_available(self.k));
}
@@ -348,7 +351,7 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
self.instance
.get(column.index())
.and_then(|column| column.get(row))
.map(|v| Some(*v))
.map(|v| circuit::Value::known(*v))
.ok_or(Error::BoundsFailure)
}
@@ -360,7 +363,7 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> circuit::Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -378,7 +381,8 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
.advice
.get_mut(column.index())
.and_then(|v| v.get_mut(row))
.ok_or(Error::BoundsFailure)? = CellValue::Assigned(to()?.into().evaluate());
.ok_or(Error::BoundsFailure)? =
CellValue::Assigned(to().into_field().evaluate().assign()?);
Ok(())
}
@@ -391,7 +395,7 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> circuit::Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -409,7 +413,8 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
.fixed
.get_mut(column.index())
.and_then(|v| v.get_mut(row))
.ok_or(Error::BoundsFailure)? = CellValue::Assigned(to()?.into().evaluate());
.ok_or(Error::BoundsFailure)? =
CellValue::Assigned(to().into_field().evaluate().assign()?);
Ok(())
}
@@ -433,14 +438,14 @@ impl<F: Field + Group> Assignment<F> for MockProver<F> {
&mut self,
col: Column<Fixed>,
from_row: usize,
to: Option<Assigned<F>>,
to: circuit::Value<Assigned<F>>,
) -> Result<(), Error> {
if !self.usable_rows.contains(&from_row) {
return Err(Error::not_enough_rows_available(self.k));
}
for row in self.usable_rows.clone().skip(from_row) {
self.assign_fixed(|| "", col, row, || to.ok_or(Error::Synthesis))?;
self.assign_fixed(|| "", col, row, || to)?;
}
Ok(())
@@ -876,7 +881,7 @@ mod tests {
use super::{FailureLocation, MockProver, VerifyFailure};
use crate::{
circuit::{Layouter, SimpleFloorPlanner},
circuit::{Layouter, SimpleFloorPlanner, Value},
plonk::{
Advice, Any, Circuit, Column, ConstraintSystem, Error, Expression, Selector,
TableColumn,
@@ -933,7 +938,7 @@ mod tests {
config.q.enable(&mut region, 1)?;
// Assign a = 0.
region.assign_advice(|| "a", config.a, 0, || Ok(Fp::zero()))?;
region.assign_advice(|| "a", config.a, 0, || Value::known(Fp::zero()))?;
// BUG: Forget to assign b = 0! This could go unnoticed during
// development, because cell values default to zero, which in this
@@ -1011,7 +1016,7 @@ mod tests {
|| format!("table[{}] = {}", i, 2 * i),
config.table,
i - 1,
|| Ok(Fp::from(2 * i as u64)),
|| Value::known(Fp::from(2 * i as u64)),
)
})
.fold(Ok(()), |acc, res| acc.and(res))
@@ -1026,8 +1031,18 @@ mod tests {
config.q.enable(&mut region, 1)?;
// Assign a = 2 and a = 6.
region.assign_advice(|| "a = 2", config.a, 0, || Ok(Fp::from(2)))?;
region.assign_advice(|| "a = 6", config.a, 1, || Ok(Fp::from(6)))?;
region.assign_advice(
|| "a = 2",
config.a,
0,
|| Value::known(Fp::from(2)),
)?;
region.assign_advice(
|| "a = 6",
config.a,
1,
|| Value::known(Fp::from(6)),
)?;
Ok(())
},
@@ -1041,10 +1056,20 @@ mod tests {
config.q.enable(&mut region, 1)?;
// Assign a = 4.
region.assign_advice(|| "a = 4", config.a, 0, || Ok(Fp::from(4)))?;
region.assign_advice(
|| "a = 4",
config.a,
0,
|| Value::known(Fp::from(4)),
)?;
// BUG: Assign a = 5, which doesn't exist in the table!
region.assign_advice(|| "a = 5", config.a, 1, || Ok(Fp::from(5)))?;
region.assign_advice(
|| "a = 5",
config.a,
1,
|| Value::known(Fp::from(5)),
)?;
Ok(())
},

View File

@@ -11,6 +11,7 @@ use ff::{Field, PrimeField};
use group::prime::PrimeGroup;
use crate::{
circuit::Value,
plonk::{
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
FloorPlanner, Instance, Selector,
@@ -68,8 +69,8 @@ impl<F: Field> Assignment<F> for Assembly {
Ok(())
}
fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Option<F>, Error> {
Ok(None)
fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Value<F>, Error> {
Ok(Value::unknown())
}
fn assign_advice<V, VR, A, AR>(
@@ -80,7 +81,7 @@ impl<F: Field> Assignment<F> for Assembly {
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -96,7 +97,7 @@ impl<F: Field> Assignment<F> for Assembly {
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -112,7 +113,7 @@ impl<F: Field> Assignment<F> for Assembly {
&mut self,
_: Column<Fixed>,
_: usize,
_: Option<Assigned<F>>,
_: Value<Assigned<F>>,
) -> Result<(), Error> {
Ok(())
}

View File

@@ -1,9 +1,12 @@
use ff::Field;
use tabbycat::{AttrList, Edge, GraphBuilder, GraphType, Identity, StmtList};
use crate::plonk::{
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
FloorPlanner, Instance, Selector,
use crate::{
circuit::Value,
plonk::{
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
FloorPlanner, Instance, Selector,
},
};
pub mod layout;
@@ -96,8 +99,8 @@ impl<F: Field> Assignment<F> for Graph {
Ok(())
}
fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Option<F>, Error> {
Ok(None)
fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Value<F>, Error> {
Ok(Value::unknown())
}
fn assign_advice<V, VR, A, AR>(
@@ -108,7 +111,7 @@ impl<F: Field> Assignment<F> for Graph {
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -125,7 +128,7 @@ impl<F: Field> Assignment<F> for Graph {
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -149,7 +152,7 @@ impl<F: Field> Assignment<F> for Graph {
&mut self,
_: Column<Fixed>,
_: usize,
_: Option<Assigned<F>>,
_: Value<Assigned<F>>,
) -> Result<(), Error> {
Ok(())
}

View File

@@ -7,10 +7,12 @@ use std::cmp;
use std::collections::HashSet;
use std::ops::Range;
use crate::circuit::layouter::RegionColumn;
use crate::plonk::{
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
FloorPlanner, Instance, Selector,
use crate::{
circuit::{layouter::RegionColumn, Value},
plonk::{
Advice, Any, Assigned, Assignment, Circuit, Column, ConstraintSystem, Error, Fixed,
FloorPlanner, Instance, Selector,
},
};
/// Graphical renderer for circuit layouts.
@@ -430,8 +432,8 @@ impl<F: Field> Assignment<F> for Layout {
Ok(())
}
fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Option<F>, Error> {
Ok(None)
fn query_instance(&self, _: Column<Instance>, _: usize) -> Result<Value<F>, Error> {
Ok(Value::unknown())
}
fn assign_advice<V, VR, A, AR>(
@@ -442,7 +444,7 @@ impl<F: Field> Assignment<F> for Layout {
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -459,7 +461,7 @@ impl<F: Field> Assignment<F> for Layout {
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -483,7 +485,7 @@ impl<F: Field> Assignment<F> for Layout {
&mut self,
_: Column<Fixed>,
_: usize,
_: Option<Assigned<F>>,
_: Value<Assigned<F>>,
) -> Result<(), Error> {
Ok(())
}

View File

@@ -250,6 +250,13 @@ impl<F: Field> Mul<F> for Assigned<F> {
}
}
impl<F: Field> Mul<F> for &Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: F) -> Assigned<F> {
*self * rhs
}
}
impl<F: Field> Mul<&Assigned<F>> for Assigned<F> {
type Output = Assigned<F>;
fn mul(self, rhs: &Assigned<F>) -> Assigned<F> {

View File

@@ -7,8 +7,10 @@ use std::{
};
use super::{lookup, permutation, Assigned, Error};
use crate::circuit::Layouter;
use crate::{circuit::Region, poly::Rotation};
use crate::{
circuit::{Layouter, Region, Value},
poly::Rotation,
};
mod compress_selectors;
@@ -226,7 +228,11 @@ impl TryFrom<Column<Any>> for Column<Instance> {
/// Selectors are disabled on all rows by default, and must be explicitly enabled on each
/// row when required:
/// ```
/// use halo2_proofs::{arithmetic::FieldExt, circuit::{Chip, Layouter}, plonk::{Advice, Column, Error, Selector}};
/// use halo2_proofs::{
/// arithmetic::FieldExt,
/// circuit::{Chip, Layouter, Value},
/// plonk::{Advice, Column, Error, Selector},
/// };
/// # use ff::Field;
/// # use halo2_proofs::plonk::Fixed;
///
@@ -240,8 +246,8 @@ impl TryFrom<Column<Any>> for Column<Instance> {
/// let config = chip.config();
/// # let config: Config = todo!();
/// layouter.assign_region(|| "bar", |mut region| {
/// region.assign_advice(|| "a", config.a, 0, || Ok(F::one()))?;
/// region.assign_advice(|| "a", config.b, 1, || Ok(F::one()))?;
/// region.assign_advice(|| "a", config.a, 0, || Value::known(F::one()))?;
/// region.assign_advice(|| "a", config.b, 1, || Value::known(F::one()))?;
/// config.s.enable(&mut region, 1)
/// })?;
/// Ok(())
@@ -329,7 +335,7 @@ pub trait Assignment<F: Field> {
/// Queries the cell of an instance column at a particular absolute row.
///
/// Returns the cell's value, if known.
fn query_instance(&self, column: Column<Instance>, row: usize) -> Result<Option<F>, Error>;
fn query_instance(&self, column: Column<Instance>, row: usize) -> Result<Value<F>, Error>;
/// Assign an advice column value (witness)
fn assign_advice<V, VR, A, AR>(
@@ -340,7 +346,7 @@ pub trait Assignment<F: Field> {
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>;
@@ -354,7 +360,7 @@ pub trait Assignment<F: Field> {
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>;
@@ -373,7 +379,7 @@ pub trait Assignment<F: Field> {
&mut self,
column: Column<Fixed>,
row: usize,
to: Option<Assigned<F>>,
to: Value<Assigned<F>>,
) -> Result<(), Error>;
/// Creates a new (sub)namespace and enters into it.

View File

@@ -12,11 +12,15 @@ use super::{
},
permutation, Assigned, Error, LagrangeCoeff, Polynomial, ProvingKey, VerifyingKey,
};
use crate::poly::{
commitment::{Blind, Params},
EvaluationDomain,
use crate::{
arithmetic::CurveAffine,
circuit::Value,
poly::{
batch_invert_assigned,
commitment::{Blind, Params},
EvaluationDomain,
},
};
use crate::{arithmetic::CurveAffine, poly::batch_invert_assigned};
pub(crate) fn create_domain<C, ConcreteCircuit>(
params: &Params<C>,
@@ -78,13 +82,13 @@ impl<F: Field> Assignment<F> for Assembly<F> {
Ok(())
}
fn query_instance(&self, _: Column<Instance>, row: usize) -> Result<Option<F>, Error> {
fn query_instance(&self, _: Column<Instance>, row: usize) -> Result<Value<F>, Error> {
if !self.usable_rows.contains(&row) {
return Err(Error::not_enough_rows_available(self.k));
}
// There is no instance in this context.
Ok(None)
Ok(Value::unknown())
}
fn assign_advice<V, VR, A, AR>(
@@ -95,7 +99,7 @@ impl<F: Field> Assignment<F> for Assembly<F> {
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -112,7 +116,7 @@ impl<F: Field> Assignment<F> for Assembly<F> {
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -125,7 +129,7 @@ impl<F: Field> Assignment<F> for Assembly<F> {
.fixed
.get_mut(column.index())
.and_then(|v| v.get_mut(row))
.ok_or(Error::BoundsFailure)? = to()?.into();
.ok_or(Error::BoundsFailure)? = to().into_field().assign()?;
Ok(())
}
@@ -149,7 +153,7 @@ impl<F: Field> Assignment<F> for Assembly<F> {
&mut self,
column: Column<Fixed>,
from_row: usize,
to: Option<Assigned<F>>,
to: Value<Assigned<F>>,
) -> Result<(), Error> {
if !self.usable_rows.contains(&from_row) {
return Err(Error::not_enough_rows_available(self.k));
@@ -160,8 +164,9 @@ impl<F: Field> Assignment<F> for Assembly<F> {
.get_mut(column.index())
.ok_or(Error::BoundsFailure)?;
let filler = to.assign()?;
for row in self.usable_rows.clone().skip(from_row) {
col[row] = to.ok_or(Error::Synthesis)?;
col[row] = filler;
}
Ok(())

View File

@@ -12,15 +12,16 @@ use super::{
lookup, permutation, vanishing, ChallengeBeta, ChallengeGamma, ChallengeTheta, ChallengeX,
ChallengeY, Error, ProvingKey,
};
use crate::poly::{
self,
commitment::{Blind, Params},
multiopen::{self, ProverQuery},
Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial,
};
use crate::{
arithmetic::{eval_polynomial, CurveAffine, FieldExt},
circuit::Value,
plonk::Assigned,
poly::{
self,
commitment::{Blind, Params},
multiopen::{self, ProverQuery},
Coeff, ExtendedLagrangeCoeff, LagrangeCoeff, Polynomial,
},
};
use crate::{
poly::batch_invert_assigned,
@@ -171,7 +172,7 @@ pub fn create_proof<
&self,
column: Column<Instance>,
row: usize,
) -> Result<Option<F>, Error> {
) -> Result<Value<F>, Error> {
if !self.usable_rows.contains(&row) {
return Err(Error::not_enough_rows_available(self.k));
}
@@ -179,7 +180,7 @@ pub fn create_proof<
self.instances
.get(column.index())
.and_then(|column| column.get(row))
.map(|v| Some(*v))
.map(|v| Value::known(*v))
.ok_or(Error::BoundsFailure)
}
@@ -191,7 +192,7 @@ pub fn create_proof<
to: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -204,7 +205,7 @@ pub fn create_proof<
.advice
.get_mut(column.index())
.and_then(|v| v.get_mut(row))
.ok_or(Error::BoundsFailure)? = to()?.into();
.ok_or(Error::BoundsFailure)? = to().into_field().assign()?;
Ok(())
}
@@ -217,7 +218,7 @@ pub fn create_proof<
_: V,
) -> Result<(), Error>
where
V: FnOnce() -> Result<VR, Error>,
V: FnOnce() -> Value<VR>,
VR: Into<Assigned<F>>,
A: FnOnce() -> AR,
AR: Into<String>,
@@ -243,7 +244,7 @@ pub fn create_proof<
&mut self,
_: Column<Fixed>,
_: usize,
_: Option<Assigned<F>>,
_: Value<Assigned<F>>,
) -> Result<(), Error> {
Ok(())
}

View File

@@ -3,12 +3,12 @@
use assert_matches::assert_matches;
use halo2_proofs::arithmetic::{CurveAffine, FieldExt};
use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner};
use halo2_proofs::circuit::{Cell, Layouter, SimpleFloorPlanner, Value};
use halo2_proofs::dev::MockProver;
use halo2_proofs::pasta::{Eq, EqAffine, Fp};
use halo2_proofs::plonk::{
create_proof, keygen_pk, keygen_vk, verify_proof, Advice, BatchVerifier, Circuit, Column,
ConstraintSystem, Error, Fixed, SingleVerifier, TableColumn, VerificationStrategy,
create_proof, keygen_pk, keygen_vk, verify_proof, Advice, Assigned, BatchVerifier, Circuit,
Column, ConstraintSystem, Error, Fixed, SingleVerifier, TableColumn, VerificationStrategy,
};
use halo2_proofs::poly::commitment::{Guard, MSM};
use halo2_proofs::poly::{commitment::Params, Rotation};
@@ -51,18 +51,18 @@ fn plonk_api() {
f: F,
) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>;
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn raw_add<F>(
&self,
layouter: &mut impl Layouter<FF>,
f: F,
) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>;
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>;
fn copy(&self, layouter: &mut impl Layouter<FF>, a: Cell, b: Cell) -> Result<(), Error>;
fn public_input<F>(&self, layouter: &mut impl Layouter<FF>, f: F) -> Result<Cell, Error>
where
F: FnMut() -> Result<FF, Error>;
F: FnMut() -> Value<FF>;
fn lookup_table(
&self,
layouter: &mut impl Layouter<FF>,
@@ -72,7 +72,7 @@ fn plonk_api() {
#[derive(Clone)]
struct MyCircuit<F: FieldExt> {
a: Option<F>,
a: Value<F>,
lookup_table: Vec<F>,
}
@@ -97,7 +97,7 @@ fn plonk_api() {
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>,
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
layouter.assign_region(
|| "raw_multiply",
@@ -108,39 +108,44 @@ fn plonk_api() {
self.config.a,
0,
|| {
value = Some(f()?);
Ok(value.ok_or(Error::Synthesis)?.0)
value = Some(f());
value.unwrap().map(|v| v.0)
},
)?;
region.assign_advice(
|| "lhs^4",
self.config.d,
0,
|| Ok(value.ok_or(Error::Synthesis)?.0.square().square()),
|| value.unwrap().map(|v| v.0).square().square(),
)?;
let rhs = region.assign_advice(
|| "rhs",
self.config.b,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1),
|| value.unwrap().map(|v| v.1),
)?;
region.assign_advice(
|| "rhs^4",
self.config.e,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1.square().square()),
|| value.unwrap().map(|v| v.1).square().square(),
)?;
let out = region.assign_advice(
|| "out",
self.config.c,
0,
|| Ok(value.ok_or(Error::Synthesis)?.2),
|| value.unwrap().map(|v| v.2),
)?;
region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::zero()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::zero()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::zero()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::zero()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?;
region.assign_fixed(
|| "a * b",
self.config.sm,
0,
|| Value::known(FF::one()),
)?;
Ok((lhs.cell(), rhs.cell(), out.cell()))
},
)
@@ -151,7 +156,7 @@ fn plonk_api() {
mut f: F,
) -> Result<(Cell, Cell, Cell), Error>
where
F: FnMut() -> Result<(FF, FF, FF), Error>,
F: FnMut() -> Value<(Assigned<FF>, Assigned<FF>, Assigned<FF>)>,
{
layouter.assign_region(
|| "raw_add",
@@ -162,39 +167,44 @@ fn plonk_api() {
self.config.a,
0,
|| {
value = Some(f()?);
Ok(value.ok_or(Error::Synthesis)?.0)
value = Some(f());
value.unwrap().map(|v| v.0)
},
)?;
region.assign_advice(
|| "lhs^4",
self.config.d,
0,
|| Ok(value.ok_or(Error::Synthesis)?.0.square().square()),
|| value.unwrap().map(|v| v.0).square().square(),
)?;
let rhs = region.assign_advice(
|| "rhs",
self.config.b,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1),
|| value.unwrap().map(|v| v.1),
)?;
region.assign_advice(
|| "rhs^4",
self.config.e,
0,
|| Ok(value.ok_or(Error::Synthesis)?.1.square().square()),
|| value.unwrap().map(|v| v.1).square().square(),
)?;
let out = region.assign_advice(
|| "out",
self.config.c,
0,
|| Ok(value.ok_or(Error::Synthesis)?.2),
|| value.unwrap().map(|v| v.2),
)?;
region.assign_fixed(|| "a", self.config.sa, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Ok(FF::one()))?;
region.assign_fixed(|| "a * b", self.config.sm, 0, || Ok(FF::zero()))?;
region.assign_fixed(|| "a", self.config.sa, 0, || Value::known(FF::one()))?;
region.assign_fixed(|| "b", self.config.sb, 0, || Value::known(FF::one()))?;
region.assign_fixed(|| "c", self.config.sc, 0, || Value::known(FF::one()))?;
region.assign_fixed(
|| "a * b",
self.config.sm,
0,
|| Value::known(FF::zero()),
)?;
Ok((lhs.cell(), rhs.cell(), out.cell()))
},
)
@@ -215,13 +225,18 @@ fn plonk_api() {
}
fn public_input<F>(&self, layouter: &mut impl Layouter<FF>, mut f: F) -> Result<Cell, Error>
where
F: FnMut() -> Result<FF, Error>,
F: FnMut() -> Value<FF>,
{
layouter.assign_region(
|| "public_input",
|mut region| {
let value = region.assign_advice(|| "value", self.config.a, 0, &mut f)?;
region.assign_fixed(|| "public", self.config.sp, 0, || Ok(FF::one()))?;
region.assign_fixed(
|| "public",
self.config.sp,
0,
|| Value::known(FF::one()),
)?;
Ok(value.cell())
},
@@ -236,7 +251,12 @@ fn plonk_api() {
|| "",
|mut table| {
for (index, &value) in values.iter().enumerate() {
table.assign_cell(|| "table col", self.config.sl, index, || Ok(value))?;
table.assign_cell(
|| "table col",
self.config.sl,
index,
|| Value::known(value),
)?;
}
Ok(())
},
@@ -251,7 +271,7 @@ fn plonk_api() {
fn without_witnesses(&self) -> Self {
Self {
a: None,
a: Value::unknown(),
lookup_table: self.lookup_table.clone(),
}
}
@@ -353,25 +373,20 @@ fn plonk_api() {
) -> Result<(), Error> {
let cs = StandardPlonk::new(config);
let _ = cs.public_input(&mut layouter, || Ok(F::one() + F::one()))?;
let _ = cs.public_input(&mut layouter, || Value::known(F::one() + F::one()))?;
for _ in 0..10 {
let mut a_squared = None;
let a: Value<Assigned<_>> = self.a.into();
let mut a_squared = Value::unknown();
let (a0, _, c0) = cs.raw_multiply(&mut layouter, || {
a_squared = self.a.map(|a| a.square());
Ok((
self.a.ok_or(Error::Synthesis)?,
self.a.ok_or(Error::Synthesis)?,
a_squared.ok_or(Error::Synthesis)?,
))
a_squared = a.square();
a.zip(a_squared).map(|(a, a_squared)| (a, a, a_squared))
})?;
let (a1, b1, _) = cs.raw_add(&mut layouter, || {
let fin = a_squared.and_then(|a2| self.a.map(|a| a + a2));
Ok((
self.a.ok_or(Error::Synthesis)?,
a_squared.ok_or(Error::Synthesis)?,
fin.ok_or(Error::Synthesis)?,
))
let fin = a_squared + a;
a.zip(a_squared)
.zip(fin)
.map(|((a, a_squared), fin)| (a, a_squared, fin))
})?;
cs.copy(&mut layouter, a0, a1)?;
cs.copy(&mut layouter, b1, c0)?;
@@ -388,12 +403,12 @@ fn plonk_api() {
let lookup_table = vec![instance, a, a, Fp::zero()];
let empty_circuit: MyCircuit<Fp> = MyCircuit {
a: None,
a: Value::unknown(),
lookup_table: lookup_table.clone(),
};
let circuit: MyCircuit<Fp> = MyCircuit {
a: Some(a),
a: Value::known(a),
lookup_table,
};