Minor refactors, cleanups, clippy fixes, docfixes.

Co-authored-by: Daira Hopwood <daira@jacaranda.org>
Co-authored-by: Jack Grigg <jack@electriccoin.co>
This commit is contained in:
therealyingtong 2021-07-08 13:14:53 +08:00
parent 96863c9f73
commit 22ec16f129
9 changed files with 132 additions and 167 deletions

View File

@ -284,8 +284,12 @@ pub struct EccScalarFixed {
/// A signed short scalar used for fixed-base scalar multiplication.
/// A short scalar must have magnitude in the range [0..2^64), with
/// a sign of either 1 or -1.
/// This is decomposed into 22 3-bit windows in little-endian order,
/// i.e. `windows` = [k_0, k_1, ..., k_21] (for a 64-bit magnitude)
/// This is decomposed into 3-bit windows in little-endian order
/// using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3)
/// for element α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}.
/// Each `a_i` is in the range [0..2^3).
///
/// `windows` = [k_0, k_1, ..., k_21] (for a 64-bit magnitude)
/// where `scalar = k_0 + k_1 * (2^3) + ... + k_84 * (2^3)^84` and
/// each `k_i` is in the range [0..2^3).
/// k_21 must be a single bit, i.e. 0 or 1.

View File

@ -1,8 +1,5 @@
use super::{add, CellValue, EccConfig, EccPoint, EccScalarVar, Var};
use crate::{
circuit::gadget::utilities::copy,
constants::{NUM_COMPLETE_BITS, T_Q},
};
use crate::{circuit::gadget::utilities::copy, constants::T_Q};
use std::ops::{Deref, Range};
use bigint::U256;
@ -20,12 +17,18 @@ mod complete;
mod incomplete;
mod overflow;
/// Number of bits for which complete addition needs to be used in variable-base
/// scalar multiplication
const NUM_COMPLETE_BITS: usize = 3;
// 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_RANGE: Range<usize> = 0..INCOMPLETE_LEN;
// Bits k_{254} to k_{4} inclusive are used in incomplete addition.
// The `hi` half is k_{254} to k_{130} inclusive (length 125 bits).
// (It is a coincidence that k_{130} matches the boundary of the
// overflow check described in [the book](https://zcash.github.io/halo2/design/gadgets/ecc/var-base-scalar-mul.html#overflow-check).)
const INCOMPLETE_HI_RANGE: Range<usize> = 0..(INCOMPLETE_LEN / 2);
// Bits k_{254} to k_{4} inclusive are used in incomplete addition.

View File

@ -16,6 +16,7 @@ use halo2::{
},
poly::Rotation,
};
use lazy_static::lazy_static;
use pasta_curves::{
arithmetic::{CurveAffine, FieldExt},
pallas,
@ -25,6 +26,13 @@ pub mod base_field_elem;
pub mod full_width;
pub mod short;
lazy_static! {
static ref TWO_SCALAR: pallas::Scalar = pallas::Scalar::from_u64(2);
// H = 2^3 (3-bit window)
static ref H_SCALAR: pallas::Scalar = pallas::Scalar::from_u64(constants::H as u64);
static ref H_BASE: pallas::Base = pallas::Base::from_u64(constants::H as u64);
}
// 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)]
@ -301,6 +309,57 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
Ok(())
}
fn process_window(
&self,
region: &mut Region<'_, pallas::Base>,
offset: usize,
w: usize,
k: Option<pallas::Scalar>,
k_usize: Option<usize>,
base: OrchardFixedBases,
) -> Result<EccPoint, Error> {
let base_value = base.generator();
let base_u = base.u();
// Compute [(k_w + 2) ⋅ 8^w]B
let mul_b = {
let mul_b =
k.map(|k| base_value * (k + *TWO_SCALAR) * H_SCALAR.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 = k_usize.map(|k| base_u[w].0[k]);
region.assign_advice(
|| "u",
self.u,
offset + w,
|| u_val.ok_or(Error::SynthesisError),
)?;
Ok(mul_b)
}
fn initialize_accumulator(
&self,
region: &mut Region<'_, pallas::Base>,
@ -311,60 +370,10 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
// 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 })
let w = 0;
let k0 = scalar.windows_field()[0];
let k0_usize = scalar.windows_usize()[0];
self.process_window(region, offset, w, k0, k0_usize, base)
}
fn add_incomplete(
@ -375,57 +384,18 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
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)]
for (w, (k, k_usize)) in scalar_windows_field[..(scalar_windows_field.len() - 1)]
.iter()
.zip(scalar_windows_usize[..(scalar_windows_field.len() - 1)].iter())
.enumerate()
// Skip k_0 (already processed).
.skip(1)
{
// 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),
)?;
let mul_b = self.process_window(region, offset, w, *k, *k_usize, base)?;
// Add to the accumulator
acc = self
@ -442,9 +412,6 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
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 =
@ -459,7 +426,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
// 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(&[
acc + (*TWO_SCALAR).pow(&[
constants::FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1,
0,
0,
@ -469,7 +436,7 @@ impl<const NUM_WINDOWS: usize> Config<NUM_WINDOWS> {
// `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);
.map(|k| k * (*H_SCALAR).pow(&[(NUM_WINDOWS - 1) as u64, 0, 0, 0]) - offset_acc);
let mul_b = {
let mul_b = scalar.map(|scalar| base.generator() * scalar);
@ -545,9 +512,9 @@ impl ScalarFixed {
.map(|idx| {
let z_cur = zs[idx].value();
let z_next = zs[idx + 1].value();
let word = z_cur.zip(z_next).map(|(z_cur, z_next)| {
z_cur - z_next * pallas::Base::from_u64(constants::H as u64)
});
let word = z_cur
.zip(z_next)
.map(|(z_cur, z_next)| z_cur - z_next * *H_BASE);
word.map(|word| pallas::Scalar::from_bytes(&word.to_bytes()).unwrap())
})
.collect::<Vec<_>>()

View File

@ -2,7 +2,8 @@ use super::super::{EccBaseFieldElemFixed, EccConfig, EccPoint, OrchardFixedBases
use crate::{
circuit::gadget::utilities::{
bitrange_subset, copy, lookup_range_check::LookupRangeCheckConfig, CellValue, Var,
bitrange_subset, copy, lookup_range_check::LookupRangeCheckConfig, range_check, CellValue,
Var,
},
constants::{self, util::decompose_scalar_fixed, T_P},
primitives::sinsemilla,
@ -37,7 +38,7 @@ impl From<&EccConfig> for Config {
let add_incomplete_advices = config.super_config.add_incomplete_config.advice_columns();
for canon_advice in config.canon_advices.iter() {
assert!(
!add_incomplete_advices.contains(&canon_advice),
!add_incomplete_advices.contains(canon_advice),
"Deconflict canon_advice columns with incomplete addition columns."
);
}
@ -48,29 +49,21 @@ impl From<&EccConfig> for Config {
impl Config {
pub fn create_gate(&self, meta: &mut ConstraintSystem<pallas::Base>) {
// Check that an expression is in the range [0..range),
// i.e. 0 ≤ word < range.
let range_check = |word: Expression<pallas::Base>, range: usize| {
(0..range).fold(Expression::Constant(pallas::Base::one()), |acc, i| {
acc * (word.clone() - Expression::Constant(pallas::Base::from_u64(i as u64)))
})
};
// Decompose the base field element α into three-bit windows
// using a running sum `z`, where z_{i+1} = (z_i - a_i) / (2^3)
// for α = a_0 + (2^3) a_1 + ... + (2^{3(84)}) a_{84}.
// for α = a_0 + 2^3 a_1 + ... + 2^{3*84} a_84.
//
// We set z_0 = α, which implies:
// z_1 = (α - a_0) / 2^3, (subtract the lowest 3 bits)
// = a_1 + (2^3) a_2 + ... + 2^{3(83)} a_{84},
// = a_1 + 2^3 a_2 + ... + 2^{3*83} a_84,
// z_2 = (z_1 - a_1) / 2^3
// = a_2 + (2^3) a_3 + ... + 2^{3(82)} a_{84},
// = a_2 + 2^3 a_3 + ... + 2^{3*82} a_84,
// ...,
// z_{84} = a_{84}
// z_n = (z_{84} - a_{84}) / 2^3
// z_84 = a_84
// z_n = (z_84 - a_84) / 2^3
// = 0.
//
// This gate checks that each a_i = z_i - z_{i+1} * (2^3) is within
// This gate checks that each a_i = z_i - z_{i+1} * 2^3 is within
// 3 bits.
//
// This gate also checks that this window uses the correct y_p and
@ -81,8 +74,8 @@ impl Config {
let z_cur = meta.query_advice(self.super_config.window, Rotation::cur());
let z_next = meta.query_advice(self.super_config.window, Rotation::next());
// z_{i+1} = (z_i - a_i) / (2^3)
// => a_i = z_i - (z_{i+1} * (2^3))
// z_{i+1} = (z_i - a_i) / 2^3
// => a_i = z_i - z_{i+1} * 2^3
let word = z_cur - z_next * pallas::Base::from_u64(constants::H as u64);
// (word - 7) * (word - 6) * ... * (word - 1) * word = 0
@ -108,7 +101,7 @@ impl Config {
// The last three bits of α.
let z_84_alpha = meta.query_advice(self.canon_advices[2], Rotation::prev());
// Decompose α into three pieces,
// Decompose α into three pieces, in little-endian order:
// α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit).
//
// α_0 is derived, not witnessed.
@ -152,8 +145,9 @@ impl Config {
// = 2^254 + 45560315531419706090280762371685220353.
// Note that t_p < 2^130.
//
// α has been decomposed into three pieces,
// α has been decomposed into three pieces in little-endian order:
// α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit).
// = α_0 + 2^252 α_1 + 2^254 α_2.
//
// If the MSB α_2 = 1, then:
// - α_2 = 1 => α_1 = 0, and
@ -259,8 +253,9 @@ impl Config {
// = 2^254 + 45560315531419706090280762371685220353.
// Note that t_p < 2^130.
//
// α has been decomposed into three pieces,
// α has been decomposed into three pieces in little-endian order:
// α = α_0 (252 bits) || α_1 (2 bits) || α_2 (1 bit).
// = α_0 + 2^252 α_1 + 2^254 α_2.
//
// If the MSB α_2 = 1, then:
// - α_2 = 1 => α_1 = 0, and
@ -274,14 +269,6 @@ impl Config {
let (alpha, running_sum) = (scalar.base_field_elem, &scalar.running_sum);
let z_43_alpha = running_sum[42];
let z_44_alpha = running_sum[43];
{
let a_43 = z_44_alpha
.value()
.zip(z_43_alpha.value())
.map(|(z_44, z_43)| z_43 - z_44 * pallas::Base::from_u64(8));
println!("a_43: {:?}", a_43);
}
let z_84_alpha = running_sum[83];
let z_85_alpha = running_sum[84];
@ -485,7 +472,7 @@ impl Config {
.zip(word)
.map(|(z_cur_val, word)| (z_cur_val - word) * eight_inv);
let cell = region.assign_advice(
|| format!("word {:?}", idx),
|| format!("z_{:?}", idx + 1),
self.super_config.window,
offset + idx,
|| z_next_val.ok_or(Error::SynthesisError),
@ -538,7 +525,7 @@ pub mod tests {
chip.clone(),
layouter.namespace(|| "commit_ivk_r"),
commit_ivk_r,
&zero,
zero,
)?;
// note_commit_r
@ -548,7 +535,7 @@ pub mod tests {
chip.clone(),
layouter.namespace(|| "note_commit_r"),
note_commit_r,
&zero,
zero,
)?;
// nullifier_k
@ -558,7 +545,7 @@ pub mod tests {
chip.clone(),
layouter.namespace(|| "nullifier_k"),
nullifier_k,
&zero,
zero,
)?;
// value_commit_r
@ -568,7 +555,7 @@ pub mod tests {
chip.clone(),
layouter.namespace(|| "value_commit_r"),
value_commit_r,
&zero,
zero,
)?;
// spend_auth_g
@ -578,7 +565,7 @@ pub mod tests {
chip,
layouter.namespace(|| "spend_auth_g"),
spend_auth_g,
&zero,
zero,
)?;
Ok(())
@ -637,7 +624,7 @@ pub mod tests {
)?;
let result =
base.mul_base_field_elem(layouter.namespace(|| "mul by zero"), scalar_fixed)?;
result.constrain_equal(layouter.namespace(|| "[0]B = 𝒪"), &zero)?;
result.constrain_equal(layouter.namespace(|| "[0]B = 𝒪"), zero)?;
}
// [-1]B is the largest base field element

View File

@ -88,7 +88,7 @@ pub mod tests {
chip.clone(),
layouter.namespace(|| "commit_ivk_r"),
commit_ivk_r,
&zero,
zero,
)?;
// note_commit_r
@ -98,7 +98,7 @@ pub mod tests {
chip.clone(),
layouter.namespace(|| "note_commit_r"),
note_commit_r,
&zero,
zero,
)?;
// nullifier_k
@ -108,7 +108,7 @@ pub mod tests {
chip.clone(),
layouter.namespace(|| "nullifier_k"),
nullifier_k,
&zero,
zero,
)?;
// value_commit_r
@ -118,7 +118,7 @@ pub mod tests {
chip.clone(),
layouter.namespace(|| "value_commit_r"),
value_commit_r,
&zero,
zero,
)?;
// spend_auth_g
@ -128,7 +128,7 @@ pub mod tests {
chip,
layouter.namespace(|| "spend_auth_g"),
spend_auth_g,
&zero,
zero,
)?;
Ok(())
@ -188,7 +188,7 @@ pub mod tests {
Some(scalar_fixed),
)?;
let result = base.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?;
result.constrain_equal(layouter.namespace(|| "[0]B = 𝒪"), &zero)?;
result.constrain_equal(layouter.namespace(|| "[0]B = 𝒪"), zero)?;
}
// [-1]B is the largest scalar field element.

View File

@ -194,7 +194,7 @@ pub mod tests {
Some(scalar_fixed),
)?;
let result = value_commit_v.mul(layouter.namespace(|| "mul by zero"), &scalar_fixed)?;
result.constrain_equal(layouter.namespace(|| "[0]B = 𝒪"), &zero)?;
result.constrain_equal(layouter.namespace(|| "[0]B = 𝒪"), zero)?;
}
// Random [a]B

View File

@ -1,9 +1,12 @@
use super::{CellValue, EccConfig, Var};
use crate::constants::{self, util};
use crate::{
circuit::gadget::utilities::range_check,
constants::{self, util},
};
use arrayvec::ArrayVec;
use halo2::{
circuit::Region,
plonk::{Advice, Column, ConstraintSystem, Error, Expression, Permutation, Selector},
plonk::{Advice, Column, ConstraintSystem, Error, Permutation, Selector},
poly::Rotation,
};
use pasta_curves::{arithmetic::FieldExt, pallas};
@ -38,11 +41,7 @@ impl Config {
// Constrain each window to a 3-bit value:
// 1 * (window - 0) * (window - 1) * ... * (window - 7)
let range_check =
(0..constants::H).fold(Expression::Constant(pallas::Base::one()), |acc, i| {
acc * (window.clone() - Expression::Constant(pallas::Base::from_u64(i as u64)))
});
vec![q_scalar_fixed * range_check]
vec![q_scalar_fixed * range_check(window, constants::H)]
});
}

View File

@ -1,7 +1,7 @@
use ff::PrimeFieldBits;
use halo2::{
circuit::{Cell, Layouter, Region},
plonk::{Advice, Column, Error, Permutation},
plonk::{Advice, Column, Error, Expression, Permutation},
};
use pasta_curves::arithmetic::FieldExt;
use std::array;
@ -123,3 +123,12 @@ pub fn bitrange_subset<F: FieldExt + PrimeFieldBits>(
F::from_bytes(&bytearray.try_into().unwrap()).unwrap()
}
/// Check that an expression is in the small range [0..range),
/// i.e. 0 ≤ word < range.
pub fn range_check<F: FieldExt>(word: Expression<F>, range: usize) -> Expression<F> {
(0..range)
.map(|i| Expression::Constant(F::from_u64(i as u64)))
.reduce(|acc, i| acc * (word.clone() - i))
.expect("range > 0")
}

View File

@ -82,10 +82,6 @@ pub const NUM_WINDOWS: usize =
pub const NUM_WINDOWS_SHORT: usize =
(L_VALUE + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE;
/// Number of bits for which complete addition needs to be used in variable-base
/// scalar multiplication
pub const NUM_COMPLETE_BITS: usize = 3;
/// For each fixed base, we calculate its scalar multiples in three-bit windows.
/// Each window will have $2^3 = 8$ points.
fn compute_window_table<C: CurveAffine>(base: C, num_windows: usize) -> Vec<[C; H]> {