mirror of https://github.com/zcash/halo2.git
Compare commits
18 Commits
54ad0cff8a
...
8b00c34c15
Author | SHA1 | Date |
---|---|---|
Paul | 8b00c34c15 | |
Daira-Emma Hopwood | 7df93fd855 | |
adria0 | daaa638966 | |
Constance | 5f436dc338 | |
Constance | 622875ead0 | |
Constance | ac7a90d9ad | |
Constance | d76d2317b7 | |
Constance | 87464d4b3f | |
Constance Beguier | cba30b1b84 | |
Constance Beguier | f51eebeb4e | |
Constance Beguier | 4c3c00bced | |
Constance Beguier | 475f54daa4 | |
Constance | 4ce262d8e7 | |
Constance | 54697b22ed | |
Paul | 8cfe0ae67d | |
Constance | b1e397f5b1 | |
Constance | 731bc1021a | |
Constance | 35c815d36e |
|
@ -12,7 +12,7 @@ jobs:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions-rs/toolchain@v1
|
- uses: actions-rs/toolchain@v1
|
||||||
with:
|
with:
|
||||||
toolchain: nightly
|
toolchain: '1.76.0'
|
||||||
override: true
|
override: true
|
||||||
|
|
||||||
# - name: Setup mdBook
|
# - name: Setup mdBook
|
||||||
|
@ -26,7 +26,7 @@ jobs:
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
command: install
|
command: install
|
||||||
args: mdbook --git https://github.com/HollowMan6/mdBook.git --rev 62e01b34c23b957579c04ee1b24b57814ed8a4d5
|
args: mdbook --git https://github.com/HollowMan6/mdBook.git --rev 5830c9555a4dc051675d17f1fcb04dd0920543e8
|
||||||
|
|
||||||
- name: Install mdbook-katex and mdbook-pdf
|
- name: Install mdbook-katex and mdbook-pdf
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
|
@ -40,6 +40,11 @@ jobs:
|
||||||
- name: Build halo2 book
|
- name: Build halo2 book
|
||||||
run: mdbook build book/
|
run: mdbook build book/
|
||||||
|
|
||||||
|
- uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
toolchain: nightly-2023-10-05
|
||||||
|
override: true
|
||||||
|
|
||||||
- name: Build latest rustdocs
|
- name: Build latest rustdocs
|
||||||
uses: actions-rs/cargo@v1
|
uses: actions-rs/cargo@v1
|
||||||
with:
|
with:
|
||||||
|
|
|
@ -125,33 +125,33 @@ jobs:
|
||||||
- name: Test halo2 book
|
- name: Test halo2 book
|
||||||
run: mdbook test -L target/debug/deps book/
|
run: mdbook test -L target/debug/deps book/
|
||||||
|
|
||||||
codecov:
|
# codecov:
|
||||||
name: Code coverage
|
# name: Code coverage
|
||||||
runs-on: ubuntu-latest
|
# runs-on: ubuntu-latest
|
||||||
|
#
|
||||||
steps:
|
# steps:
|
||||||
- uses: actions/checkout@v3
|
# - uses: actions/checkout@v3
|
||||||
# Use stable for this to ensure that cargo-tarpaulin can be built.
|
# # Use stable for this to ensure that cargo-tarpaulin can be built.
|
||||||
- id: prepare
|
# - id: prepare
|
||||||
uses: ./.github/actions/prepare
|
# uses: ./.github/actions/prepare
|
||||||
with:
|
# with:
|
||||||
toolchain: stable
|
# toolchain: stable
|
||||||
nightly-features: true
|
# nightly-features: true
|
||||||
- name: Install cargo-tarpaulin
|
# - name: Install cargo-tarpaulin
|
||||||
uses: actions-rs/cargo@v1
|
# uses: actions-rs/cargo@v1
|
||||||
with:
|
# with:
|
||||||
command: install
|
# command: install
|
||||||
args: cargo-tarpaulin
|
# args: cargo-tarpaulin
|
||||||
- name: Generate coverage report
|
# - name: Generate coverage report
|
||||||
uses: actions-rs/cargo@v1
|
# uses: actions-rs/cargo@v1
|
||||||
with:
|
# with:
|
||||||
command: tarpaulin
|
# command: tarpaulin
|
||||||
args: >
|
# args: >
|
||||||
${{ steps.prepare.outputs.feature-flags }}
|
# ${{ steps.prepare.outputs.feature-flags }}
|
||||||
--timeout 600
|
# --timeout 600
|
||||||
--out Xml
|
# --out Xml
|
||||||
- name: Upload coverage to Codecov
|
# - name: Upload coverage to Codecov
|
||||||
uses: codecov/codecov-action@v3.1.4
|
# uses: codecov/codecov-action@v3.1.4
|
||||||
|
|
||||||
doc-links:
|
doc-links:
|
||||||
name: Intra-doc links
|
name: Intra-doc links
|
||||||
|
|
|
@ -14,8 +14,6 @@ title = "The halo2 Book"
|
||||||
macros = "macros.txt"
|
macros = "macros.txt"
|
||||||
renderers = ["html"]
|
renderers = ["html"]
|
||||||
|
|
||||||
[output.katex]
|
|
||||||
|
|
||||||
[output.html]
|
[output.html]
|
||||||
|
|
||||||
[output.html.print]
|
[output.html.print]
|
||||||
|
|
|
@ -4,7 +4,7 @@ use std::fmt::Debug;
|
||||||
|
|
||||||
use halo2_proofs::{
|
use halo2_proofs::{
|
||||||
arithmetic::CurveAffine,
|
arithmetic::CurveAffine,
|
||||||
circuit::{Chip, Layouter, Value},
|
circuit::{AssignedCell, Chip, Layouter, Value},
|
||||||
plonk::Error,
|
plonk::Error,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -60,6 +60,15 @@ pub trait EccInstructions<C: CurveAffine>:
|
||||||
value: Value<C>,
|
value: Value<C>,
|
||||||
) -> Result<Self::Point, Error>;
|
) -> Result<Self::Point, Error>;
|
||||||
|
|
||||||
|
/// Witnesses the given constant point as a private input to the circuit.
|
||||||
|
/// This allows the point to be the identity, mapped to (0, 0) in
|
||||||
|
/// affine coordinates.
|
||||||
|
fn witness_point_from_constant(
|
||||||
|
&self,
|
||||||
|
layouter: &mut impl Layouter<C::Base>,
|
||||||
|
value: C,
|
||||||
|
) -> Result<Self::Point, Error>;
|
||||||
|
|
||||||
/// Witnesses the given point as a private input to the circuit.
|
/// Witnesses the given point as a private input to the circuit.
|
||||||
/// This returns an error if the point is the identity.
|
/// This returns an error if the point is the identity.
|
||||||
fn witness_point_non_id(
|
fn witness_point_non_id(
|
||||||
|
@ -111,6 +120,15 @@ pub trait EccInstructions<C: CurveAffine>:
|
||||||
b: &B,
|
b: &B,
|
||||||
) -> Result<Self::Point, Error>;
|
) -> Result<Self::Point, Error>;
|
||||||
|
|
||||||
|
/// Performs variable-base sign-scalar multiplication, returning `[sign] point`
|
||||||
|
/// `sign` must be in {-1, 1}.
|
||||||
|
fn mul_sign(
|
||||||
|
&self,
|
||||||
|
layouter: &mut impl Layouter<C::Base>,
|
||||||
|
sign: &AssignedCell<C::Base, C::Base>,
|
||||||
|
point: &Self::Point,
|
||||||
|
) -> Result<Self::Point, Error>;
|
||||||
|
|
||||||
/// Performs variable-base scalar multiplication, returning `[scalar] base`.
|
/// Performs variable-base scalar multiplication, returning `[scalar] base`.
|
||||||
fn mul(
|
fn mul(
|
||||||
&self,
|
&self,
|
||||||
|
@ -390,6 +408,16 @@ impl<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug + Eq> Point<C,
|
||||||
point.map(|inner| Point { chip, inner })
|
point.map(|inner| Point { chip, inner })
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a new point with the given fixed value.
|
||||||
|
pub fn new_from_constant(
|
||||||
|
chip: EccChip,
|
||||||
|
mut layouter: impl Layouter<C::Base>,
|
||||||
|
value: C,
|
||||||
|
) -> Result<Self, Error> {
|
||||||
|
let point = chip.witness_point_from_constant(&mut layouter, value);
|
||||||
|
point.map(|inner| Point { chip, inner })
|
||||||
|
}
|
||||||
|
|
||||||
/// Constrains this point to be equal in value to another point.
|
/// Constrains this point to be equal in value to another point.
|
||||||
pub fn constrain_equal<Other: Into<Point<C, EccChip>> + Clone>(
|
pub fn constrain_equal<Other: Into<Point<C, EccChip>> + Clone>(
|
||||||
&self,
|
&self,
|
||||||
|
@ -432,6 +460,21 @@ impl<C: CurveAffine, EccChip: EccInstructions<C> + Clone + Debug + Eq> Point<C,
|
||||||
inner,
|
inner,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns `[sign] self`.
|
||||||
|
/// `sign` must be in {-1, 1}.
|
||||||
|
pub fn mul_sign(
|
||||||
|
&self,
|
||||||
|
mut layouter: impl Layouter<C::Base>,
|
||||||
|
sign: &AssignedCell<C::Base, C::Base>,
|
||||||
|
) -> Result<Point<C, EccChip>, Error> {
|
||||||
|
self.chip
|
||||||
|
.mul_sign(&mut layouter, sign, &self.inner)
|
||||||
|
.map(|point| Point {
|
||||||
|
chip: self.chip.clone(),
|
||||||
|
inner: point,
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The affine short Weierstrass x-coordinate of a point on a specific elliptic curve.
|
/// The affine short Weierstrass x-coordinate of a point on a specific elliptic curve.
|
||||||
|
@ -750,6 +793,7 @@ pub(crate) mod tests {
|
||||||
meta.advice_column(),
|
meta.advice_column(),
|
||||||
];
|
];
|
||||||
let lookup_table = meta.lookup_table_column();
|
let lookup_table = meta.lookup_table_column();
|
||||||
|
let table_range_check_tag = meta.lookup_table_column();
|
||||||
let lagrange_coeffs = [
|
let lagrange_coeffs = [
|
||||||
meta.fixed_column(),
|
meta.fixed_column(),
|
||||||
meta.fixed_column(),
|
meta.fixed_column(),
|
||||||
|
@ -764,7 +808,12 @@ pub(crate) mod tests {
|
||||||
let constants = meta.fixed_column();
|
let constants = meta.fixed_column();
|
||||||
meta.enable_constant(constants);
|
meta.enable_constant(constants);
|
||||||
|
|
||||||
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup_table);
|
let range_check = LookupRangeCheckConfig::configure(
|
||||||
|
meta,
|
||||||
|
advices[9],
|
||||||
|
lookup_table,
|
||||||
|
table_range_check_tag,
|
||||||
|
);
|
||||||
EccChip::<TestFixedBases>::configure(meta, advices, lagrange_coeffs, range_check)
|
EccChip::<TestFixedBases>::configure(meta, advices, lagrange_coeffs, range_check)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -865,6 +914,14 @@ pub(crate) mod tests {
|
||||||
)?;
|
)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Test variable-base sign-scalar multiplication
|
||||||
|
{
|
||||||
|
super::chip::mul_fixed::short::tests::test_mul_sign(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "variable-base sign-scalar mul"),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
// Test full-width fixed-base scalar multiplication
|
// Test full-width fixed-base scalar multiplication
|
||||||
{
|
{
|
||||||
super::chip::mul_fixed::full_width::tests::test_mul_fixed(
|
super::chip::mul_fixed::full_width::tests::test_mul_fixed(
|
||||||
|
|
|
@ -453,6 +453,18 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn witness_point_from_constant(
|
||||||
|
&self,
|
||||||
|
layouter: &mut impl Layouter<pallas::Base>,
|
||||||
|
value: pallas::Affine,
|
||||||
|
) -> Result<Self::Point, Error> {
|
||||||
|
let config = self.config().witness_point;
|
||||||
|
layouter.assign_region(
|
||||||
|
|| "witness point (constant)",
|
||||||
|
|mut region| config.constant_point(value, 0, &mut region),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn witness_point_non_id(
|
fn witness_point_non_id(
|
||||||
&self,
|
&self,
|
||||||
layouter: &mut impl Layouter<pallas::Base>,
|
layouter: &mut impl Layouter<pallas::Base>,
|
||||||
|
@ -532,6 +544,24 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Performs variable-base sign-scalar multiplication, returning `[sign] point`
|
||||||
|
/// `sign` must be in {-1, 1}.
|
||||||
|
fn mul_sign(
|
||||||
|
&self,
|
||||||
|
layouter: &mut impl Layouter<pallas::Base>,
|
||||||
|
sign: &AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
point: &Self::Point,
|
||||||
|
) -> Result<Self::Point, Error> {
|
||||||
|
// Multiply point by sign, using the same gate as mul_fixed::short.
|
||||||
|
// This also constrains sign to be in {-1, 1}.
|
||||||
|
let config_short = self.config().mul_fixed_short.clone();
|
||||||
|
config_short.assign_scalar_sign(
|
||||||
|
layouter.namespace(|| "variable-base sign-scalar mul"),
|
||||||
|
sign,
|
||||||
|
point,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn mul(
|
fn mul(
|
||||||
&self,
|
&self,
|
||||||
layouter: &mut impl Layouter<pallas::Base>,
|
layouter: &mut impl Layouter<pallas::Base>,
|
||||||
|
|
|
@ -4,7 +4,7 @@ use super::super::{EccPoint, EccScalarFixedShort, FixedPoints, L_SCALAR_SHORT, N
|
||||||
use crate::{ecc::chip::MagnitudeSign, utilities::bool_check};
|
use crate::{ecc::chip::MagnitudeSign, utilities::bool_check};
|
||||||
|
|
||||||
use halo2_proofs::{
|
use halo2_proofs::{
|
||||||
circuit::{Layouter, Region},
|
circuit::{AssignedCell, Layouter, Region},
|
||||||
plonk::{ConstraintSystem, Constraints, Error, Expression, Selector},
|
plonk::{ConstraintSystem, Constraints, Error, Expression, Selector},
|
||||||
poly::Rotation,
|
poly::Rotation,
|
||||||
};
|
};
|
||||||
|
@ -241,11 +241,73 @@ impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
|
||||||
|
|
||||||
Ok((result, scalar))
|
Ok((result, scalar))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Multiply the point by sign, using the q_mul_fixed_short gate.
|
||||||
|
/// Constraints `sign` in {-1, 1}
|
||||||
|
pub fn assign_scalar_sign(
|
||||||
|
&self,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
sign: &AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
point: &EccPoint,
|
||||||
|
) -> Result<EccPoint, Error> {
|
||||||
|
let signed_point = layouter.assign_region(
|
||||||
|
|| "Signed point",
|
||||||
|
|mut region| {
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
// Enable mul_fixed_short selector to check the sign logic.
|
||||||
|
self.q_mul_fixed_short.enable(&mut region, offset)?;
|
||||||
|
|
||||||
|
// Set "last window" to 0 (this field is irrelevant here).
|
||||||
|
region.assign_advice_from_constant(
|
||||||
|
|| "u=0",
|
||||||
|
self.super_config.u,
|
||||||
|
offset,
|
||||||
|
pallas::Base::zero(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Copy sign to `window` column
|
||||||
|
sign.copy_advice(|| "sign", &mut region, self.super_config.window, offset)?;
|
||||||
|
|
||||||
|
// Assign the input y-coordinate.
|
||||||
|
point.y.copy_advice(
|
||||||
|
|| "unsigned y",
|
||||||
|
&mut region,
|
||||||
|
self.super_config.add_config.y_qr,
|
||||||
|
offset,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Conditionally negate y-coordinate according to the value of sign
|
||||||
|
let signed_y_val = sign.value().and_then(|sign| {
|
||||||
|
if sign == &-pallas::Base::one() {
|
||||||
|
-point.y.value()
|
||||||
|
} else {
|
||||||
|
point.y.value().cloned()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Assign the output signed y-coordinate.
|
||||||
|
let signed_y = region.assign_advice(
|
||||||
|
|| "signed y",
|
||||||
|
self.super_config.add_config.y_p,
|
||||||
|
offset,
|
||||||
|
|| signed_y_val,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(EccPoint {
|
||||||
|
x: point.x.clone(),
|
||||||
|
y: signed_y,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(signed_point)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub mod tests {
|
pub mod tests {
|
||||||
use group::{ff::PrimeField, Curve};
|
use group::{ff::PrimeField, Curve, Group};
|
||||||
use halo2_proofs::{
|
use halo2_proofs::{
|
||||||
arithmetic::CurveAffine,
|
arithmetic::CurveAffine,
|
||||||
circuit::{AssignedCell, Chip, Layouter, Value},
|
circuit::{AssignedCell, Chip, Layouter, Value},
|
||||||
|
@ -446,6 +508,7 @@ pub mod tests {
|
||||||
meta.advice_column(),
|
meta.advice_column(),
|
||||||
];
|
];
|
||||||
let lookup_table = meta.lookup_table_column();
|
let lookup_table = meta.lookup_table_column();
|
||||||
|
let table_range_check_tag = meta.lookup_table_column();
|
||||||
let lagrange_coeffs = [
|
let lagrange_coeffs = [
|
||||||
meta.fixed_column(),
|
meta.fixed_column(),
|
||||||
meta.fixed_column(),
|
meta.fixed_column(),
|
||||||
|
@ -461,7 +524,12 @@ pub mod tests {
|
||||||
let constants = meta.fixed_column();
|
let constants = meta.fixed_column();
|
||||||
meta.enable_constant(constants);
|
meta.enable_constant(constants);
|
||||||
|
|
||||||
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup_table);
|
let range_check = LookupRangeCheckConfig::configure(
|
||||||
|
meta,
|
||||||
|
advices[9],
|
||||||
|
lookup_table,
|
||||||
|
table_range_check_tag,
|
||||||
|
);
|
||||||
EccChip::<TestFixedBases>::configure(meta, advices, lagrange_coeffs, range_check)
|
EccChip::<TestFixedBases>::configure(meta, advices, lagrange_coeffs, range_check)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -582,7 +650,7 @@ pub mod tests {
|
||||||
)],
|
)],
|
||||||
},
|
},
|
||||||
VerifyFailure::Permutation {
|
VerifyFailure::Permutation {
|
||||||
column: (Any::Fixed, 9).into(),
|
column: (Any::Fixed, 10).into(),
|
||||||
location: FailureLocation::OutsideRegion { row: 0 },
|
location: FailureLocation::OutsideRegion { row: 0 },
|
||||||
},
|
},
|
||||||
VerifyFailure::Permutation {
|
VerifyFailure::Permutation {
|
||||||
|
@ -657,4 +725,223 @@ pub mod tests {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(crate) fn test_mul_sign(
|
||||||
|
chip: EccChip<TestFixedBases>,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Generate a random non-identity point P
|
||||||
|
let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine();
|
||||||
|
let p = Point::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "P"),
|
||||||
|
Value::known(p_val),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Create -P
|
||||||
|
let p_neg_val = -p_val;
|
||||||
|
let p_neg = Point::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "-P"),
|
||||||
|
Value::known(p_neg_val),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Create the identity point
|
||||||
|
let identity = Point::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "identity"),
|
||||||
|
Value::known(pallas::Point::identity().to_affine()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Create -1 and 1 scalars
|
||||||
|
let pos_sign = chip.load_private(
|
||||||
|
layouter.namespace(|| "positive sign"),
|
||||||
|
chip.config().advices[0],
|
||||||
|
Value::known(pallas::Base::one()),
|
||||||
|
)?;
|
||||||
|
let neg_sign = chip.load_private(
|
||||||
|
layouter.namespace(|| "negative sign"),
|
||||||
|
chip.config().advices[1],
|
||||||
|
Value::known(-pallas::Base::one()),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// [1] P == P
|
||||||
|
{
|
||||||
|
let result = p.mul_sign(layouter.namespace(|| "[1] P"), &pos_sign)?;
|
||||||
|
result.constrain_equal(layouter.namespace(|| "constrain [1] P"), &p)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [-1] P == -P
|
||||||
|
{
|
||||||
|
let result = p.mul_sign(layouter.namespace(|| "[1] P"), &neg_sign)?;
|
||||||
|
result.constrain_equal(layouter.namespace(|| "constrain [1] P"), &p_neg)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [1] 0 == 0
|
||||||
|
{
|
||||||
|
let result = identity.mul_sign(layouter.namespace(|| "[1] O"), &pos_sign)?;
|
||||||
|
result.constrain_equal(layouter.namespace(|| "constrain [1] 0"), &identity)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [-1] 0 == 0
|
||||||
|
{
|
||||||
|
let result = identity.mul_sign(layouter.namespace(|| "[-1] O"), &neg_sign)?;
|
||||||
|
result.constrain_equal(layouter.namespace(|| "constrain [1] 0"), &identity)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn invalid_sign_in_mul_sign() {
|
||||||
|
use crate::{ecc::chip::EccConfig, utilities::UtilitiesInstructions};
|
||||||
|
use halo2_proofs::{
|
||||||
|
circuit::{Layouter, SimpleFloorPlanner},
|
||||||
|
dev::{FailureLocation, MockProver, VerifyFailure},
|
||||||
|
plonk::{Circuit, ConstraintSystem, Error},
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MyCircuit {
|
||||||
|
base: Value<pallas::Affine>,
|
||||||
|
sign: Value<pallas::Base>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl UtilitiesInstructions<pallas::Base> for MyCircuit {
|
||||||
|
type Var = AssignedCell<pallas::Base, pallas::Base>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Circuit<pallas::Base> for MyCircuit {
|
||||||
|
type Config = EccConfig<TestFixedBases>;
|
||||||
|
type FloorPlanner = SimpleFloorPlanner;
|
||||||
|
|
||||||
|
fn without_witnesses(&self) -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
|
||||||
|
let advices = [
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
];
|
||||||
|
let lookup_table = meta.lookup_table_column();
|
||||||
|
let table_range_check_tag = meta.lookup_table_column();
|
||||||
|
let lagrange_coeffs = [
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
];
|
||||||
|
|
||||||
|
// Shared fixed column for loading constants
|
||||||
|
let constants = meta.fixed_column();
|
||||||
|
meta.enable_constant(constants);
|
||||||
|
|
||||||
|
let range_check = LookupRangeCheckConfig::configure(
|
||||||
|
meta,
|
||||||
|
advices[9],
|
||||||
|
lookup_table,
|
||||||
|
table_range_check_tag,
|
||||||
|
);
|
||||||
|
EccChip::<TestFixedBases>::configure(meta, advices, lagrange_coeffs, range_check)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn synthesize(
|
||||||
|
&self,
|
||||||
|
config: Self::Config,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let chip = EccChip::construct(config.clone());
|
||||||
|
|
||||||
|
let column = config.advices[0];
|
||||||
|
|
||||||
|
//let short_config = config.mul_fixed_short.clone();
|
||||||
|
let base = Point::new(chip, layouter.namespace(|| "load base"), self.base)?;
|
||||||
|
|
||||||
|
let sign =
|
||||||
|
self.load_private(layouter.namespace(|| "load sign"), column, self.sign)?;
|
||||||
|
|
||||||
|
base.mul_sign(layouter.namespace(|| "[sign] base"), &sign)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copied from halo2_proofs::dev::util
|
||||||
|
fn format_value(v: pallas::Base) -> String {
|
||||||
|
use ff::Field;
|
||||||
|
if v.is_zero_vartime() {
|
||||||
|
"0".into()
|
||||||
|
} else if v == pallas::Base::one() {
|
||||||
|
"1".into()
|
||||||
|
} else if v == -pallas::Base::one() {
|
||||||
|
"-1".into()
|
||||||
|
} else {
|
||||||
|
// Format value as hex.
|
||||||
|
let s = format!("{:?}", v);
|
||||||
|
// Remove leading zeroes.
|
||||||
|
let s = s.strip_prefix("0x").unwrap();
|
||||||
|
let s = s.trim_start_matches('0');
|
||||||
|
format!("0x{}", s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sign that is not +/- 1 should fail
|
||||||
|
// Generate a random non-identity point
|
||||||
|
let point = pallas::Point::random(rand::rngs::OsRng);
|
||||||
|
let circuit = MyCircuit {
|
||||||
|
base: Value::known(point.to_affine()),
|
||||||
|
sign: Value::known(pallas::Base::zero()),
|
||||||
|
};
|
||||||
|
|
||||||
|
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(), 1, "sign_check").into(),
|
||||||
|
location: FailureLocation::InRegion {
|
||||||
|
region: (2, "Signed point").into(),
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
cell_values: vec![(((Any::Advice, 4).into(), 0).into(), "0".to_string())],
|
||||||
|
},
|
||||||
|
VerifyFailure::ConstraintNotSatisfied {
|
||||||
|
constraint: (
|
||||||
|
(17, "Short fixed-base mul gate").into(),
|
||||||
|
3,
|
||||||
|
"negation_check"
|
||||||
|
)
|
||||||
|
.into(),
|
||||||
|
location: FailureLocation::InRegion {
|
||||||
|
region: (2, "Signed point").into(),
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
cell_values: vec![
|
||||||
|
(
|
||||||
|
((Any::Advice, 1).into(), 0).into(),
|
||||||
|
format_value(*point.to_affine().coordinates().unwrap().y()),
|
||||||
|
),
|
||||||
|
(
|
||||||
|
((Any::Advice, 3).into(), 0).into(),
|
||||||
|
format_value(*point.to_affine().coordinates().unwrap().y()),
|
||||||
|
),
|
||||||
|
(((Any::Advice, 4).into(), 0).into(), "0".to_string()),
|
||||||
|
],
|
||||||
|
}
|
||||||
|
])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -102,6 +102,21 @@ impl Config {
|
||||||
Ok((x_var, y_var))
|
Ok((x_var, y_var))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn assign_xy_from_constant(
|
||||||
|
&self,
|
||||||
|
value: (Assigned<pallas::Base>, Assigned<pallas::Base>),
|
||||||
|
offset: usize,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
) -> Result<Coordinates, Error> {
|
||||||
|
// Assign `x` value
|
||||||
|
let x_var = region.assign_advice_from_constant(|| "x", self.x, offset, value.0)?;
|
||||||
|
|
||||||
|
// Assign `y` value
|
||||||
|
let y_var = region.assign_advice_from_constant(|| "y", self.y, offset, value.1)?;
|
||||||
|
|
||||||
|
Ok((x_var, y_var))
|
||||||
|
}
|
||||||
|
|
||||||
/// Assigns a point that can be the identity.
|
/// Assigns a point that can be the identity.
|
||||||
pub(super) fn point(
|
pub(super) fn point(
|
||||||
&self,
|
&self,
|
||||||
|
@ -126,6 +141,28 @@ impl Config {
|
||||||
.map(|(x, y)| EccPoint::from_coordinates_unchecked(x, y))
|
.map(|(x, y)| EccPoint::from_coordinates_unchecked(x, y))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Assigns a constant point that can be the identity.
|
||||||
|
pub(super) fn constant_point(
|
||||||
|
&self,
|
||||||
|
value: pallas::Affine,
|
||||||
|
offset: usize,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
) -> Result<EccPoint, Error> {
|
||||||
|
// Enable `q_point` selector
|
||||||
|
self.q_point.enable(region, offset)?;
|
||||||
|
|
||||||
|
let value = if value == pallas::Affine::identity() {
|
||||||
|
// Map the identity to (0, 0).
|
||||||
|
(Assigned::Zero, Assigned::Zero)
|
||||||
|
} else {
|
||||||
|
let value = value.coordinates().unwrap();
|
||||||
|
(value.x().into(), value.y().into())
|
||||||
|
};
|
||||||
|
|
||||||
|
self.assign_xy_from_constant(value, offset, region)
|
||||||
|
.map(|(x, y)| EccPoint::from_coordinates_unchecked(x, y))
|
||||||
|
}
|
||||||
|
|
||||||
/// Assigns a non-identity point.
|
/// Assigns a non-identity point.
|
||||||
pub(super) fn point_non_id(
|
pub(super) fn point_non_id(
|
||||||
&self,
|
&self,
|
||||||
|
|
|
@ -78,7 +78,7 @@ pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS
|
||||||
/// This returns both the resulting point, as well as the message
|
/// This returns both the resulting point, as well as the message
|
||||||
/// decomposition in the form of intermediate values in a cumulative
|
/// decomposition in the form of intermediate values in a cumulative
|
||||||
/// sum.
|
/// sum.
|
||||||
///
|
/// The initial point `Q` is a public point.
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
fn hash_to_point(
|
fn hash_to_point(
|
||||||
|
@ -88,6 +88,20 @@ pub trait SinsemillaInstructions<C: CurveAffine, const K: usize, const MAX_WORDS
|
||||||
message: Self::Message,
|
message: Self::Message,
|
||||||
) -> Result<(Self::NonIdentityPoint, Vec<Self::RunningSum>), Error>;
|
) -> Result<(Self::NonIdentityPoint, Vec<Self::RunningSum>), Error>;
|
||||||
|
|
||||||
|
/// Hashes a message to an ECC curve point.
|
||||||
|
/// This returns both the resulting point, as well as the message
|
||||||
|
/// decomposition in the form of intermediate values in a cumulative
|
||||||
|
/// sum.
|
||||||
|
/// The initial point `Q` is a private point.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn hash_to_point_with_private_init(
|
||||||
|
&self,
|
||||||
|
layouter: impl Layouter<C::Base>,
|
||||||
|
Q: &Self::NonIdentityPoint,
|
||||||
|
message: Self::Message,
|
||||||
|
) -> Result<(Self::NonIdentityPoint, Vec<Self::RunningSum>), Error>;
|
||||||
|
|
||||||
/// Extracts the x-coordinate of the output of a Sinsemilla hash.
|
/// Extracts the x-coordinate of the output of a Sinsemilla hash.
|
||||||
fn extract(point: &Self::NonIdentityPoint) -> Self::X;
|
fn extract(point: &Self::NonIdentityPoint) -> Self::X;
|
||||||
}
|
}
|
||||||
|
@ -329,6 +343,21 @@ where
|
||||||
.map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs))
|
.map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
/// Evaluate the Sinsemilla hash of `message` from the private initial point `Q`.
|
||||||
|
pub fn hash_to_point_with_private_init(
|
||||||
|
&self,
|
||||||
|
layouter: impl Layouter<C::Base>,
|
||||||
|
Q: &<SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::NonIdentityPoint,
|
||||||
|
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
|
||||||
|
) -> Result<(ecc::NonIdentityPoint<C, EccChip>, Vec<SinsemillaChip::RunningSum>), Error> {
|
||||||
|
assert_eq!(self.sinsemilla_chip, message.chip);
|
||||||
|
self.sinsemilla_chip
|
||||||
|
.hash_to_point_with_private_init(layouter, Q, message.inner)
|
||||||
|
.map(|(point, zs)| (ecc::NonIdentityPoint::from_inner(self.ecc_chip.clone(), point), zs))
|
||||||
|
}
|
||||||
|
|
||||||
/// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash].
|
/// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash].
|
||||||
///
|
///
|
||||||
/// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash
|
/// [concretesinsemillahash]: https://zips.z.cash/protocol/protocol.pdf#concretesinsemillahash
|
||||||
|
@ -412,6 +441,63 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
/// Evaluates the Sinsemilla hash of `message` from the public initial point `Q` stored
|
||||||
|
/// into `CommitDomain`.
|
||||||
|
pub fn hash(
|
||||||
|
&self,
|
||||||
|
layouter: impl Layouter<C::Base>,
|
||||||
|
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
ecc::NonIdentityPoint<C, EccChip>,
|
||||||
|
Vec<SinsemillaChip::RunningSum>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
|
assert_eq!(self.M.sinsemilla_chip, message.chip);
|
||||||
|
self.M.hash_to_point(layouter, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
/// Evaluates the Sinsemilla hash of `message` from the private initial point `Q`.
|
||||||
|
pub fn hash_with_private_init(
|
||||||
|
&self,
|
||||||
|
layouter: impl Layouter<C::Base>,
|
||||||
|
Q: &<SinsemillaChip as SinsemillaInstructions<C, K, MAX_WORDS>>::NonIdentityPoint,
|
||||||
|
message: Message<C, SinsemillaChip, K, MAX_WORDS>,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
ecc::NonIdentityPoint<C, EccChip>,
|
||||||
|
Vec<SinsemillaChip::RunningSum>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
|
assert_eq!(self.M.sinsemilla_chip, message.chip);
|
||||||
|
self.M.hash_to_point_with_private_init(layouter, Q, message)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
/// Returns the public initial point `Q` stored into `CommitDomain`.
|
||||||
|
pub fn q_init(&self) -> C {
|
||||||
|
self.M.Q
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
/// Evaluates the blinding factor equal to $\[r\] R$ where `r` is stored in the `CommitDomain`.
|
||||||
|
pub fn blinding_factor(
|
||||||
|
&self,
|
||||||
|
mut layouter: impl Layouter<C::Base>,
|
||||||
|
r: ecc::ScalarFixed<C, EccChip>,
|
||||||
|
) -> Result<
|
||||||
|
ecc::Point<C, EccChip>,
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
|
let (blind, _) = self.R.mul(layouter.namespace(|| "[r] R"), r)?;
|
||||||
|
Ok(blind)
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
|
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
|
||||||
///
|
///
|
||||||
|
@ -429,8 +515,8 @@ where
|
||||||
Error,
|
Error,
|
||||||
> {
|
> {
|
||||||
assert_eq!(self.M.sinsemilla_chip, message.chip);
|
assert_eq!(self.M.sinsemilla_chip, message.chip);
|
||||||
let (blind, _) = self.R.mul(layouter.namespace(|| "[r] R"), r)?;
|
let blind = self.blinding_factor(layouter.namespace(|| "[r] R"), r)?;
|
||||||
let (p, zs) = self.M.hash_to_point(layouter.namespace(|| "M"), message)?;
|
let (p, zs) = self.hash(layouter.namespace(|| "M"), message)?;
|
||||||
let commitment = p.add(layouter.namespace(|| "M + [r] R"), &blind)?;
|
let commitment = p.add(layouter.namespace(|| "M + [r] R"), &blind)?;
|
||||||
Ok((commitment, zs))
|
Ok((commitment, zs))
|
||||||
}
|
}
|
||||||
|
@ -551,6 +637,7 @@ pub(crate) mod tests {
|
||||||
meta.enable_constant(constants);
|
meta.enable_constant(constants);
|
||||||
|
|
||||||
let table_idx = meta.lookup_table_column();
|
let table_idx = meta.lookup_table_column();
|
||||||
|
let table_range_check_tag = meta.lookup_table_column();
|
||||||
let lagrange_coeffs = [
|
let lagrange_coeffs = [
|
||||||
meta.fixed_column(),
|
meta.fixed_column(),
|
||||||
meta.fixed_column(),
|
meta.fixed_column(),
|
||||||
|
@ -567,9 +654,15 @@ pub(crate) mod tests {
|
||||||
table_idx,
|
table_idx,
|
||||||
meta.lookup_table_column(),
|
meta.lookup_table_column(),
|
||||||
meta.lookup_table_column(),
|
meta.lookup_table_column(),
|
||||||
|
table_range_check_tag,
|
||||||
);
|
);
|
||||||
|
|
||||||
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], table_idx);
|
let range_check = LookupRangeCheckConfig::configure(
|
||||||
|
meta,
|
||||||
|
advices[9],
|
||||||
|
table_idx,
|
||||||
|
table_range_check_tag,
|
||||||
|
);
|
||||||
|
|
||||||
let ecc_config =
|
let ecc_config =
|
||||||
EccChip::<TestFixedBases>::configure(meta, advices, lagrange_coeffs, range_check);
|
EccChip::<TestFixedBases>::configure(meta, advices, lagrange_coeffs, range_check);
|
||||||
|
|
|
@ -153,7 +153,7 @@ where
|
||||||
advices: [Column<Advice>; 5],
|
advices: [Column<Advice>; 5],
|
||||||
witness_pieces: Column<Advice>,
|
witness_pieces: Column<Advice>,
|
||||||
fixed_y_q: Column<Fixed>,
|
fixed_y_q: Column<Fixed>,
|
||||||
lookup: (TableColumn, TableColumn, TableColumn),
|
lookup: (TableColumn, TableColumn, TableColumn, TableColumn),
|
||||||
range_check: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
|
range_check: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
|
||||||
) -> <Self as Chip<pallas::Base>>::Config {
|
) -> <Self as Chip<pallas::Base>>::Config {
|
||||||
// Enable equality on all advice columns
|
// Enable equality on all advice columns
|
||||||
|
@ -178,6 +178,7 @@ where
|
||||||
table_idx: lookup.0,
|
table_idx: lookup.0,
|
||||||
table_x: lookup.1,
|
table_x: lookup.1,
|
||||||
table_y: lookup.2,
|
table_y: lookup.2,
|
||||||
|
table_range_check_tag: lookup.3,
|
||||||
},
|
},
|
||||||
lookup_config: range_check,
|
lookup_config: range_check,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
|
@ -203,7 +204,7 @@ where
|
||||||
// https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial
|
// https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial
|
||||||
meta.create_gate("Initial y_Q", |meta| {
|
meta.create_gate("Initial y_Q", |meta| {
|
||||||
let q_s4 = meta.query_selector(config.q_sinsemilla4);
|
let q_s4 = meta.query_selector(config.q_sinsemilla4);
|
||||||
let y_q = meta.query_fixed(config.fixed_y_q);
|
let y_q = meta.query_advice(config.double_and_add.x_p, Rotation::prev());
|
||||||
|
|
||||||
// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
|
// Y_A = (lambda_1 + lambda_2) * (x_a - x_r)
|
||||||
let Y_A_cur = Y_A(meta, Rotation::cur());
|
let Y_A_cur = Y_A(meta, Rotation::cur());
|
||||||
|
@ -321,6 +322,20 @@ where
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn hash_to_point_with_private_init(
|
||||||
|
&self,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
Q: &Self::NonIdentityPoint,
|
||||||
|
message: Self::Message,
|
||||||
|
) -> Result<(Self::NonIdentityPoint, Vec<Self::RunningSum>), Error> {
|
||||||
|
layouter.assign_region(
|
||||||
|
|| "hash_to_point",
|
||||||
|
|mut region| self.hash_message_with_private_init(&mut region, Q, &message),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fn extract(point: &Self::NonIdentityPoint) -> Self::X {
|
fn extract(point: &Self::NonIdentityPoint) -> Self::X {
|
||||||
point.x()
|
point.x()
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,7 +6,7 @@ use halo2_proofs::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{CommitDomains, FixedPoints, HashDomains};
|
use super::{CommitDomains, FixedPoints, HashDomains};
|
||||||
use crate::sinsemilla::primitives::{self as sinsemilla, SINSEMILLA_S};
|
use crate::sinsemilla::primitives::{self as sinsemilla, K, SINSEMILLA_S};
|
||||||
use pasta_curves::pallas;
|
use pasta_curves::pallas;
|
||||||
|
|
||||||
/// Table containing independent generators S[0..2^k]
|
/// Table containing independent generators S[0..2^k]
|
||||||
|
@ -15,6 +15,7 @@ pub struct GeneratorTableConfig {
|
||||||
pub table_idx: TableColumn,
|
pub table_idx: TableColumn,
|
||||||
pub table_x: TableColumn,
|
pub table_x: TableColumn,
|
||||||
pub table_y: TableColumn,
|
pub table_y: TableColumn,
|
||||||
|
pub table_range_check_tag: TableColumn,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GeneratorTableConfig {
|
impl GeneratorTableConfig {
|
||||||
|
@ -77,6 +78,22 @@ impl GeneratorTableConfig {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Load the generator table into the circuit.
|
||||||
|
///
|
||||||
|
/// | table_idx | table_x | table_y | table_range_check_tag |
|
||||||
|
/// -------------------------------------------------------------------
|
||||||
|
/// | 0 | X(S\[0\]) | Y(S\[0\]) | 0 |
|
||||||
|
/// | 1 | X(S\[1\]) | Y(S\[1\]) | 0 |
|
||||||
|
/// | ... | ... | ... | 0 |
|
||||||
|
/// | 2^10-1 | X(S\[2^10-1\]) | Y(S\[2^10-1\]) | 0 |
|
||||||
|
/// | 0 | X(S\[0\]) | Y(S\[0\]) | 4 |
|
||||||
|
/// | 1 | X(S\[1\]) | Y(S\[1\]) | 4 |
|
||||||
|
/// | ... | ... | ... | 4 |
|
||||||
|
/// | 2^4-1 | X(S\[2^4-1\]) | Y(S\[2^4-1\]) | 4 |
|
||||||
|
/// | 0 | X(S\[0\]) | Y(S\[0\]) | 5 |
|
||||||
|
/// | 1 | X(S\[1\]) | Y(S\[1\]) | 5 |
|
||||||
|
/// | ... | ... | ... | 5 |
|
||||||
|
/// | 2^5-1 | X(S\[2^5-1\]) | Y(S\[2^5-1\]) | 5 |
|
||||||
pub fn load(&self, layouter: &mut impl Layouter<pallas::Base>) -> Result<(), Error> {
|
pub fn load(&self, layouter: &mut impl Layouter<pallas::Base>) -> Result<(), Error> {
|
||||||
layouter.assign_table(
|
layouter.assign_table(
|
||||||
|| "generator_table",
|
|| "generator_table",
|
||||||
|
@ -90,6 +107,66 @@ impl GeneratorTableConfig {
|
||||||
)?;
|
)?;
|
||||||
table.assign_cell(|| "table_x", self.table_x, index, || Value::known(*x))?;
|
table.assign_cell(|| "table_x", self.table_x, index, || Value::known(*x))?;
|
||||||
table.assign_cell(|| "table_y", self.table_y, index, || Value::known(*y))?;
|
table.assign_cell(|| "table_y", self.table_y, index, || Value::known(*y))?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_range_check_tag",
|
||||||
|
self.table_range_check_tag,
|
||||||
|
index,
|
||||||
|
|| Value::known(pallas::Base::zero()),
|
||||||
|
)?;
|
||||||
|
if index < (1 << 4) {
|
||||||
|
let new_index = index + (1 << K);
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_idx",
|
||||||
|
self.table_idx,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(pallas::Base::from(index as u64)),
|
||||||
|
)?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_x",
|
||||||
|
self.table_x,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(*x),
|
||||||
|
)?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_y",
|
||||||
|
self.table_y,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(*y),
|
||||||
|
)?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_range_check_tag",
|
||||||
|
self.table_range_check_tag,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(pallas::Base::from(4_u64)),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
if index < (1 << 5) {
|
||||||
|
let new_index = index + (1 << 10) + (1 << 4);
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_idx",
|
||||||
|
self.table_idx,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(pallas::Base::from(index as u64)),
|
||||||
|
)?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_x",
|
||||||
|
self.table_x,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(*x),
|
||||||
|
)?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_y",
|
||||||
|
self.table_y,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(*y),
|
||||||
|
)?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_range_check_tag",
|
||||||
|
self.table_range_check_tag,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(pallas::Base::from(5_u64)),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
|
|
|
@ -41,85 +41,9 @@ where
|
||||||
),
|
),
|
||||||
Error,
|
Error,
|
||||||
> {
|
> {
|
||||||
let config = self.config().clone();
|
let (offset, x_a, y_a) = self.public_initialization(region, Q)?;
|
||||||
let mut offset = 0;
|
|
||||||
|
|
||||||
// Get the `x`- and `y`-coordinates of the starting `Q` base.
|
let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?;
|
||||||
let x_q = *Q.coordinates().unwrap().x();
|
|
||||||
let y_q = *Q.coordinates().unwrap().y();
|
|
||||||
|
|
||||||
// Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4
|
|
||||||
// selector.
|
|
||||||
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,
|
|
||||||
|| Value::known(y_q),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Value::known(y_q.into()).into()
|
|
||||||
};
|
|
||||||
|
|
||||||
// Constrain the initial x_q to equal the x-coordinate of the domain's `Q`.
|
|
||||||
let mut x_a: X<pallas::Base> = {
|
|
||||||
let x_a = region.assign_advice_from_constant(
|
|
||||||
|| "fixed x_q",
|
|
||||||
config.double_and_add.x_a,
|
|
||||||
offset,
|
|
||||||
x_q.into(),
|
|
||||||
)?;
|
|
||||||
|
|
||||||
x_a.into()
|
|
||||||
};
|
|
||||||
|
|
||||||
let mut zs_sum: Vec<Vec<AssignedCell<pallas::Base, pallas::Base>>> = Vec::new();
|
|
||||||
|
|
||||||
// Hash each piece in the message.
|
|
||||||
for (idx, piece) in message.iter().enumerate() {
|
|
||||||
let final_piece = idx == message.len() - 1;
|
|
||||||
|
|
||||||
// The value of the accumulator after this piece is processed.
|
|
||||||
let (x, y, zs) = self.hash_piece(region, offset, piece, x_a, y_a, final_piece)?;
|
|
||||||
|
|
||||||
// Since each message word takes one row to process, we increase
|
|
||||||
// the offset by `piece.num_words` on each iteration.
|
|
||||||
offset += piece.num_words();
|
|
||||||
|
|
||||||
// Update the accumulator to the latest value.
|
|
||||||
x_a = x;
|
|
||||||
y_a = y;
|
|
||||||
zs_sum.push(zs);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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.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
|
|
||||||
// multiplied by zero.)
|
|
||||||
{
|
|
||||||
region.assign_advice(
|
|
||||||
|| "dummy lambda2",
|
|
||||||
config.double_and_add.lambda_2,
|
|
||||||
offset,
|
|
||||||
|| Value::known(pallas::Base::zero()),
|
|
||||||
)?;
|
|
||||||
region.assign_advice(
|
|
||||||
|| "dummy x_p",
|
|
||||||
config.double_and_add.x_p,
|
|
||||||
offset,
|
|
||||||
|| Value::known(pallas::Base::zero()),
|
|
||||||
)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
y_a_cell
|
|
||||||
};
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
|
@ -169,6 +93,239 @@ where
|
||||||
))
|
))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// [Specification](https://p.z.cash/halo2-0.1:sinsemilla-constraints?partial).
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
pub(super) fn hash_message_with_private_init(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
Q: &NonIdentityEccPoint,
|
||||||
|
message: &<Self as SinsemillaInstructions<
|
||||||
|
pallas::Affine,
|
||||||
|
{ sinsemilla::K },
|
||||||
|
{ sinsemilla::C },
|
||||||
|
>>::Message,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
NonIdentityEccPoint,
|
||||||
|
Vec<Vec<AssignedCell<pallas::Base, pallas::Base>>>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
|
let (offset, x_a, y_a) = self.private_initialization(region, Q)?;
|
||||||
|
|
||||||
|
let (x_a, y_a, zs_sum) = self.hash_all_pieces(region, offset, message, x_a, y_a)?;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
// Check equivalence to result from primitives::sinsemilla::hash_to_point
|
||||||
|
{
|
||||||
|
use crate::sinsemilla::primitives::{K, S_PERSONALIZATION};
|
||||||
|
|
||||||
|
use group::{prime::PrimeCurveAffine, Curve};
|
||||||
|
use pasta_curves::arithmetic::CurveExt;
|
||||||
|
|
||||||
|
let field_elems: Value<Vec<_>> = message
|
||||||
|
.iter()
|
||||||
|
.map(|piece| piece.field_elem().map(|elem| (elem, piece.num_words())))
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
field_elems
|
||||||
|
.zip(x_a.value().zip(y_a.value()))
|
||||||
|
.zip(Q.point())
|
||||||
|
.assert_if_known(|((field_elems, (x_a, y_a)), Q)| {
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
// 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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
/// Assign the coordinates of the initial public point `Q`
|
||||||
|
///
|
||||||
|
/// | offset | x_A | x_P | q_sinsemilla4 |
|
||||||
|
/// --------------------------------------
|
||||||
|
/// | 0 | | y_Q | |
|
||||||
|
/// | 1 | x_Q | | 1 |
|
||||||
|
fn public_initialization(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
Q: pallas::Affine,
|
||||||
|
) -> Result<(usize, X<pallas::Base>, Y<pallas::Base>), Error> {
|
||||||
|
let config = self.config().clone();
|
||||||
|
let mut offset = 0;
|
||||||
|
|
||||||
|
// Get the `x`- and `y`-coordinates of the starting `Q` base.
|
||||||
|
let x_q = *Q.coordinates().unwrap().x();
|
||||||
|
let y_q = *Q.coordinates().unwrap().y();
|
||||||
|
|
||||||
|
// Constrain the initial x_a, lambda_1, lambda_2, x_p using the q_sinsemilla4
|
||||||
|
// selector.
|
||||||
|
let y_a: Y<pallas::Base> = {
|
||||||
|
// Enable `q_sinsemilla4` on the second row.
|
||||||
|
config.q_sinsemilla4.enable(region, offset + 1)?;
|
||||||
|
let y_a: AssignedCell<Assigned<pallas::Base>, pallas::Base> = region
|
||||||
|
.assign_advice_from_constant(
|
||||||
|
|| "fixed y_q",
|
||||||
|
config.double_and_add.x_p,
|
||||||
|
offset,
|
||||||
|
y_q.into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
y_a.value_field().into()
|
||||||
|
};
|
||||||
|
offset += 1;
|
||||||
|
|
||||||
|
// Constrain the initial x_q to equal the x-coordinate of the domain's `Q`.
|
||||||
|
let x_a: X<pallas::Base> = {
|
||||||
|
let x_a = region.assign_advice_from_constant(
|
||||||
|
|| "fixed x_q",
|
||||||
|
config.double_and_add.x_a,
|
||||||
|
offset,
|
||||||
|
x_q.into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
x_a.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((offset, x_a, y_a))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
/// Assign the coordinates of the initial private point `Q`
|
||||||
|
///
|
||||||
|
/// | offset | x_A | x_P | q_sinsemilla4 |
|
||||||
|
/// --------------------------------------
|
||||||
|
/// | 0 | | y_Q | |
|
||||||
|
/// | 1 | x_Q | | 1 |
|
||||||
|
fn private_initialization(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
Q: &NonIdentityEccPoint,
|
||||||
|
) -> Result<(usize, X<pallas::Base>, Y<pallas::Base>), Error> {
|
||||||
|
let config = self.config().clone();
|
||||||
|
let mut offset = 0;
|
||||||
|
|
||||||
|
// Assign `x_Q` and `y_Q` in the region and constrain the initial x_a, lambda_1, lambda_2,
|
||||||
|
// x_p, y_Q using the q_sinsemilla4 selector.
|
||||||
|
let y_a: Y<pallas::Base> = {
|
||||||
|
// Enable `q_sinsemilla4` on the second row.
|
||||||
|
config.q_sinsemilla4.enable(region, offset + 1)?;
|
||||||
|
let q_y: AssignedCell<Assigned<pallas::Base>, pallas::Base> = Q.y().into();
|
||||||
|
let y_a: AssignedCell<Assigned<pallas::Base>, pallas::Base> =
|
||||||
|
q_y.copy_advice(|| "fixed y_q", region, config.double_and_add.x_p, offset)?;
|
||||||
|
|
||||||
|
y_a.value_field().into()
|
||||||
|
};
|
||||||
|
offset += 1;
|
||||||
|
|
||||||
|
let x_a: X<pallas::Base> = {
|
||||||
|
let q_x: AssignedCell<Assigned<pallas::Base>, pallas::Base> = Q.x().into();
|
||||||
|
let x_a = q_x.copy_advice(|| "fixed x_q", region, config.double_and_add.x_a, offset)?;
|
||||||
|
|
||||||
|
x_a.into()
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((offset, x_a, y_a))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
/// Hash `message` from the initial point `Q`.
|
||||||
|
fn hash_all_pieces(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
mut offset: usize,
|
||||||
|
message: &<Self as SinsemillaInstructions<
|
||||||
|
pallas::Affine,
|
||||||
|
{ sinsemilla::K },
|
||||||
|
{ sinsemilla::C },
|
||||||
|
>>::Message,
|
||||||
|
mut x_a: X<pallas::Base>,
|
||||||
|
mut y_a: Y<pallas::Base>,
|
||||||
|
) -> Result<
|
||||||
|
(
|
||||||
|
X<pallas::Base>,
|
||||||
|
AssignedCell<Assigned<pallas::Base>, pallas::Base>,
|
||||||
|
Vec<Vec<AssignedCell<pallas::Base, pallas::Base>>>,
|
||||||
|
),
|
||||||
|
Error,
|
||||||
|
> {
|
||||||
|
let config = self.config().clone();
|
||||||
|
|
||||||
|
let mut zs_sum: Vec<Vec<AssignedCell<pallas::Base, pallas::Base>>> = Vec::new();
|
||||||
|
|
||||||
|
// Hash each piece in the message.
|
||||||
|
for (idx, piece) in message.iter().enumerate() {
|
||||||
|
let final_piece = idx == message.len() - 1;
|
||||||
|
|
||||||
|
// The value of the accumulator after this piece is processed.
|
||||||
|
let (x, y, zs) = self.hash_piece(region, offset, piece, x_a, y_a, final_piece)?;
|
||||||
|
|
||||||
|
// Since each message word takes one row to process, we increase
|
||||||
|
// the offset by `piece.num_words` on each iteration.
|
||||||
|
offset += piece.num_words();
|
||||||
|
|
||||||
|
// Update the accumulator to the latest value.
|
||||||
|
x_a = x;
|
||||||
|
y_a = y;
|
||||||
|
zs_sum.push(zs);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.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
|
||||||
|
// multiplied by zero.)
|
||||||
|
{
|
||||||
|
region.assign_advice(
|
||||||
|
|| "dummy lambda2",
|
||||||
|
config.double_and_add.lambda_2,
|
||||||
|
offset,
|
||||||
|
|| Value::known(pallas::Base::zero()),
|
||||||
|
)?;
|
||||||
|
region.assign_advice(
|
||||||
|
|| "dummy x_p",
|
||||||
|
config.double_and_add.x_p,
|
||||||
|
offset,
|
||||||
|
|| Value::known(pallas::Base::zero()),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
y_a_cell
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((x_a, y_a, zs_sum))
|
||||||
|
}
|
||||||
|
|
||||||
#[allow(clippy::type_complexity)]
|
#[allow(clippy::type_complexity)]
|
||||||
/// Hashes a message piece containing `piece.length` number of `K`-bit words.
|
/// Hashes a message piece containing `piece.length` number of `K`-bit words.
|
||||||
///
|
///
|
||||||
|
|
|
@ -246,9 +246,11 @@ pub mod tests {
|
||||||
meta.lookup_table_column(),
|
meta.lookup_table_column(),
|
||||||
meta.lookup_table_column(),
|
meta.lookup_table_column(),
|
||||||
meta.lookup_table_column(),
|
meta.lookup_table_column(),
|
||||||
|
meta.lookup_table_column(),
|
||||||
);
|
);
|
||||||
|
|
||||||
let range_check = LookupRangeCheckConfig::configure(meta, advices[9], lookup.0);
|
let range_check =
|
||||||
|
LookupRangeCheckConfig::configure(meta, advices[9], lookup.0, lookup.3);
|
||||||
|
|
||||||
let sinsemilla_config_1 = SinsemillaChip::configure(
|
let sinsemilla_config_1 = SinsemillaChip::configure(
|
||||||
meta,
|
meta,
|
||||||
|
|
|
@ -441,6 +441,18 @@ where
|
||||||
let chip = CondSwapChip::<pallas::Base>::construct(config);
|
let chip = CondSwapChip::<pallas::Base>::construct(config);
|
||||||
chip.swap(layouter, pair, swap)
|
chip.swap(layouter, pair, swap)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mux(
|
||||||
|
&self,
|
||||||
|
layouter: &mut impl Layouter<pallas::Base>,
|
||||||
|
choice: Self::Var,
|
||||||
|
left: Self::Var,
|
||||||
|
right: Self::Var,
|
||||||
|
) -> Result<Self::Var, Error> {
|
||||||
|
let config = self.config().cond_swap_config.clone();
|
||||||
|
let chip = CondSwapChip::<pallas::Base>::construct(config);
|
||||||
|
chip.mux(layouter, choice, left, right)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<Hash, Commit, F> SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }>
|
impl<Hash, Commit, F> SinsemillaInstructions<pallas::Affine, { sinsemilla::K }, { sinsemilla::C }>
|
||||||
|
@ -523,6 +535,19 @@ where
|
||||||
chip.hash_to_point(layouter, Q, message)
|
chip.hash_to_point(layouter, Q, message)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn hash_to_point_with_private_init(
|
||||||
|
&self,
|
||||||
|
layouter: impl Layouter<pallas::Base>,
|
||||||
|
Q: &Self::NonIdentityPoint,
|
||||||
|
message: Self::Message,
|
||||||
|
) -> Result<(Self::NonIdentityPoint, Vec<Vec<Self::CellValue>>), Error> {
|
||||||
|
let config = self.config().sinsemilla_config.clone();
|
||||||
|
let chip = SinsemillaChip::<Hash, Commit, F>::construct(config);
|
||||||
|
chip.hash_to_point_with_private_init(layouter, Q, message)
|
||||||
|
}
|
||||||
|
|
||||||
fn extract(point: &Self::NonIdentityPoint) -> Self::X {
|
fn extract(point: &Self::NonIdentityPoint) -> Self::X {
|
||||||
SinsemillaChip::<Hash, Commit, F>::extract(point)
|
SinsemillaChip::<Hash, Commit, F>::extract(point)
|
||||||
}
|
}
|
||||||
|
|
|
@ -184,6 +184,7 @@ impl HashDomain {
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
pub struct CommitDomain {
|
pub struct CommitDomain {
|
||||||
|
/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can be used
|
||||||
M: HashDomain,
|
M: HashDomain,
|
||||||
R: pallas::Point,
|
R: pallas::Point,
|
||||||
}
|
}
|
||||||
|
@ -200,6 +201,17 @@ impl CommitDomain {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Constructs a new `CommitDomain` from different values for `hash_domain` and `blind_domain`
|
||||||
|
pub fn new_with_personalization(hash_domain: &str, blind_domain: &str) -> Self {
|
||||||
|
let m_prefix = format!("{}-M", hash_domain);
|
||||||
|
let r_prefix = format!("{}-r", blind_domain);
|
||||||
|
let hasher_r = pallas::Point::hash_to_curve(&r_prefix);
|
||||||
|
CommitDomain {
|
||||||
|
M: HashDomain::new(&m_prefix),
|
||||||
|
R: hasher_r(&[]),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
|
/// $\mathsf{SinsemillaCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
|
||||||
///
|
///
|
||||||
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
|
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
|
||||||
|
@ -214,6 +226,26 @@ impl CommitDomain {
|
||||||
.map(|p| p + Wnaf::new().scalar(r).base(self.R))
|
.map(|p| p + Wnaf::new().scalar(r).base(self.R))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash].
|
||||||
|
///
|
||||||
|
/// [concretesinsemillahash]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillahash
|
||||||
|
pub fn hash_to_point(&self, msg: impl Iterator<Item = bool>) -> CtOption<pallas::Point> {
|
||||||
|
self.M.hash_to_point(msg)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns `SinsemillaCommit_r(personalization, msg) = hash_point + [r]R`
|
||||||
|
/// where `SinsemillaHash(personalization, msg) = hash_point`
|
||||||
|
/// and `R` is derived from the `personalization`.
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
pub fn commit_from_hash_point(
|
||||||
|
&self,
|
||||||
|
hash_point: CtOption<pallas::Point>,
|
||||||
|
r: &pallas::Scalar,
|
||||||
|
) -> CtOption<pallas::Point> {
|
||||||
|
// We use complete addition for the blinding factor.
|
||||||
|
hash_point.map(|p| p + Wnaf::new().scalar(r).base(self.R))
|
||||||
|
}
|
||||||
|
|
||||||
/// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
|
/// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit].
|
||||||
///
|
///
|
||||||
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
|
/// [concretesinsemillacommit]: https://zips.z.cash/protocol/nu5.pdf#concretesinsemillacommit
|
||||||
|
@ -305,4 +337,32 @@ mod tests {
|
||||||
assert_eq!(computed, actual);
|
assert_eq!(computed, actual);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn commit_in_several_steps() {
|
||||||
|
use rand::{rngs::OsRng, Rng};
|
||||||
|
|
||||||
|
use ff::Field;
|
||||||
|
|
||||||
|
use crate::sinsemilla::primitives::CommitDomain;
|
||||||
|
|
||||||
|
let domain = CommitDomain::new("z.cash:ZSA-NoteCommit");
|
||||||
|
|
||||||
|
let mut os_rng = OsRng::default();
|
||||||
|
let msg: Vec<bool> = (0..36).map(|_| os_rng.gen::<bool>()).collect();
|
||||||
|
|
||||||
|
let rcm = pallas::Scalar::random(&mut os_rng);
|
||||||
|
|
||||||
|
// Evaluate the commitment with commit function
|
||||||
|
let commit1 = domain.commit(msg.clone().into_iter(), &rcm);
|
||||||
|
|
||||||
|
// Evaluate the commitment with the following steps
|
||||||
|
// 1. hash msg
|
||||||
|
// 2. evaluate the commitment from the hash point
|
||||||
|
let hash_point = domain.M.hash_to_point(msg.into_iter());
|
||||||
|
let commit2 = domain.commit_from_hash_point(hash_point, &rcm);
|
||||||
|
|
||||||
|
// Test equality
|
||||||
|
assert_eq!(commit1.unwrap(), commit2.unwrap());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,12 +2,14 @@
|
||||||
|
|
||||||
use super::{bool_check, ternary, UtilitiesInstructions};
|
use super::{bool_check, ternary, UtilitiesInstructions};
|
||||||
|
|
||||||
|
use crate::ecc::chip::{EccPoint, NonIdentityEccPoint};
|
||||||
use group::ff::{Field, PrimeField};
|
use group::ff::{Field, PrimeField};
|
||||||
use halo2_proofs::{
|
use halo2_proofs::{
|
||||||
circuit::{AssignedCell, Chip, Layouter, Value},
|
circuit::{AssignedCell, Chip, Layouter, Value},
|
||||||
plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Selector},
|
plonk::{self, Advice, Column, ConstraintSystem, Constraints, Error, Selector},
|
||||||
poly::Rotation,
|
poly::Rotation,
|
||||||
};
|
};
|
||||||
|
use pasta_curves::pallas;
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
|
|
||||||
/// Instructions for a conditional swap gadget.
|
/// Instructions for a conditional swap gadget.
|
||||||
|
@ -24,6 +26,16 @@ pub trait CondSwapInstructions<F: Field>: UtilitiesInstructions<F> {
|
||||||
pair: (Self::Var, Value<F>),
|
pair: (Self::Var, Value<F>),
|
||||||
swap: Value<bool>,
|
swap: Value<bool>,
|
||||||
) -> Result<(Self::Var, Self::Var), Error>;
|
) -> Result<(Self::Var, Self::Var), Error>;
|
||||||
|
|
||||||
|
/// Given an input `(choice, left, right)` where `choice` is a boolean flag,
|
||||||
|
/// returns `left` if `choice` is not set and `right` if `choice` is set.
|
||||||
|
fn mux(
|
||||||
|
&self,
|
||||||
|
layouter: &mut impl Layouter<F>,
|
||||||
|
choice: Self::Var,
|
||||||
|
left: Self::Var,
|
||||||
|
right: Self::Var,
|
||||||
|
) -> Result<Self::Var, Error>;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A chip implementing a conditional swap.
|
/// A chip implementing a conditional swap.
|
||||||
|
@ -121,6 +133,97 @@ impl<F: PrimeField> CondSwapInstructions<F> for CondSwapChip<F> {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn mux(
|
||||||
|
&self,
|
||||||
|
layouter: &mut impl Layouter<F>,
|
||||||
|
choice: Self::Var,
|
||||||
|
left: Self::Var,
|
||||||
|
right: Self::Var,
|
||||||
|
) -> Result<Self::Var, Error> {
|
||||||
|
let config = self.config();
|
||||||
|
|
||||||
|
layouter.assign_region(
|
||||||
|
|| "mux",
|
||||||
|
|mut region| {
|
||||||
|
// Enable `q_swap` selector
|
||||||
|
config.q_swap.enable(&mut region, 0)?;
|
||||||
|
|
||||||
|
// Copy in `a` value
|
||||||
|
let left = left.copy_advice(|| "copy left", &mut region, config.a, 0)?;
|
||||||
|
|
||||||
|
// Copy in `b` value
|
||||||
|
let right = right.copy_advice(|| "copy right", &mut region, config.b, 0)?;
|
||||||
|
|
||||||
|
// Copy `choice` value
|
||||||
|
let choice = choice.copy_advice(|| "copy choice", &mut region, config.swap, 0)?;
|
||||||
|
|
||||||
|
let a_swapped = left
|
||||||
|
.value()
|
||||||
|
.zip(right.value())
|
||||||
|
.zip(choice.value())
|
||||||
|
.map(|((left, right), choice)| {
|
||||||
|
if *choice == F::from(0_u64) {
|
||||||
|
left
|
||||||
|
} else {
|
||||||
|
right
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cloned();
|
||||||
|
let b_swapped = left
|
||||||
|
.value()
|
||||||
|
.zip(right.value())
|
||||||
|
.zip(choice.value())
|
||||||
|
.map(|((left, right), choice)| {
|
||||||
|
if *choice == F::from(0_u64) {
|
||||||
|
right
|
||||||
|
} else {
|
||||||
|
left
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.cloned();
|
||||||
|
|
||||||
|
region.assign_advice(|| "out b_swap", self.config.b_swapped, 0, || b_swapped)?;
|
||||||
|
region.assign_advice(|| "out a_swap", self.config.a_swapped, 0, || a_swapped)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl CondSwapChip<pallas::Base> {
|
||||||
|
/// Given an input `(choice, left, right)` where `choice` is a boolean flag and `left` and `right` are `EccPoint`,
|
||||||
|
/// returns `left` if `choice` is not set and `right` if `choice` is set.
|
||||||
|
pub fn mux_on_points(
|
||||||
|
&self,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
choice: &AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
left: &EccPoint,
|
||||||
|
right: &EccPoint,
|
||||||
|
) -> Result<EccPoint, plonk::Error> {
|
||||||
|
let x_cell = self.mux(&mut layouter, choice.clone(), left.x(), right.x())?;
|
||||||
|
let y_cell = self.mux(&mut layouter, choice.clone(), left.y(), right.y())?;
|
||||||
|
Ok(EccPoint::from_coordinates_unchecked(
|
||||||
|
x_cell.into(),
|
||||||
|
y_cell.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given an input `(choice, left, right)` where `choice` is a boolean flag and `left` and `right` are
|
||||||
|
/// `NonIdentityEccPoint`, returns `left` if `choice` is not set and `right` if `choice` is set.
|
||||||
|
pub fn mux_on_non_identity_points(
|
||||||
|
&self,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
choice: &AssignedCell<pallas::Base, pallas::Base>,
|
||||||
|
left: &NonIdentityEccPoint,
|
||||||
|
right: &NonIdentityEccPoint,
|
||||||
|
) -> Result<NonIdentityEccPoint, plonk::Error> {
|
||||||
|
let x_cell = self.mux(&mut layouter, choice.clone(), left.x(), right.x())?;
|
||||||
|
let y_cell = self.mux(&mut layouter, choice.clone(), left.y(), right.y())?;
|
||||||
|
Ok(NonIdentityEccPoint::from_coordinates_unchecked(
|
||||||
|
x_cell.into(),
|
||||||
|
y_cell.into(),
|
||||||
|
))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<F: PrimeField> CondSwapChip<F> {
|
impl<F: PrimeField> CondSwapChip<F> {
|
||||||
|
@ -291,4 +394,231 @@ mod tests {
|
||||||
assert_eq!(prover.verify(), Ok(()));
|
assert_eq!(prover.verify(), Ok(()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mux() {
|
||||||
|
use crate::{
|
||||||
|
ecc::{
|
||||||
|
chip::{EccChip, EccConfig},
|
||||||
|
tests::TestFixedBases,
|
||||||
|
NonIdentityPoint, Point,
|
||||||
|
},
|
||||||
|
utilities::lookup_range_check::LookupRangeCheckConfig,
|
||||||
|
};
|
||||||
|
|
||||||
|
use group::{cofactor::CofactorCurveAffine, Curve, Group};
|
||||||
|
use halo2_proofs::{
|
||||||
|
circuit::{Layouter, SimpleFloorPlanner, Value},
|
||||||
|
dev::MockProver,
|
||||||
|
plonk::{Advice, Circuit, Column, ConstraintSystem, Error, Instance},
|
||||||
|
};
|
||||||
|
use pasta_curves::arithmetic::CurveAffine;
|
||||||
|
use pasta_curves::{pallas, EpAffine};
|
||||||
|
|
||||||
|
use rand::rngs::OsRng;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct MyConfig {
|
||||||
|
primary: Column<Instance>,
|
||||||
|
advice: Column<Advice>,
|
||||||
|
cond_swap_config: CondSwapConfig,
|
||||||
|
ecc_config: EccConfig<TestFixedBases>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct MyCircuit {
|
||||||
|
left_point: Value<EpAffine>,
|
||||||
|
right_point: Value<EpAffine>,
|
||||||
|
choice: Value<pallas::Base>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Circuit<pallas::Base> for MyCircuit {
|
||||||
|
type Config = MyConfig;
|
||||||
|
type FloorPlanner = SimpleFloorPlanner;
|
||||||
|
|
||||||
|
fn without_witnesses(&self) -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn configure(meta: &mut ConstraintSystem<pallas::Base>) -> Self::Config {
|
||||||
|
let advices = [
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
meta.advice_column(),
|
||||||
|
];
|
||||||
|
|
||||||
|
for advice in advices.iter() {
|
||||||
|
meta.enable_equality(*advice);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Instance column used for public inputs
|
||||||
|
let primary = meta.instance_column();
|
||||||
|
meta.enable_equality(primary);
|
||||||
|
|
||||||
|
let cond_swap_config =
|
||||||
|
CondSwapChip::configure(meta, advices[0..5].try_into().unwrap());
|
||||||
|
|
||||||
|
let table_idx = meta.lookup_table_column();
|
||||||
|
let table_range_check_tag = meta.lookup_table_column();
|
||||||
|
|
||||||
|
let lagrange_coeffs = [
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
meta.fixed_column(),
|
||||||
|
];
|
||||||
|
meta.enable_constant(lagrange_coeffs[0]);
|
||||||
|
|
||||||
|
let range_check = LookupRangeCheckConfig::configure(
|
||||||
|
meta,
|
||||||
|
advices[9],
|
||||||
|
table_idx,
|
||||||
|
table_range_check_tag,
|
||||||
|
);
|
||||||
|
|
||||||
|
let ecc_config = EccChip::<TestFixedBases>::configure(
|
||||||
|
meta,
|
||||||
|
advices,
|
||||||
|
lagrange_coeffs,
|
||||||
|
range_check,
|
||||||
|
);
|
||||||
|
|
||||||
|
MyConfig {
|
||||||
|
primary,
|
||||||
|
advice: advices[0],
|
||||||
|
cond_swap_config,
|
||||||
|
ecc_config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn synthesize(
|
||||||
|
&self,
|
||||||
|
config: Self::Config,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Construct a CondSwap chip
|
||||||
|
let cond_swap_chip = CondSwapChip::construct(config.cond_swap_config);
|
||||||
|
|
||||||
|
// Construct an ECC chip
|
||||||
|
let ecc_chip = EccChip::construct(config.ecc_config);
|
||||||
|
|
||||||
|
// Assign choice
|
||||||
|
let choice = layouter.assign_region(
|
||||||
|
|| "load private",
|
||||||
|
|mut region| {
|
||||||
|
region.assign_advice(|| "load private", config.advice, 0, || self.choice)
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Test mux on non identity points
|
||||||
|
// Assign left point
|
||||||
|
let left_non_identity_point = NonIdentityPoint::new(
|
||||||
|
ecc_chip.clone(),
|
||||||
|
layouter.namespace(|| "left point"),
|
||||||
|
self.left_point.map(|left_point| left_point),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Assign right point
|
||||||
|
let right_non_identity_point = NonIdentityPoint::new(
|
||||||
|
ecc_chip.clone(),
|
||||||
|
layouter.namespace(|| "right point"),
|
||||||
|
self.right_point.map(|right_point| right_point),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Apply mux
|
||||||
|
let result_non_identity_point = cond_swap_chip.mux_on_non_identity_points(
|
||||||
|
layouter.namespace(|| "MUX"),
|
||||||
|
&choice,
|
||||||
|
left_non_identity_point.inner(),
|
||||||
|
right_non_identity_point.inner(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Check equality with instance
|
||||||
|
layouter.constrain_instance(
|
||||||
|
result_non_identity_point.x().cell(),
|
||||||
|
config.primary,
|
||||||
|
0,
|
||||||
|
)?;
|
||||||
|
layouter.constrain_instance(
|
||||||
|
result_non_identity_point.y().cell(),
|
||||||
|
config.primary,
|
||||||
|
1,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Test mux on points
|
||||||
|
// Assign left point
|
||||||
|
let left_point = Point::new(
|
||||||
|
ecc_chip.clone(),
|
||||||
|
layouter.namespace(|| "left point"),
|
||||||
|
self.left_point.map(|left_point| left_point),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Assign right point
|
||||||
|
let right_point = Point::new(
|
||||||
|
ecc_chip,
|
||||||
|
layouter.namespace(|| "right point"),
|
||||||
|
self.right_point.map(|right_point| right_point),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Apply mux
|
||||||
|
let result = cond_swap_chip.mux_on_points(
|
||||||
|
layouter.namespace(|| "MUX"),
|
||||||
|
&choice,
|
||||||
|
left_point.inner(),
|
||||||
|
right_point.inner(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Check equality with instance
|
||||||
|
layouter.constrain_instance(result.x().cell(), config.primary, 0)?;
|
||||||
|
layouter.constrain_instance(result.y().cell(), config.primary, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test different circuits
|
||||||
|
let mut circuits = vec![];
|
||||||
|
let mut instances = vec![];
|
||||||
|
for choice in [false, true] {
|
||||||
|
let choice_value = if choice {
|
||||||
|
pallas::Base::one()
|
||||||
|
} else {
|
||||||
|
pallas::Base::zero()
|
||||||
|
};
|
||||||
|
let left_point = pallas::Point::random(OsRng).to_affine();
|
||||||
|
let right_point = pallas::Point::random(OsRng).to_affine();
|
||||||
|
circuits.push(MyCircuit {
|
||||||
|
left_point: Value::known(left_point),
|
||||||
|
right_point: Value::known(right_point),
|
||||||
|
choice: Value::known(choice_value),
|
||||||
|
});
|
||||||
|
let expected_output = if choice { right_point } else { left_point };
|
||||||
|
let (expected_x, expected_y) = if bool::from(expected_output.is_identity()) {
|
||||||
|
(pallas::Base::zero(), pallas::Base::zero())
|
||||||
|
} else {
|
||||||
|
let coords = expected_output.coordinates().unwrap();
|
||||||
|
(*coords.x(), *coords.y())
|
||||||
|
};
|
||||||
|
instances.push([[expected_x, expected_y]]);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (circuit, instance) in circuits.iter().zip(instances.iter()) {
|
||||||
|
let prover = MockProver::<pallas::Base>::run(
|
||||||
|
5,
|
||||||
|
circuit,
|
||||||
|
instance.iter().map(|p| p.to_vec()).collect(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
assert_eq!(prover.verify(), Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -60,8 +60,11 @@ pub struct LookupRangeCheckConfig<F: PrimeFieldBits, const K: usize> {
|
||||||
q_lookup: Selector,
|
q_lookup: Selector,
|
||||||
q_running: Selector,
|
q_running: Selector,
|
||||||
q_bitshift: Selector,
|
q_bitshift: Selector,
|
||||||
|
q_range_check_4: Selector,
|
||||||
|
q_range_check_5: Selector,
|
||||||
running_sum: Column<Advice>,
|
running_sum: Column<Advice>,
|
||||||
table_idx: TableColumn,
|
table_idx: TableColumn,
|
||||||
|
table_range_check_tag: TableColumn,
|
||||||
_marker: PhantomData<F>,
|
_marker: PhantomData<F>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -81,18 +84,24 @@ impl<F: PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K> {
|
||||||
meta: &mut ConstraintSystem<F>,
|
meta: &mut ConstraintSystem<F>,
|
||||||
running_sum: Column<Advice>,
|
running_sum: Column<Advice>,
|
||||||
table_idx: TableColumn,
|
table_idx: TableColumn,
|
||||||
|
table_range_check_tag: TableColumn,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
meta.enable_equality(running_sum);
|
meta.enable_equality(running_sum);
|
||||||
|
|
||||||
let q_lookup = meta.complex_selector();
|
let q_lookup = meta.complex_selector();
|
||||||
let q_running = meta.complex_selector();
|
let q_running = meta.complex_selector();
|
||||||
let q_bitshift = meta.selector();
|
let q_bitshift = meta.selector();
|
||||||
|
let q_range_check_4 = meta.complex_selector();
|
||||||
|
let q_range_check_5 = meta.complex_selector();
|
||||||
let config = LookupRangeCheckConfig {
|
let config = LookupRangeCheckConfig {
|
||||||
q_lookup,
|
q_lookup,
|
||||||
q_running,
|
q_running,
|
||||||
q_bitshift,
|
q_bitshift,
|
||||||
|
q_range_check_4,
|
||||||
|
q_range_check_5,
|
||||||
running_sum,
|
running_sum,
|
||||||
table_idx,
|
table_idx,
|
||||||
|
table_range_check_tag,
|
||||||
_marker: PhantomData,
|
_marker: PhantomData,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -100,7 +109,10 @@ impl<F: PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K> {
|
||||||
meta.lookup(|meta| {
|
meta.lookup(|meta| {
|
||||||
let q_lookup = meta.query_selector(config.q_lookup);
|
let q_lookup = meta.query_selector(config.q_lookup);
|
||||||
let q_running = meta.query_selector(config.q_running);
|
let q_running = meta.query_selector(config.q_running);
|
||||||
|
let q_range_check_4 = meta.query_selector(config.q_range_check_4);
|
||||||
|
let q_range_check_5 = meta.query_selector(config.q_range_check_5);
|
||||||
let z_cur = meta.query_advice(config.running_sum, Rotation::cur());
|
let z_cur = meta.query_advice(config.running_sum, Rotation::cur());
|
||||||
|
let one = Expression::Constant(F::ONE);
|
||||||
|
|
||||||
// In the case of a running sum decomposition, we recover the word from
|
// In the case of a running sum decomposition, we recover the word from
|
||||||
// the difference of the running sums:
|
// the difference of the running sums:
|
||||||
|
@ -117,17 +129,40 @@ impl<F: PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K> {
|
||||||
|
|
||||||
// In the short range check, the word is directly witnessed.
|
// In the short range check, the word is directly witnessed.
|
||||||
let short_lookup = {
|
let short_lookup = {
|
||||||
let short_word = z_cur;
|
let short_word = z_cur.clone();
|
||||||
let q_short = Expression::Constant(F::ONE) - q_running;
|
let q_short = one.clone() - q_running;
|
||||||
|
|
||||||
q_short * short_word
|
q_short * short_word
|
||||||
};
|
};
|
||||||
|
|
||||||
// Combine the running sum and short lookups:
|
// q_range_check is equal to
|
||||||
vec![(
|
// - 1 if q_range_check_4 = 1 or q_range_check_5 = 1
|
||||||
q_lookup * (running_sum_lookup + short_lookup),
|
// - 0 otherwise
|
||||||
config.table_idx,
|
let q_range_check = one.clone()
|
||||||
)]
|
- (one.clone() - q_range_check_4.clone()) * (one.clone() - q_range_check_5.clone());
|
||||||
|
|
||||||
|
// num_bits is equal to
|
||||||
|
// - 5 if q_range_check_5 = 1
|
||||||
|
// - 4 if q_range_check_4 = 1 and q_range_check_5 = 0
|
||||||
|
// - 0 otherwise
|
||||||
|
let num_bits = q_range_check_5.clone() * Expression::Constant(F::from(5_u64))
|
||||||
|
+ (one.clone() - q_range_check_5)
|
||||||
|
* q_range_check_4
|
||||||
|
* Expression::Constant(F::from(4_u64));
|
||||||
|
|
||||||
|
// Combine the running sum, short lookups and optimized range checks:
|
||||||
|
vec![
|
||||||
|
(
|
||||||
|
q_lookup.clone()
|
||||||
|
* ((one - q_range_check.clone()) * (running_sum_lookup + short_lookup)
|
||||||
|
+ q_range_check.clone() * z_cur),
|
||||||
|
config.table_idx,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
q_lookup * q_range_check * num_bits,
|
||||||
|
config.table_range_check_tag,
|
||||||
|
),
|
||||||
|
]
|
||||||
});
|
});
|
||||||
|
|
||||||
// For short lookups, check that the word has been shifted by the correct number of bits.
|
// For short lookups, check that the word has been shifted by the correct number of bits.
|
||||||
|
@ -152,9 +187,9 @@ impl<F: PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K> {
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
// Loads the values [0..2^K) into `table_idx`. This is only used in testing
|
// Fill `table_idx` and `table_range_check_tag`.
|
||||||
// for now, since the Sinsemilla chip provides a pre-loaded table in the
|
// This is only used in testing for now, since the Sinsemilla chip provides a pre-loaded table
|
||||||
// Orchard context.
|
// in the Orchard context.
|
||||||
pub fn load(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
|
pub fn load(&self, layouter: &mut impl Layouter<F>) -> Result<(), Error> {
|
||||||
layouter.assign_table(
|
layouter.assign_table(
|
||||||
|| "table_idx",
|
|| "table_idx",
|
||||||
|
@ -167,6 +202,42 @@ impl<F: PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K> {
|
||||||
index,
|
index,
|
||||||
|| Value::known(F::from(index as u64)),
|
|| Value::known(F::from(index as u64)),
|
||||||
)?;
|
)?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_range_check_tag",
|
||||||
|
self.table_range_check_tag,
|
||||||
|
index,
|
||||||
|
|| Value::known(F::ZERO),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
for index in 0..(1 << 4) {
|
||||||
|
let new_index = index + (1 << K);
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_idx",
|
||||||
|
self.table_idx,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(F::from(index as u64)),
|
||||||
|
)?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_range_check_tag",
|
||||||
|
self.table_range_check_tag,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(F::from(4_u64)),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
for index in 0..(1 << 5) {
|
||||||
|
let new_index = index + (1 << K) + (1 << 4);
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_idx",
|
||||||
|
self.table_idx,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(F::from(index as u64)),
|
||||||
|
)?;
|
||||||
|
table.assign_cell(
|
||||||
|
|| "table_range_check_tag",
|
||||||
|
self.table_range_check_tag,
|
||||||
|
new_index,
|
||||||
|
|| Value::known(F::from(5_u64)),
|
||||||
|
)?;
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
},
|
},
|
||||||
|
@ -350,33 +421,43 @@ impl<F: PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K> {
|
||||||
element: AssignedCell<F, F>,
|
element: AssignedCell<F, F>,
|
||||||
num_bits: usize,
|
num_bits: usize,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
// Enable lookup for `element`, to constrain it to 10 bits.
|
// Enable lookup for `element`.
|
||||||
self.q_lookup.enable(region, 0)?;
|
self.q_lookup.enable(region, 0)?;
|
||||||
|
|
||||||
// Enable lookup for shifted element, to constrain it to 10 bits.
|
match num_bits {
|
||||||
self.q_lookup.enable(region, 1)?;
|
4 => {
|
||||||
|
self.q_range_check_4.enable(region, 0)?;
|
||||||
|
}
|
||||||
|
5 => {
|
||||||
|
self.q_range_check_5.enable(region, 0)?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Enable lookup for shifted element, to constrain it to 10 bits.
|
||||||
|
self.q_lookup.enable(region, 1)?;
|
||||||
|
|
||||||
// Check element has been shifted by the correct number of bits.
|
// Check element has been shifted by the correct number of bits.
|
||||||
self.q_bitshift.enable(region, 1)?;
|
self.q_bitshift.enable(region, 1)?;
|
||||||
|
|
||||||
// Assign shifted `element * 2^{K - num_bits}`
|
// Assign shifted `element * 2^{K - num_bits}`
|
||||||
let shifted = element.value().into_field() * F::from(1 << (K - num_bits));
|
let shifted = element.value().into_field() * F::from(1 << (K - num_bits));
|
||||||
|
|
||||||
region.assign_advice(
|
region.assign_advice(
|
||||||
|| format!("element * 2^({}-{})", K, num_bits),
|
|| format!("element * 2^({}-{})", K, num_bits),
|
||||||
self.running_sum,
|
self.running_sum,
|
||||||
1,
|
1,
|
||||||
|| shifted,
|
|| shifted,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// Assign 2^{-num_bits} from a fixed column.
|
// Assign 2^{-num_bits} from a fixed column.
|
||||||
let inv_two_pow_s = F::from(1 << num_bits).invert().unwrap();
|
let inv_two_pow_s = F::from(1 << num_bits).invert().unwrap();
|
||||||
region.assign_advice_from_constant(
|
region.assign_advice_from_constant(
|
||||||
|| format!("2^(-{})", num_bits),
|
|| format!("2^(-{})", num_bits),
|
||||||
self.running_sum,
|
self.running_sum,
|
||||||
2,
|
2,
|
||||||
inv_two_pow_s,
|
inv_two_pow_s,
|
||||||
)?;
|
)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
@ -418,10 +499,16 @@ mod tests {
|
||||||
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
|
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
|
||||||
let running_sum = meta.advice_column();
|
let running_sum = meta.advice_column();
|
||||||
let table_idx = meta.lookup_table_column();
|
let table_idx = meta.lookup_table_column();
|
||||||
|
let table_range_check_tag = meta.lookup_table_column();
|
||||||
let constants = meta.fixed_column();
|
let constants = meta.fixed_column();
|
||||||
meta.enable_constant(constants);
|
meta.enable_constant(constants);
|
||||||
|
|
||||||
LookupRangeCheckConfig::<F, K>::configure(meta, running_sum, table_idx)
|
LookupRangeCheckConfig::<F, K>::configure(
|
||||||
|
meta,
|
||||||
|
running_sum,
|
||||||
|
table_idx,
|
||||||
|
table_range_check_tag,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn synthesize(
|
fn synthesize(
|
||||||
|
@ -517,10 +604,16 @@ mod tests {
|
||||||
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
|
fn configure(meta: &mut ConstraintSystem<F>) -> Self::Config {
|
||||||
let running_sum = meta.advice_column();
|
let running_sum = meta.advice_column();
|
||||||
let table_idx = meta.lookup_table_column();
|
let table_idx = meta.lookup_table_column();
|
||||||
|
let table_range_check_tag = meta.lookup_table_column();
|
||||||
let constants = meta.fixed_column();
|
let constants = meta.fixed_column();
|
||||||
meta.enable_constant(constants);
|
meta.enable_constant(constants);
|
||||||
|
|
||||||
LookupRangeCheckConfig::<F, K>::configure(meta, running_sum, table_idx)
|
LookupRangeCheckConfig::<F, K>::configure(
|
||||||
|
meta,
|
||||||
|
running_sum,
|
||||||
|
table_idx,
|
||||||
|
table_range_check_tag,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn synthesize(
|
fn synthesize(
|
||||||
|
@ -646,5 +739,34 @@ mod tests {
|
||||||
}])
|
}])
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Element within 4 bits
|
||||||
|
{
|
||||||
|
let circuit: MyCircuit<pallas::Base> = MyCircuit {
|
||||||
|
element: Value::known(pallas::Base::from((1 << 4) - 1)),
|
||||||
|
num_bits: 4,
|
||||||
|
};
|
||||||
|
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
|
||||||
|
assert_eq!(prover.verify(), Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Element larger than 5 bits
|
||||||
|
{
|
||||||
|
let circuit: MyCircuit<pallas::Base> = MyCircuit {
|
||||||
|
element: Value::known(pallas::Base::from(1 << 5)),
|
||||||
|
num_bits: 5,
|
||||||
|
};
|
||||||
|
let prover = MockProver::<pallas::Base>::run(11, &circuit, vec![]).unwrap();
|
||||||
|
assert_eq!(
|
||||||
|
prover.verify(),
|
||||||
|
Err(vec![VerifyFailure::Lookup {
|
||||||
|
lookup_index: 0,
|
||||||
|
location: FailureLocation::InRegion {
|
||||||
|
region: (1, "Range check 5 bits").into(),
|
||||||
|
offset: 0,
|
||||||
|
},
|
||||||
|
}])
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -139,6 +139,16 @@ impl<F: Field> AssignedCell<Assigned<F>, F> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl<F: Field> From<AssignedCell<F, F>> for AssignedCell<Assigned<F>, F> {
|
||||||
|
fn from(ac: AssignedCell<F, F>) -> Self {
|
||||||
|
AssignedCell {
|
||||||
|
value: ac.value.map(|a| a.into()),
|
||||||
|
cell: ac.cell,
|
||||||
|
_marker: Default::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl<V: Clone, F: Field> AssignedCell<V, F>
|
impl<V: Clone, F: Field> AssignedCell<V, F>
|
||||||
where
|
where
|
||||||
for<'v> Assigned<F>: From<&'v V>,
|
for<'v> Assigned<F>: From<&'v V>,
|
||||||
|
|
Loading…
Reference in New Issue