mirror of https://github.com/zcash/halo2.git
chip::witness_scalar_fixed.rs: Implement witness_scalar_fixed instruction.
A scalar used in fixed-base scalar mul needs to be decomposed into windows to use with the fixed-base window table. Both full-width and short signed scalars share some logic (captured in the function decompose_scalar_fixed()). A short signed scalar introduces additional logic: its magnitude is decomposed, and its sign is separately witnessed. This is handled in the submodule witness_scalar_fixed::short.
This commit is contained in:
parent
cc9dd20536
commit
a263774abf
|
@ -15,7 +15,7 @@ pub(super) mod add_incomplete;
|
|||
pub(super) mod mul;
|
||||
// pub(super) mod mul_fixed;
|
||||
pub(super) mod witness_point;
|
||||
// pub(super) mod witness_scalar_fixed;
|
||||
pub(super) mod witness_scalar_fixed;
|
||||
|
||||
/// A curve point represented in affine (x, y) coordinates. Each coordinate is
|
||||
/// assigned to a cell.
|
||||
|
@ -186,6 +186,19 @@ impl EccChip {
|
|||
mul_config.create_gate(meta);
|
||||
}
|
||||
|
||||
// Create witness scalar_fixed gate that applies to both full-width and
|
||||
// short scalars
|
||||
{
|
||||
let config: witness_scalar_fixed::Config = (&config).into();
|
||||
config.create_gate(meta);
|
||||
}
|
||||
|
||||
// Create witness scalar_fixed gate that only apploes to short scalars
|
||||
{
|
||||
let config: witness_scalar_fixed::short::Config = (&config).into();
|
||||
config.create_gate(meta);
|
||||
}
|
||||
|
||||
config
|
||||
}
|
||||
}
|
||||
|
@ -262,18 +275,26 @@ impl EccInstructions<pallas::Affine> for EccChip {
|
|||
|
||||
fn witness_scalar_fixed(
|
||||
&self,
|
||||
_layouter: &mut impl Layouter<pallas::Base>,
|
||||
_value: Option<pallas::Scalar>,
|
||||
layouter: &mut impl Layouter<pallas::Base>,
|
||||
value: Option<pallas::Scalar>,
|
||||
) -> Result<Self::ScalarFixed, Error> {
|
||||
todo!()
|
||||
let config: witness_scalar_fixed::full_width::Config = self.config().into();
|
||||
layouter.assign_region(
|
||||
|| "witness scalar for fixed-base mul",
|
||||
|mut region| config.assign_region(value, 0, &mut region),
|
||||
)
|
||||
}
|
||||
|
||||
fn witness_scalar_fixed_short(
|
||||
&self,
|
||||
_layouter: &mut impl Layouter<pallas::Base>,
|
||||
_value: Option<pallas::Scalar>,
|
||||
layouter: &mut impl Layouter<pallas::Base>,
|
||||
value: Option<pallas::Scalar>,
|
||||
) -> Result<Self::ScalarFixedShort, Error> {
|
||||
todo!()
|
||||
let config: witness_scalar_fixed::short::Config = self.config().into();
|
||||
layouter.assign_region(
|
||||
|| "witness short scalar for fixed-base mul",
|
||||
|mut region| config.assign_region(value, 0, &mut region),
|
||||
)
|
||||
}
|
||||
|
||||
fn witness_point(
|
||||
|
|
|
@ -0,0 +1,94 @@
|
|||
use super::{CellValue, EccConfig, Var};
|
||||
use crate::constants::{self, util};
|
||||
use arrayvec::ArrayVec;
|
||||
use ff::PrimeFieldBits;
|
||||
use halo2::{
|
||||
circuit::Region,
|
||||
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector},
|
||||
poly::Rotation,
|
||||
};
|
||||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||
|
||||
pub mod full_width;
|
||||
pub mod short;
|
||||
|
||||
pub struct Config {
|
||||
q_scalar_fixed: Selector,
|
||||
// Decomposition of scalar into `k`-bit windows.
|
||||
window: Column<Advice>,
|
||||
}
|
||||
|
||||
impl From<&EccConfig> for Config {
|
||||
fn from(ecc_config: &EccConfig) -> Self {
|
||||
Self {
|
||||
q_scalar_fixed: ecc_config.q_scalar_fixed,
|
||||
window: ecc_config.advices[0],
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
||||
// Range check gate applies to both full-width and short scalars.
|
||||
// Check that `k` is within the allowed window size
|
||||
meta.create_gate("witness scalar fixed", |meta| {
|
||||
let q_scalar_fixed = meta.query_selector(self.q_scalar_fixed);
|
||||
let window = meta.query_advice(self.window, Rotation::cur());
|
||||
|
||||
let range_check =
|
||||
(0..constants::H).fold(Expression::Constant(pallas::Base::one()), |acc, i| {
|
||||
acc * (window.clone() - Expression::Constant(pallas::Base::from_u64(i as u64)))
|
||||
});
|
||||
vec![q_scalar_fixed * range_check]
|
||||
});
|
||||
}
|
||||
|
||||
fn decompose_scalar_fixed<const NUM_WINDOWS: usize, const SCALAR_NUM_BITS: usize>(
|
||||
&self,
|
||||
scalar: Option<pallas::Scalar>,
|
||||
offset: usize,
|
||||
region: &mut Region<'_, pallas::Base>,
|
||||
) -> Result<ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS>, Error>
|
||||
where
|
||||
pallas::Scalar: PrimeFieldBits,
|
||||
{
|
||||
// Enable `q_scalar_fixed` selector
|
||||
for idx in 0..NUM_WINDOWS {
|
||||
self.q_scalar_fixed.enable(region, offset + idx)?;
|
||||
}
|
||||
|
||||
// Decompose scalar into `k-bit` windows
|
||||
let scalar_windows: Option<Vec<u8>> = scalar.map(|scalar| {
|
||||
util::decompose_scalar_fixed::<pallas::Scalar>(
|
||||
scalar,
|
||||
SCALAR_NUM_BITS,
|
||||
constants::FIXED_BASE_WINDOW_SIZE,
|
||||
)
|
||||
});
|
||||
|
||||
// Store the scalar decomposition
|
||||
let mut windows: ArrayVec<CellValue<pallas::Base>, NUM_WINDOWS> = ArrayVec::new();
|
||||
|
||||
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_u64(window as u64)))
|
||||
.collect()
|
||||
} else {
|
||||
vec![None; NUM_WINDOWS]
|
||||
};
|
||||
|
||||
for (idx, window) in scalar_windows.into_iter().enumerate() {
|
||||
let window_cell = region.assign_advice(
|
||||
|| format!("k[{:?}]", offset + idx),
|
||||
self.window,
|
||||
offset + idx,
|
||||
|| window.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
windows.push(CellValue::new(window_cell, window));
|
||||
}
|
||||
|
||||
Ok(windows)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
use super::super::{EccConfig, EccScalarFixed};
|
||||
use crate::constants::{L_ORCHARD_SCALAR, NUM_WINDOWS};
|
||||
use halo2::{circuit::Region, plonk::Error};
|
||||
use pasta_curves::pallas;
|
||||
|
||||
pub struct Config(super::Config);
|
||||
|
||||
impl From<&EccConfig> for Config {
|
||||
fn from(ecc_config: &EccConfig) -> Self {
|
||||
Self(ecc_config.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn assign_region(
|
||||
&self,
|
||||
value: Option<pallas::Scalar>,
|
||||
offset: usize,
|
||||
region: &mut Region<'_, pallas::Base>,
|
||||
) -> Result<EccScalarFixed, Error> {
|
||||
let windows = self
|
||||
.0
|
||||
.decompose_scalar_fixed::<NUM_WINDOWS, L_ORCHARD_SCALAR>(value, offset, region)?;
|
||||
|
||||
Ok(EccScalarFixed { value, windows })
|
||||
}
|
||||
}
|
|
@ -0,0 +1,96 @@
|
|||
use super::super::{CellValue, EccConfig, EccScalarFixedShort, Var};
|
||||
use crate::constants::{L_VALUE, NUM_WINDOWS_SHORT};
|
||||
use ff::PrimeFieldBits;
|
||||
use halo2::{
|
||||
circuit::Region,
|
||||
plonk::{ConstraintSystem, Error, Expression, Selector},
|
||||
poly::Rotation,
|
||||
};
|
||||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||
|
||||
pub struct Config {
|
||||
q_scalar_fixed_short: Selector,
|
||||
super_config: super::Config,
|
||||
}
|
||||
|
||||
impl From<&EccConfig> for Config {
|
||||
fn from(ecc_config: &EccConfig) -> Self {
|
||||
Self {
|
||||
q_scalar_fixed_short: ecc_config.q_scalar_fixed_short,
|
||||
super_config: ecc_config.into(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
pub fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
||||
// Check that sign is either 1 or -1.
|
||||
meta.create_gate("check sign", |meta| {
|
||||
let q_scalar_fixed_short = meta.query_selector(self.q_scalar_fixed_short);
|
||||
let sign = meta.query_advice(self.super_config.window, Rotation::cur());
|
||||
|
||||
vec![
|
||||
q_scalar_fixed_short
|
||||
* (sign.clone() + Expression::Constant(pallas::Base::one()))
|
||||
* (sign - Expression::Constant(pallas::Base::one())),
|
||||
]
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl Config {
|
||||
#[allow(clippy::op_ref)]
|
||||
pub fn assign_region(
|
||||
&self,
|
||||
value: Option<pallas::Scalar>,
|
||||
offset: usize,
|
||||
region: &mut Region<'_, pallas::Base>,
|
||||
) -> Result<EccScalarFixedShort, Error>
|
||||
where
|
||||
pallas::Scalar: PrimeFieldBits,
|
||||
{
|
||||
// Enable `q_scalar_fixed_short`
|
||||
self.q_scalar_fixed_short
|
||||
.enable(region, offset + NUM_WINDOWS_SHORT)?;
|
||||
|
||||
// Compute the scalar's sign and magnitude
|
||||
let sign = value.map(|value| {
|
||||
// t = (p - 1)/2
|
||||
let t = (pallas::Scalar::zero() - &pallas::Scalar::one()) * &pallas::Scalar::TWO_INV;
|
||||
if value > t {
|
||||
-pallas::Scalar::one()
|
||||
} else {
|
||||
pallas::Scalar::one()
|
||||
}
|
||||
});
|
||||
|
||||
let magnitude = sign.zip(value).map(|(sign, value)| sign * &value);
|
||||
|
||||
// Decompose magnitude into `k`-bit windows
|
||||
let windows = self
|
||||
.super_config
|
||||
.decompose_scalar_fixed::<NUM_WINDOWS_SHORT, L_VALUE>(magnitude, offset, region)?;
|
||||
|
||||
// Assign the sign and enable `q_scalar_fixed_short`
|
||||
let sign = sign.map(|sign| {
|
||||
assert!(sign == pallas::Scalar::one() || sign == -pallas::Scalar::one());
|
||||
if sign == pallas::Scalar::one() {
|
||||
pallas::Base::one()
|
||||
} else {
|
||||
-pallas::Base::one()
|
||||
}
|
||||
});
|
||||
let sign_cell = region.assign_advice(
|
||||
|| "sign",
|
||||
self.super_config.window,
|
||||
NUM_WINDOWS_SHORT,
|
||||
|| sign.ok_or(Error::SynthesisError),
|
||||
)?;
|
||||
|
||||
Ok(EccScalarFixedShort {
|
||||
magnitude,
|
||||
sign: CellValue::<pallas::Base>::new(sign_cell, sign),
|
||||
windows,
|
||||
})
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue