mirror of https://github.com/zcash/halo2.git
chip::mul_fixed.rs: Implement fixed-base scalar mul instruction.
Fixed-base scalar mul makes use of the add_incomplete and add instructions internally. The full-width and short signed share some common logic, which is captured in chip::mul_fixed.rs. The signed short variant introduces additional logic to handle the scalar's sign. This is done in the submodule mul_fixed::short.
This commit is contained in:
parent
a263774abf
commit
ae25310385
|
@ -13,7 +13,7 @@ use pasta_curves::{arithmetic::CurveAffine, pallas};
|
||||||
pub(super) mod add;
|
pub(super) mod add;
|
||||||
pub(super) mod add_incomplete;
|
pub(super) mod add_incomplete;
|
||||||
pub(super) mod mul;
|
pub(super) mod mul;
|
||||||
// pub(super) mod mul_fixed;
|
pub(super) mod mul_fixed;
|
||||||
pub(super) mod witness_point;
|
pub(super) mod witness_point;
|
||||||
pub(super) mod witness_scalar_fixed;
|
pub(super) mod witness_scalar_fixed;
|
||||||
|
|
||||||
|
@ -199,6 +199,20 @@ impl EccChip {
|
||||||
config.create_gate(meta);
|
config.create_gate(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Create fixed-base scalar mul gate that os used in both full-width
|
||||||
|
// and short multiplication.
|
||||||
|
{
|
||||||
|
let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into();
|
||||||
|
mul_fixed_config.create_gate(meta);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create gate that is only used in short fixed-base scalar mul.
|
||||||
|
{
|
||||||
|
let short_config: mul_fixed::short::Config<{ constants::NUM_WINDOWS_SHORT }> =
|
||||||
|
(&config).into();
|
||||||
|
short_config.create_gate(meta);
|
||||||
|
}
|
||||||
|
|
||||||
config
|
config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -354,19 +368,29 @@ impl EccInstructions<pallas::Affine> for EccChip {
|
||||||
|
|
||||||
fn mul_fixed(
|
fn mul_fixed(
|
||||||
&self,
|
&self,
|
||||||
_layouter: &mut impl Layouter<pallas::Base>,
|
layouter: &mut impl Layouter<pallas::Base>,
|
||||||
_scalar: &Self::ScalarFixed,
|
scalar: &Self::ScalarFixed,
|
||||||
_base: &Self::FixedPoints,
|
base: &Self::FixedPoints,
|
||||||
) -> Result<Self::Point, Error> {
|
) -> Result<Self::Point, Error> {
|
||||||
todo!()
|
let config: mul_fixed::full_width::Config<{ constants::NUM_WINDOWS }> =
|
||||||
|
self.config().into();
|
||||||
|
layouter.assign_region(
|
||||||
|
|| format!("fixed-base mul of {:?}", base),
|
||||||
|
|mut region| config.assign_region(scalar, *base, 0, &mut region),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn mul_fixed_short(
|
fn mul_fixed_short(
|
||||||
&self,
|
&self,
|
||||||
_layouter: &mut impl Layouter<pallas::Base>,
|
layouter: &mut impl Layouter<pallas::Base>,
|
||||||
_scalar: &Self::ScalarFixedShort,
|
scalar: &Self::ScalarFixedShort,
|
||||||
_base: &Self::FixedPointsShort,
|
base: &Self::FixedPointsShort,
|
||||||
) -> Result<Self::Point, Error> {
|
) -> Result<Self::Point, Error> {
|
||||||
todo!()
|
let config: mul_fixed::short::Config<{ constants::NUM_WINDOWS_SHORT }> =
|
||||||
|
self.config().into();
|
||||||
|
layouter.assign_region(
|
||||||
|
|| format!("short fixed-base mul of {:?}", base),
|
||||||
|
|mut region| config.assign_region(scalar, base, 0, &mut region),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,529 @@
|
||||||
|
use std::array;
|
||||||
|
|
||||||
|
use super::{
|
||||||
|
add, add_incomplete, copy, CellValue, EccConfig, EccPoint, EccScalarFixed, EccScalarFixedShort,
|
||||||
|
Var,
|
||||||
|
};
|
||||||
|
use crate::constants::{
|
||||||
|
self,
|
||||||
|
load::{OrchardFixedBase, OrchardFixedBasesFull, ValueCommitV, WindowUs},
|
||||||
|
};
|
||||||
|
|
||||||
|
use group::Curve;
|
||||||
|
use halo2::{
|
||||||
|
circuit::Region,
|
||||||
|
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector},
|
||||||
|
poly::Rotation,
|
||||||
|
};
|
||||||
|
use pasta_curves::{
|
||||||
|
arithmetic::{CurveAffine, FieldExt},
|
||||||
|
pallas,
|
||||||
|
};
|
||||||
|
|
||||||
|
pub mod full_width;
|
||||||
|
pub mod short;
|
||||||
|
|
||||||
|
// A sum type for both full-width and short bases. This enables us to use the
|
||||||
|
// shared functionality of full-width and short fixed-base scalar multiplication.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
enum OrchardFixedBases {
|
||||||
|
Full(OrchardFixedBasesFull),
|
||||||
|
ValueCommitV,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<OrchardFixedBasesFull> for OrchardFixedBases {
|
||||||
|
fn from(full_width_base: OrchardFixedBasesFull) -> Self {
|
||||||
|
Self::Full(full_width_base)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ValueCommitV> for OrchardFixedBases {
|
||||||
|
fn from(_value_commit_v: ValueCommitV) -> Self {
|
||||||
|
Self::ValueCommitV
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl OrchardFixedBases {
|
||||||
|
pub fn generator(self) -> pallas::Affine {
|
||||||
|
match self {
|
||||||
|
Self::ValueCommitV => constants::value_commit_v::generator(),
|
||||||
|
Self::Full(base) => {
|
||||||
|
let base: OrchardFixedBase = base.into();
|
||||||
|
base.generator
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn u(self) -> Vec<WindowUs> {
|
||||||
|
match self {
|
||||||
|
Self::ValueCommitV => ValueCommitV::get().u_short.0.as_ref().to_vec(),
|
||||||
|
Self::Full(base) => {
|
||||||
|
let base: OrchardFixedBase = base.into();
|
||||||
|
base.u.0.as_ref().to_vec()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct Config<const NUM_WINDOWS: usize> {
|
||||||
|
// Selector used in both short and full-width fixed-base scalar mul.
|
||||||
|
q_mul_fixed: Selector,
|
||||||
|
|
||||||
|
// The fixed Lagrange interpolation coefficients for `x_p`.
|
||||||
|
lagrange_coeffs: [Column<Fixed>; constants::H],
|
||||||
|
// The fixed `z` for each window such that `y + z = u^2`.
|
||||||
|
fixed_z: Column<Fixed>,
|
||||||
|
// Decomposition of an `n-1`-bit scalar into `k`-bit windows:
|
||||||
|
// a = a_0 + 2^k(a_1) + 2^{2k}(a_2) + ... + 2^{(n-1)k}(a_{n-1})
|
||||||
|
window: Column<Advice>,
|
||||||
|
// x-coordinate of the multiple of the fixed base at the current window.
|
||||||
|
x_p: Column<Advice>,
|
||||||
|
// y-coordinate of the multiple of the fixed base at the current window.
|
||||||
|
y_p: Column<Advice>,
|
||||||
|
// y-coordinate of accumulator (only used in the final row).
|
||||||
|
u: Column<Advice>,
|
||||||
|
// Permutation
|
||||||
|
perm: Permutation,
|
||||||
|
// Configuration for `add`
|
||||||
|
add_config: add::Config,
|
||||||
|
// Configuration for `add_incomplete`
|
||||||
|
add_incomplete_config: add_incomplete::Config,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
|
||||||
|
fn from(ecc_config: &EccConfig) -> Self {
|
||||||
|
let config = Self {
|
||||||
|
q_mul_fixed: ecc_config.q_mul_fixed,
|
||||||
|
lagrange_coeffs: ecc_config.lagrange_coeffs,
|
||||||
|
fixed_z: ecc_config.fixed_z,
|
||||||
|
x_p: ecc_config.advices[0],
|
||||||
|
y_p: ecc_config.advices[1],
|
||||||
|
window: ecc_config.advices[4],
|
||||||
|
u: ecc_config.advices[5],
|
||||||
|
perm: ecc_config.perm.clone(),
|
||||||
|
add_config: ecc_config.into(),
|
||||||
|
add_incomplete_config: ecc_config.into(),
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check relationships between this config and `add_config`.
|
||||||
|
assert_eq!(
|
||||||
|
config.x_p, config.add_config.x_p,
|
||||||
|
"add is used internally in mul_fixed."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.y_p, config.add_config.y_p,
|
||||||
|
"add is used internally in mul_fixed."
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check relationships between this config and `add_incomplete_config`.
|
||||||
|
assert_eq!(
|
||||||
|
config.x_p, config.add_incomplete_config.x_p,
|
||||||
|
"add_incomplete is used internally in mul_fixed."
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
config.y_p, config.add_incomplete_config.y_p,
|
||||||
|
"add_incomplete is used internally in mul_fixed."
|
||||||
|
);
|
||||||
|
for advice in [config.x_p, config.y_p, config.window, config.u].iter() {
|
||||||
|
assert_ne!(
|
||||||
|
*advice, config.add_config.x_qr,
|
||||||
|
"Do not overlap with output columns of add."
|
||||||
|
);
|
||||||
|
assert_ne!(
|
||||||
|
*advice, config.add_config.y_qr,
|
||||||
|
"Do not overlap with output columns of add."
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
config
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
|
||||||
|
#[allow(clippy::op_ref)]
|
||||||
|
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
||||||
|
meta.create_gate("Fixed-base scalar mul gate", |meta| {
|
||||||
|
let q_mul_fixed = meta.query_selector(self.q_mul_fixed);
|
||||||
|
let y_p = meta.query_advice(self.y_p, Rotation::cur());
|
||||||
|
|
||||||
|
let window = meta.query_advice(self.window, Rotation::cur());
|
||||||
|
let x_p = meta.query_advice(self.x_p, Rotation::cur());
|
||||||
|
|
||||||
|
let z = meta.query_fixed(self.fixed_z, Rotation::cur());
|
||||||
|
let u = meta.query_advice(self.u, Rotation::cur());
|
||||||
|
|
||||||
|
let window_pow: Vec<Expression<pallas::Base>> = (0..constants::H)
|
||||||
|
.map(|pow| {
|
||||||
|
(0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| {
|
||||||
|
acc * window.clone()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
|
||||||
|
let interpolated_x = window_pow.iter().zip(self.lagrange_coeffs.iter()).fold(
|
||||||
|
Expression::Constant(pallas::Base::zero()),
|
||||||
|
|acc, (window_pow, coeff)| {
|
||||||
|
acc + (window_pow.clone() * meta.query_fixed(*coeff, Rotation::cur()))
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
// Check interpolation of x-coordinate
|
||||||
|
let x_check = interpolated_x - x_p;
|
||||||
|
// Check that `y + z = u^2`, where `z` is fixed and `u`, `y` are witnessed
|
||||||
|
let y_check = u.clone() * u - y_p - z;
|
||||||
|
|
||||||
|
array::IntoIter::new([x_check, y_check]).map(move |poly| q_mul_fixed.clone() * poly)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::type_complexity)]
|
||||||
|
fn assign_region_inner(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
offset: usize,
|
||||||
|
scalar: &ScalarFixed,
|
||||||
|
base: OrchardFixedBases,
|
||||||
|
) -> Result<(EccPoint, EccPoint), Error> {
|
||||||
|
// Assign fixed columns for given fixed base
|
||||||
|
self.assign_fixed_constants(region, offset, base)?;
|
||||||
|
|
||||||
|
// Copy the scalar decomposition
|
||||||
|
self.copy_scalar(region, offset, scalar)?;
|
||||||
|
|
||||||
|
// Initialize accumulator
|
||||||
|
let acc = self.initialize_accumulator(region, offset, base, scalar)?;
|
||||||
|
|
||||||
|
// Process all windows excluding least and most significant windows
|
||||||
|
let acc = self.add_incomplete(region, offset, acc, base, scalar)?;
|
||||||
|
|
||||||
|
// Process most significant window using complete addition
|
||||||
|
let mul_b = self.process_msb(region, offset, base, scalar)?;
|
||||||
|
|
||||||
|
Ok((acc, mul_b))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn assign_fixed_constants(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
offset: usize,
|
||||||
|
base: OrchardFixedBases,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
let (lagrange_coeffs, z) = match base {
|
||||||
|
OrchardFixedBases::ValueCommitV => {
|
||||||
|
assert_eq!(NUM_WINDOWS, constants::NUM_WINDOWS_SHORT);
|
||||||
|
let base = ValueCommitV::get();
|
||||||
|
(
|
||||||
|
base.lagrange_coeffs_short.0.as_ref().to_vec(),
|
||||||
|
base.z_short.0.as_ref().to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
OrchardFixedBases::Full(base) => {
|
||||||
|
assert_eq!(NUM_WINDOWS, constants::NUM_WINDOWS);
|
||||||
|
let base: OrchardFixedBase = base.into();
|
||||||
|
(
|
||||||
|
base.lagrange_coeffs.0.as_ref().to_vec(),
|
||||||
|
base.z.0.as_ref().to_vec(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign fixed columns for given fixed base
|
||||||
|
for window in 0..NUM_WINDOWS {
|
||||||
|
// Enable `q_mul_fixed` selector
|
||||||
|
self.q_mul_fixed.enable(region, window + offset)?;
|
||||||
|
|
||||||
|
// Assign x-coordinate Lagrange interpolation coefficients
|
||||||
|
for k in 0..(constants::H) {
|
||||||
|
region.assign_fixed(
|
||||||
|
|| {
|
||||||
|
format!(
|
||||||
|
"Lagrange interpolation coeff for window: {:?}, k: {:?}",
|
||||||
|
window, k
|
||||||
|
)
|
||||||
|
},
|
||||||
|
self.lagrange_coeffs[k],
|
||||||
|
window + offset,
|
||||||
|
|| Ok(lagrange_coeffs[window].0[k]),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Assign z-values for each window
|
||||||
|
region.assign_fixed(
|
||||||
|
|| format!("z-value for window: {:?}", window),
|
||||||
|
self.fixed_z,
|
||||||
|
window + offset,
|
||||||
|
|| Ok(z[window]),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn copy_scalar(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
offset: usize,
|
||||||
|
scalar: &ScalarFixed,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// Copy the scalar decomposition (`k`-bit windows)
|
||||||
|
for (window_idx, window) in scalar.windows().iter().enumerate() {
|
||||||
|
copy(
|
||||||
|
region,
|
||||||
|
|| format!("k[{:?}]", window),
|
||||||
|
self.window,
|
||||||
|
window_idx + offset,
|
||||||
|
window,
|
||||||
|
&self.perm,
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn initialize_accumulator(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
offset: usize,
|
||||||
|
base: OrchardFixedBases,
|
||||||
|
scalar: &ScalarFixed,
|
||||||
|
) -> Result<EccPoint, Error> {
|
||||||
|
// Recall that the message at each window `w` is represented as
|
||||||
|
// `m_w = [(k_w + 2) ⋅ 8^w]B`.
|
||||||
|
// When `w = 0`, we have `m_0 = [(k_0 + 2)]B`.
|
||||||
|
let m0 = {
|
||||||
|
let k0 = scalar.windows_field()[0];
|
||||||
|
let m0 = k0.map(|k0| base.generator() * (k0 + pallas::Scalar::from_u64(2)));
|
||||||
|
let m0 = m0.map(|m0| m0.to_affine().coordinates().unwrap());
|
||||||
|
|
||||||
|
let x = m0.map(|m0| *m0.x());
|
||||||
|
let x_cell = region.assign_advice(
|
||||||
|
|| "m0_x",
|
||||||
|
self.x_p,
|
||||||
|
offset,
|
||||||
|
|| x.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
let x = CellValue::new(x_cell, x);
|
||||||
|
|
||||||
|
let y = m0.map(|m0| *m0.y());
|
||||||
|
let y_cell = region.assign_advice(
|
||||||
|
|| "m0_y",
|
||||||
|
self.y_p,
|
||||||
|
offset,
|
||||||
|
|| y.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
let y = CellValue::new(y_cell, y);
|
||||||
|
|
||||||
|
EccPoint { x, y }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign u = (y_p + z_w).sqrt() for `m0`
|
||||||
|
{
|
||||||
|
let k0 = scalar.windows_usize()[0];
|
||||||
|
let u0 = &base.u()[0];
|
||||||
|
let u0 = k0.map(|k0| u0.0[k0]);
|
||||||
|
|
||||||
|
region.assign_advice(|| "u", self.u, offset, || u0.ok_or(Error::SynthesisError))?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy `m0` into `x_qr`, `y_qr` cells on row 1 of the incomplete addition.
|
||||||
|
let x = copy(
|
||||||
|
region,
|
||||||
|
|| "initialize acc x",
|
||||||
|
self.add_incomplete_config.x_qr,
|
||||||
|
offset + 1,
|
||||||
|
&m0.x,
|
||||||
|
&self.perm,
|
||||||
|
)?;
|
||||||
|
let y = copy(
|
||||||
|
region,
|
||||||
|
|| "initialize acc y",
|
||||||
|
self.add_incomplete_config.y_qr,
|
||||||
|
offset + 1,
|
||||||
|
&m0.y,
|
||||||
|
&self.perm,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(EccPoint { x, y })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_incomplete(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
offset: usize,
|
||||||
|
mut acc: EccPoint,
|
||||||
|
base: OrchardFixedBases,
|
||||||
|
scalar: &ScalarFixed,
|
||||||
|
) -> Result<EccPoint, Error> {
|
||||||
|
// This is 2^w, where w is the window width
|
||||||
|
let h = pallas::Scalar::from_u64(constants::H as u64);
|
||||||
|
|
||||||
|
let base_value = base.generator();
|
||||||
|
let base_u = base.u();
|
||||||
|
let scalar_windows_field = scalar.windows_field();
|
||||||
|
let scalar_windows_usize = scalar.windows_usize();
|
||||||
|
|
||||||
|
for (w, k) in scalar_windows_field[1..(scalar_windows_field.len() - 1)]
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
{
|
||||||
|
// Offset window index by 1 since we are starting on k_1
|
||||||
|
let w = w + 1;
|
||||||
|
|
||||||
|
// Compute [(k_w + 2) ⋅ 8^w]B
|
||||||
|
let mul_b = {
|
||||||
|
let mul_b = k.map(|k| {
|
||||||
|
base_value * (k + pallas::Scalar::from_u64(2)) * h.pow(&[w as u64, 0, 0, 0])
|
||||||
|
});
|
||||||
|
let mul_b = mul_b.map(|mul_b| mul_b.to_affine().coordinates().unwrap());
|
||||||
|
|
||||||
|
let x = mul_b.map(|mul_b| *mul_b.x());
|
||||||
|
let x_cell = region.assign_advice(
|
||||||
|
|| format!("mul_b_x, window {}", w),
|
||||||
|
self.x_p,
|
||||||
|
offset + w,
|
||||||
|
|| x.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
let x = CellValue::new(x_cell, x);
|
||||||
|
|
||||||
|
let y = mul_b.map(|mul_b| *mul_b.y());
|
||||||
|
let y_cell = region.assign_advice(
|
||||||
|
|| format!("mul_b_y, window {}", w),
|
||||||
|
self.y_p,
|
||||||
|
offset + w,
|
||||||
|
|| y.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
let y = CellValue::new(y_cell, y);
|
||||||
|
|
||||||
|
EccPoint { x, y }
|
||||||
|
};
|
||||||
|
|
||||||
|
// Assign u = (y_p + z_w).sqrt()
|
||||||
|
let u_val = scalar_windows_usize[w].map(|k| base_u[w].0[k]);
|
||||||
|
region.assign_advice(
|
||||||
|
|| "u",
|
||||||
|
self.u,
|
||||||
|
offset + w,
|
||||||
|
|| u_val.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Add to the accumulator
|
||||||
|
acc = self
|
||||||
|
.add_incomplete_config
|
||||||
|
.assign_region(&mul_b, &acc, offset + w, region)?;
|
||||||
|
}
|
||||||
|
Ok(acc)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_msb(
|
||||||
|
&self,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
offset: usize,
|
||||||
|
base: OrchardFixedBases,
|
||||||
|
scalar: &ScalarFixed,
|
||||||
|
) -> Result<EccPoint, Error> {
|
||||||
|
// This is 2^w, where w is the window width
|
||||||
|
let h = pallas::Scalar::from_u64(constants::H as u64);
|
||||||
|
|
||||||
|
// Assign u = (y_p + z_w).sqrt() for the most significant window
|
||||||
|
{
|
||||||
|
let u_val =
|
||||||
|
scalar.windows_usize()[NUM_WINDOWS - 1].map(|k| base.u()[NUM_WINDOWS - 1].0[k]);
|
||||||
|
region.assign_advice(
|
||||||
|
|| "u",
|
||||||
|
self.u,
|
||||||
|
offset + NUM_WINDOWS - 1,
|
||||||
|
|| u_val.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// offset_acc = \sum_{j = 0}^{NUM_WINDOWS - 2} 2^{FIXED_BASE_WINDOW_SIZE * j+1}
|
||||||
|
let offset_acc = (0..(NUM_WINDOWS - 1)).fold(pallas::Scalar::zero(), |acc, w| {
|
||||||
|
acc + pallas::Scalar::from_u64(2).pow(&[
|
||||||
|
constants::FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
0,
|
||||||
|
])
|
||||||
|
});
|
||||||
|
|
||||||
|
// `scalar = [k * 8^84 - offset_acc]`, where `offset_acc = \sum_{j = 0}^{83} 2^{FIXED_BASE_WINDOW_SIZE * j + 1}`.
|
||||||
|
let scalar = scalar.windows_field()[scalar.windows_field().len() - 1]
|
||||||
|
.map(|k| k * h.pow(&[(NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc);
|
||||||
|
|
||||||
|
let mul_b = {
|
||||||
|
let mul_b = scalar.map(|scalar| base.generator() * scalar);
|
||||||
|
let mul_b = mul_b.map(|mul_b| mul_b.to_affine().coordinates().unwrap());
|
||||||
|
|
||||||
|
let x = mul_b.map(|mul_b| *mul_b.x());
|
||||||
|
let x_cell = region.assign_advice(
|
||||||
|
|| format!("mul_b_x, window {}", NUM_WINDOWS - 1),
|
||||||
|
self.x_p,
|
||||||
|
offset + NUM_WINDOWS - 1,
|
||||||
|
|| x.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
let x = CellValue::new(x_cell, x);
|
||||||
|
|
||||||
|
let y = mul_b.map(|mul_b| *mul_b.y());
|
||||||
|
let y_cell = region.assign_advice(
|
||||||
|
|| format!("mul_b_y, window {}", NUM_WINDOWS - 1),
|
||||||
|
self.y_p,
|
||||||
|
offset + NUM_WINDOWS - 1,
|
||||||
|
|| y.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
let y = CellValue::new(y_cell, y);
|
||||||
|
|
||||||
|
EccPoint { x, y }
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(mul_b)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum ScalarFixed {
|
||||||
|
FullWidth(EccScalarFixed),
|
||||||
|
Short(EccScalarFixedShort),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&EccScalarFixed> for ScalarFixed {
|
||||||
|
fn from(scalar_fixed: &EccScalarFixed) -> Self {
|
||||||
|
Self::FullWidth(scalar_fixed.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&EccScalarFixedShort> for ScalarFixed {
|
||||||
|
fn from(scalar_fixed: &EccScalarFixedShort) -> Self {
|
||||||
|
Self::Short(scalar_fixed.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ScalarFixed {
|
||||||
|
fn windows(&self) -> &[CellValue<pallas::Base>] {
|
||||||
|
match self {
|
||||||
|
ScalarFixed::FullWidth(scalar) => &scalar.windows,
|
||||||
|
ScalarFixed::Short(scalar) => &scalar.windows,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The scalar decomposition was done in the base field. For computation
|
||||||
|
// outside the circuit, we now convert them back into the scalar field.
|
||||||
|
fn windows_field(&self) -> Vec<Option<pallas::Scalar>> {
|
||||||
|
self.windows()
|
||||||
|
.iter()
|
||||||
|
.map(|bits| {
|
||||||
|
bits.value()
|
||||||
|
.map(|value| pallas::Scalar::from_bytes(&value.to_bytes()).unwrap())
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
|
||||||
|
// The scalar decomposition is guaranteed to be in three-bit windows,
|
||||||
|
// so we also cast the least significant byte in their serialisation
|
||||||
|
// into usize for convenient indexing into `u`-values
|
||||||
|
fn windows_usize(&self) -> Vec<Option<usize>> {
|
||||||
|
self.windows()
|
||||||
|
.iter()
|
||||||
|
.map(|bits| bits.value().map(|value| value.to_bytes()[0] as usize))
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,171 @@
|
||||||
|
use super::super::{EccConfig, EccPoint, EccScalarFixed, OrchardFixedBasesFull};
|
||||||
|
|
||||||
|
use halo2::{circuit::Region, plonk::Error};
|
||||||
|
use pasta_curves::pallas;
|
||||||
|
|
||||||
|
pub struct Config<const NUM_WINDOWS: usize>(super::Config<NUM_WINDOWS>);
|
||||||
|
|
||||||
|
impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
|
||||||
|
fn from(config: &EccConfig) -> Self {
|
||||||
|
Self(config.into())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
|
||||||
|
pub fn assign_region(
|
||||||
|
&self,
|
||||||
|
scalar: &EccScalarFixed,
|
||||||
|
base: OrchardFixedBasesFull,
|
||||||
|
offset: usize,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
) -> Result<EccPoint, Error> {
|
||||||
|
let (acc, mul_b) =
|
||||||
|
self.0
|
||||||
|
.assign_region_inner(region, offset, &scalar.into(), base.into())?;
|
||||||
|
|
||||||
|
// Add to the accumulator and return the final result as `[scalar]B`.
|
||||||
|
let result = self
|
||||||
|
.0
|
||||||
|
.add_config
|
||||||
|
.assign_region(&mul_b, &acc, offset + NUM_WINDOWS, region)?;
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
// Check that the correct multiple is obtained.
|
||||||
|
{
|
||||||
|
use group::Curve;
|
||||||
|
use halo2::arithmetic::FieldExt;
|
||||||
|
|
||||||
|
let base: super::OrchardFixedBases = base.into();
|
||||||
|
let scalar = scalar
|
||||||
|
.value
|
||||||
|
.map(|scalar| pallas::Scalar::from_bytes(&scalar.to_bytes()).unwrap());
|
||||||
|
let real_mul = scalar.map(|scalar| base.generator() * scalar);
|
||||||
|
let result = result.point();
|
||||||
|
|
||||||
|
if let (Some(real_mul), Some(result)) = (real_mul, result) {
|
||||||
|
assert_eq!(real_mul.to_affine(), result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use ff::PrimeFieldBits;
|
||||||
|
use halo2::{circuit::Layouter, plonk::Error};
|
||||||
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||||
|
|
||||||
|
use crate::circuit::gadget::ecc::{
|
||||||
|
chip::{EccChip, OrchardFixedBasesFull},
|
||||||
|
FixedPoint, ScalarFixed,
|
||||||
|
};
|
||||||
|
use crate::constants;
|
||||||
|
|
||||||
|
pub fn test_mul_fixed(
|
||||||
|
chip: EccChip,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// commit_ivk_r
|
||||||
|
let commit_ivk_r = OrchardFixedBasesFull::CommitIvkR;
|
||||||
|
let commit_ivk_r = FixedPoint::from_inner(chip.clone(), commit_ivk_r);
|
||||||
|
test_single_base(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "commit_ivk_r"),
|
||||||
|
commit_ivk_r,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// note_commit_r
|
||||||
|
let note_commit_r = OrchardFixedBasesFull::NoteCommitR;
|
||||||
|
let note_commit_r = FixedPoint::from_inner(chip.clone(), note_commit_r);
|
||||||
|
test_single_base(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "note_commit_r"),
|
||||||
|
note_commit_r,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// nullifier_k
|
||||||
|
let nullifier_k = OrchardFixedBasesFull::NullifierK;
|
||||||
|
let nullifier_k = FixedPoint::from_inner(chip.clone(), nullifier_k);
|
||||||
|
test_single_base(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "nullifier_k"),
|
||||||
|
nullifier_k,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// value_commit_r
|
||||||
|
let value_commit_r = OrchardFixedBasesFull::ValueCommitR;
|
||||||
|
let value_commit_r = FixedPoint::from_inner(chip.clone(), value_commit_r);
|
||||||
|
test_single_base(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "value_commit_r"),
|
||||||
|
value_commit_r,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// spend_auth_g
|
||||||
|
let spend_auth_g = OrchardFixedBasesFull::SpendAuthG;
|
||||||
|
let spend_auth_g = FixedPoint::from_inner(chip.clone(), spend_auth_g);
|
||||||
|
test_single_base(chip, layouter.namespace(|| "spend_auth_g"), spend_auth_g)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(clippy::op_ref)]
|
||||||
|
fn test_single_base(
|
||||||
|
chip: EccChip,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
base: FixedPoint<pallas::Affine, EccChip>,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
pallas::Scalar: PrimeFieldBits,
|
||||||
|
{
|
||||||
|
// [a]B
|
||||||
|
{
|
||||||
|
let scalar_fixed = pallas::Scalar::rand();
|
||||||
|
|
||||||
|
let scalar_fixed = ScalarFixed::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "ScalarFixed"),
|
||||||
|
Some(scalar_fixed),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
base.mul(layouter.namespace(|| "mul"), &scalar_fixed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.)
|
||||||
|
{
|
||||||
|
let h = pallas::Scalar::from_u64(constants::H as u64);
|
||||||
|
let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334"
|
||||||
|
.chars()
|
||||||
|
.fold(pallas::Scalar::zero(), |acc, c| {
|
||||||
|
acc * &h + &pallas::Scalar::from_u64(c.to_digit(8).unwrap().into())
|
||||||
|
});
|
||||||
|
|
||||||
|
let scalar_fixed = ScalarFixed::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "ScalarFixed"),
|
||||||
|
Some(scalar_fixed),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
base.mul(layouter.namespace(|| "mul with double"), &scalar_fixed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [0]B should return (0,0) since it uses complete addition
|
||||||
|
// on the last step.
|
||||||
|
{
|
||||||
|
let scalar_fixed = pallas::Scalar::zero();
|
||||||
|
let scalar_fixed = ScalarFixed::new(
|
||||||
|
chip,
|
||||||
|
layouter.namespace(|| "ScalarFixed"),
|
||||||
|
Some(scalar_fixed),
|
||||||
|
)?;
|
||||||
|
base.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,246 @@
|
||||||
|
use std::array;
|
||||||
|
|
||||||
|
use super::super::{copy, CellValue, EccConfig, EccPoint, EccScalarFixedShort, Var};
|
||||||
|
use crate::constants::ValueCommitV;
|
||||||
|
|
||||||
|
use halo2::{
|
||||||
|
circuit::Region,
|
||||||
|
plonk::{ConstraintSystem, Error, Selector},
|
||||||
|
poly::Rotation,
|
||||||
|
};
|
||||||
|
use pasta_curves::pallas;
|
||||||
|
|
||||||
|
pub struct Config<const NUM_WINDOWS: usize> {
|
||||||
|
// Selector used for fixed-base scalar mul with short signed exponent.
|
||||||
|
q_mul_fixed_short: Selector,
|
||||||
|
super_config: super::Config<NUM_WINDOWS>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const NUM_WINDOWS: usize> From<&EccConfig> for Config<NUM_WINDOWS> {
|
||||||
|
fn from(config: &EccConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
q_mul_fixed_short: config.q_mul_fixed_short,
|
||||||
|
super_config: config.into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
|
||||||
|
// We reuse the constraints in the `mul_fixed` gate so exclude them here.
|
||||||
|
// Here, we add some new constraints specific to the short signed case.
|
||||||
|
pub(crate) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
||||||
|
meta.create_gate("Short fixed-base mul gate", |meta| {
|
||||||
|
let q_mul_fixed_short = meta.query_selector(self.q_mul_fixed_short);
|
||||||
|
let y_p = meta.query_advice(self.super_config.y_p, Rotation::cur());
|
||||||
|
let y_a = meta.query_advice(self.super_config.add_config.y_qr, Rotation::cur());
|
||||||
|
let sign = meta.query_advice(self.super_config.window, Rotation::cur());
|
||||||
|
|
||||||
|
// `(x_a, y_a)` is the result of `[m]B`, where `m` is the magnitude.
|
||||||
|
// We conditionally negate this result using `y_p = y_a * s`, where `s` is the sign.
|
||||||
|
|
||||||
|
// Check that the final `y_p = y_a` or `y_p = -y_a`
|
||||||
|
let y_check = q_mul_fixed_short.clone()
|
||||||
|
* (y_p.clone() - y_a.clone())
|
||||||
|
* (y_p.clone() + y_a.clone());
|
||||||
|
|
||||||
|
// Check that the correct sign is witnessed s.t. sign * y_p = y_a
|
||||||
|
let negation_check = sign * y_p - y_a;
|
||||||
|
|
||||||
|
array::IntoIter::new([y_check, negation_check])
|
||||||
|
.map(move |poly| q_mul_fixed_short.clone() * poly)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn assign_region(
|
||||||
|
&self,
|
||||||
|
scalar: &EccScalarFixedShort,
|
||||||
|
base: &ValueCommitV,
|
||||||
|
offset: usize,
|
||||||
|
region: &mut Region<'_, pallas::Base>,
|
||||||
|
) -> Result<EccPoint, Error> {
|
||||||
|
let (acc, mul_b) = self.super_config.assign_region_inner(
|
||||||
|
region,
|
||||||
|
offset,
|
||||||
|
&scalar.into(),
|
||||||
|
base.clone().into(),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Add to the cumulative sum to get `[magnitude]B`.
|
||||||
|
let magnitude_mul = self.super_config.add_config.assign_region(
|
||||||
|
&mul_b,
|
||||||
|
&acc,
|
||||||
|
offset + NUM_WINDOWS,
|
||||||
|
region,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Increase offset by 1 after complete addition
|
||||||
|
let offset = offset + 1;
|
||||||
|
|
||||||
|
// Assign sign to `window` column
|
||||||
|
let sign = copy(
|
||||||
|
region,
|
||||||
|
|| "sign",
|
||||||
|
self.super_config.window,
|
||||||
|
offset + NUM_WINDOWS,
|
||||||
|
&scalar.sign,
|
||||||
|
&self.super_config.perm,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Conditionally negate `y`-coordinate
|
||||||
|
let y_val = if let Some(sign) = sign.value() {
|
||||||
|
if sign == -pallas::Base::one() {
|
||||||
|
magnitude_mul.y.value().map(|y: pallas::Base| -y)
|
||||||
|
} else {
|
||||||
|
magnitude_mul.y.value()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
// Enable mul_fixed_short selector on final row
|
||||||
|
self.q_mul_fixed_short
|
||||||
|
.enable(region, offset + NUM_WINDOWS)?;
|
||||||
|
|
||||||
|
// Assign final `x, y` to `x_p, y_p` columns and return final point
|
||||||
|
let x_val = magnitude_mul.x.value();
|
||||||
|
let x_var = region.assign_advice(
|
||||||
|
|| "x_var",
|
||||||
|
self.super_config.x_p,
|
||||||
|
offset + NUM_WINDOWS,
|
||||||
|
|| x_val.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
let y_var = region.assign_advice(
|
||||||
|
|| "y_var",
|
||||||
|
self.super_config.y_p,
|
||||||
|
offset + NUM_WINDOWS,
|
||||||
|
|| y_val.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let result = EccPoint {
|
||||||
|
x: CellValue::new(x_var, x_val),
|
||||||
|
y: CellValue::new(y_var, y_val),
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
// Check that the correct multiple is obtained.
|
||||||
|
{
|
||||||
|
use group::Curve;
|
||||||
|
|
||||||
|
let base: super::OrchardFixedBases = base.clone().into();
|
||||||
|
|
||||||
|
let scalar = scalar
|
||||||
|
.magnitude
|
||||||
|
.zip(scalar.sign.value())
|
||||||
|
.map(|(magnitude, sign)| {
|
||||||
|
let sign = if sign == pallas::Base::one() {
|
||||||
|
pallas::Scalar::one()
|
||||||
|
} else if sign == -pallas::Base::one() {
|
||||||
|
-pallas::Scalar::one()
|
||||||
|
} else {
|
||||||
|
panic!("Sign should be 1 or -1.")
|
||||||
|
};
|
||||||
|
magnitude * sign
|
||||||
|
});
|
||||||
|
let real_mul = scalar.map(|scalar| base.generator() * scalar);
|
||||||
|
let result = result.point();
|
||||||
|
|
||||||
|
if let (Some(real_mul), Some(result)) = (real_mul, result) {
|
||||||
|
assert_eq!(real_mul.to_affine(), result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub mod tests {
|
||||||
|
use ff::PrimeFieldBits;
|
||||||
|
use halo2::{circuit::Layouter, plonk::Error};
|
||||||
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||||
|
|
||||||
|
use crate::circuit::gadget::ecc::{chip::EccChip, FixedPointShort, ScalarFixedShort};
|
||||||
|
use crate::constants::load::ValueCommitV;
|
||||||
|
|
||||||
|
#[allow(clippy::op_ref)]
|
||||||
|
pub fn test_mul_fixed_short(
|
||||||
|
chip: EccChip,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
) -> Result<(), Error>
|
||||||
|
where
|
||||||
|
pallas::Scalar: PrimeFieldBits,
|
||||||
|
{
|
||||||
|
// value_commit_v
|
||||||
|
let value_commit_v = ValueCommitV::get();
|
||||||
|
let value_commit_v = FixedPointShort::from_inner(chip.clone(), value_commit_v);
|
||||||
|
|
||||||
|
// [0]B should return (0,0) since it uses complete addition
|
||||||
|
// on the last step.
|
||||||
|
{
|
||||||
|
let scalar_fixed = pallas::Scalar::zero();
|
||||||
|
let scalar_fixed = ScalarFixedShort::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "ScalarFixedShort"),
|
||||||
|
Some(scalar_fixed),
|
||||||
|
)?;
|
||||||
|
value_commit_v.mul(layouter.namespace(|| "mul"), &scalar_fixed)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Random [a]B
|
||||||
|
{
|
||||||
|
let scalar_fixed_short = pallas::Scalar::from_u64(rand::random::<u64>());
|
||||||
|
let mut sign = pallas::Scalar::one();
|
||||||
|
if rand::random::<bool>() {
|
||||||
|
sign = -sign;
|
||||||
|
}
|
||||||
|
let scalar_fixed_short = sign * &scalar_fixed_short;
|
||||||
|
|
||||||
|
let scalar_fixed_short = ScalarFixedShort::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "ScalarFixedShort"),
|
||||||
|
Some(scalar_fixed_short),
|
||||||
|
)?;
|
||||||
|
value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [2^64 - 1]B
|
||||||
|
{
|
||||||
|
let scalar_fixed_short = pallas::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64);
|
||||||
|
|
||||||
|
let scalar_fixed_short = ScalarFixedShort::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "ScalarFixedShort"),
|
||||||
|
Some(scalar_fixed_short),
|
||||||
|
)?;
|
||||||
|
value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// [-(2^64 - 1)]B
|
||||||
|
{
|
||||||
|
let scalar_fixed_short = -pallas::Scalar::from_u64(0xFFFF_FFFF_FFFF_FFFFu64);
|
||||||
|
|
||||||
|
let scalar_fixed_short = ScalarFixedShort::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "ScalarFixedShort"),
|
||||||
|
Some(scalar_fixed_short),
|
||||||
|
)?;
|
||||||
|
value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// There is a single canonical sequence of window values for which a doubling occurs on the last step:
|
||||||
|
// 1333333333333333333334 in octal.
|
||||||
|
// [0xB6DB_6DB6_DB6D_B6DC] B
|
||||||
|
{
|
||||||
|
let scalar_fixed_short = pallas::Scalar::from_u64(0xB6DB_6DB6_DB6D_B6DCu64);
|
||||||
|
|
||||||
|
let scalar_fixed_short = ScalarFixedShort::new(
|
||||||
|
chip,
|
||||||
|
layouter.namespace(|| "ScalarFixedShort"),
|
||||||
|
Some(scalar_fixed_short),
|
||||||
|
)?;
|
||||||
|
value_commit_v.mul(layouter.namespace(|| "mul fixed"), &scalar_fixed_short)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue