mirror of https://github.com/zcash/orchard.git
Provide compute_lagrange_coeffs() functionality in ECCChip.
This involves moving helper functions from src/constants to a new module, ecc::chip::constants. Co-authored-by: Jack Grigg <jack@electriccoin.co>
This commit is contained in:
parent
85b481af35
commit
28f2d7a84b
|
@ -6,7 +6,6 @@ use crate::{
|
|||
primitives::sinsemilla,
|
||||
};
|
||||
use arrayvec::ArrayVec;
|
||||
use ff::PrimeField;
|
||||
|
||||
use ff::Field;
|
||||
use group::prime::PrimeCurveAffine;
|
||||
|
@ -20,35 +19,12 @@ use std::convert::TryInto;
|
|||
|
||||
pub(super) mod add;
|
||||
pub(super) mod add_incomplete;
|
||||
pub mod constants;
|
||||
pub(super) mod mul;
|
||||
pub(super) mod mul_fixed;
|
||||
pub(super) mod witness_point;
|
||||
|
||||
/// Window size for fixed-base scalar multiplication
|
||||
pub const FIXED_BASE_WINDOW_SIZE: usize = 3;
|
||||
|
||||
/// $2^{`FIXED_BASE_WINDOW_SIZE`}$
|
||||
pub const H: usize = 1 << FIXED_BASE_WINDOW_SIZE;
|
||||
|
||||
/// Number of windows for a full-width scalar
|
||||
pub const NUM_WINDOWS: usize =
|
||||
(pallas::Scalar::NUM_BITS as usize + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE;
|
||||
|
||||
/// Number of windows for a short signed scalar
|
||||
pub const NUM_WINDOWS_SHORT: usize =
|
||||
(L_SCALAR_SHORT + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE;
|
||||
|
||||
/// $\ell_\mathsf{value}$
|
||||
/// Number of bits in an unsigned short scalar.
|
||||
pub(crate) const L_SCALAR_SHORT: usize = 64;
|
||||
|
||||
/// The Pallas scalar field modulus is $q = 2^{254} + \mathsf{t_q}$.
|
||||
/// <https://github.com/zcash/pasta>
|
||||
pub(crate) const T_Q: u128 = 45560315531506369815346746415080538113;
|
||||
|
||||
/// The Pallas base field modulus is $p = 2^{254} + \mathsf{t_p}$.
|
||||
/// <https://github.com/zcash/pasta>
|
||||
pub(crate) const T_P: u128 = 45560315531419706090280762371685220353;
|
||||
pub use constants::*;
|
||||
|
||||
/// A curve point represented in affine (x, y) coordinates, or the
|
||||
/// identity represented as (0, 0).
|
||||
|
@ -184,16 +160,49 @@ pub struct EccConfig<FixedPoints: super::FixedPoints<pallas::Affine>> {
|
|||
pub lookup_config: LookupRangeCheckConfig<pallas::Base, { sinsemilla::K }>,
|
||||
}
|
||||
|
||||
/// A trait representing the kind of scalar used with a particular `FixedPoint`.
|
||||
///
|
||||
/// This trait exists because of limitations around const generics.
|
||||
pub trait ScalarKind {
|
||||
const NUM_WINDOWS: usize;
|
||||
}
|
||||
|
||||
/// Type marker representing a full-width scalar for use in fixed-base scalar
|
||||
/// multiplication.
|
||||
pub enum FullScalar {}
|
||||
impl ScalarKind for FullScalar {
|
||||
const NUM_WINDOWS: usize = NUM_WINDOWS;
|
||||
}
|
||||
|
||||
/// Type marker representing a signed 64-bit scalar for use in fixed-base scalar
|
||||
/// multiplication.
|
||||
pub enum ShortScalar {}
|
||||
impl ScalarKind for ShortScalar {
|
||||
const NUM_WINDOWS: usize = NUM_WINDOWS_SHORT;
|
||||
}
|
||||
|
||||
/// Type marker representing a base field element being used as a scalar in fixed-base
|
||||
/// scalar multiplication.
|
||||
pub enum BaseFieldElem {}
|
||||
impl ScalarKind for BaseFieldElem {
|
||||
const NUM_WINDOWS: usize = NUM_WINDOWS;
|
||||
}
|
||||
|
||||
/// Returns information about a fixed point.
|
||||
///
|
||||
/// TODO: When associated consts can be used as const generics, introduce a
|
||||
/// `const NUM_WINDOWS: usize` associated const, and return `NUM_WINDOWS`-sized
|
||||
/// arrays instead of `Vec`s.
|
||||
pub trait FixedPoint<C: CurveAffine>: std::fmt::Debug + Eq + Clone {
|
||||
type ScalarKind: ScalarKind;
|
||||
|
||||
fn generator(&self) -> C;
|
||||
fn u(&self) -> Vec<[[u8; 32]; H]>;
|
||||
fn z(&self) -> Vec<u64>;
|
||||
fn lagrange_coeffs(&self) -> Vec<[C::Base; H]>;
|
||||
|
||||
fn lagrange_coeffs(&self) -> Vec<[C::Base; H]> {
|
||||
compute_lagrange_coeffs(self.generator(), Self::ScalarKind::NUM_WINDOWS)
|
||||
}
|
||||
}
|
||||
|
||||
/// A chip implementing EccInstructions
|
||||
|
@ -353,9 +362,12 @@ impl EccBaseFieldElemFixed {
|
|||
|
||||
impl<Fixed: FixedPoints<pallas::Affine>> EccInstructions<pallas::Affine> for EccChip<Fixed>
|
||||
where
|
||||
<Fixed as FixedPoints<pallas::Affine>>::Base: FixedPoint<pallas::Affine>,
|
||||
<Fixed as FixedPoints<pallas::Affine>>::FullScalar: FixedPoint<pallas::Affine>,
|
||||
<Fixed as FixedPoints<pallas::Affine>>::ShortScalar: FixedPoint<pallas::Affine>,
|
||||
<Fixed as FixedPoints<pallas::Affine>>::Base:
|
||||
FixedPoint<pallas::Affine, ScalarKind = BaseFieldElem>,
|
||||
<Fixed as FixedPoints<pallas::Affine>>::FullScalar:
|
||||
FixedPoint<pallas::Affine, ScalarKind = FullScalar>,
|
||||
<Fixed as FixedPoints<pallas::Affine>>::ShortScalar:
|
||||
FixedPoint<pallas::Affine, ScalarKind = ShortScalar>,
|
||||
{
|
||||
type ScalarFixed = EccScalarFixed;
|
||||
type ScalarFixedShort = EccScalarFixedShort;
|
||||
|
|
|
@ -0,0 +1,273 @@
|
|||
use arrayvec::ArrayVec;
|
||||
use group::{
|
||||
ff::{Field, PrimeField},
|
||||
Curve,
|
||||
};
|
||||
use halo2::arithmetic::lagrange_interpolate;
|
||||
use pasta_curves::{
|
||||
arithmetic::{CurveAffine, FieldExt},
|
||||
pallas,
|
||||
};
|
||||
|
||||
/// Window size for fixed-base scalar multiplication
|
||||
pub const FIXED_BASE_WINDOW_SIZE: usize = 3;
|
||||
|
||||
/// $2^{`FIXED_BASE_WINDOW_SIZE`}$
|
||||
pub const H: usize = 1 << FIXED_BASE_WINDOW_SIZE;
|
||||
|
||||
/// Number of windows for a full-width scalar
|
||||
pub const NUM_WINDOWS: usize =
|
||||
(pallas::Scalar::NUM_BITS as usize + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE;
|
||||
|
||||
/// Number of windows for a short signed scalar
|
||||
pub const NUM_WINDOWS_SHORT: usize =
|
||||
(L_SCALAR_SHORT + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE;
|
||||
|
||||
/// $\ell_\mathsf{value}$
|
||||
/// Number of bits in an unsigned short scalar.
|
||||
pub(crate) const L_SCALAR_SHORT: usize = 64;
|
||||
|
||||
/// The Pallas scalar field modulus is $q = 2^{254} + \mathsf{t_q}$.
|
||||
/// <https://github.com/zcash/pasta>
|
||||
pub(crate) const T_Q: u128 = 45560315531506369815346746415080538113;
|
||||
|
||||
/// The Pallas base field modulus is $p = 2^{254} + \mathsf{t_p}$.
|
||||
/// <https://github.com/zcash/pasta>
|
||||
pub(crate) const T_P: u128 = 45560315531419706090280762371685220353;
|
||||
|
||||
/// For each fixed base, we calculate its scalar multiples in three-bit windows.
|
||||
/// Each window will have $2^3 = 8$ points.
|
||||
pub fn compute_window_table<C: CurveAffine>(base: C, num_windows: usize) -> Vec<[C; H]> {
|
||||
let mut window_table: Vec<[C; H]> = Vec::with_capacity(num_windows);
|
||||
|
||||
// Generate window table entries for all windows but the last.
|
||||
// For these first `num_windows - 1` windows, we compute the multiple [(k+2)*(2^3)^w]B.
|
||||
// Here, w ranges from [0..`num_windows - 1`)
|
||||
for w in 0..(num_windows - 1) {
|
||||
window_table.push(
|
||||
(0..H)
|
||||
.map(|k| {
|
||||
// scalar = (k+2)*(8^w)
|
||||
let scalar = C::Scalar::from(k as u64 + 2)
|
||||
* C::Scalar::from(H as u64).pow(&[w as u64, 0, 0, 0]);
|
||||
(base * scalar).to_affine()
|
||||
})
|
||||
.collect::<ArrayVec<C, H>>()
|
||||
.into_inner()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// Generate window table entries for the last window, w = `num_windows - 1`.
|
||||
// For the last window, we compute [k * (2^3)^w - sum]B, where sum is defined
|
||||
// as sum = \sum_{j = 0}^{`num_windows - 2`} 2^{3j+1}
|
||||
let sum = (0..(num_windows - 1)).fold(C::Scalar::zero(), |acc, j| {
|
||||
acc + C::Scalar::from(2).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * j as u64 + 1, 0, 0, 0])
|
||||
});
|
||||
window_table.push(
|
||||
(0..H)
|
||||
.map(|k| {
|
||||
// scalar = k * (2^3)^w - sum, where w = `num_windows - 1`
|
||||
let scalar = C::Scalar::from(k as u64)
|
||||
* C::Scalar::from(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0])
|
||||
- sum;
|
||||
(base * scalar).to_affine()
|
||||
})
|
||||
.collect::<ArrayVec<C, H>>()
|
||||
.into_inner()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
window_table
|
||||
}
|
||||
|
||||
/// For each window, we interpolate the $x$-coordinate.
|
||||
/// Here, we pre-compute and store the coefficients of the interpolation polynomial.
|
||||
pub fn compute_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) -> Vec<[C::Base; H]> {
|
||||
// We are interpolating over the 3-bit window, k \in [0..8)
|
||||
let points: Vec<_> = (0..H).map(|i| C::Base::from(i as u64)).collect();
|
||||
|
||||
let window_table = compute_window_table(base, num_windows);
|
||||
|
||||
window_table
|
||||
.iter()
|
||||
.map(|window_points| {
|
||||
let x_window_points: Vec<_> = window_points
|
||||
.iter()
|
||||
.map(|point| *point.coordinates().unwrap().x())
|
||||
.collect();
|
||||
lagrange_interpolate(&points, &x_window_points)
|
||||
.into_iter()
|
||||
.collect::<ArrayVec<C::Base, H>>()
|
||||
.into_inner()
|
||||
.unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// For each window, $z$ is a field element such that for each point $(x, y)$ in the window:
|
||||
/// - $z + y = u^2$ (some square in the field); and
|
||||
/// - $z - y$ is not a square.
|
||||
/// If successful, return a vector of `(z: u64, us: [C::Base; H])` for each window.
|
||||
///
|
||||
/// This function was used to generate the `z`s and `u`s for the Orchard fixed
|
||||
/// bases. The outputs of this function have been stored as constants, and it
|
||||
/// is not called anywhere in this codebase. However, we keep this function here
|
||||
/// as a utility for those who wish to use it with different parameters.
|
||||
pub fn find_zs_and_us<C: CurveAffine>(
|
||||
base: C,
|
||||
num_windows: usize,
|
||||
) -> Option<Vec<(u64, [C::Base; H])>> {
|
||||
// Closure to find z and u's for one window
|
||||
let find_z_and_us = |window_points: &[C]| {
|
||||
assert_eq!(H, window_points.len());
|
||||
|
||||
let ys: Vec<_> = window_points
|
||||
.iter()
|
||||
.map(|point| *point.coordinates().unwrap().y())
|
||||
.collect();
|
||||
(0..(1000 * (1 << (2 * H)))).find_map(|z| {
|
||||
ys.iter()
|
||||
.map(|&y| {
|
||||
if (-y + C::Base::from(z)).sqrt().is_none().into() {
|
||||
(y + C::Base::from(z)).sqrt().into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Option<ArrayVec<C::Base, H>>>()
|
||||
.map(|us| (z, us.into_inner().unwrap()))
|
||||
})
|
||||
};
|
||||
|
||||
let window_table = compute_window_table(base, num_windows);
|
||||
window_table
|
||||
.iter()
|
||||
.map(|window_points| find_z_and_us(window_points))
|
||||
.collect()
|
||||
}
|
||||
|
||||
// Test that the z-values and u-values satisfy the conditions:
|
||||
// 1. z + y = u^2,
|
||||
// 2. z - y is not a square
|
||||
// for the y-coordinate of each fixed-base multiple in each window.
|
||||
pub fn test_zs_and_us<C: CurveAffine>(base: C, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize) {
|
||||
let window_table = compute_window_table(base, num_windows);
|
||||
|
||||
for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) {
|
||||
for (u, point) in u.iter().zip(window_points.iter()) {
|
||||
let y = *point.coordinates().unwrap().y();
|
||||
let u = C::Base::from_bytes(u).unwrap();
|
||||
assert_eq!(C::Base::from_u64(*z) + y, u * u); // allow either square root
|
||||
assert!(bool::from((C::Base::from_u64(*z) - y).sqrt().is_none()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Test that Lagrange interpolation coefficients reproduce the correct x-coordinate
|
||||
// for each fixed-base multiple in each window.
|
||||
pub fn test_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) {
|
||||
/// Evaluate y = f(x) given the coefficients of f(x)
|
||||
fn evaluate<C: CurveAffine>(x: u8, coeffs: &[C::Base]) -> C::Base {
|
||||
let x = C::Base::from(x as u64);
|
||||
coeffs
|
||||
.iter()
|
||||
.rev()
|
||||
.cloned()
|
||||
.reduce(|acc, coeff| acc * x + coeff)
|
||||
.unwrap_or_else(C::Base::zero)
|
||||
}
|
||||
|
||||
let lagrange_coeffs = compute_lagrange_coeffs(base, num_windows);
|
||||
|
||||
// Check first 84 windows, i.e. `k_0, k_1, ..., k_83`
|
||||
for (idx, coeffs) in lagrange_coeffs[0..(num_windows - 1)].iter().enumerate() {
|
||||
// Test each three-bit chunk in this window.
|
||||
for bits in 0..(H as u8) {
|
||||
{
|
||||
// Interpolate the x-coordinate using this window's coefficients
|
||||
let interpolated_x = evaluate::<C>(bits, coeffs);
|
||||
|
||||
// Compute the actual x-coordinate of the multiple [(k+2)*(8^w)]B.
|
||||
let point = base
|
||||
* C::Scalar::from(bits as u64 + 2)
|
||||
* C::Scalar::from(H as u64).pow(&[idx as u64, 0, 0, 0]);
|
||||
let x = *point.to_affine().coordinates().unwrap().x();
|
||||
|
||||
// Check that the interpolated x-coordinate matches the actual one.
|
||||
assert_eq!(x, interpolated_x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check last window.
|
||||
for bits in 0..(H as u8) {
|
||||
// Interpolate the x-coordinate using the last window's coefficients
|
||||
let interpolated_x = evaluate::<C>(bits, &lagrange_coeffs[num_windows - 1]);
|
||||
|
||||
// Compute the actual x-coordinate of the multiple [k * (8^84) - offset]B,
|
||||
// where offset = \sum_{j = 0}^{83} 2^{3j+1}
|
||||
let offset = (0..(num_windows - 1)).fold(C::Scalar::zero(), |acc, w| {
|
||||
acc + C::Scalar::from(2).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, 0, 0])
|
||||
});
|
||||
let scalar = C::Scalar::from(bits as u64)
|
||||
* C::Scalar::from(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0])
|
||||
- offset;
|
||||
let point = base * scalar;
|
||||
let x = *point.to_affine().coordinates().unwrap().x();
|
||||
|
||||
// Check that the interpolated x-coordinate matches the actual one.
|
||||
assert_eq!(x, interpolated_x);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use group::{ff::Field, Curve, Group};
|
||||
use pasta_curves::{
|
||||
arithmetic::{CurveAffine, FieldExt},
|
||||
pallas,
|
||||
};
|
||||
use proptest::prelude::*;
|
||||
|
||||
use super::{compute_window_table, find_zs_and_us, test_lagrange_coeffs, H, NUM_WINDOWS};
|
||||
|
||||
prop_compose! {
|
||||
/// Generate an arbitrary Pallas point.
|
||||
pub fn arb_point()(bytes in prop::array::uniform32(0u8..)) -> pallas::Point {
|
||||
// Instead of rejecting out-of-range bytes, let's reduce them.
|
||||
let mut buf = [0; 64];
|
||||
buf[..32].copy_from_slice(&bytes);
|
||||
let scalar = pallas::Scalar::from_bytes_wide(&buf);
|
||||
pallas::Point::generator() * scalar
|
||||
}
|
||||
}
|
||||
|
||||
proptest! {
|
||||
#[test]
|
||||
fn lagrange_coeffs(
|
||||
base in arb_point(),
|
||||
) {
|
||||
test_lagrange_coeffs(base.to_affine(), NUM_WINDOWS);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn zs_and_us() {
|
||||
let base = pallas::Point::random(rand::rngs::OsRng);
|
||||
let (z, u): (Vec<u64>, Vec<[pallas::Base; H]>) =
|
||||
find_zs_and_us(base.to_affine(), NUM_WINDOWS)
|
||||
.unwrap()
|
||||
.into_iter()
|
||||
.unzip();
|
||||
let window_table = compute_window_table(base.to_affine(), NUM_WINDOWS);
|
||||
|
||||
for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) {
|
||||
for (u, point) in u.iter().zip(window_points.iter()) {
|
||||
let y = *point.coordinates().unwrap().y();
|
||||
assert_eq!(pallas::Base::from(*z) + y, u * u); // allow either square root
|
||||
assert!(bool::from((pallas::Base::from(*z) - y).sqrt().is_none()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -25,8 +25,8 @@ pub mod short;
|
|||
lazy_static! {
|
||||
static ref TWO_SCALAR: pallas::Scalar = pallas::Scalar::from(2);
|
||||
// H = 2^3 (3-bit window)
|
||||
static ref H_SCALAR: pallas::Scalar = pallas::Scalar::from_u64(H as u64);
|
||||
static ref H_BASE: pallas::Base = pallas::Base::from_u64(H as u64);
|
||||
static ref H_SCALAR: pallas::Scalar = pallas::Scalar::from(H as u64);
|
||||
static ref H_BASE: pallas::Base = pallas::Base::from(H as u64);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||
|
@ -259,7 +259,7 @@ impl<FixedPoints: super::FixedPoints<pallas::Affine>> Config<FixedPoints> {
|
|||
window + offset,
|
||||
|| {
|
||||
let z = &constants.as_ref().unwrap().1;
|
||||
Ok(pallas::Base::from_u64(z[window]))
|
||||
Ok(pallas::Base::from(z[window]))
|
||||
},
|
||||
)?;
|
||||
}
|
||||
|
|
|
@ -182,7 +182,7 @@ pub mod tests {
|
|||
use rand::rngs::OsRng;
|
||||
|
||||
use crate::circuit::gadget::ecc::{
|
||||
chip::{EccChip, FixedPoint as FixedPointTrait, H},
|
||||
chip::{EccChip, FixedPoint as _, H},
|
||||
FixedPoint, NonIdentityPoint, Point,
|
||||
};
|
||||
use crate::constants::{OrchardFixedBases, OrchardFixedBasesFull};
|
||||
|
|
|
@ -216,7 +216,7 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use group::ff::Field;
|
||||
use group::ff::{Field, PrimeField};
|
||||
use halo2::{
|
||||
circuit::{Layouter, SimpleFloorPlanner},
|
||||
dev::{MockProver, VerifyFailure},
|
||||
|
@ -225,11 +225,11 @@ mod tests {
|
|||
use pasta_curves::{arithmetic::FieldExt, pallas};
|
||||
use rand::rngs::OsRng;
|
||||
|
||||
const FIXED_BASE_WINDOW_SIZE: usize = 3;
|
||||
const NUM_WINDOWS: usize = 85;
|
||||
const NUM_WINDOWS_SHORT: usize = 22;
|
||||
const L_BASE: usize = 255;
|
||||
const L_SHORT: usize = 64;
|
||||
use crate::circuit::gadget::ecc::chip::{
|
||||
FIXED_BASE_WINDOW_SIZE, L_SCALAR_SHORT as L_SHORT, NUM_WINDOWS, NUM_WINDOWS_SHORT,
|
||||
};
|
||||
|
||||
const L_BASE: usize = pallas::Base::NUM_BITS as usize;
|
||||
|
||||
#[test]
|
||||
fn test_running_sum() {
|
||||
|
|
|
@ -5,7 +5,6 @@ pub mod util;
|
|||
|
||||
pub use fixed_bases::{NullifierK, OrchardFixedBases, OrchardFixedBasesFull, ValueCommitV, H};
|
||||
pub use sinsemilla::{OrchardCommitDomains, OrchardHashDomains};
|
||||
pub use util::{evaluate, gen_const_array};
|
||||
|
||||
/// $\mathsf{MerkleDepth^{Orchard}}$
|
||||
pub(crate) const MERKLE_DEPTH_ORCHARD: usize = 32;
|
||||
|
|
|
@ -1,16 +1,12 @@
|
|||
//! Orchard fixed bases.
|
||||
use super::{L_ORCHARD_SCALAR, L_VALUE};
|
||||
use crate::circuit::gadget::ecc::{chip::FixedPoint, FixedPoints};
|
||||
|
||||
use arrayvec::ArrayVec;
|
||||
use ff::Field;
|
||||
use group::Curve;
|
||||
use halo2::arithmetic::lagrange_interpolate;
|
||||
use pasta_curves::{
|
||||
arithmetic::{CurveAffine, FieldExt},
|
||||
pallas,
|
||||
use crate::circuit::gadget::ecc::{
|
||||
chip::{BaseFieldElem, FixedPoint, FullScalar, ShortScalar},
|
||||
FixedPoints,
|
||||
};
|
||||
|
||||
use pasta_curves::pallas;
|
||||
|
||||
pub mod commit_ivk_r;
|
||||
pub mod note_commit_r;
|
||||
pub mod nullifier_k;
|
||||
|
@ -51,120 +47,6 @@ pub const NUM_WINDOWS: usize =
|
|||
pub const NUM_WINDOWS_SHORT: usize =
|
||||
(L_VALUE + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE;
|
||||
|
||||
/// 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]> {
|
||||
let mut window_table: Vec<[C; H]> = Vec::with_capacity(num_windows);
|
||||
|
||||
// Generate window table entries for all windows but the last.
|
||||
// For these first `num_windows - 1` windows, we compute the multiple [(k+2)*(2^3)^w]B.
|
||||
// Here, w ranges from [0..`num_windows - 1`)
|
||||
for w in 0..(num_windows - 1) {
|
||||
window_table.push(
|
||||
(0..H)
|
||||
.map(|k| {
|
||||
// scalar = (k+2)*(8^w)
|
||||
let scalar = C::ScalarExt::from_u64(k as u64 + 2)
|
||||
* C::ScalarExt::from_u64(H as u64).pow(&[w as u64, 0, 0, 0]);
|
||||
(base * scalar).to_affine()
|
||||
})
|
||||
.collect::<ArrayVec<C, H>>()
|
||||
.into_inner()
|
||||
.unwrap(),
|
||||
);
|
||||
}
|
||||
|
||||
// Generate window table entries for the last window, w = `num_windows - 1`.
|
||||
// For the last window, we compute [k * (2^3)^w - sum]B, where sum is defined
|
||||
// as sum = \sum_{j = 0}^{`num_windows - 2`} 2^{3j+1}
|
||||
let sum = (0..(num_windows - 1)).fold(C::ScalarExt::zero(), |acc, j| {
|
||||
acc + C::ScalarExt::from_u64(2).pow(&[
|
||||
FIXED_BASE_WINDOW_SIZE as u64 * j as u64 + 1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
])
|
||||
});
|
||||
window_table.push(
|
||||
(0..H)
|
||||
.map(|k| {
|
||||
// scalar = k * (2^3)^w - sum, where w = `num_windows - 1`
|
||||
let scalar = C::ScalarExt::from_u64(k as u64)
|
||||
* C::ScalarExt::from_u64(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0])
|
||||
- sum;
|
||||
(base * scalar).to_affine()
|
||||
})
|
||||
.collect::<ArrayVec<C, H>>()
|
||||
.into_inner()
|
||||
.unwrap(),
|
||||
);
|
||||
|
||||
window_table
|
||||
}
|
||||
|
||||
/// For each window, we interpolate the $x$-coordinate.
|
||||
/// Here, we pre-compute and store the coefficients of the interpolation polynomial.
|
||||
fn compute_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) -> Vec<[C::Base; H]> {
|
||||
// We are interpolating over the 3-bit window, k \in [0..8)
|
||||
let points: Vec<_> = (0..H).map(|i| C::Base::from_u64(i as u64)).collect();
|
||||
|
||||
let window_table = compute_window_table(base, num_windows);
|
||||
|
||||
window_table
|
||||
.iter()
|
||||
.map(|window_points| {
|
||||
let x_window_points: Vec<_> = window_points
|
||||
.iter()
|
||||
.map(|point| *point.coordinates().unwrap().x())
|
||||
.collect();
|
||||
lagrange_interpolate(&points, &x_window_points)
|
||||
.into_iter()
|
||||
.collect::<ArrayVec<C::Base, H>>()
|
||||
.into_inner()
|
||||
.unwrap()
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// For each window, $z$ is a field element such that for each point $(x, y)$ in the window:
|
||||
/// - $z + y = u^2$ (some square in the field); and
|
||||
/// - $z - y$ is not a square.
|
||||
/// If successful, return a vector of `(z: u64, us: [C::Base; H])` for each window.
|
||||
///
|
||||
/// This function was used to generate the `z`s and `u`s for the Orchard fixed
|
||||
/// bases. The outputs of this function have been stored as constants, and it
|
||||
/// is not called anywhere in this codebase. However, we keep this function here
|
||||
/// as a utility for those who wish to use it with different parameters.
|
||||
fn find_zs_and_us<C: CurveAffine>(base: C, num_windows: usize) -> Option<Vec<(u64, [C::Base; H])>> {
|
||||
// Closure to find z and u's for one window
|
||||
let find_z_and_us = |window_points: &[C]| {
|
||||
assert_eq!(H, window_points.len());
|
||||
|
||||
let ys: Vec<_> = window_points
|
||||
.iter()
|
||||
.map(|point| *point.coordinates().unwrap().y())
|
||||
.collect();
|
||||
(0..(1000 * (1 << (2 * H)))).find_map(|z| {
|
||||
ys.iter()
|
||||
.map(|&y| {
|
||||
if (-y + C::Base::from_u64(z)).sqrt().is_none().into() {
|
||||
(y + C::Base::from_u64(z)).sqrt().into()
|
||||
} else {
|
||||
None
|
||||
}
|
||||
})
|
||||
.collect::<Option<ArrayVec<C::Base, H>>>()
|
||||
.map(|us| (z, us.into_inner().unwrap()))
|
||||
})
|
||||
};
|
||||
|
||||
let window_table = compute_window_table(base, num_windows);
|
||||
window_table
|
||||
.iter()
|
||||
.map(|window_points| find_z_and_us(window_points))
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
|
||||
// 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.
|
||||
|
@ -216,6 +98,8 @@ impl FixedPoints<pallas::Affine> for OrchardFixedBases {
|
|||
}
|
||||
|
||||
impl FixedPoint<pallas::Affine> for OrchardFixedBasesFull {
|
||||
type ScalarKind = FullScalar;
|
||||
|
||||
fn generator(&self) -> pallas::Affine {
|
||||
match self {
|
||||
Self::CommitIvkR => commit_ivk_r::generator(),
|
||||
|
@ -242,13 +126,11 @@ impl FixedPoint<pallas::Affine> for OrchardFixedBasesFull {
|
|||
Self::SpendAuthG => spend_auth_g::Z.to_vec(),
|
||||
}
|
||||
}
|
||||
|
||||
fn lagrange_coeffs(&self) -> Vec<[pallas::Base; H]> {
|
||||
compute_lagrange_coeffs(self.generator(), NUM_WINDOWS)
|
||||
}
|
||||
}
|
||||
|
||||
impl FixedPoint<pallas::Affine> for NullifierK {
|
||||
type ScalarKind = BaseFieldElem;
|
||||
|
||||
fn generator(&self) -> pallas::Affine {
|
||||
nullifier_k::generator()
|
||||
}
|
||||
|
@ -260,13 +142,11 @@ impl FixedPoint<pallas::Affine> for NullifierK {
|
|||
fn z(&self) -> Vec<u64> {
|
||||
nullifier_k::Z.to_vec()
|
||||
}
|
||||
|
||||
fn lagrange_coeffs(&self) -> Vec<[pallas::Base; H]> {
|
||||
compute_lagrange_coeffs(self.generator(), NUM_WINDOWS)
|
||||
}
|
||||
}
|
||||
|
||||
impl FixedPoint<pallas::Affine> for ValueCommitV {
|
||||
type ScalarKind = ShortScalar;
|
||||
|
||||
fn generator(&self) -> pallas::Affine {
|
||||
value_commit_v::generator()
|
||||
}
|
||||
|
@ -278,78 +158,4 @@ impl FixedPoint<pallas::Affine> for ValueCommitV {
|
|||
fn z(&self) -> Vec<u64> {
|
||||
value_commit_v::Z_SHORT.to_vec()
|
||||
}
|
||||
|
||||
fn lagrange_coeffs(&self) -> Vec<[pallas::Base; H]> {
|
||||
compute_lagrange_coeffs(self.generator(), NUM_WINDOWS_SHORT)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
// Test that Lagrange interpolation coefficients reproduce the correct x-coordinate
|
||||
// for each fixed-base multiple in each window.
|
||||
fn test_lagrange_coeffs<C: CurveAffine>(base: C, num_windows: usize) {
|
||||
let lagrange_coeffs = compute_lagrange_coeffs(base, num_windows);
|
||||
|
||||
// Check first 84 windows, i.e. `k_0, k_1, ..., k_83`
|
||||
for (idx, coeffs) in lagrange_coeffs[0..(num_windows - 1)].iter().enumerate() {
|
||||
// Test each three-bit chunk in this window.
|
||||
for bits in 0..(1 << FIXED_BASE_WINDOW_SIZE) {
|
||||
{
|
||||
// Interpolate the x-coordinate using this window's coefficients
|
||||
let interpolated_x = super::evaluate::<C>(bits, coeffs);
|
||||
|
||||
// Compute the actual x-coordinate of the multiple [(k+2)*(8^w)]B.
|
||||
let point = base
|
||||
* C::Scalar::from_u64(bits as u64 + 2)
|
||||
* C::Scalar::from_u64(H as u64).pow(&[idx as u64, 0, 0, 0]);
|
||||
let x = *point.to_affine().coordinates().unwrap().x();
|
||||
|
||||
// Check that the interpolated x-coordinate matches the actual one.
|
||||
assert_eq!(x, interpolated_x);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Check last window.
|
||||
for bits in 0..(1 << FIXED_BASE_WINDOW_SIZE) {
|
||||
// Interpolate the x-coordinate using the last window's coefficients
|
||||
let interpolated_x = super::evaluate::<C>(bits, &lagrange_coeffs[num_windows - 1]);
|
||||
|
||||
// Compute the actual x-coordinate of the multiple [k * (8^84) - offset]B,
|
||||
// where offset = \sum_{j = 0}^{83} 2^{3j+1}
|
||||
let offset = (0..(num_windows - 1)).fold(C::Scalar::zero(), |acc, w| {
|
||||
acc + C::Scalar::from_u64(2).pow(&[
|
||||
FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
])
|
||||
});
|
||||
let scalar = C::Scalar::from_u64(bits as u64)
|
||||
* C::Scalar::from_u64(H as u64).pow(&[(num_windows - 1) as u64, 0, 0, 0])
|
||||
- offset;
|
||||
let point = base * scalar;
|
||||
let x = *point.to_affine().coordinates().unwrap().x();
|
||||
|
||||
// Check that the interpolated x-coordinate matches the actual one.
|
||||
assert_eq!(x, interpolated_x);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
// Test that the z-values and u-values satisfy the conditions:
|
||||
// 1. z + y = u^2,
|
||||
// 2. z - y is not a square
|
||||
// for the y-coordinate of each fixed-base multiple in each window.
|
||||
fn test_zs_and_us<C: CurveAffine>(base: C, z: &[u64], u: &[[[u8; 32]; H]], num_windows: usize) {
|
||||
let window_table = compute_window_table(base, num_windows);
|
||||
|
||||
for ((u, z), window_points) in u.iter().zip(z.iter()).zip(window_table) {
|
||||
for (u, point) in u.iter().zip(window_points.iter()) {
|
||||
let y = *point.coordinates().unwrap().y();
|
||||
let u = C::Base::from_bytes(u).unwrap();
|
||||
assert_eq!(C::Base::from_u64(*z) + y, u * u); // allow either square root
|
||||
assert!(bool::from((C::Base::from_u64(*z) - y).sqrt().is_none()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2928,10 +2928,9 @@ pub fn generator() -> pallas::Affine {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{
|
||||
test_lagrange_coeffs, test_zs_and_us, COMMIT_IVK_PERSONALIZATION, NUM_WINDOWS,
|
||||
};
|
||||
use super::super::{COMMIT_IVK_PERSONALIZATION, NUM_WINDOWS};
|
||||
use super::*;
|
||||
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
|
||||
use crate::primitives::sinsemilla::CommitDomain;
|
||||
use group::Curve;
|
||||
use pasta_curves::{arithmetic::CurveAffine, pallas};
|
||||
|
|
|
@ -2928,11 +2928,11 @@ pub fn generator() -> pallas::Affine {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{
|
||||
test_lagrange_coeffs, test_zs_and_us, NOTE_COMMITMENT_PERSONALIZATION, NUM_WINDOWS,
|
||||
};
|
||||
use super::super::{NOTE_COMMITMENT_PERSONALIZATION, NUM_WINDOWS};
|
||||
use super::*;
|
||||
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
|
||||
use crate::primitives::sinsemilla::CommitDomain;
|
||||
|
||||
use group::Curve;
|
||||
use pasta_curves::{arithmetic::CurveAffine, pallas};
|
||||
|
||||
|
|
|
@ -2927,10 +2927,9 @@ pub fn generator() -> pallas::Affine {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{
|
||||
test_lagrange_coeffs, test_zs_and_us, NUM_WINDOWS, ORCHARD_PERSONALIZATION,
|
||||
};
|
||||
use super::super::{NUM_WINDOWS, ORCHARD_PERSONALIZATION};
|
||||
use super::*;
|
||||
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
|
||||
use group::Curve;
|
||||
use pasta_curves::{arithmetic::CurveExt, pallas};
|
||||
|
||||
|
|
|
@ -2929,10 +2929,9 @@ pub fn generator() -> pallas::Affine {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{
|
||||
test_lagrange_coeffs, test_zs_and_us, NUM_WINDOWS, ORCHARD_PERSONALIZATION,
|
||||
};
|
||||
use super::super::{NUM_WINDOWS, ORCHARD_PERSONALIZATION};
|
||||
use super::*;
|
||||
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
|
||||
use group::Curve;
|
||||
use pasta_curves::{
|
||||
arithmetic::{CurveAffine, CurveExt},
|
||||
|
|
|
@ -2929,10 +2929,9 @@ pub fn generator() -> pallas::Affine {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{
|
||||
test_lagrange_coeffs, test_zs_and_us, NUM_WINDOWS, VALUE_COMMITMENT_PERSONALIZATION,
|
||||
};
|
||||
use super::super::{NUM_WINDOWS, VALUE_COMMITMENT_PERSONALIZATION};
|
||||
use super::*;
|
||||
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
|
||||
use group::Curve;
|
||||
use pasta_curves::{
|
||||
arithmetic::{CurveAffine, CurveExt},
|
||||
|
|
|
@ -782,10 +782,9 @@ pub fn generator() -> pallas::Affine {
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::{
|
||||
test_lagrange_coeffs, test_zs_and_us, NUM_WINDOWS_SHORT, VALUE_COMMITMENT_PERSONALIZATION,
|
||||
};
|
||||
use super::super::{NUM_WINDOWS_SHORT, VALUE_COMMITMENT_PERSONALIZATION};
|
||||
use super::*;
|
||||
use crate::circuit::gadget::ecc::chip::constants::{test_lagrange_coeffs, test_zs_and_us};
|
||||
use group::Curve;
|
||||
use pasta_curves::{
|
||||
arithmetic::{CurveAffine, CurveExt},
|
||||
|
@ -803,13 +802,13 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn lagrange_coeffs_short() {
|
||||
fn lagrange_coeffs() {
|
||||
let base = super::generator();
|
||||
test_lagrange_coeffs(base, NUM_WINDOWS_SHORT);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn z_short() {
|
||||
fn z() {
|
||||
let base = super::generator();
|
||||
test_zs_and_us(base, &Z_SHORT, &U_SHORT, NUM_WINDOWS_SHORT);
|
||||
}
|
||||
|
|
|
@ -1,17 +1,3 @@
|
|||
use ff::Field;
|
||||
use halo2::arithmetic::CurveAffine;
|
||||
|
||||
/// Evaluate y = f(x) given the coefficients of f(x)
|
||||
pub fn evaluate<C: CurveAffine>(x: u8, coeffs: &[C::Base]) -> C::Base {
|
||||
let x = C::Base::from(x as u64);
|
||||
coeffs
|
||||
.iter()
|
||||
.rev()
|
||||
.cloned()
|
||||
.reduce(|acc, coeff| acc * x + coeff)
|
||||
.unwrap_or_else(C::Base::zero)
|
||||
}
|
||||
|
||||
/// Takes in an FnMut closure and returns a constant-length array with elements of
|
||||
/// type `Output`.
|
||||
pub fn gen_const_array<Output: Copy + Default, const LEN: usize>(
|
||||
|
|
Loading…
Reference in New Issue