2021-08-19 21:21:46 -07:00
|
|
|
use super::super::{
|
|
|
|
EccConfig, EccPoint, EccScalarFixed, FixedPoints, FIXED_BASE_WINDOW_SIZE, H, L_ORCHARD_SCALAR,
|
|
|
|
NUM_WINDOWS,
|
2021-07-09 22:19:42 -07:00
|
|
|
};
|
2021-08-19 21:21:46 -07:00
|
|
|
|
2021-08-20 00:43:45 -07:00
|
|
|
use utilities::{decompose_word, range_check, CellValue, Var};
|
2021-07-17 08:59:25 -07:00
|
|
|
use arrayvec::ArrayVec;
|
2021-07-09 22:19:42 -07:00
|
|
|
use halo2::{
|
|
|
|
circuit::{Layouter, Region},
|
|
|
|
plonk::{ConstraintSystem, Error, Selector},
|
|
|
|
poly::Rotation,
|
|
|
|
};
|
2021-07-17 08:59:25 -07:00
|
|
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
2021-06-04 23:34:44 -07:00
|
|
|
|
2021-08-18 23:59:39 -07:00
|
|
|
pub struct Config<Fixed: FixedPoints<pallas::Affine>> {
|
2021-07-17 08:59:25 -07:00
|
|
|
q_mul_fixed_full: Selector,
|
2021-08-18 23:59:39 -07:00
|
|
|
super_config: super::Config<Fixed, NUM_WINDOWS>,
|
2021-07-09 22:19:42 -07:00
|
|
|
}
|
2021-06-04 23:34:44 -07:00
|
|
|
|
2021-08-18 23:59:39 -07:00
|
|
|
impl<Fixed: FixedPoints<pallas::Affine>> From<&EccConfig> for Config<Fixed> {
|
2021-06-04 23:34:44 -07:00
|
|
|
fn from(config: &EccConfig) -> Self {
|
2021-07-09 22:19:42 -07:00
|
|
|
Self {
|
2021-07-17 08:59:25 -07:00
|
|
|
q_mul_fixed_full: config.q_mul_fixed_full,
|
2021-07-09 22:19:42 -07:00
|
|
|
super_config: config.into(),
|
|
|
|
}
|
2021-06-04 23:34:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-18 23:59:39 -07:00
|
|
|
impl<Fixed: FixedPoints<pallas::Affine>> Config<Fixed> {
|
2021-07-09 22:19:42 -07:00
|
|
|
pub fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
|
|
|
// Check that each window `k` is within 3 bits
|
|
|
|
meta.create_gate("Full-width fixed-base scalar mul", |meta| {
|
2021-07-17 08:59:25 -07:00
|
|
|
let q_mul_fixed_full = meta.query_selector(self.q_mul_fixed_full);
|
2021-07-09 22:19:42 -07:00
|
|
|
let window = meta.query_advice(self.super_config.window, Rotation::cur());
|
|
|
|
|
|
|
|
self.super_config
|
2021-07-17 08:59:25 -07:00
|
|
|
.coords_check(meta, q_mul_fixed_full.clone(), window.clone())
|
2021-07-09 22:19:42 -07:00
|
|
|
.into_iter()
|
|
|
|
// Constrain each window to a 3-bit value:
|
|
|
|
// 1 * (window - 0) * (window - 1) * ... * (window - 7)
|
|
|
|
.chain(Some((
|
|
|
|
"window range check",
|
2021-08-19 21:21:46 -07:00
|
|
|
q_mul_fixed_full * range_check(window, H),
|
2021-07-09 22:19:42 -07:00
|
|
|
)))
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows.
|
|
|
|
///
|
|
|
|
/// The scalar is allowed to be non-canonical.
|
|
|
|
fn witness(
|
|
|
|
&self,
|
|
|
|
region: &mut Region<'_, pallas::Base>,
|
|
|
|
offset: usize,
|
|
|
|
scalar: Option<pallas::Scalar>,
|
|
|
|
) -> Result<EccScalarFixed, Error> {
|
2021-07-17 08:59:25 -07:00
|
|
|
let windows = self.decompose_scalar_fixed::<L_ORCHARD_SCALAR>(scalar, offset, region)?;
|
2021-07-09 22:19:42 -07:00
|
|
|
|
|
|
|
Ok(EccScalarFixed {
|
|
|
|
value: scalar,
|
|
|
|
windows,
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2021-07-17 08:59:25 -07:00
|
|
|
/// Witnesses the given scalar as `NUM_WINDOWS` 3-bit windows.
|
|
|
|
///
|
|
|
|
/// The scalar is allowed to be non-canonical.
|
|
|
|
fn decompose_scalar_fixed<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> {
|
|
|
|
// Enable `q_mul_fixed_full` selector
|
|
|
|
for idx in 0..NUM_WINDOWS {
|
|
|
|
self.q_mul_fixed_full.enable(region, offset + idx)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// Decompose scalar into `k-bit` windows
|
|
|
|
let scalar_windows: Option<Vec<u8>> = scalar.map(|scalar| {
|
2021-08-19 21:21:46 -07:00
|
|
|
decompose_word::<pallas::Scalar>(scalar, SCALAR_NUM_BITS, FIXED_BASE_WINDOW_SIZE)
|
2021-07-17 08:59:25 -07:00
|
|
|
});
|
|
|
|
|
|
|
|
// 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.super_config.window,
|
|
|
|
offset + idx,
|
|
|
|
|| window.ok_or(Error::SynthesisError),
|
|
|
|
)?;
|
|
|
|
windows.push(CellValue::new(window_cell, window));
|
|
|
|
}
|
|
|
|
|
|
|
|
Ok(windows)
|
|
|
|
}
|
|
|
|
|
2021-07-06 20:26:32 -07:00
|
|
|
pub fn assign(
|
2021-06-04 23:34:44 -07:00
|
|
|
&self,
|
2021-07-06 20:26:32 -07:00
|
|
|
mut layouter: impl Layouter<pallas::Base>,
|
2021-07-09 22:19:42 -07:00
|
|
|
scalar: Option<pallas::Scalar>,
|
2021-08-18 23:59:39 -07:00
|
|
|
base: &Fixed,
|
2021-07-09 22:19:42 -07:00
|
|
|
) -> Result<(EccPoint, EccScalarFixed), Error> {
|
|
|
|
let (scalar, acc, mul_b) = layouter.assign_region(
|
2021-07-07 21:00:52 -07:00
|
|
|
|| "Full-width fixed-base mul (incomplete addition)",
|
2021-07-06 20:26:32 -07:00
|
|
|
|mut region| {
|
|
|
|
let offset = 0;
|
|
|
|
|
2021-07-09 22:19:42 -07:00
|
|
|
let scalar = self.witness(&mut region, offset, scalar)?;
|
2021-06-18 02:41:13 -07:00
|
|
|
|
2021-07-09 22:19:42 -07:00
|
|
|
let (acc, mul_b) = self.super_config.assign_region_inner(
|
2021-07-06 20:26:32 -07:00
|
|
|
&mut region,
|
|
|
|
offset,
|
2021-07-09 22:19:42 -07:00
|
|
|
&(&scalar).into(),
|
2021-08-18 23:59:39 -07:00
|
|
|
base,
|
2021-07-17 08:59:25 -07:00
|
|
|
self.q_mul_fixed_full,
|
2021-07-09 22:19:42 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
Ok((scalar, acc, mul_b))
|
2021-07-07 21:00:52 -07:00
|
|
|
},
|
|
|
|
)?;
|
2021-06-04 23:34:44 -07:00
|
|
|
|
2021-07-07 21:00:52 -07:00
|
|
|
// Add to the accumulator and return the final result as `[scalar]B`.
|
|
|
|
let result = layouter.assign_region(
|
|
|
|
|| "Full-width fixed-base mul (last window, complete addition)",
|
|
|
|
|mut region| {
|
2021-09-27 01:49:08 -07:00
|
|
|
self.super_config.add_config.assign_region(
|
|
|
|
&mul_b.into(),
|
|
|
|
&acc.into(),
|
|
|
|
0,
|
|
|
|
&mut region,
|
|
|
|
)
|
2021-07-07 21:00:52 -07:00
|
|
|
},
|
|
|
|
)?;
|
2021-06-04 23:34:44 -07:00
|
|
|
|
2021-07-07 21:00:52 -07:00
|
|
|
#[cfg(test)]
|
|
|
|
// Check that the correct multiple is obtained.
|
|
|
|
{
|
|
|
|
use group::Curve;
|
2021-06-04 23:34:44 -07:00
|
|
|
|
2021-07-07 21:00:52 -07:00
|
|
|
let real_mul = scalar.value.map(|scalar| base.generator() * scalar);
|
|
|
|
let result = result.point();
|
2021-06-04 23:34:44 -07:00
|
|
|
|
2021-07-07 21:00:52 -07:00
|
|
|
if let (Some(real_mul), Some(result)) = (real_mul, result) {
|
|
|
|
assert_eq!(real_mul.to_affine(), result);
|
|
|
|
}
|
|
|
|
}
|
2021-06-04 23:34:44 -07:00
|
|
|
|
2021-07-09 22:19:42 -07:00
|
|
|
Ok((result, scalar))
|
2021-06-04 23:34:44 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[cfg(test)]
|
|
|
|
pub mod tests {
|
2021-07-08 00:06:47 -07:00
|
|
|
use group::Curve;
|
2021-06-04 23:34:44 -07:00
|
|
|
use halo2::{circuit::Layouter, plonk::Error};
|
|
|
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
|
|
|
|
|
|
|
use crate::circuit::gadget::ecc::{
|
2021-08-19 21:21:46 -07:00
|
|
|
chip::EccChip, FixedPoint, FixedPoints, NonIdentityPoint, Point, H,
|
2021-06-04 23:34:44 -07:00
|
|
|
};
|
2021-08-19 21:21:46 -07:00
|
|
|
use crate::constants::OrchardFixedBases;
|
2021-06-04 23:34:44 -07:00
|
|
|
|
|
|
|
pub fn test_mul_fixed(
|
2021-08-18 23:59:39 -07:00
|
|
|
chip: EccChip<OrchardFixedBases>,
|
2021-06-04 23:34:44 -07:00
|
|
|
mut layouter: impl Layouter<pallas::Base>,
|
|
|
|
) -> Result<(), Error> {
|
|
|
|
// commit_ivk_r
|
2021-08-18 23:59:39 -07:00
|
|
|
let commit_ivk_r = OrchardFixedBases::CommitIvkR;
|
2021-06-04 23:34:44 -07:00
|
|
|
test_single_base(
|
|
|
|
chip.clone(),
|
|
|
|
layouter.namespace(|| "commit_ivk_r"),
|
2021-07-08 00:06:47 -07:00
|
|
|
FixedPoint::from_inner(chip.clone(), commit_ivk_r),
|
|
|
|
commit_ivk_r.generator(),
|
2021-06-04 23:34:44 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
// note_commit_r
|
2021-08-18 23:59:39 -07:00
|
|
|
let note_commit_r = OrchardFixedBases::NoteCommitR;
|
2021-06-04 23:34:44 -07:00
|
|
|
test_single_base(
|
|
|
|
chip.clone(),
|
|
|
|
layouter.namespace(|| "note_commit_r"),
|
2021-07-08 00:06:47 -07:00
|
|
|
FixedPoint::from_inner(chip.clone(), note_commit_r),
|
|
|
|
note_commit_r.generator(),
|
2021-06-04 23:34:44 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
// value_commit_r
|
2021-08-18 23:59:39 -07:00
|
|
|
let value_commit_r = OrchardFixedBases::ValueCommitR;
|
2021-06-04 23:34:44 -07:00
|
|
|
test_single_base(
|
|
|
|
chip.clone(),
|
|
|
|
layouter.namespace(|| "value_commit_r"),
|
2021-07-08 00:06:47 -07:00
|
|
|
FixedPoint::from_inner(chip.clone(), value_commit_r),
|
|
|
|
value_commit_r.generator(),
|
2021-06-04 23:34:44 -07:00
|
|
|
)?;
|
|
|
|
|
|
|
|
// spend_auth_g
|
2021-08-18 23:59:39 -07:00
|
|
|
let spend_auth_g = OrchardFixedBases::SpendAuthG;
|
2021-07-03 02:06:55 -07:00
|
|
|
test_single_base(
|
2021-07-08 00:06:47 -07:00
|
|
|
chip.clone(),
|
2021-07-03 02:06:55 -07:00
|
|
|
layouter.namespace(|| "spend_auth_g"),
|
2021-07-08 00:06:47 -07:00
|
|
|
FixedPoint::from_inner(chip, spend_auth_g),
|
|
|
|
spend_auth_g.generator(),
|
2021-07-03 02:06:55 -07:00
|
|
|
)?;
|
2021-06-04 23:34:44 -07:00
|
|
|
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
|
|
|
#[allow(clippy::op_ref)]
|
|
|
|
fn test_single_base(
|
2021-08-18 23:59:39 -07:00
|
|
|
chip: EccChip<OrchardFixedBases>,
|
2021-06-04 23:34:44 -07:00
|
|
|
mut layouter: impl Layouter<pallas::Base>,
|
2021-08-18 23:59:39 -07:00
|
|
|
base: FixedPoint<pallas::Affine, EccChip<OrchardFixedBases>>,
|
2021-07-08 00:06:47 -07:00
|
|
|
base_val: pallas::Affine,
|
|
|
|
) -> Result<(), Error> {
|
2021-09-27 03:14:01 -07:00
|
|
|
fn constrain_equal_non_id(
|
2021-08-18 23:59:39 -07:00
|
|
|
chip: EccChip<OrchardFixedBases>,
|
2021-07-08 00:06:47 -07:00
|
|
|
mut layouter: impl Layouter<pallas::Base>,
|
|
|
|
base_val: pallas::Affine,
|
|
|
|
scalar_val: pallas::Scalar,
|
2021-08-18 23:59:39 -07:00
|
|
|
result: Point<pallas::Affine, EccChip<OrchardFixedBases>>,
|
2021-07-08 00:06:47 -07:00
|
|
|
) -> Result<(), Error> {
|
2021-09-27 03:14:01 -07:00
|
|
|
let expected = NonIdentityPoint::new(
|
2021-07-08 00:06:47 -07:00
|
|
|
chip,
|
|
|
|
layouter.namespace(|| "expected point"),
|
|
|
|
Some((base_val * scalar_val).to_affine()),
|
|
|
|
)?;
|
|
|
|
result.constrain_equal(layouter.namespace(|| "constrain result"), &expected)
|
|
|
|
}
|
|
|
|
|
2021-06-04 23:34:44 -07:00
|
|
|
// [a]B
|
|
|
|
{
|
|
|
|
let scalar_fixed = pallas::Scalar::rand();
|
|
|
|
|
2021-07-09 22:19:42 -07:00
|
|
|
let (result, _) = base.mul(layouter.namespace(|| "random [a]B"), Some(scalar_fixed))?;
|
2021-09-27 03:14:01 -07:00
|
|
|
constrain_equal_non_id(
|
2021-06-04 23:34:44 -07:00
|
|
|
chip.clone(),
|
2021-07-08 00:06:47 -07:00
|
|
|
layouter.namespace(|| "random [a]B"),
|
|
|
|
base_val,
|
|
|
|
scalar_fixed,
|
|
|
|
result,
|
2021-06-04 23:34:44 -07:00
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// There is a single canonical sequence of window values for which a doubling occurs on the last step:
|
|
|
|
// 1333333333333333333333333333333333333333333333333333333333333333333333333333333333334 in octal.
|
|
|
|
// (There is another *non-canonical* sequence
|
|
|
|
// 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.)
|
|
|
|
{
|
2021-08-19 21:21:46 -07:00
|
|
|
let h = pallas::Scalar::from_u64(H as u64);
|
2021-06-04 23:34:44 -07:00
|
|
|
let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334"
|
|
|
|
.chars()
|
|
|
|
.fold(pallas::Scalar::zero(), |acc, c| {
|
|
|
|
acc * &h + &pallas::Scalar::from_u64(c.to_digit(8).unwrap().into())
|
|
|
|
});
|
2021-07-09 22:19:42 -07:00
|
|
|
let (result, _) =
|
|
|
|
base.mul(layouter.namespace(|| "mul with double"), Some(scalar_fixed))?;
|
2021-07-08 00:06:47 -07:00
|
|
|
|
2021-09-27 03:14:01 -07:00
|
|
|
constrain_equal_non_id(
|
2021-06-04 23:34:44 -07:00
|
|
|
chip.clone(),
|
2021-07-08 00:06:47 -07:00
|
|
|
layouter.namespace(|| "mul with double"),
|
|
|
|
base_val,
|
|
|
|
scalar_fixed,
|
|
|
|
result,
|
2021-06-04 23:34:44 -07:00
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
|
|
|
// [0]B should return (0,0) since it uses complete addition
|
|
|
|
// on the last step.
|
|
|
|
{
|
|
|
|
let scalar_fixed = pallas::Scalar::zero();
|
2021-07-09 22:19:42 -07:00
|
|
|
let (result, _) = base.mul(layouter.namespace(|| "mul by zero"), Some(scalar_fixed))?;
|
2021-09-28 09:39:41 -07:00
|
|
|
assert!(result.inner().is_identity().unwrap());
|
2021-06-04 23:34:44 -07:00
|
|
|
}
|
|
|
|
|
2021-06-18 02:41:13 -07:00
|
|
|
// [-1]B is the largest scalar field element.
|
|
|
|
{
|
|
|
|
let scalar_fixed = -pallas::Scalar::one();
|
2021-07-09 22:19:42 -07:00
|
|
|
let (result, _) = base.mul(layouter.namespace(|| "mul by -1"), Some(scalar_fixed))?;
|
2021-09-27 03:14:01 -07:00
|
|
|
constrain_equal_non_id(
|
2021-06-18 02:41:13 -07:00
|
|
|
chip,
|
2021-07-08 00:06:47 -07:00
|
|
|
layouter.namespace(|| "mul by -1"),
|
|
|
|
base_val,
|
|
|
|
scalar_fixed,
|
|
|
|
result,
|
2021-06-18 02:41:13 -07:00
|
|
|
)?;
|
|
|
|
}
|
|
|
|
|
2021-06-04 23:34:44 -07:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
}
|