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:
therealyingtong 2021-06-05 14:23:56 +08:00
parent cc9dd20536
commit a263774abf
4 changed files with 245 additions and 7 deletions

View File

@ -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(

View File

@ -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)
}
}

View File

@ -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 })
}
}

View File

@ -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,
})
}
}