mirror of https://github.com/zcash/orchard.git
mul::overflow.rs: Overflow check in variable-base scalar mul
Simplify the canonicity check for variable-base scalar multiplication, by range-checking the low 130 bits rather than the low 127 bits. Signed-off-by: Daira Hopwood <daira@jacaranda.org> Co-authored-by: ying tong <yingtong@z.cash>
This commit is contained in:
parent
a3ca27b756
commit
5ae9890913
|
@ -21,6 +21,7 @@ rustdoc-args = [ "--html-in-header", "katex-header.html" ]
|
||||||
[dependencies]
|
[dependencies]
|
||||||
aes = "0.6"
|
aes = "0.6"
|
||||||
arrayvec = "0.7.0"
|
arrayvec = "0.7.0"
|
||||||
|
bigint = "4"
|
||||||
bitvec = "0.22"
|
bitvec = "0.22"
|
||||||
blake2b_simd = "0.5"
|
blake2b_simd = "0.5"
|
||||||
ff = "0.10"
|
ff = "0.10"
|
||||||
|
|
|
@ -427,6 +427,7 @@ mod tests {
|
||||||
meta.advice_column(),
|
meta.advice_column(),
|
||||||
];
|
];
|
||||||
|
|
||||||
|
let lookup_table = meta.fixed_column();
|
||||||
let perm = meta.permutation(
|
let perm = meta.permutation(
|
||||||
&advices
|
&advices
|
||||||
.iter()
|
.iter()
|
||||||
|
@ -434,7 +435,7 @@ mod tests {
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
);
|
);
|
||||||
|
|
||||||
EccChip::configure(meta, advices, perm)
|
EccChip::configure(meta, advices, lookup_table, perm)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn synthesize(
|
fn synthesize(
|
||||||
|
@ -443,7 +444,11 @@ mod tests {
|
||||||
config: Self::Config,
|
config: Self::Config,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
let mut layouter = SingleChipLayouter::new(cs)?;
|
let mut layouter = SingleChipLayouter::new(cs)?;
|
||||||
let chip = EccChip::construct(config);
|
let chip = EccChip::construct(config.clone());
|
||||||
|
|
||||||
|
// Load 10-bit lookup table. In the Action circuit, this will be
|
||||||
|
// provided by the Sinsemilla chip.
|
||||||
|
config.lookup_config.load(&mut layouter)?;
|
||||||
|
|
||||||
// Generate a random point P
|
// Generate a random point P
|
||||||
let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); // P
|
let p_val = pallas::Point::random(rand::rngs::OsRng).to_affine(); // P
|
||||||
|
|
|
@ -1,6 +1,11 @@
|
||||||
use super::EccInstructions;
|
use super::EccInstructions;
|
||||||
use crate::circuit::gadget::utilities::{copy, CellValue, Var};
|
use crate::{
|
||||||
use crate::constants::{self, OrchardFixedBasesFull, ValueCommitV};
|
circuit::gadget::utilities::{
|
||||||
|
copy, lookup_range_check::LookupRangeCheckConfig, CellValue, Var,
|
||||||
|
},
|
||||||
|
constants::{self, OrchardFixedBasesFull, ValueCommitV},
|
||||||
|
primitives::sinsemilla,
|
||||||
|
};
|
||||||
use arrayvec::ArrayVec;
|
use arrayvec::ArrayVec;
|
||||||
|
|
||||||
use group::prime::PrimeCurveAffine;
|
use group::prime::PrimeCurveAffine;
|
||||||
|
@ -78,8 +83,10 @@ pub struct EccConfig {
|
||||||
|
|
||||||
/// Incomplete addition
|
/// Incomplete addition
|
||||||
pub q_add_incomplete: Selector,
|
pub q_add_incomplete: Selector,
|
||||||
|
|
||||||
/// Complete addition
|
/// Complete addition
|
||||||
pub q_add: Selector,
|
pub q_add: Selector,
|
||||||
|
|
||||||
/// Variable-base scalar multiplication (hi half)
|
/// Variable-base scalar multiplication (hi half)
|
||||||
pub q_mul_hi: Selector,
|
pub q_mul_hi: Selector,
|
||||||
/// Variable-base scalar multiplication (lo half)
|
/// Variable-base scalar multiplication (lo half)
|
||||||
|
@ -89,19 +96,27 @@ pub struct EccConfig {
|
||||||
/// Selector used in scalar decomposition for variable-base scalar mul
|
/// Selector used in scalar decomposition for variable-base scalar mul
|
||||||
pub q_init_z: Selector,
|
pub q_init_z: Selector,
|
||||||
/// Variable-base scalar multiplication (final scalar)
|
/// Variable-base scalar multiplication (final scalar)
|
||||||
pub q_mul_complete: Selector,
|
pub q_mul_z: Selector,
|
||||||
|
/// Variable-base scalar multiplication (overflow check)
|
||||||
|
pub q_mul_overflow: Selector,
|
||||||
|
|
||||||
/// Fixed-base full-width scalar multiplication
|
/// Fixed-base full-width scalar multiplication
|
||||||
pub q_mul_fixed: Selector,
|
pub q_mul_fixed: Selector,
|
||||||
|
|
||||||
/// Fixed-base signed short scalar multiplication
|
/// Fixed-base signed short scalar multiplication
|
||||||
pub q_mul_fixed_short: Selector,
|
pub q_mul_fixed_short: Selector,
|
||||||
|
|
||||||
/// Witness point
|
/// Witness point
|
||||||
pub q_point: Selector,
|
pub q_point: Selector,
|
||||||
/// Witness full-width scalar for fixed-base scalar mul
|
/// Witness full-width scalar for fixed-base scalar mul
|
||||||
pub q_scalar_fixed: Selector,
|
pub q_scalar_fixed: Selector,
|
||||||
/// Witness signed short scalar for full-width fixed-base scalar mul
|
/// Witness signed short scalar for full-width fixed-base scalar mul
|
||||||
pub q_scalar_fixed_short: Selector,
|
pub q_scalar_fixed_short: Selector,
|
||||||
|
|
||||||
/// Permutation
|
/// Permutation
|
||||||
pub perm: Permutation,
|
pub perm: Permutation,
|
||||||
|
/// 10-bit lookup
|
||||||
|
pub lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A chip implementing EccInstructions
|
/// A chip implementing EccInstructions
|
||||||
|
@ -132,8 +147,12 @@ impl EccChip {
|
||||||
pub fn configure(
|
pub fn configure(
|
||||||
meta: &mut ConstraintSystem<pallas::Base>,
|
meta: &mut ConstraintSystem<pallas::Base>,
|
||||||
advices: [Column<Advice>; 10],
|
advices: [Column<Advice>; 10],
|
||||||
|
lookup_table: Column<Fixed>,
|
||||||
perm: Permutation,
|
perm: Permutation,
|
||||||
) -> <Self as Chip<pallas::Base>>::Config {
|
) -> <Self as Chip<pallas::Base>>::Config {
|
||||||
|
let lookup_config =
|
||||||
|
LookupRangeCheckConfig::configure(meta, advices[9], lookup_table, perm.clone());
|
||||||
|
|
||||||
let config = EccConfig {
|
let config = EccConfig {
|
||||||
advices,
|
advices,
|
||||||
lagrange_coeffs: [
|
lagrange_coeffs: [
|
||||||
|
@ -153,13 +172,15 @@ impl EccChip {
|
||||||
q_mul_lo: meta.selector(),
|
q_mul_lo: meta.selector(),
|
||||||
q_mul_decompose_var: meta.selector(),
|
q_mul_decompose_var: meta.selector(),
|
||||||
q_init_z: meta.selector(),
|
q_init_z: meta.selector(),
|
||||||
q_mul_complete: meta.selector(),
|
q_mul_z: meta.selector(),
|
||||||
|
q_mul_overflow: meta.selector(),
|
||||||
q_mul_fixed: meta.selector(),
|
q_mul_fixed: meta.selector(),
|
||||||
q_mul_fixed_short: meta.selector(),
|
q_mul_fixed_short: meta.selector(),
|
||||||
q_point: meta.selector(),
|
q_point: meta.selector(),
|
||||||
q_scalar_fixed: meta.selector(),
|
q_scalar_fixed: meta.selector(),
|
||||||
q_scalar_fixed_short: meta.selector(),
|
q_scalar_fixed_short: meta.selector(),
|
||||||
perm,
|
perm,
|
||||||
|
lookup_config,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Create witness point gate
|
// Create witness point gate
|
||||||
|
@ -193,13 +214,13 @@ impl EccChip {
|
||||||
config.create_gate(meta);
|
config.create_gate(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create witness scalar_fixed gate that only apploes to short scalars
|
// Create witness scalar_fixed gate that only applies to short scalars
|
||||||
{
|
{
|
||||||
let config: witness_scalar_fixed::short::Config = (&config).into();
|
let config: witness_scalar_fixed::short::Config = (&config).into();
|
||||||
config.create_gate(meta);
|
config.create_gate(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create fixed-base scalar mul gate that os used in both full-width
|
// Create fixed-base scalar mul gate that is used in both full-width
|
||||||
// and short multiplication.
|
// and short multiplication.
|
||||||
{
|
{
|
||||||
let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into();
|
let mul_fixed_config: mul_fixed::Config<{ constants::NUM_WINDOWS }> = (&config).into();
|
||||||
|
@ -217,6 +238,17 @@ impl EccChip {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A base-field element used as the scalar in variable-base scalar multiplication.
|
||||||
|
#[derive(Copy, Clone, Debug)]
|
||||||
|
pub struct EccScalarVar(CellValue<pallas::Base>);
|
||||||
|
impl std::ops::Deref for EccScalarVar {
|
||||||
|
type Target = CellValue<pallas::Base>;
|
||||||
|
|
||||||
|
fn deref(&self) -> &CellValue<pallas::Base> {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A full-width scalar used for fixed-base scalar multiplication.
|
/// A full-width scalar used for fixed-base scalar multiplication.
|
||||||
/// This is decomposed in chunks of `window_width` bits in little-endian order.
|
/// This is decomposed in chunks of `window_width` bits in little-endian order.
|
||||||
/// For example, if `window_width` = 3, we will have [k_0, k_1, ..., k_n]
|
/// For example, if `window_width` = 3, we will have [k_0, k_1, ..., k_n]
|
||||||
|
@ -243,7 +275,7 @@ pub struct EccScalarFixedShort {
|
||||||
impl EccInstructions<pallas::Affine> for EccChip {
|
impl EccInstructions<pallas::Affine> for EccChip {
|
||||||
type ScalarFixed = EccScalarFixed;
|
type ScalarFixed = EccScalarFixed;
|
||||||
type ScalarFixedShort = EccScalarFixedShort;
|
type ScalarFixedShort = EccScalarFixedShort;
|
||||||
type ScalarVar = CellValue<pallas::Base>;
|
type ScalarVar = EccScalarVar;
|
||||||
type Point = EccPoint;
|
type Point = EccPoint;
|
||||||
type X = CellValue<pallas::Base>;
|
type X = CellValue<pallas::Base>;
|
||||||
type FixedPoints = OrchardFixedBasesFull;
|
type FixedPoints = OrchardFixedBasesFull;
|
||||||
|
@ -277,12 +309,12 @@ impl EccInstructions<pallas::Affine> for EccChip {
|
||||||
|| "Witness scalar for variable-base mul",
|
|| "Witness scalar for variable-base mul",
|
||||||
|mut region| {
|
|mut region| {
|
||||||
let cell = region.assign_advice(
|
let cell = region.assign_advice(
|
||||||
|| "Witness scalar var",
|
|| "witness scalar var",
|
||||||
config.advices[0],
|
config.advices[0],
|
||||||
0,
|
0,
|
||||||
|| value.ok_or(Error::SynthesisError),
|
|| value.ok_or(Error::SynthesisError),
|
||||||
)?;
|
)?;
|
||||||
Ok(CellValue::new(cell, value))
|
Ok(EccScalarVar(CellValue::new(cell, value)))
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -360,9 +392,10 @@ impl EccInstructions<pallas::Affine> for EccChip {
|
||||||
base: &Self::Point,
|
base: &Self::Point,
|
||||||
) -> Result<Self::Point, Error> {
|
) -> Result<Self::Point, Error> {
|
||||||
let config: mul::Config = self.config().into();
|
let config: mul::Config = self.config().into();
|
||||||
layouter.assign_region(
|
config.assign(
|
||||||
|| "variable-base scalar mul",
|
layouter.namespace(|| "variable-base scalar mul"),
|
||||||
|mut region| config.assign_region(scalar, base, 0, &mut region),
|
*scalar,
|
||||||
|
base,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,12 +1,13 @@
|
||||||
use super::{add, copy, CellValue, EccConfig, EccPoint, Var};
|
use super::{add, CellValue, EccConfig, EccPoint, EccScalarVar, Var};
|
||||||
use crate::constants::{NUM_COMPLETE_BITS, T_Q};
|
use crate::constants::{NUM_COMPLETE_BITS, T_Q};
|
||||||
use std::ops::{Deref, Range};
|
use std::ops::{Deref, Range};
|
||||||
|
|
||||||
use ff::{PrimeField, PrimeFieldBits};
|
use bigint::U256;
|
||||||
|
use ff::PrimeField;
|
||||||
use halo2::{
|
use halo2::{
|
||||||
arithmetic::FieldExt,
|
arithmetic::FieldExt,
|
||||||
circuit::Region,
|
circuit::{Layouter, Region},
|
||||||
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector},
|
plonk::{ConstraintSystem, Error, Permutation, Selector},
|
||||||
poly::Rotation,
|
poly::Rotation,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -14,6 +15,7 @@ use pasta_curves::pallas;
|
||||||
|
|
||||||
mod complete;
|
mod complete;
|
||||||
mod incomplete;
|
mod incomplete;
|
||||||
|
mod overflow;
|
||||||
|
|
||||||
// Bits used in incomplete addition. k_{254} to k_{4} inclusive
|
// Bits used in incomplete addition. k_{254} to k_{4} inclusive
|
||||||
const INCOMPLETE_LEN: usize = pallas::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS;
|
const INCOMPLETE_LEN: usize = pallas::Scalar::NUM_BITS as usize - 1 - NUM_COMPLETE_BITS;
|
||||||
|
@ -32,38 +34,35 @@ const INCOMPLETE_LO_RANGE: Range<usize> = (INCOMPLETE_LEN / 2)..INCOMPLETE_LEN;
|
||||||
const COMPLETE_RANGE: Range<usize> = INCOMPLETE_LEN..(INCOMPLETE_LEN + NUM_COMPLETE_BITS);
|
const COMPLETE_RANGE: Range<usize> = INCOMPLETE_LEN..(INCOMPLETE_LEN + NUM_COMPLETE_BITS);
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
// Selector used to constrain the cells used in complete addition.
|
|
||||||
q_mul_complete: Selector,
|
|
||||||
// Selector used to check recovery of the original scalar after decomposition.
|
|
||||||
q_mul_decompose_var: Selector,
|
|
||||||
// Selector used to constrain the initialization of the running sum to be zero.
|
// Selector used to constrain the initialization of the running sum to be zero.
|
||||||
q_init_z: Selector,
|
q_init_z: Selector,
|
||||||
// Advice column used to decompose scalar in complete addition.
|
// Selector used to check z_0 = 2*z_1 + k_0
|
||||||
z_complete: Column<Advice>,
|
q_mul_z: Selector,
|
||||||
// Advice column where the scalar is copied for use in the final recovery check.
|
|
||||||
scalar: Column<Advice>,
|
|
||||||
// Permutation
|
// Permutation
|
||||||
perm: Permutation,
|
perm: Permutation,
|
||||||
// Configuration used in complete addition
|
// Configuration used in complete addition
|
||||||
add_config: add::Config,
|
add_config: add::Config,
|
||||||
// Configuration used for `hi` bits of the scalar
|
// Configuration used for `hi` bits of the scalar
|
||||||
hi_config: incomplete::Config,
|
hi_config: incomplete::HiConfig,
|
||||||
// Configuration used for `lo` bits of the scalar
|
// Configuration used for `lo` bits of the scalar
|
||||||
lo_config: incomplete::Config,
|
lo_config: incomplete::LoConfig,
|
||||||
|
// Configuration used for complete addition part of double-and-add algorithm
|
||||||
|
complete_config: complete::Config,
|
||||||
|
// Configuration used to check for overflow
|
||||||
|
overflow_config: overflow::Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&EccConfig> for Config {
|
impl From<&EccConfig> for Config {
|
||||||
fn from(ecc_config: &EccConfig) -> Self {
|
fn from(ecc_config: &EccConfig) -> Self {
|
||||||
let config = Self {
|
let config = Self {
|
||||||
q_mul_complete: ecc_config.q_mul_complete,
|
|
||||||
q_mul_decompose_var: ecc_config.q_mul_decompose_var,
|
|
||||||
q_init_z: ecc_config.q_init_z,
|
q_init_z: ecc_config.q_init_z,
|
||||||
z_complete: ecc_config.advices[9],
|
q_mul_z: ecc_config.q_mul_z,
|
||||||
scalar: ecc_config.advices[1],
|
|
||||||
perm: ecc_config.perm.clone(),
|
perm: ecc_config.perm.clone(),
|
||||||
add_config: ecc_config.into(),
|
add_config: ecc_config.into(),
|
||||||
hi_config: incomplete::Config::hi_config(ecc_config),
|
hi_config: ecc_config.into(),
|
||||||
lo_config: incomplete::Config::lo_config(ecc_config),
|
lo_config: ecc_config.into(),
|
||||||
|
complete_config: ecc_config.into(),
|
||||||
|
overflow_config: ecc_config.into(),
|
||||||
};
|
};
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
|
@ -76,10 +75,6 @@ impl From<&EccConfig> for Config {
|
||||||
);
|
);
|
||||||
|
|
||||||
let add_config_advices = config.add_config.advice_columns();
|
let add_config_advices = config.add_config.advice_columns();
|
||||||
assert!(
|
|
||||||
!add_config_advices.contains(&config.z_complete),
|
|
||||||
"z_complete cannot overlap with complete addition columns."
|
|
||||||
);
|
|
||||||
assert!(
|
assert!(
|
||||||
!add_config_advices.contains(&config.hi_config.z),
|
!add_config_advices.contains(&config.hi_config.z),
|
||||||
"hi_config z cannot overlap with complete addition columns."
|
"hi_config z cannot overlap with complete addition columns."
|
||||||
|
@ -91,52 +86,34 @@ impl From<&EccConfig> for Config {
|
||||||
|
|
||||||
impl Config {
|
impl Config {
|
||||||
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
||||||
self.hi_config.create_gate(meta);
|
// Gate used to check that the running sum for scalar decomposition is initialized to zero.
|
||||||
self.lo_config.create_gate(meta);
|
|
||||||
|
|
||||||
let complete_config: complete::Config = self.into();
|
|
||||||
complete_config.create_gate(meta);
|
|
||||||
|
|
||||||
self.create_init_scalar_gate(meta);
|
|
||||||
self.create_final_scalar_gate(meta);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Gate used to check that the running sum for scalar decomposition is initialized to zero.
|
|
||||||
fn create_init_scalar_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
|
||||||
meta.create_gate("Initialize running sum for variable-base mul", |meta| {
|
meta.create_gate("Initialize running sum for variable-base mul", |meta| {
|
||||||
let q_init_z = meta.query_selector(self.q_init_z);
|
let q_init_z = meta.query_selector(self.q_init_z);
|
||||||
let z = meta.query_advice(self.hi_config.z, Rotation::cur());
|
let z = meta.query_advice(self.hi_config.z, Rotation::cur());
|
||||||
|
|
||||||
vec![q_init_z * z]
|
vec![q_init_z * z]
|
||||||
});
|
});
|
||||||
|
|
||||||
|
self.hi_config.create_gate(meta);
|
||||||
|
self.lo_config.create_gate(meta);
|
||||||
|
self.complete_config.create_gate(meta);
|
||||||
|
self.overflow_config.create_gate(meta);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Gate used to check final scalar is recovered.
|
pub(super) fn assign(
|
||||||
fn create_final_scalar_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
|
||||||
meta.create_gate("Decompose scalar for variable-base mul", |meta| {
|
|
||||||
let q_mul_decompose_var = meta.query_selector(self.q_mul_decompose_var);
|
|
||||||
let scalar = meta.query_advice(self.scalar, Rotation::cur());
|
|
||||||
let z_cur = meta.query_advice(self.z_complete, Rotation::cur());
|
|
||||||
|
|
||||||
// The scalar field `F_q = 2^254 + t_q`.
|
|
||||||
// -((2^127)^2) = -(2^254) = t_q (mod q)
|
|
||||||
let t_q = -(pallas::Scalar::from_u128(1u128 << 127).square());
|
|
||||||
let t_q = pallas::Base::from_bytes(&t_q.to_bytes()).unwrap();
|
|
||||||
|
|
||||||
// Check that `k = scalar + t_q`
|
|
||||||
vec![q_mul_decompose_var * (scalar + Expression::Constant(t_q) - z_cur)]
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn assign_region(
|
|
||||||
&self,
|
&self,
|
||||||
scalar: &CellValue<pallas::Base>,
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
alpha: EccScalarVar,
|
||||||
base: &EccPoint,
|
base: &EccPoint,
|
||||||
offset: usize,
|
|
||||||
region: &mut Region<'_, pallas::Base>,
|
|
||||||
) -> Result<EccPoint, Error> {
|
) -> Result<EccPoint, Error> {
|
||||||
// Decompose the scalar bitwise (big-endian bit order).
|
let (result, zs): (EccPoint, Vec<Z<pallas::Base>>) = layouter.assign_region(
|
||||||
let bits = decompose_for_scalar_mul(scalar.value());
|
|| "variable-base scalar mul",
|
||||||
|
|mut region| {
|
||||||
|
let offset = 0;
|
||||||
|
// Decompose `k = alpha + t_q` bitwise (big-endian bit order).
|
||||||
|
let bits = decompose_for_scalar_mul(alpha.value());
|
||||||
|
|
||||||
|
// Define ranges for each part of the algorithm.
|
||||||
let bits_incomplete_hi = &bits[INCOMPLETE_HI_RANGE];
|
let bits_incomplete_hi = &bits[INCOMPLETE_HI_RANGE];
|
||||||
let bits_incomplete_lo = &bits[INCOMPLETE_LO_RANGE];
|
let bits_incomplete_lo = &bits[INCOMPLETE_LO_RANGE];
|
||||||
let lsb = bits[pallas::Scalar::NUM_BITS as usize - 1];
|
let lsb = bits[pallas::Scalar::NUM_BITS as usize - 1];
|
||||||
|
@ -144,7 +121,7 @@ impl Config {
|
||||||
// Initialize the accumulator `acc = [2]base`
|
// Initialize the accumulator `acc = [2]base`
|
||||||
let acc = self
|
let acc = self
|
||||||
.add_config
|
.add_config
|
||||||
.assign_region(&base, &base, offset, region)?;
|
.assign_region(&base, &base, offset, &mut region)?;
|
||||||
|
|
||||||
// Increase the offset by 1 after complete addition.
|
// Increase the offset by 1 after complete addition.
|
||||||
let offset = offset + 1;
|
let offset = offset + 1;
|
||||||
|
@ -152,11 +129,15 @@ impl Config {
|
||||||
// Initialize the running sum for scalar decomposition to zero
|
// Initialize the running sum for scalar decomposition to zero
|
||||||
let z_init = {
|
let z_init = {
|
||||||
// Constrain the initialization of `z` to equal zero.
|
// Constrain the initialization of `z` to equal zero.
|
||||||
self.q_init_z.enable(region, offset)?;
|
self.q_init_z.enable(&mut region, offset)?;
|
||||||
|
|
||||||
let z_val = pallas::Base::zero();
|
let z_val = pallas::Base::zero();
|
||||||
let z_cell =
|
let z_cell = region.assign_advice(
|
||||||
region.assign_advice(|| "initial z", self.hi_config.z, offset, || Ok(z_val))?;
|
|| "initial z",
|
||||||
|
self.hi_config.z,
|
||||||
|
offset,
|
||||||
|
|| Ok(z_val),
|
||||||
|
)?;
|
||||||
|
|
||||||
Z(CellValue::new(z_cell, Some(z_val)))
|
Z(CellValue::new(z_cell, Some(z_val)))
|
||||||
};
|
};
|
||||||
|
@ -166,7 +147,7 @@ impl Config {
|
||||||
|
|
||||||
// Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition
|
// Double-and-add (incomplete addition) for the `hi` half of the scalar decomposition
|
||||||
let (x_a, y_a, zs_incomplete_hi) = self.hi_config.double_and_add(
|
let (x_a, y_a, zs_incomplete_hi) = self.hi_config.double_and_add(
|
||||||
region,
|
&mut region,
|
||||||
offset,
|
offset,
|
||||||
&base,
|
&base,
|
||||||
bits_incomplete_hi,
|
bits_incomplete_hi,
|
||||||
|
@ -176,7 +157,7 @@ impl Config {
|
||||||
// Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition
|
// Double-and-add (incomplete addition) for the `lo` half of the scalar decomposition
|
||||||
let z = &zs_incomplete_hi[zs_incomplete_hi.len() - 1];
|
let z = &zs_incomplete_hi[zs_incomplete_hi.len() - 1];
|
||||||
let (x_a, y_a, zs_incomplete_lo) = self.lo_config.double_and_add(
|
let (x_a, y_a, zs_incomplete_lo) = self.lo_config.double_and_add(
|
||||||
region,
|
&mut region,
|
||||||
offset,
|
offset,
|
||||||
&base,
|
&base,
|
||||||
bits_incomplete_lo,
|
bits_incomplete_lo,
|
||||||
|
@ -194,19 +175,26 @@ impl Config {
|
||||||
// Complete addition
|
// Complete addition
|
||||||
let (acc, zs_complete) = {
|
let (acc, zs_complete) = {
|
||||||
let z = &zs_incomplete_lo[zs_incomplete_lo.len() - 1];
|
let z = &zs_incomplete_lo[zs_incomplete_lo.len() - 1];
|
||||||
let complete_config: complete::Config = self.into();
|
|
||||||
// Bits used in complete addition. k_{3} to k_{1} inclusive
|
// Bits used in complete addition. k_{3} to k_{1} inclusive
|
||||||
// The LSB k_{0} is handled separately.
|
// The LSB k_{0} is handled separately.
|
||||||
let bits_complete = &bits[COMPLETE_RANGE];
|
let bits_complete = &bits[COMPLETE_RANGE];
|
||||||
complete_config.assign_region(region, offset, bits_complete, base, x_a, y_a, *z)?
|
self.complete_config.assign_region(
|
||||||
|
&mut region,
|
||||||
|
offset,
|
||||||
|
bits_complete,
|
||||||
|
base,
|
||||||
|
x_a,
|
||||||
|
y_a,
|
||||||
|
*z,
|
||||||
|
)?
|
||||||
};
|
};
|
||||||
|
|
||||||
// Each iteration of the complete addition uses two rows.
|
// Each iteration of the complete addition uses two rows.
|
||||||
let offset = offset + COMPLETE_RANGE.len() * 2;
|
let offset = offset + COMPLETE_RANGE.len() * 2;
|
||||||
|
|
||||||
// Process the least significant bit
|
// Process the least significant bit
|
||||||
let z = &zs_complete[zs_complete.len() - 1];
|
let z_1 = zs_complete.last().unwrap();
|
||||||
let (result, z_final) = self.process_lsb(region, offset, scalar, base, acc, lsb, *z)?;
|
let (result, z_0) = self.process_lsb(&mut region, offset, base, acc, *z_1, lsb)?;
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
// Check that the correct multiple is obtained.
|
// Check that the correct multiple is obtained.
|
||||||
|
@ -214,10 +202,10 @@ impl Config {
|
||||||
use group::Curve;
|
use group::Curve;
|
||||||
|
|
||||||
let base = base.point();
|
let base = base.point();
|
||||||
let scalar = scalar
|
let alpha = alpha
|
||||||
.value()
|
.value()
|
||||||
.map(|scalar| pallas::Scalar::from_bytes(&scalar.to_bytes()).unwrap());
|
.map(|alpha| pallas::Scalar::from_bytes(&alpha.to_bytes()).unwrap());
|
||||||
let real_mul = base.zip(scalar).map(|(base, scalar)| base * scalar);
|
let real_mul = base.zip(alpha).map(|(base, alpha)| base * alpha);
|
||||||
let result = result.point();
|
let result = result.point();
|
||||||
|
|
||||||
if let (Some(real_mul), Some(result)) = (real_mul, result) {
|
if let (Some(real_mul), Some(result)) = (real_mul, result) {
|
||||||
|
@ -225,58 +213,56 @@ impl Config {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let zs = {
|
||||||
let mut zs = vec![z_init];
|
let mut zs = vec![z_init];
|
||||||
zs.extend_from_slice(&zs_incomplete_hi);
|
zs.extend_from_slice(&zs_incomplete_hi);
|
||||||
zs.extend_from_slice(&zs_incomplete_lo);
|
zs.extend_from_slice(&zs_incomplete_lo);
|
||||||
zs.extend_from_slice(&zs_complete);
|
zs.extend_from_slice(&zs_complete);
|
||||||
zs.extend_from_slice(&[z_final]);
|
zs.extend_from_slice(&[z_0]);
|
||||||
|
assert_eq!(zs.len(), pallas::Scalar::NUM_BITS as usize + 1);
|
||||||
|
|
||||||
// This reverses zs to give us [z_0, z_1, ..., z_{254}, z_{255}].
|
// This reverses zs to give us [z_0, z_1, ..., z_{254}, z_{255}].
|
||||||
zs.reverse();
|
zs.reverse();
|
||||||
|
zs
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok((result, zs))
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
self.overflow_config
|
||||||
|
.overflow_check(layouter.namespace(|| "overflow check"), alpha, &zs)?;
|
||||||
|
|
||||||
Ok(result)
|
Ok(result)
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(clippy::too_many_arguments)]
|
|
||||||
fn process_lsb(
|
fn process_lsb(
|
||||||
&self,
|
&self,
|
||||||
region: &mut Region<'_, pallas::Base>,
|
region: &mut Region<'_, pallas::Base>,
|
||||||
offset: usize,
|
offset: usize,
|
||||||
_scalar: &CellValue<pallas::Base>,
|
|
||||||
base: &EccPoint,
|
base: &EccPoint,
|
||||||
acc: EccPoint,
|
acc: EccPoint,
|
||||||
|
z_1: Z<pallas::Base>,
|
||||||
lsb: Option<bool>,
|
lsb: Option<bool>,
|
||||||
z: Z<pallas::Base>,
|
|
||||||
) -> Result<(EccPoint, Z<pallas::Base>), Error> {
|
) -> Result<(EccPoint, Z<pallas::Base>), Error> {
|
||||||
// Copy in the penultimate `z_1` value.
|
// Assign z_0 = 2⋅z_1 + k_0
|
||||||
copy(
|
let z_0 = {
|
||||||
region,
|
let z_0_val = z_1.value().zip(lsb).map(|(z_1, lsb)| {
|
||||||
|| "penultimate z_1 running sum",
|
let lsb = pallas::Base::from_u64(lsb as u64);
|
||||||
self.z_complete,
|
z_1 * pallas::Base::from_u64(2) + lsb
|
||||||
offset,
|
|
||||||
&z.0,
|
|
||||||
&self.perm,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
// Increase offset after copying in `z_1`.
|
|
||||||
let offset = offset + 1;
|
|
||||||
|
|
||||||
// Assign the final `z_0` value.
|
|
||||||
let z = {
|
|
||||||
let z_val = z.0.value().zip(lsb).map(|(z_val, lsb)| {
|
|
||||||
pallas::Base::from_u64(2) * z_val + pallas::Base::from_u64(lsb as u64)
|
|
||||||
});
|
});
|
||||||
let z_cell = region.assign_advice(
|
let z_0_cell = region.assign_advice(
|
||||||
|| "final z_0",
|
|| "z_0",
|
||||||
self.z_complete,
|
self.complete_config.z_complete,
|
||||||
offset,
|
offset,
|
||||||
|| z_val.ok_or(Error::SynthesisError),
|
|| z_0_val.ok_or(Error::SynthesisError),
|
||||||
)?;
|
)?;
|
||||||
Z(CellValue::new(z_cell, z_val))
|
|
||||||
};
|
|
||||||
|
|
||||||
// Enforce that the final bit decomposition from `z_1` to `z_0` was done correctly.
|
// Check that z_0 was properly derived from z_1.
|
||||||
self.q_mul_complete.enable(region, offset)?;
|
self.q_mul_z.enable(region, offset)?;
|
||||||
|
|
||||||
|
Z(CellValue::new(z_0_cell, z_0_val))
|
||||||
|
};
|
||||||
|
|
||||||
// If `lsb` is 0, return `Acc + (-P)`. If `lsb` is 1, simply return `Acc + 0`.
|
// If `lsb` is 0, return `Acc + (-P)`. If `lsb` is 1, simply return `Acc + 0`.
|
||||||
let x_p = if let Some(lsb) = lsb {
|
let x_p = if let Some(lsb) = lsb {
|
||||||
|
@ -301,14 +287,14 @@ impl Config {
|
||||||
let x_p_cell = region.assign_advice(
|
let x_p_cell = region.assign_advice(
|
||||||
|| "x_p",
|
|| "x_p",
|
||||||
self.add_config.x_p,
|
self.add_config.x_p,
|
||||||
offset + 1,
|
offset,
|
||||||
|| x_p.ok_or(Error::SynthesisError),
|
|| x_p.ok_or(Error::SynthesisError),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let y_p_cell = region.assign_advice(
|
let y_p_cell = region.assign_advice(
|
||||||
|| "y_p",
|
|| "y_p",
|
||||||
self.add_config.y_p,
|
self.add_config.y_p,
|
||||||
offset + 1,
|
offset,
|
||||||
|| y_p.ok_or(Error::SynthesisError),
|
|| y_p.ok_or(Error::SynthesisError),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
@ -318,11 +304,9 @@ impl Config {
|
||||||
};
|
};
|
||||||
|
|
||||||
// Return the result of the final complete addition as `[scalar]B`
|
// Return the result of the final complete addition as `[scalar]B`
|
||||||
let result = self
|
let result = self.add_config.assign_region(&p, &acc, offset, region)?;
|
||||||
.add_config
|
|
||||||
.assign_region(&p, &acc, offset + 1, region)?;
|
|
||||||
|
|
||||||
Ok((result, z))
|
Ok((result, z_0))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,29 +344,36 @@ impl<F: FieldExt> Deref for Z<F> {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decompose_for_scalar_mul(scalar: Option<pallas::Base>) -> Vec<Option<bool>> {
|
fn decompose_for_scalar_mul(scalar: Option<pallas::Base>) -> Vec<Option<bool>> {
|
||||||
let bits = scalar.map(|scalar| {
|
let bitstring = scalar.map(|scalar| {
|
||||||
// We use `k = scalar + t_q` in the double-and-add algorithm, where
|
// We use `k = scalar + t_q` in the double-and-add algorithm, where
|
||||||
// the scalar field `F_q = 2^254 + t_q`.
|
// the scalar field `F_q = 2^254 + t_q`.
|
||||||
let k = {
|
// Note that the addition `scalar + t_q` is not reduced.
|
||||||
let scalar = pallas::Scalar::from_bytes(&scalar.to_bytes()).unwrap();
|
//
|
||||||
scalar + pallas::Scalar::from_u128(T_Q)
|
let scalar = U256::from_little_endian(&scalar.to_bytes());
|
||||||
|
let t_q = U256::from_little_endian(&T_Q.to_le_bytes());
|
||||||
|
let k = scalar + t_q;
|
||||||
|
|
||||||
|
// Big-endian bit representation of `k`.
|
||||||
|
let bitstring: Vec<bool> = {
|
||||||
|
let mut le_bytes = [0u8; 32];
|
||||||
|
k.to_little_endian(&mut le_bytes);
|
||||||
|
le_bytes.iter().fold(Vec::new(), |mut bitstring, byte| {
|
||||||
|
let bits = (0..8)
|
||||||
|
.map(|shift| (byte >> shift) % 2 == 1)
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
bitstring.extend_from_slice(&bits);
|
||||||
|
bitstring
|
||||||
|
})
|
||||||
};
|
};
|
||||||
|
|
||||||
// `k` is decomposed bitwise (big-endian) into `[k_n, ..., lsb]`, where
|
// Take the first 255 bits.
|
||||||
// each `k_i` is a bit and `scalar = k_n * 2^n + ... + k_1 * 2 + lsb`.
|
let mut bitstring = bitstring[0..pallas::Scalar::NUM_BITS as usize].to_vec();
|
||||||
let mut bits: Vec<bool> = k
|
bitstring.reverse();
|
||||||
.to_le_bits()
|
bitstring
|
||||||
.into_iter()
|
|
||||||
.take(pallas::Scalar::NUM_BITS as usize)
|
|
||||||
.collect();
|
|
||||||
bits.reverse();
|
|
||||||
assert_eq!(bits.len(), pallas::Scalar::NUM_BITS as usize);
|
|
||||||
|
|
||||||
bits
|
|
||||||
});
|
});
|
||||||
|
|
||||||
if let Some(bits) = bits {
|
if let Some(bitstring) = bitstring {
|
||||||
bits.into_iter().map(Some).collect()
|
bitstring.into_iter().map(Some).collect()
|
||||||
} else {
|
} else {
|
||||||
vec![None; pallas::Scalar::NUM_BITS as usize]
|
vec![None; pallas::Scalar::NUM_BITS as usize]
|
||||||
}
|
}
|
||||||
|
@ -401,14 +392,13 @@ pub mod tests {
|
||||||
zero: &Point<pallas::Affine, EccChip>,
|
zero: &Point<pallas::Affine, EccChip>,
|
||||||
p: &Point<pallas::Affine, EccChip>,
|
p: &Point<pallas::Affine, EccChip>,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
|
// [a]B
|
||||||
let scalar_val = pallas::Base::rand();
|
let scalar_val = pallas::Base::rand();
|
||||||
let scalar = ScalarVar::new(
|
let scalar = ScalarVar::new(
|
||||||
chip.clone(),
|
chip.clone(),
|
||||||
layouter.namespace(|| "ScalarVar"),
|
layouter.namespace(|| "ScalarVar"),
|
||||||
Some(scalar_val),
|
Some(scalar_val),
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
// [a]B
|
|
||||||
p.mul(layouter.namespace(|| "mul"), &scalar)?;
|
p.mul(layouter.namespace(|| "mul"), &scalar)?;
|
||||||
|
|
||||||
// [a]𝒪 should return an error since variable-base scalar multiplication
|
// [a]𝒪 should return an error since variable-base scalar multiplication
|
||||||
|
@ -419,6 +409,15 @@ pub mod tests {
|
||||||
// [0]B should return (0,0) since variable-base scalar multiplication
|
// [0]B should return (0,0) since variable-base scalar multiplication
|
||||||
// uses complete addition for the final bits of the scalar.
|
// uses complete addition for the final bits of the scalar.
|
||||||
let scalar_val = pallas::Base::zero();
|
let scalar_val = pallas::Base::zero();
|
||||||
|
let scalar = ScalarVar::new(
|
||||||
|
chip.clone(),
|
||||||
|
layouter.namespace(|| "ScalarVar"),
|
||||||
|
Some(scalar_val),
|
||||||
|
)?;
|
||||||
|
p.mul(layouter.namespace(|| "mul"), &scalar)?;
|
||||||
|
|
||||||
|
// [-1]B (the largest possible base field element)
|
||||||
|
let scalar_val = -pallas::Base::one();
|
||||||
let scalar = ScalarVar::new(chip, layouter.namespace(|| "ScalarVar"), Some(scalar_val))?;
|
let scalar = ScalarVar::new(chip, layouter.namespace(|| "ScalarVar"), Some(scalar_val))?;
|
||||||
p.mul(layouter.namespace(|| "mul"), &scalar)?;
|
p.mul(layouter.namespace(|| "mul"), &scalar)?;
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use super::super::{add, copy, CellValue, EccPoint, Var};
|
use super::super::{add, copy, CellValue, EccConfig, EccPoint, Var};
|
||||||
use super::{COMPLETE_RANGE, X, Y, Z};
|
use super::{COMPLETE_RANGE, X, Y, Z};
|
||||||
|
|
||||||
use halo2::{
|
use halo2::{
|
||||||
|
@ -11,23 +11,31 @@ use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||||
|
|
||||||
pub struct Config {
|
pub struct Config {
|
||||||
// Selector used to constrain the cells used in complete addition.
|
// Selector used to constrain the cells used in complete addition.
|
||||||
q_mul_complete: Selector,
|
q_mul_z: Selector,
|
||||||
// Advice column used to decompose scalar in complete addition.
|
// Advice column used to decompose scalar in complete addition.
|
||||||
z_complete: Column<Advice>,
|
pub z_complete: Column<Advice>,
|
||||||
// Permutation
|
// Permutation
|
||||||
perm: Permutation,
|
perm: Permutation,
|
||||||
// Configuration used in complete addition
|
// Configuration used in complete addition
|
||||||
add_config: add::Config,
|
add_config: add::Config,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl From<&super::Config> for Config {
|
impl From<&EccConfig> for Config {
|
||||||
fn from(config: &super::Config) -> Self {
|
fn from(ecc_config: &EccConfig) -> Self {
|
||||||
Self {
|
let config = Self {
|
||||||
q_mul_complete: config.q_mul_complete,
|
q_mul_z: ecc_config.q_mul_z,
|
||||||
z_complete: config.z_complete,
|
z_complete: ecc_config.advices[9],
|
||||||
perm: config.perm.clone(),
|
perm: ecc_config.perm.clone(),
|
||||||
add_config: config.add_config.clone(),
|
add_config: ecc_config.into(),
|
||||||
}
|
};
|
||||||
|
|
||||||
|
let add_config_advices = config.add_config.advice_columns();
|
||||||
|
assert!(
|
||||||
|
!add_config_advices.contains(&config.z_complete),
|
||||||
|
"z_complete cannot overlap with complete addition columns."
|
||||||
|
);
|
||||||
|
|
||||||
|
config
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +48,7 @@ impl Config {
|
||||||
meta.create_gate(
|
meta.create_gate(
|
||||||
"Decompose scalar for complete bits of variable-base mul",
|
"Decompose scalar for complete bits of variable-base mul",
|
||||||
|meta| {
|
|meta| {
|
||||||
let q_mul_complete = meta.query_selector(self.q_mul_complete);
|
let q_mul_z = meta.query_selector(self.q_mul_z);
|
||||||
let z_cur = meta.query_advice(self.z_complete, Rotation::cur());
|
let z_cur = meta.query_advice(self.z_complete, Rotation::cur());
|
||||||
let z_prev = meta.query_advice(self.z_complete, Rotation::prev());
|
let z_prev = meta.query_advice(self.z_complete, Rotation::prev());
|
||||||
|
|
||||||
|
@ -49,7 +57,7 @@ impl Config {
|
||||||
// (k_i) ⋅ (k_i - 1) = 0
|
// (k_i) ⋅ (k_i - 1) = 0
|
||||||
let bool_check = k.clone() * (k + Expression::Constant(-pallas::Base::one()));
|
let bool_check = k.clone() * (k + Expression::Constant(-pallas::Base::one()));
|
||||||
|
|
||||||
vec![q_mul_complete * bool_check]
|
vec![q_mul_z * bool_check]
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -76,9 +84,9 @@ impl Config {
|
||||||
// Each iteration uses 2 rows (two complete additions)
|
// Each iteration uses 2 rows (two complete additions)
|
||||||
let row = 2 * row;
|
let row = 2 * row;
|
||||||
// Check scalar decomposition for each iteration. Since the gate enabled by
|
// Check scalar decomposition for each iteration. Since the gate enabled by
|
||||||
// `q_mul_complete` queries the previous row, we enable the selector on
|
// `q_mul_z` queries the previous row, we enable the selector on
|
||||||
// `row + offset + 1` (instead of `row + offset`).
|
// `row + offset + 1` (instead of `row + offset`).
|
||||||
self.q_mul_complete.enable(region, row + offset + 1)?;
|
self.q_mul_z.enable(region, row + offset + 1)?;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Use x_a, y_a output from incomplete addition
|
// Use x_a, y_a output from incomplete addition
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
use std::array;
|
use std::{array, ops::Deref};
|
||||||
|
|
||||||
use super::super::{copy, CellValue, EccConfig, EccPoint, Var};
|
use super::super::{copy, CellValue, EccConfig, EccPoint, Var};
|
||||||
use super::{INCOMPLETE_HI_RANGE, INCOMPLETE_LO_RANGE, X, Y, Z};
|
use super::{INCOMPLETE_HI_RANGE, INCOMPLETE_LO_RANGE, X, Y, Z};
|
||||||
|
@ -32,11 +32,12 @@ pub(super) struct Config {
|
||||||
pub(super) perm: Permutation,
|
pub(super) perm: Permutation,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Config {
|
|
||||||
// Columns used in processing the `hi` bits of the scalar.
|
// Columns used in processing the `hi` bits of the scalar.
|
||||||
// `x_p, y_p` are shared across the `hi` and `lo` halves.
|
// `x_p, y_p` are shared across the `hi` and `lo` halves.
|
||||||
pub(super) fn hi_config(ecc_config: &EccConfig) -> Self {
|
pub(super) struct HiConfig(Config);
|
||||||
Self {
|
impl From<&EccConfig> for HiConfig {
|
||||||
|
fn from(ecc_config: &EccConfig) -> Self {
|
||||||
|
let config = Config {
|
||||||
num_bits: INCOMPLETE_HI_RANGE.len(),
|
num_bits: INCOMPLETE_HI_RANGE.len(),
|
||||||
q_mul: ecc_config.q_mul_hi,
|
q_mul: ecc_config.q_mul_hi,
|
||||||
x_p: ecc_config.advices[0],
|
x_p: ecc_config.advices[0],
|
||||||
|
@ -46,13 +47,24 @@ impl Config {
|
||||||
lambda1: ecc_config.advices[4],
|
lambda1: ecc_config.advices[4],
|
||||||
lambda2: ecc_config.advices[5],
|
lambda2: ecc_config.advices[5],
|
||||||
perm: ecc_config.perm.clone(),
|
perm: ecc_config.perm.clone(),
|
||||||
|
};
|
||||||
|
Self(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Deref for HiConfig {
|
||||||
|
type Target = Config;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Config {
|
||||||
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Columns used in processing the `lo` bits of the scalar.
|
// Columns used in processing the `lo` bits of the scalar.
|
||||||
// `x_p, y_p` are shared across the `hi` and `lo` halves.
|
// `x_p, y_p` are shared across the `hi` and `lo` halves.
|
||||||
pub(super) fn lo_config(ecc_config: &EccConfig) -> Self {
|
pub(super) struct LoConfig(Config);
|
||||||
Self {
|
impl From<&EccConfig> for LoConfig {
|
||||||
|
fn from(ecc_config: &EccConfig) -> Self {
|
||||||
|
let config = Config {
|
||||||
num_bits: INCOMPLETE_LO_RANGE.len(),
|
num_bits: INCOMPLETE_LO_RANGE.len(),
|
||||||
q_mul: ecc_config.q_mul_lo,
|
q_mul: ecc_config.q_mul_lo,
|
||||||
x_p: ecc_config.advices[0],
|
x_p: ecc_config.advices[0],
|
||||||
|
@ -62,9 +74,19 @@ impl Config {
|
||||||
lambda1: ecc_config.advices[8],
|
lambda1: ecc_config.advices[8],
|
||||||
lambda2: ecc_config.advices[2],
|
lambda2: ecc_config.advices[2],
|
||||||
perm: ecc_config.perm.clone(),
|
perm: ecc_config.perm.clone(),
|
||||||
|
};
|
||||||
|
Self(config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Deref for LoConfig {
|
||||||
|
type Target = Config;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Config {
|
||||||
|
&self.0
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
// Gate for incomplete addition part of variable-base scalar multiplication.
|
// Gate for incomplete addition part of variable-base scalar multiplication.
|
||||||
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
||||||
meta.create_gate("Incomplete addition for variable-base scalar mul", |meta| {
|
meta.create_gate("Incomplete addition for variable-base scalar mul", |meta| {
|
||||||
|
|
|
@ -0,0 +1,244 @@
|
||||||
|
use super::super::{copy, CellValue, EccConfig, EccScalarVar, Var};
|
||||||
|
use super::Z;
|
||||||
|
use crate::{
|
||||||
|
circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig, constants::T_Q,
|
||||||
|
primitives::sinsemilla,
|
||||||
|
};
|
||||||
|
use halo2::{
|
||||||
|
circuit::Layouter,
|
||||||
|
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector},
|
||||||
|
poly::Rotation,
|
||||||
|
};
|
||||||
|
|
||||||
|
use ff::Field;
|
||||||
|
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||||
|
|
||||||
|
use std::iter;
|
||||||
|
|
||||||
|
pub struct Config {
|
||||||
|
// Selector to check decomposition of lsb
|
||||||
|
q_mul_z: Selector,
|
||||||
|
// Selector to check z_0 = alpha + t_q (mod p)
|
||||||
|
q_mul_overflow: Selector,
|
||||||
|
// 10-bit lookup table
|
||||||
|
lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
|
||||||
|
// Advice columns
|
||||||
|
advices: [Column<Advice>; 3],
|
||||||
|
// Permutation
|
||||||
|
perm: Permutation,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<&EccConfig> for Config {
|
||||||
|
fn from(ecc_config: &EccConfig) -> Self {
|
||||||
|
Self {
|
||||||
|
q_mul_z: ecc_config.q_mul_z,
|
||||||
|
q_mul_overflow: ecc_config.q_mul_overflow,
|
||||||
|
lookup_config: ecc_config.lookup_config.clone(),
|
||||||
|
advices: [
|
||||||
|
ecc_config.advices[0],
|
||||||
|
ecc_config.advices[1],
|
||||||
|
ecc_config.advices[2],
|
||||||
|
],
|
||||||
|
perm: ecc_config.perm.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub(super) fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
|
||||||
|
meta.create_gate("overflow checks", |meta| {
|
||||||
|
let q_mul_overflow = meta.query_selector(self.q_mul_overflow);
|
||||||
|
|
||||||
|
// Constant expressions
|
||||||
|
let one = Expression::Constant(pallas::Base::one());
|
||||||
|
let two_pow_124 = Expression::Constant(pallas::Base::from_u128(1 << 124));
|
||||||
|
let two_pow_130 =
|
||||||
|
two_pow_124.clone() * Expression::Constant(pallas::Base::from_u128(1 << 6));
|
||||||
|
|
||||||
|
let z_0 = meta.query_advice(self.advices[0], Rotation::prev());
|
||||||
|
let z_130 = meta.query_advice(self.advices[0], Rotation::cur());
|
||||||
|
let eta = meta.query_advice(self.advices[0], Rotation::next());
|
||||||
|
|
||||||
|
let k_254 = meta.query_advice(self.advices[1], Rotation::prev());
|
||||||
|
let alpha = meta.query_advice(self.advices[1], Rotation::cur());
|
||||||
|
|
||||||
|
// s_minus_lo_130 = s - sum_{i = 0}^{129} 2^i ⋅ s_i
|
||||||
|
let s_minus_lo_130 = meta.query_advice(self.advices[1], Rotation::next());
|
||||||
|
|
||||||
|
let s = meta.query_advice(self.advices[2], Rotation::cur());
|
||||||
|
let s_check = s - (alpha.clone() + k_254.clone() * two_pow_130);
|
||||||
|
|
||||||
|
// q = 2^254 + t_q is the Pallas scalar field modulus.
|
||||||
|
// We cast t_q into the base field to check alpha + t_q (mod p).
|
||||||
|
let t_q = pallas::Base::from_u128(T_Q);
|
||||||
|
let t_q = Expression::Constant(t_q);
|
||||||
|
|
||||||
|
// z_0 - alpha - t_q = 0 (mod p)
|
||||||
|
let recovery = z_0 - alpha - t_q;
|
||||||
|
|
||||||
|
// k_254 * (z_130 - 2^124) = 0
|
||||||
|
let lo_zero = k_254.clone() * (z_130.clone() - two_pow_124);
|
||||||
|
|
||||||
|
// k_254 * s_minus_lo_130 = 0
|
||||||
|
let s_minus_lo_130_check = k_254.clone() * s_minus_lo_130.clone();
|
||||||
|
|
||||||
|
// (1 - k_254) * (1 - z_130 * eta) * s_minus_lo_130 = 0
|
||||||
|
let canonicity = (one.clone() - k_254) * (one - z_130 * eta) * s_minus_lo_130;
|
||||||
|
|
||||||
|
iter::empty()
|
||||||
|
.chain(Some(s_check))
|
||||||
|
.chain(Some(recovery))
|
||||||
|
.chain(Some(lo_zero))
|
||||||
|
.chain(Some(s_minus_lo_130_check))
|
||||||
|
.chain(Some(canonicity))
|
||||||
|
.map(|poly| q_mul_overflow.clone() * poly)
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn overflow_check(
|
||||||
|
&self,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
alpha: EccScalarVar,
|
||||||
|
zs: &[Z<pallas::Base>], // [z_0, z_1, ..., z_{254}, z_{255}]
|
||||||
|
) -> Result<(), Error> {
|
||||||
|
// s = alpha + k_254 ⋅ 2^130 is witnessed here, and then copied into
|
||||||
|
// the decomposition as well as the overflow check gate.
|
||||||
|
// In the overflow check gate, we check that s is properly derived
|
||||||
|
// from alpha and k_254.
|
||||||
|
let s = {
|
||||||
|
let k_254 = *zs[254];
|
||||||
|
let s_val = alpha
|
||||||
|
.value()
|
||||||
|
.zip(k_254.value())
|
||||||
|
.map(|(alpha, k_254)| alpha + k_254 * pallas::Base::from_u128(1 << 65).square());
|
||||||
|
|
||||||
|
layouter.assign_region(
|
||||||
|
|| "s = alpha + k_254 ⋅ 2^130",
|
||||||
|
|mut region| {
|
||||||
|
let s_cell = region.assign_advice(
|
||||||
|
|| "s = alpha + k_254 ⋅ 2^130",
|
||||||
|
self.advices[0],
|
||||||
|
0,
|
||||||
|
|| s_val.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
Ok(CellValue::new(s_cell, s_val))
|
||||||
|
},
|
||||||
|
)?
|
||||||
|
};
|
||||||
|
|
||||||
|
// Subtract the first 130 low bits of s = alpha + k_254 ⋅ 2^130
|
||||||
|
// using thirteen 10-bit lookups, s_{0..=129}
|
||||||
|
let s_minus_lo_130 =
|
||||||
|
self.s_minus_lo_130(layouter.namespace(|| "decompose s_{0..=129}"), s)?;
|
||||||
|
|
||||||
|
layouter.assign_region(
|
||||||
|
|| "overflow check",
|
||||||
|
|mut region| {
|
||||||
|
let offset = 0;
|
||||||
|
|
||||||
|
// Enable overflow check gate
|
||||||
|
self.q_mul_overflow.enable(&mut region, offset + 1)?;
|
||||||
|
|
||||||
|
// Copy `z_0`
|
||||||
|
copy(
|
||||||
|
&mut region,
|
||||||
|
|| "copy z_0",
|
||||||
|
self.advices[0],
|
||||||
|
offset,
|
||||||
|
&*zs[0],
|
||||||
|
&self.perm,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Copy `z_130`
|
||||||
|
copy(
|
||||||
|
&mut region,
|
||||||
|
|| "copy z_130",
|
||||||
|
self.advices[0],
|
||||||
|
offset + 1,
|
||||||
|
&*zs[130],
|
||||||
|
&self.perm,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Witness η = inv0(z_130), where inv0(x) = 0 if x = 0, 1/x otherwise
|
||||||
|
{
|
||||||
|
let eta = zs[130].value().map(|z_130| {
|
||||||
|
if z_130 == pallas::Base::zero() {
|
||||||
|
pallas::Base::zero()
|
||||||
|
} else {
|
||||||
|
z_130.invert().unwrap()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
region.assign_advice(
|
||||||
|
|| "η = inv0(z_130)",
|
||||||
|
self.advices[0],
|
||||||
|
offset + 2,
|
||||||
|
|| eta.ok_or(Error::SynthesisError),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy `k_254` = z_254
|
||||||
|
copy(
|
||||||
|
&mut region,
|
||||||
|
|| "copy k_254",
|
||||||
|
self.advices[1],
|
||||||
|
offset,
|
||||||
|
&*zs[254],
|
||||||
|
&self.perm,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Copy original alpha
|
||||||
|
copy(
|
||||||
|
&mut region,
|
||||||
|
|| "copy original alpha",
|
||||||
|
self.advices[1],
|
||||||
|
offset + 1,
|
||||||
|
&*alpha,
|
||||||
|
&self.perm,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Copy weighted sum of the decomposition of s = alpha + k_254 ⋅ 2^130.
|
||||||
|
copy(
|
||||||
|
&mut region,
|
||||||
|
|| "copy s_minus_lo_130",
|
||||||
|
self.advices[1],
|
||||||
|
offset + 2,
|
||||||
|
&s_minus_lo_130,
|
||||||
|
&self.perm,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
// Copy witnessed s to check that it was properly derived from alpha and k_254.
|
||||||
|
copy(
|
||||||
|
&mut region,
|
||||||
|
|| "copy s",
|
||||||
|
self.advices[2],
|
||||||
|
offset + 1,
|
||||||
|
&s,
|
||||||
|
&self.perm,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn s_minus_lo_130(
|
||||||
|
&self,
|
||||||
|
mut layouter: impl Layouter<pallas::Base>,
|
||||||
|
s: CellValue<pallas::Base>,
|
||||||
|
) -> Result<CellValue<pallas::Base>, Error> {
|
||||||
|
// Number of k-bit words we can use in the lookup decomposition.
|
||||||
|
let num_words = 130 / sinsemilla::K;
|
||||||
|
assert!(num_words * sinsemilla::K == 130);
|
||||||
|
|
||||||
|
// Decompose the low 130 bits of `s` using thirteen 10-bit lookups.
|
||||||
|
let zs = self.lookup_config.copy_check(
|
||||||
|
layouter.namespace(|| "Decompose low 130 bits of s"),
|
||||||
|
s,
|
||||||
|
num_words,
|
||||||
|
)?;
|
||||||
|
Ok(zs[zs.len() - 1])
|
||||||
|
}
|
||||||
|
}
|
|
@ -240,6 +240,12 @@ pub(crate) fn extract_p_bottom(point: CtOption<pallas::Point>) -> CtOption<palla
|
||||||
point.map(|p| extract_p(&p))
|
point.map(|p| extract_p(&p))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The field element representation of a u64 integer represented by
|
||||||
|
/// an L-bit little-endian bitstring.
|
||||||
|
pub fn lebs2ip_field<F: FieldExt, const L: usize>(bits: &[bool; L]) -> F {
|
||||||
|
F::from_u64(lebs2ip::<L>(bits))
|
||||||
|
}
|
||||||
|
|
||||||
/// The u64 integer represented by an L-bit little-endian bitstring.
|
/// The u64 integer represented by an L-bit little-endian bitstring.
|
||||||
///
|
///
|
||||||
/// # Panics
|
/// # Panics
|
||||||
|
|
Loading…
Reference in New Issue