Return full running sum [z_0, ..., z_W] from lookup_range_check and decompose_running_sum.

Previously, these two helpers were returning different outputs.
They have now been standardised to return only the full running
sum.

Note the z_0 is the original element being decomposed by the
helper.
This commit is contained in:
therealyingtong 2021-07-24 23:54:54 +08:00
parent 092cc389bb
commit 4d1cd2651a
8 changed files with 54 additions and 48 deletions

View File

@ -291,7 +291,7 @@ pub struct EccScalarFixed {
pub struct EccScalarFixedShort { pub struct EccScalarFixedShort {
magnitude: CellValue<pallas::Base>, magnitude: CellValue<pallas::Base>,
sign: CellValue<pallas::Base>, sign: CellValue<pallas::Base>,
running_sum: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS_SHORT }>, running_sum: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS_SHORT + 1 }>,
} }
/// A base field element used for fixed-base scalar multiplication. /// A base field element used for fixed-base scalar multiplication.
@ -300,13 +300,13 @@ pub struct EccScalarFixedShort {
/// for element α = a_0 + (2^3) a_1 + ... + (2^{3(n-1)}) a_{n-1}. /// 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). /// Each `a_i` is in the range [0..2^3).
/// ///
/// `windows` = [z_1, ..., z_85], where we expect z_85 = 0. /// `running_sum` = [z_0, ..., z_85], where we expect z_85 = 0.
/// Since z_0 is initialized as the scalar α, we store it as /// Since z_0 is initialized as the scalar α, we store it as
/// `base_field_elem`. /// `base_field_elem`.
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct EccBaseFieldElemFixed { struct EccBaseFieldElemFixed {
base_field_elem: CellValue<pallas::Base>, base_field_elem: CellValue<pallas::Base>,
running_sum: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS }>, running_sum: ArrayVec<CellValue<pallas::Base>, { constants::NUM_WINDOWS + 1 }>,
} }
impl EccBaseFieldElemFixed { impl EccBaseFieldElemFixed {

View File

@ -511,16 +511,8 @@ impl ScalarFixed {
.collect::<Vec<_>>() .collect::<Vec<_>>()
}; };
match self { match self {
Self::BaseFieldElem(scalar) => { Self::BaseFieldElem(scalar) => running_sum_to_windows(scalar.running_sum.to_vec()),
let mut zs = vec![scalar.base_field_elem]; Self::Short(scalar) => running_sum_to_windows(scalar.running_sum.to_vec()),
zs.extend_from_slice(&scalar.running_sum);
running_sum_to_windows(zs)
}
Self::Short(scalar) => {
let mut zs = vec![scalar.magnitude];
zs.extend_from_slice(&scalar.running_sum);
running_sum_to_windows(zs)
}
Self::FullWidth(scalar) => scalar Self::FullWidth(scalar) => scalar
.windows .windows
.iter() .iter()

View File

@ -166,7 +166,7 @@ impl Config {
// Decompose scalar // Decompose scalar
let scalar = { let scalar = {
let (base_field_elem, running_sum) = self.running_sum_config.copy_decompose( let running_sum = self.running_sum_config.copy_decompose(
&mut region, &mut region,
offset, offset,
scalar, scalar,
@ -175,7 +175,7 @@ impl Config {
constants::NUM_WINDOWS, constants::NUM_WINDOWS,
)?; )?;
EccBaseFieldElemFixed { EccBaseFieldElemFixed {
base_field_elem, base_field_elem: running_sum[0],
running_sum: (*running_sum).as_slice().try_into().unwrap(), running_sum: (*running_sum).as_slice().try_into().unwrap(),
} }
}; };
@ -240,9 +240,9 @@ impl Config {
// => z_13_alpha_0_prime = 0 // => z_13_alpha_0_prime = 0
// //
let (alpha, running_sum) = (scalar.base_field_elem, &scalar.running_sum); let (alpha, running_sum) = (scalar.base_field_elem, &scalar.running_sum);
let z_43_alpha = running_sum[42]; let z_43_alpha = running_sum[43];
let z_44_alpha = running_sum[43]; let z_44_alpha = running_sum[44];
let z_84_alpha = running_sum[83]; let z_84_alpha = running_sum[84];
// α_0 = α - z_84_alpha * 2^252 // α_0 = α - z_84_alpha * 2^252
let alpha_0 = alpha let alpha_0 = alpha
@ -260,12 +260,13 @@ impl Config {
let t_p = pallas::Base::from_u128(T_P); let t_p = pallas::Base::from_u128(T_P);
alpha_0 + two_pow_130 - t_p alpha_0 + two_pow_130 - t_p
}); });
let (alpha_0_prime, zs) = self.lookup_config.witness_check( let zs = self.lookup_config.witness_check(
layouter.namespace(|| "Lookup range check alpha_0 + 2^130 - t_p"), layouter.namespace(|| "Lookup range check alpha_0 + 2^130 - t_p"),
alpha_0_prime, alpha_0_prime,
13, 13,
false, false,
)?; )?;
let alpha_0_prime = zs[0];
(alpha_0_prime, zs[13]) (alpha_0_prime, zs[13])
}; };

View File

@ -80,7 +80,7 @@ impl Config {
let (magnitude, sign) = magnitude_sign; let (magnitude, sign) = magnitude_sign;
// Decompose magnitude // Decompose magnitude
let (magnitude, running_sum) = self.running_sum_config.copy_decompose( let running_sum = self.running_sum_config.copy_decompose(
region, region,
offset, offset,
magnitude, magnitude,
@ -150,7 +150,7 @@ impl Config {
// Copy last window to `u` column. // Copy last window to `u` column.
// (Although the last window is not a `u` value; we are copying it into the `u` // (Although the last window is not a `u` value; we are copying it into the `u`
// column because there is an available cell there.) // column because there is an available cell there.)
let z_21 = scalar.running_sum[20]; let z_21 = scalar.running_sum[21];
copy( copy(
&mut region, &mut region,
|| "last_window", || "last_window",

View File

@ -392,12 +392,13 @@ impl CommitIvkConfig {
let t_p = pallas::Base::from_u128(T_P); let t_p = pallas::Base::from_u128(T_P);
a + two_pow_130 - t_p a + two_pow_130 - t_p
}); });
let (a_prime, zs) = self.sinsemilla_config.lookup_config.witness_check( let zs = self.sinsemilla_config.lookup_config.witness_check(
layouter.namespace(|| "Decompose low 130 bits of (a + 2^130 - t_P)"), layouter.namespace(|| "Decompose low 130 bits of (a + 2^130 - t_P)"),
a_prime, a_prime,
13, 13,
false, false,
)?; )?;
let a_prime = zs[0];
assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z13_a] assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z13_a]
Ok((a_prime, zs[13])) Ok((a_prime, zs[13]))
@ -428,12 +429,13 @@ impl CommitIvkConfig {
let t_p = pallas::Base::from_u128(T_P); let t_p = pallas::Base::from_u128(T_P);
b_2 + c * two_pow_5 + two_pow_140 - t_p b_2 + c * two_pow_5 + two_pow_140 - t_p
}); });
let (b2_c_prime, zs) = self.sinsemilla_config.lookup_config.witness_check( let zs = self.sinsemilla_config.lookup_config.witness_check(
layouter.namespace(|| "Decompose low 140 bits of (b_2 + c * 2^5 + 2^140 - t_P)"), layouter.namespace(|| "Decompose low 140 bits of (b_2 + c * 2^5 + 2^140 - t_P)"),
b2_c_prime, b2_c_prime,
14, 14,
false, false,
)?; )?;
let b2_c_prime = zs[0];
assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z14] assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z14]
Ok((b2_c_prime, zs[14])) Ok((b2_c_prime, zs[14]))

View File

@ -710,12 +710,13 @@ impl NoteCommitConfig {
let t_p = pallas::Base::from_u128(T_P); let t_p = pallas::Base::from_u128(T_P);
a + two_pow_130 - t_p a + two_pow_130 - t_p
}); });
let (a_prime, zs) = self.sinsemilla_config.lookup_config.witness_check( let zs = self.sinsemilla_config.lookup_config.witness_check(
layouter.namespace(|| "Decompose low 130 bits of (a + 2^130 - t_P)"), layouter.namespace(|| "Decompose low 130 bits of (a + 2^130 - t_P)"),
a_prime, a_prime,
13, 13,
false, false,
)?; )?;
let a_prime = zs[0];
assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z_13] assert_eq!(zs.len(), 14); // [z_0, z_1, ..., z_13]
Ok((a_prime, zs[13])) Ok((a_prime, zs[13]))
@ -748,12 +749,13 @@ impl NoteCommitConfig {
b_3 + (two_pow_4 * c) + two_pow_140 - t_p b_3 + (two_pow_4 * c) + two_pow_140 - t_p
}); });
let (b3_c_prime, zs) = self.sinsemilla_config.lookup_config.witness_check( let zs = self.sinsemilla_config.lookup_config.witness_check(
layouter.namespace(|| "Decompose low 140 bits of (b_3 + 2^4 c + 2^140 - t_P)"), layouter.namespace(|| "Decompose low 140 bits of (b_3 + 2^4 c + 2^140 - t_P)"),
b3_c_prime, b3_c_prime,
14, 14,
false, false,
)?; )?;
let b3_c_prime = zs[0];
assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14] assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14]
Ok((b3_c_prime, zs[14])) Ok((b3_c_prime, zs[14]))
@ -787,12 +789,13 @@ impl NoteCommitConfig {
// Decompose the low 140 bits of e1_f_prime = e_1 + 2^4 f + 2^140 - t_P, // Decompose the low 140 bits of e1_f_prime = e_1 + 2^4 f + 2^140 - t_P,
// and output the running sum at the end of it. // and output the running sum at the end of it.
// If e1_f_prime < 2^140, the running sum will be 0. // If e1_f_prime < 2^140, the running sum will be 0.
let (e1_f_prime, zs) = self.sinsemilla_config.lookup_config.witness_check( let zs = self.sinsemilla_config.lookup_config.witness_check(
layouter.namespace(|| "Decompose low 140 bits of (e_1 + 2^4 f + 2^140 - t_P)"), layouter.namespace(|| "Decompose low 140 bits of (e_1 + 2^4 f + 2^140 - t_P)"),
e1_f_prime, e1_f_prime,
14, 14,
false, false,
)?; )?;
let e1_f_prime = zs[0];
assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14] assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14]
Ok((e1_f_prime, zs[14])) Ok((e1_f_prime, zs[14]))
@ -823,12 +826,13 @@ impl NoteCommitConfig {
g_1 + (two_pow_9 * g_2) + two_pow_140 - t_p g_1 + (two_pow_9 * g_2) + two_pow_140 - t_p
}); });
let (g1_g2_prime, zs) = self.sinsemilla_config.lookup_config.witness_check( let zs = self.sinsemilla_config.lookup_config.witness_check(
layouter.namespace(|| "Decompose low 140 bits of (g_1 + (2^9)g_2 + 2^140 - t_P)"), layouter.namespace(|| "Decompose low 140 bits of (g_1 + (2^9)g_2 + 2^140 - t_P)"),
g1_g2_prime, g1_g2_prime,
14, 14,
false, false,
)?; )?;
let g1_g2_prime = zs[0];
assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14] assert_eq!(zs.len(), 15); // [z_0, z_1, ..., z_13, z_14]
Ok((g1_g2_prime, zs[14])) Ok((g1_g2_prime, zs[14]))

View File

@ -34,7 +34,7 @@ use crate::constants::util::decompose_word;
use pasta_curves::arithmetic::FieldExt; use pasta_curves::arithmetic::FieldExt;
use std::marker::PhantomData; use std::marker::PhantomData;
/// The running sum $[z_1, ..., z_W]$. If created in strict mode, $z_W = 0$. /// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$.
pub struct RunningSum<F: FieldExt + PrimeFieldBits>(Vec<CellValue<F>>); pub struct RunningSum<F: FieldExt + PrimeFieldBits>(Vec<CellValue<F>>);
impl<F: FieldExt + PrimeFieldBits> std::ops::Deref for RunningSum<F> { impl<F: FieldExt + PrimeFieldBits> std::ops::Deref for RunningSum<F> {
type Target = Vec<CellValue<F>>; type Target = Vec<CellValue<F>>;
@ -43,6 +43,7 @@ impl<F: FieldExt + PrimeFieldBits> std::ops::Deref for RunningSum<F> {
&self.0 &self.0
} }
} }
#[derive(Debug, Clone, Eq, PartialEq)] #[derive(Debug, Clone, Eq, PartialEq)]
pub struct RunningSumConfig<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize> { pub struct RunningSumConfig<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize> {
q_range_check: Selector, q_range_check: Selector,
@ -103,7 +104,7 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
strict: bool, strict: bool,
word_num_bits: usize, word_num_bits: usize,
num_windows: usize, num_windows: usize,
) -> Result<(CellValue<F>, RunningSum<F>), Error> { ) -> Result<RunningSum<F>, Error> {
let z_0 = { let z_0 = {
let cell = region.assign_advice( let cell = region.assign_advice(
|| "z_0 = alpha", || "z_0 = alpha",
@ -128,7 +129,7 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
strict: bool, strict: bool,
word_num_bits: usize, word_num_bits: usize,
num_windows: usize, num_windows: usize,
) -> Result<(CellValue<F>, RunningSum<F>), Error> { ) -> Result<RunningSum<F>, Error> {
let z_0 = copy(region, || "copy z_0 = alpha", self.z, offset, &alpha)?; let z_0 = copy(region, || "copy z_0 = alpha", self.z, offset, &alpha)?;
self.decompose(region, offset, z_0, strict, word_num_bits, num_windows) self.decompose(region, offset, z_0, strict, word_num_bits, num_windows)
} }
@ -146,7 +147,7 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
strict: bool, strict: bool,
word_num_bits: usize, word_num_bits: usize,
num_windows: usize, num_windows: usize,
) -> Result<(CellValue<F>, RunningSum<F>), Error> { ) -> Result<RunningSum<F>, Error> {
// Make sure that we do not have more windows than required for the number // Make sure that we do not have more windows than required for the number
// of bits in the word. In other words, every window must contain at least // of bits in the word. In other words, every window must contain at least
// one bit of the word (no empty windows). // one bit of the word (no empty windows).
@ -177,8 +178,8 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
} }
}; };
// Initialize empty vector to store running sum values [z_1, ..., z_W]. // Initialize empty vector to store running sum values [z_0, ..., z_W].
let mut zs: Vec<CellValue<F>> = Vec::with_capacity(num_windows); let mut zs: Vec<CellValue<F>> = vec![z_0];
let mut z = z_0; let mut z = z_0;
// Assign running sum `z_{i+1}` = (z_i - k_i) / (2^K) for i = 0..=n-1. // Assign running sum `z_{i+1}` = (z_i - k_i) / (2^K) for i = 0..=n-1.
@ -206,13 +207,14 @@ impl<F: FieldExt + PrimeFieldBits, const WINDOW_NUM_BITS: usize>
z = z_next; z = z_next;
zs.push(z); zs.push(z);
} }
assert_eq!(zs.len(), num_windows + 1);
if strict { if strict {
// Constrain the final running sum output to be zero. // Constrain the final running sum output to be zero.
region.constrain_constant(zs.last().unwrap().cell(), F::zero())?; region.constrain_constant(zs.last().unwrap().cell(), F::zero())?;
} }
Ok((z_0, RunningSum(zs))) Ok(RunningSum(zs))
} }
} }
@ -274,7 +276,7 @@ mod tests {
|| "decompose", || "decompose",
|mut region| { |mut region| {
let offset = 0; let offset = 0;
let (alpha, _zs) = config.witness_decompose( let zs = config.witness_decompose(
&mut region, &mut region,
offset, offset,
self.alpha, self.alpha,
@ -282,6 +284,7 @@ mod tests {
WORD_NUM_BITS, WORD_NUM_BITS,
NUM_WINDOWS, NUM_WINDOWS,
)?; )?;
let alpha = zs[0];
let offset = offset + NUM_WINDOWS + 1; let offset = offset + NUM_WINDOWS + 1;

View File

@ -13,6 +13,16 @@ use ff::PrimeFieldBits;
use super::*; use super::*;
/// The running sum $[z_0, ..., z_W]$. If created in strict mode, $z_W = 0$.
pub struct RunningSum<F: FieldExt + PrimeFieldBits>(Vec<CellValue<F>>);
impl<F: FieldExt + PrimeFieldBits> std::ops::Deref for RunningSum<F> {
type Target = Vec<CellValue<F>>;
fn deref(&self) -> &Vec<CellValue<F>> {
&self.0
}
}
#[derive(Eq, PartialEq, Debug, Clone)] #[derive(Eq, PartialEq, Debug, Clone)]
pub struct LookupRangeCheckConfig<F: FieldExt + PrimeFieldBits, const K: usize> { pub struct LookupRangeCheckConfig<F: FieldExt + PrimeFieldBits, const K: usize> {
pub q_lookup: Selector, pub q_lookup: Selector,
@ -125,16 +135,13 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
element: CellValue<F>, element: CellValue<F>,
num_words: usize, num_words: usize,
strict: bool, strict: bool,
) -> Result<Vec<CellValue<F>>, Error> { ) -> Result<RunningSum<F>, Error> {
layouter.assign_region( layouter.assign_region(
|| format!("{:?} words range check", num_words), || format!("{:?} words range check", num_words),
|mut region| { |mut region| {
// Copy `element` and initialize running sum `z_0 = element` to decompose it. // Copy `element` and initialize running sum `z_0 = element` to decompose it.
let z_0 = copy(&mut region, || "z_0", self.running_sum, 0, &element)?; let z_0 = copy(&mut region, || "z_0", self.running_sum, 0, &element)?;
self.range_check(&mut region, z_0, num_words, strict)
let zs = self.range_check(&mut region, z_0, num_words, strict)?;
Ok(zs)
}, },
) )
} }
@ -146,7 +153,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
value: Option<F>, value: Option<F>,
num_words: usize, num_words: usize,
strict: bool, strict: bool,
) -> Result<(CellValue<F>, Vec<CellValue<F>>), Error> { ) -> Result<RunningSum<F>, Error> {
layouter.assign_region( layouter.assign_region(
|| "Witness element", || "Witness element",
|mut region| { |mut region| {
@ -159,10 +166,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
)?; )?;
CellValue::new(cell, value) CellValue::new(cell, value)
}; };
self.range_check(&mut region, z_0, num_words, strict)
let zs = self.range_check(&mut region, z_0, num_words, strict)?;
Ok((z_0, zs))
}, },
) )
} }
@ -180,7 +184,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
element: CellValue<F>, element: CellValue<F>,
num_words: usize, num_words: usize,
strict: bool, strict: bool,
) -> Result<Vec<CellValue<F>>, Error> { ) -> Result<RunningSum<F>, Error> {
// `num_words` must fit into a single field element. // `num_words` must fit into a single field element.
assert!(num_words * K <= F::CAPACITY as usize); assert!(num_words * K <= F::CAPACITY as usize);
let num_bits = num_words * K; let num_bits = num_words * K;
@ -247,7 +251,7 @@ impl<F: FieldExt + PrimeFieldBits, const K: usize> LookupRangeCheckConfig<F, K>
region.constrain_constant(zs.last().unwrap().cell(), F::zero())?; region.constrain_constant(zs.last().unwrap().cell(), F::zero())?;
} }
Ok(zs) Ok(RunningSum(zs))
} }
/// Short range check on an existing cell that is copied into this helper. /// Short range check on an existing cell that is copied into this helper.
@ -440,7 +444,7 @@ mod tests {
for (element, expected_final_z, strict) in elements_and_expected_final_zs.iter() { for (element, expected_final_z, strict) in elements_and_expected_final_zs.iter() {
let expected_zs = expected_zs::<F, K>(*element, self.num_words); let expected_zs = expected_zs::<F, K>(*element, self.num_words);
let (_, zs) = config.witness_check( let zs = config.witness_check(
layouter.namespace(|| format!("Lookup {:?}", self.num_words)), layouter.namespace(|| format!("Lookup {:?}", self.num_words)),
Some(*element), Some(*element),
self.num_words, self.num_words,