From 1a3cbeb896a1790e2ae80ed08feec227c89c48ef Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Fri, 20 Aug 2021 12:21:46 +0800 Subject: [PATCH] Refactor src/constants and primitives::sinsemilla::constants. --- src/circuit/commit_ivk.rs | 4 +- src/circuit/gadget/ecc/chip.rs | 46 +- src/circuit/gadget/ecc/chip/mul.rs | 3 +- src/circuit/gadget/ecc/chip/mul/overflow.rs | 5 +- src/circuit/gadget/ecc/chip/mul_fixed.rs | 30 +- .../ecc/chip/mul_fixed/base_field_elem.rs | 33 +- .../gadget/ecc/chip/mul_fixed/full_width.rs | 41 +- .../gadget/ecc/chip/mul_fixed/short.rs | 9 +- src/circuit/gadget/sinsemilla.rs | 2 +- src/circuit/gadget/sinsemilla/merkle.rs | 16 +- src/circuit/gadget/sinsemilla/merkle/chip.rs | 9 +- .../gadget/utilities/decompose_running_sum.rs | 33 +- src/circuit/note_commit.rs | 4 +- src/constants.rs | 431 +----------------- src/constants/fixed_bases.rs | 355 +++++++++++++++ .../{ => fixed_bases}/commit_ivk_r.rs | 0 .../{ => fixed_bases}/note_commit_r.rs | 0 .../{ => fixed_bases}/nullifier_k.rs | 0 .../{ => fixed_bases}/spend_auth_g.rs | 0 .../{ => fixed_bases}/value_commit_r.rs | 0 .../{ => fixed_bases}/value_commit_v.rs | 0 src/constants/sinsemilla.rs | 255 +++++++++++ src/note/commitment.rs | 2 +- src/primitives/sinsemilla.rs | 105 +++-- src/primitives/sinsemilla/constants.rs | 143 ------ src/spec.rs | 4 +- src/tree.rs | 5 +- src/value.rs | 2 +- 28 files changed, 811 insertions(+), 726 deletions(-) create mode 100644 src/constants/fixed_bases.rs rename src/constants/{ => fixed_bases}/commit_ivk_r.rs (100%) rename src/constants/{ => fixed_bases}/note_commit_r.rs (100%) rename src/constants/{ => fixed_bases}/nullifier_k.rs (100%) rename src/constants/{ => fixed_bases}/spend_auth_g.rs (100%) rename src/constants/{ => fixed_bases}/value_commit_r.rs (100%) rename src/constants/{ => fixed_bases}/value_commit_v.rs (100%) create mode 100644 src/constants/sinsemilla.rs delete mode 100644 src/primitives/sinsemilla/constants.rs diff --git a/src/circuit/commit_ivk.rs b/src/circuit/commit_ivk.rs index ddb71cff..4edfd1b6 100644 --- a/src/circuit/commit_ivk.rs +++ b/src/circuit/commit_ivk.rs @@ -642,8 +642,8 @@ mod tests { utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, }, constants::{ - OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, - COMMIT_IVK_PERSONALIZATION, L_ORCHARD_BASE, T_Q, + fixed_bases::COMMIT_IVK_PERSONALIZATION, OrchardCommitDomains, OrchardFixedBases, + OrchardHashDomains, L_ORCHARD_BASE, T_Q, }, primitives::sinsemilla::CommitDomain, }; diff --git a/src/circuit/gadget/ecc/chip.rs b/src/circuit/gadget/ecc/chip.rs index ef0cc008..f45fcb11 100644 --- a/src/circuit/gadget/ecc/chip.rs +++ b/src/circuit/gadget/ecc/chip.rs @@ -3,7 +3,6 @@ use crate::{ circuit::gadget::utilities::{ lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions, }, - constants, primitives::sinsemilla, }; use arrayvec::ArrayVec; @@ -24,6 +23,40 @@ 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 = + (L_ORCHARD_SCALAR + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE; + +/// Number of windows for a short signed scalar +pub const NUM_WINDOWS_SHORT: usize = + (L_VALUE + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE; + +/// $\ell_\mathsf{value}$ +/// Number of bits in an unsigned short scalar. +pub(crate) const L_VALUE: usize = 64; + +/// $\ell^\mathsf{Orchard}_\mathsf{base}$ +/// Number of bits in a Pallas base field element. +pub(crate) const L_ORCHARD_BASE: usize = 255; + +/// $\ell^\mathsf{Orchard}_\mathsf{scalar}$ +/// Number of bits in a Pallas scalar field element. +pub(crate) const L_ORCHARD_SCALAR: usize = 255; + +/// The Pallas scalar field modulus is $q = 2^{254} + \mathsf{t_q}$. +/// +pub(crate) const T_Q: u128 = 45560315531506369815346746415080538113; + +/// The Pallas base field modulus is $p = 2^{254} + \mathsf{t_p}$. +/// +pub(crate) const T_P: u128 = 45560315531419706090280762371685220353; + /// A curve point represented in affine (x, y) coordinates, or the /// identity represented as (0, 0). /// Each coordinate is assigned to a cell. @@ -165,9 +198,9 @@ pub struct EccConfig> { /// arrays instead of `Vec`s. pub trait FixedPoint: std::fmt::Debug + Eq + Clone { fn generator(&self) -> C; - fn u(&self) -> Vec<[[u8; 32]; constants::H]>; + fn u(&self) -> Vec<[[u8; 32]; H]>; fn z(&self) -> Vec; - fn lagrange_coeffs(&self) -> Vec<[C::Base; constants::H]>; + fn lagrange_coeffs(&self) -> Vec<[C::Base; H]>; } /// A chip implementing EccInstructions @@ -276,7 +309,7 @@ impl> EccChip { #[derive(Clone, Debug)] pub struct EccScalarFixed { value: Option, - windows: ArrayVec, { constants::NUM_WINDOWS }>, + windows: ArrayVec, { NUM_WINDOWS }>, } // TODO: Make V a `u64` @@ -301,8 +334,7 @@ type MagnitudeSign = (MagnitudeCell, SignCell); pub struct EccScalarFixedShort { magnitude: MagnitudeCell, sign: SignCell, - running_sum: - ArrayVec, { constants::NUM_WINDOWS_SHORT + 1 }>, + running_sum: ArrayVec, { NUM_WINDOWS_SHORT + 1 }>, } /// A base field element used for fixed-base scalar multiplication. @@ -317,7 +349,7 @@ pub struct EccScalarFixedShort { #[derive(Clone, Debug)] struct EccBaseFieldElemFixed { base_field_elem: AssignedCell, - running_sum: ArrayVec, { constants::NUM_WINDOWS + 1 }>, + running_sum: ArrayVec, { NUM_WINDOWS + 1 }>, } impl EccBaseFieldElemFixed { diff --git a/src/circuit/gadget/ecc/chip/mul.rs b/src/circuit/gadget/ecc/chip/mul.rs index d4b5f0c2..9779fb22 100644 --- a/src/circuit/gadget/ecc/chip/mul.rs +++ b/src/circuit/gadget/ecc/chip/mul.rs @@ -1,7 +1,6 @@ -use super::{add, EccPoint, NonIdentityEccPoint}; +use super::{add, EccPoint, NonIdentityEccPoint, T_Q}; use crate::{ circuit::gadget::utilities::{bool_check, lookup_range_check::LookupRangeCheckConfig, ternary}, - constants::T_Q, primitives::sinsemilla, }; use std::{ diff --git a/src/circuit/gadget/ecc/chip/mul/overflow.rs b/src/circuit/gadget/ecc/chip/mul/overflow.rs index 9709f347..bca998d2 100644 --- a/src/circuit/gadget/ecc/chip/mul/overflow.rs +++ b/src/circuit/gadget/ecc/chip/mul/overflow.rs @@ -1,7 +1,6 @@ -use super::Z; +use super::{T_Q, Z}; use crate::{ - circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig, constants::T_Q, - primitives::sinsemilla, + circuit::gadget::utilities::lookup_range_check::LookupRangeCheckConfig, primitives::sinsemilla, }; use halo2::circuit::AssignedCell; use halo2::{ diff --git a/src/circuit/gadget/ecc/chip/mul_fixed.rs b/src/circuit/gadget/ecc/chip/mul_fixed.rs index 5139ffb8..ba6f431c 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed.rs @@ -1,9 +1,8 @@ use super::{ add, add_incomplete, EccBaseFieldElemFixed, EccScalarFixed, EccScalarFixedShort, FixedPoint, - NonIdentityEccPoint, + NonIdentityEccPoint, FIXED_BASE_WINDOW_SIZE, H, }; use crate::circuit::gadget::utilities::decompose_running_sum::RunningSumConfig; -use crate::constants; use std::marker::PhantomData; @@ -26,15 +25,15 @@ 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(constants::H as u64); - static ref H_BASE: pallas::Base = pallas::Base::from(constants::H as u64); + 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); } -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { - running_sum_config: RunningSumConfig, + running_sum_config: RunningSumConfig, // The fixed Lagrange interpolation coefficients for `x_p`. - lagrange_coeffs: [Column; constants::H], + lagrange_coeffs: [Column; H], // The fixed `z` for each window such that `y + z = u^2`. fixed_z: Column, // Decomposition of an `n-1`-bit scalar into `k`-bit windows: @@ -57,7 +56,7 @@ impl> Config { #[allow(clippy::too_many_arguments)] pub(super) fn configure( meta: &mut ConstraintSystem, - lagrange_coeffs: [Column; constants::H], + lagrange_coeffs: [Column; H], window: Column, x_p: Column, y_p: Column, @@ -138,7 +137,7 @@ impl> Config { // 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(constants::H as u64); + let word = z_cur - z_next * pallas::Base::from(H as u64); self.coords_check(meta, q_mul_fixed_running_sum, word) }); @@ -156,7 +155,7 @@ impl> Config { let z = meta.query_fixed(self.fixed_z, Rotation::cur()); let u = meta.query_advice(self.u, Rotation::cur()); - let window_pow: Vec> = (0..constants::H) + let window_pow: Vec> = (0..H) .map(|pow| { (0..pow).fold(Expression::Constant(pallas::Base::one()), |acc, _| { acc * window.clone() @@ -228,7 +227,7 @@ impl> Config { coords_check_toggle.enable(region, window + offset)?; // Assign x-coordinate Lagrange interpolation coefficients - for k in 0..(constants::H) { + for k in 0..(H) { region.assign_fixed( || { format!( @@ -374,12 +373,7 @@ impl> Config { // 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 + (*TWO_SCALAR).pow(&[ - constants::FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, - 0, - 0, - 0, - ]) + acc + (*TWO_SCALAR).pow(&[FIXED_BASE_WINDOW_SIZE as u64 * w as u64 + 1, 0, 0, 0]) }); // `scalar = [k * 8^84 - offset_acc]`, where `offset_acc = \sum_{j = 0}^{83} 2^{FIXED_BASE_WINDOW_SIZE*j + 1}`. @@ -484,7 +478,7 @@ impl ScalarFixed { .map(|window| { if let Some(window) = window { let window = window.get_lower_32() as usize; - assert!(window < constants::H); + assert!(window < H); Some(window) } else { None diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/base_field_elem.rs b/src/circuit/gadget/ecc/chip/mul_fixed/base_field_elem.rs index 32ec8449..8039edd6 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/base_field_elem.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/base_field_elem.rs @@ -1,11 +1,12 @@ -use super::super::{EccBaseFieldElemFixed, EccPoint, FixedPoints}; +use super::super::{ + EccBaseFieldElemFixed, EccPoint, FixedPoints, L_ORCHARD_BASE, NUM_WINDOWS, T_P, +}; use super::H_BASE; use crate::{ circuit::gadget::utilities::{ bitrange_subset, lookup_range_check::LookupRangeCheckConfig, range_check, }, - constants::{self, T_P}, primitives::sinsemilla, }; use halo2::circuit::AssignedCell; @@ -18,7 +19,7 @@ use pasta_curves::{arithmetic::FieldExt, pallas}; use std::convert::TryInto; -#[derive(Copy, Clone, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { q_mul_fixed_base_field: Selector, canon_advices: [Column; 3], @@ -178,8 +179,8 @@ impl> Config { offset, scalar.clone(), true, - constants::L_ORCHARD_BASE, - constants::NUM_WINDOWS, + L_ORCHARD_BASE, + NUM_WINDOWS, )?; EccBaseFieldElemFixed { base_field_elem: running_sum[0].clone(), @@ -187,15 +188,13 @@ impl> Config { } }; - let (acc, mul_b) = self - .super_config - .assign_region_inner::<_, { constants::NUM_WINDOWS }>( - &mut region, - offset, - &(&scalar).into(), - base, - self.super_config.running_sum_config.q_range_check, - )?; + let (acc, mul_b) = self.super_config.assign_region_inner::<_, NUM_WINDOWS>( + &mut region, + offset, + &(&scalar).into(), + base, + self.super_config.running_sum_config.q_range_check, + )?; Ok((scalar, acc, mul_b)) }, @@ -390,12 +389,12 @@ pub mod tests { use crate::circuit::gadget::{ ecc::{ - chip::{EccChip, FixedPoint}, + chip::{EccChip, FixedPoint, H}, FixedPointBaseField, NonIdentityPoint, Point, }, utilities::UtilitiesInstructions, }; - use crate::constants::{self, NullifierK, OrchardFixedBases}; + use crate::constants::{NullifierK, OrchardFixedBases}; pub fn test_mul_fixed_base_field( chip: EccChip, @@ -463,7 +462,7 @@ pub mod tests { // (There is another *non-canonical* sequence // 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.) { - let h = pallas::Base::from(constants::H as u64); + let h = pallas::Base::from(H as u64); let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334" .chars() .fold(pallas::Base::zero(), |acc, c| { diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs index 392bafec..0c7469ba 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/full_width.rs @@ -1,9 +1,8 @@ -use super::super::{EccPoint, EccScalarFixed, FixedPoints}; - -use crate::{ - circuit::gadget::utilities::{decompose_word, range_check}, - constants::{self, L_ORCHARD_SCALAR, NUM_WINDOWS}, +use super::super::{ + EccPoint, EccScalarFixed, FixedPoints, FIXED_BASE_WINDOW_SIZE, H, L_ORCHARD_SCALAR, NUM_WINDOWS, }; + +use crate::circuit::gadget::utilities::{decompose_word, range_check}; use arrayvec::ArrayVec; use halo2::{ circuit::{AssignedCell, Layouter, Region}, @@ -12,7 +11,7 @@ use halo2::{ }; use pasta_curves::pallas; -#[derive(Clone, Copy, Debug, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { q_mul_fixed_full: Selector, super_config: super::Config, @@ -46,7 +45,7 @@ impl> Config { // 1 * (window - 0) * (window - 1) * ... * (window - 7) .chain(Some(( "window range check", - q_mul_fixed_full * range_check(window, constants::H), + q_mul_fixed_full * range_check(window, H), ))) }); } @@ -84,11 +83,7 @@ impl> Config { // Decompose scalar into `k-bit` windows let scalar_windows: Option> = scalar.map(|scalar| { - decompose_word::( - &scalar, - SCALAR_NUM_BITS, - constants::FIXED_BASE_WINDOW_SIZE, - ) + decompose_word::(&scalar, SCALAR_NUM_BITS, FIXED_BASE_WINDOW_SIZE) }); // Store the scalar decomposition @@ -135,15 +130,13 @@ impl> Config { let scalar = self.witness(&mut region, offset, scalar)?; - let (acc, mul_b) = self - .super_config - .assign_region_inner::<_, { constants::NUM_WINDOWS }>( - &mut region, - offset, - &(&scalar).into(), - base, - self.q_mul_fixed_full, - )?; + let (acc, mul_b) = self.super_config.assign_region_inner::<_, NUM_WINDOWS>( + &mut region, + offset, + &(&scalar).into(), + base, + self.q_mul_fixed_full, + )?; Ok((scalar, acc, mul_b)) }, @@ -188,10 +181,10 @@ pub mod tests { use rand::rngs::OsRng; use crate::circuit::gadget::ecc::{ - chip::{EccChip, FixedPoint as FixedPointTrait}, + chip::{EccChip, FixedPoint as FixedPointTrait, H}, FixedPoint, NonIdentityPoint, Point, }; - use crate::constants::{self, OrchardFixedBases, OrchardFixedBasesFull}; + use crate::constants::{OrchardFixedBases, OrchardFixedBasesFull}; pub fn test_mul_fixed( chip: EccChip, @@ -277,7 +270,7 @@ pub mod tests { // (There is another *non-canonical* sequence // 5333333333333333333333333333333333333333332711161673731021062440252244051273333333333 in octal.) { - let h = pallas::Scalar::from(constants::H as u64); + let h = pallas::Scalar::from(H as u64); let scalar_fixed = "1333333333333333333333333333333333333333333333333333333333333333333333333333333333334" .chars() .fold(pallas::Scalar::zero(), |acc, c| { diff --git a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs index e978437d..2d2d42cb 100644 --- a/src/circuit/gadget/ecc/chip/mul_fixed/short.rs +++ b/src/circuit/gadget/ecc/chip/mul_fixed/short.rs @@ -1,10 +1,7 @@ use std::{array, convert::TryInto}; -use super::super::{EccPoint, EccScalarFixedShort, FixedPoints}; -use crate::{ - circuit::gadget::{ecc::chip::MagnitudeSign, utilities::bool_check}, - constants::{L_VALUE, NUM_WINDOWS_SHORT}, -}; +use super::super::{EccPoint, EccScalarFixedShort, FixedPoints, L_VALUE, NUM_WINDOWS_SHORT}; +use crate::circuit::gadget::{ecc::chip::MagnitudeSign, utilities::bool_check}; use halo2::{ circuit::{Layouter, Region}, @@ -13,7 +10,7 @@ use halo2::{ }; use pasta_curves::pallas; -#[derive(Clone, Debug, Copy, Eq, PartialEq)] +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Config> { // Selector used for fixed-base scalar mul with short signed exponent. q_mul_fixed_short: Selector, diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index 9ce3673f..a8f50fb4 100644 --- a/src/circuit/gadget/sinsemilla.rs +++ b/src/circuit/gadget/sinsemilla.rs @@ -428,8 +428,8 @@ mod tests { utilities::lookup_range_check::LookupRangeCheckConfig, }, constants::{ + fixed_bases::COMMIT_IVK_PERSONALIZATION, sinsemilla::MERKLE_CRH_PERSONALIZATION, OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, - COMMIT_IVK_PERSONALIZATION, MERKLE_CRH_PERSONALIZATION, }, primitives::sinsemilla::{self, K}, }; diff --git a/src/circuit/gadget/sinsemilla/merkle.rs b/src/circuit/gadget/sinsemilla/merkle.rs index 1640c511..531d9523 100644 --- a/src/circuit/gadget/sinsemilla/merkle.rs +++ b/src/circuit/gadget/sinsemilla/merkle.rs @@ -13,6 +13,16 @@ use std::iter; pub(in crate::circuit) mod chip; +/// SWU hash-to-curve personalization for the Merkle CRH generator +pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH"; + +/// $\mathsf{MerkleDepth^{Orchard}}$ +pub(crate) const MERKLE_DEPTH_ORCHARD: usize = 32; + +/// $\ell^\mathsf{Orchard}_\mathsf{base}$ +/// Number of bits in a Pallas base field element. +pub(crate) const L_ORCHARD_BASE: usize = 255; + /// Instructions to check the validity of a Merkle path of a given `PATH_LENGTH`. /// The hash function used is a Sinsemilla instance with `K`-bit words. /// The hash function can process `MAX_WORDS` words. @@ -130,7 +140,7 @@ where pub mod tests { use super::{ chip::{MerkleChip, MerkleConfig}, - MerklePath, + MerklePath, MERKLE_DEPTH_ORCHARD, }; use crate::{ @@ -138,9 +148,7 @@ pub mod tests { sinsemilla::chip::SinsemillaChip, utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, }, - constants::{ - OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, MERKLE_DEPTH_ORCHARD, - }, + constants::{OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains}, note::commitment::ExtractedNoteCommitment, tree, }; diff --git a/src/circuit/gadget/sinsemilla/merkle/chip.rs b/src/circuit/gadget/sinsemilla/merkle/chip.rs index 0d4a7dd4..e1773977 100644 --- a/src/circuit/gadget/sinsemilla/merkle/chip.rs +++ b/src/circuit/gadget/sinsemilla/merkle/chip.rs @@ -5,7 +5,7 @@ use halo2::{ }; use pasta_curves::{arithmetic::FieldExt, pallas}; -use super::MerkleInstructions; +use super::{MerkleInstructions, L_ORCHARD_BASE, MERKLE_DEPTH_ORCHARD}; use crate::{ circuit::gadget::{ @@ -20,7 +20,6 @@ use crate::{ UtilitiesInstructions, }, }, - constants::{L_ORCHARD_BASE, MERKLE_DEPTH_ORCHARD}, primitives::sinsemilla, }; use std::array; @@ -345,10 +344,8 @@ where // Check layer hash output against Sinsemilla primitives hash #[cfg(test)] { - use crate::{ - constants::MERKLE_CRH_PERSONALIZATION, primitives::sinsemilla::HashDomain, - spec::i2lebsp, - }; + use super::MERKLE_CRH_PERSONALIZATION; + use crate::{primitives::sinsemilla::HashDomain, spec::i2lebsp}; use group::ff::{PrimeField, PrimeFieldBits}; if let (Some(left), Some(right)) = (left.value(), right.value()) { diff --git a/src/circuit/gadget/utilities/decompose_running_sum.rs b/src/circuit/gadget/utilities/decompose_running_sum.rs index a632831e..cc37c18e 100644 --- a/src/circuit/gadget/utilities/decompose_running_sum.rs +++ b/src/circuit/gadget/utilities/decompose_running_sum.rs @@ -216,7 +216,6 @@ impl #[cfg(test)] mod tests { use super::*; - use crate::constants::{self, FIXED_BASE_WINDOW_SIZE, L_ORCHARD_BASE, L_VALUE}; use group::ff::Field; use halo2::{ circuit::{Layouter, SimpleFloorPlanner}, @@ -226,6 +225,12 @@ 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; + #[test] fn test_running_sum() { struct MyCircuit< @@ -305,15 +310,11 @@ mod tests { let alpha = pallas::Base::random(OsRng); // Strict full decomposition should pass. - let circuit: MyCircuit< - pallas::Base, - L_ORCHARD_BASE, - FIXED_BASE_WINDOW_SIZE, - { constants::NUM_WINDOWS }, - > = MyCircuit { - alpha: Some(alpha), - strict: true, - }; + let circuit: MyCircuit = + MyCircuit { + alpha: Some(alpha), + strict: true, + }; let prover = MockProver::::run(8, &circuit, vec![]).unwrap(); assert_eq!(prover.verify(), Ok(())); } @@ -325,9 +326,9 @@ mod tests { // Strict full decomposition should pass. let circuit: MyCircuit< pallas::Base, - L_VALUE, + L_SHORT, FIXED_BASE_WINDOW_SIZE, - { constants::NUM_WINDOWS_SHORT }, + { NUM_WINDOWS_SHORT }, > = MyCircuit { alpha: Some(alpha), strict: true, @@ -343,9 +344,9 @@ mod tests { // Strict partial decomposition should fail. let circuit: MyCircuit< pallas::Base, - L_VALUE, + L_SHORT, FIXED_BASE_WINDOW_SIZE, - { constants::NUM_WINDOWS_SHORT }, + { NUM_WINDOWS_SHORT }, > = MyCircuit { alpha: Some(alpha), strict: true, @@ -376,9 +377,9 @@ mod tests { // Non-strict partial decomposition should pass. let circuit: MyCircuit< pallas::Base, - { constants::L_VALUE }, + { L_SHORT }, FIXED_BASE_WINDOW_SIZE, - { constants::NUM_WINDOWS_SHORT }, + { NUM_WINDOWS_SHORT }, > = MyCircuit { alpha: Some(alpha), strict: false, diff --git a/src/circuit/note_commit.rs b/src/circuit/note_commit.rs index 9b802c7b..e6590460 100644 --- a/src/circuit/note_commit.rs +++ b/src/circuit/note_commit.rs @@ -1465,8 +1465,8 @@ mod tests { utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, }, constants::{ - OrchardCommitDomains, OrchardFixedBases, OrchardHashDomains, L_ORCHARD_BASE, L_VALUE, - NOTE_COMMITMENT_PERSONALIZATION, T_Q, + fixed_bases::NOTE_COMMITMENT_PERSONALIZATION, OrchardCommitDomains, OrchardFixedBases, + OrchardHashDomains, L_ORCHARD_BASE, L_VALUE, T_Q, }, primitives::sinsemilla::CommitDomain, }; diff --git a/src/constants.rs b/src/constants.rs index 479bc215..8c3825bc 100644 --- a/src/constants.rs +++ b/src/constants.rs @@ -1,30 +1,15 @@ //! Constants used in the Orchard protocol. -use crate::circuit::gadget::{ - ecc::{chip::FixedPoint, FixedPoints}, - sinsemilla::{CommitDomains, HashDomains}, -}; -use crate::primitives::sinsemilla::{ - Q_COMMIT_IVK_M_GENERATOR, Q_MERKLE_CRH, Q_NOTE_COMMITMENT_M_GENERATOR, -}; - -use arrayvec::ArrayVec; -use ff::{Field, PrimeField}; -use group::Curve; -use halo2::arithmetic::lagrange_interpolate; -use pasta_curves::{ - arithmetic::{CurveAffine, FieldExt}, - pallas, -}; - -pub mod commit_ivk_r; -pub mod note_commit_r; -pub mod nullifier_k; -pub mod spend_auth_g; -pub mod value_commit_r; -pub mod value_commit_v; - +pub mod fixed_bases; +pub mod sinsemilla; 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; + /// The Pallas scalar field modulus is $q = 2^{254} + \mathsf{t_q}$. /// pub(crate) const T_Q: u128 = 45560315531506369815346746415080538113; @@ -33,12 +18,6 @@ pub(crate) const T_Q: u128 = 45560315531506369815346746415080538113; /// pub(crate) const T_P: u128 = 45560315531419706090280762371685220353; -/// $\mathsf{MerkleDepth^{Orchard}}$ -pub(crate) const MERKLE_DEPTH_ORCHARD: usize = 32; - -/// $\ell^\mathsf{Orchard}_\mathsf{Merkle}$ -pub(crate) const L_ORCHARD_MERKLE: usize = 255; - /// $\ell^\mathsf{Orchard}_\mathsf{base}$ pub(crate) const L_ORCHARD_BASE: usize = 255; @@ -48,406 +27,14 @@ pub(crate) const L_ORCHARD_SCALAR: usize = 255; /// $\ell_\mathsf{value}$ pub(crate) const L_VALUE: usize = 64; -/// T_PRIME_BITS is the smallest multiple of 10 such that 2^T_PRIME_BITS -/// is larger than t_P. t_P is defined in q_P = 2^254 + t_P for the -/// Pallas base field. -pub(crate) const PALLAS_T_PRIME_BITS: usize = 130; - -/// SWU hash-to-curve personalization for the spending key base point and -/// the nullifier base point K^Orchard -pub const ORCHARD_PERSONALIZATION: &str = "z.cash:Orchard"; - /// SWU hash-to-curve personalization for the group hash for key diversification pub const KEY_DIVERSIFICATION_PERSONALIZATION: &str = "z.cash:Orchard-gd"; -/// SWU hash-to-curve personalization for the value commitment generator -pub const VALUE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-cv"; - -/// SWU hash-to-curve value for the value commitment generator -pub const VALUE_COMMITMENT_V_BYTES: [u8; 1] = *b"v"; - -/// SWU hash-to-curve value for the value commitment generator -pub const VALUE_COMMITMENT_R_BYTES: [u8; 1] = *b"r"; - -/// SWU hash-to-curve personalization for the note commitment generator -pub const NOTE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-NoteCommit"; - -/// SWU hash-to-curve personalization for the IVK commitment generator -pub const COMMIT_IVK_PERSONALIZATION: &str = "z.cash:Orchard-CommitIvk"; - -/// SWU hash-to-curve personalization for the Merkle CRH generator -pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH"; - -/// 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::Base::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_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(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::>() - .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::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::>() - .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(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::>() - .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(base: C, num_windows: usize) -> Option> { - // 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::>>() - .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. -pub enum OrchardFixedBases { - Full(OrchardFixedBasesFull), - NullifierK, - ValueCommitV, -} - -impl From for OrchardFixedBases { - fn from(full_width_base: OrchardFixedBasesFull) -> Self { - Self::Full(full_width_base) - } -} - -impl From for OrchardFixedBases { - fn from(_value_commit_v: ValueCommitV) -> Self { - Self::ValueCommitV - } -} - -impl From for OrchardFixedBases { - fn from(_nullifier_k: NullifierK) -> Self { - Self::NullifierK - } -} - -/// The Orchard fixed bases used in scalar mul with full-width scalars. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub enum OrchardFixedBasesFull { - CommitIvkR, - NoteCommitR, - ValueCommitR, - SpendAuthG, -} - -/// NullifierK is used in scalar mul with a base field element. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct NullifierK; - -/// ValueCommitV is used in scalar mul with a short signed scalar. -#[derive(Copy, Clone, Debug, Eq, PartialEq)] -pub struct ValueCommitV; - -impl FixedPoints for OrchardFixedBases { - type FullScalar = OrchardFixedBasesFull; - type Base = NullifierK; - type ShortScalar = ValueCommitV; -} - -impl FixedPoint for OrchardFixedBasesFull { - fn generator(&self) -> pallas::Affine { - match self { - Self::CommitIvkR => commit_ivk_r::generator(), - Self::NoteCommitR => note_commit_r::generator(), - Self::ValueCommitR => value_commit_r::generator(), - Self::SpendAuthG => spend_auth_g::generator(), - } - } - - fn u(&self) -> Vec<[[u8; 32]; H]> { - match self { - Self::CommitIvkR => commit_ivk_r::U.to_vec(), - Self::NoteCommitR => note_commit_r::U.to_vec(), - Self::ValueCommitR => value_commit_r::U.to_vec(), - Self::SpendAuthG => spend_auth_g::U.to_vec(), - } - } - - fn z(&self) -> Vec { - match self { - Self::CommitIvkR => commit_ivk_r::Z.to_vec(), - Self::NoteCommitR => note_commit_r::Z.to_vec(), - Self::ValueCommitR => value_commit_r::Z.to_vec(), - 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 for NullifierK { - fn generator(&self) -> pallas::Affine { - nullifier_k::generator() - } - - fn u(&self) -> Vec<[[u8; 32]; H]> { - nullifier_k::U.to_vec() - } - - fn z(&self) -> Vec { - nullifier_k::Z.to_vec() - } - - fn lagrange_coeffs(&self) -> Vec<[pallas::Base; H]> { - compute_lagrange_coeffs(self.generator(), NUM_WINDOWS) - } -} - -impl FixedPoint for ValueCommitV { - fn generator(&self) -> pallas::Affine { - value_commit_v::generator() - } - - fn u(&self) -> Vec<[[u8; 32]; H]> { - value_commit_v::U_SHORT.to_vec() - } - - fn z(&self) -> Vec { - value_commit_v::Z_SHORT.to_vec() - } - - fn lagrange_coeffs(&self) -> Vec<[pallas::Base; H]> { - compute_lagrange_coeffs(self.generator(), NUM_WINDOWS_SHORT) - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum OrchardHashDomains { - NoteCommit, - CommitIvk, - MerkleCrh, -} - -#[allow(non_snake_case)] -impl HashDomains for OrchardHashDomains { - fn Q(&self) -> pallas::Affine { - match self { - OrchardHashDomains::CommitIvk => pallas::Affine::from_xy( - pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.0).unwrap(), - pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.1).unwrap(), - ) - .unwrap(), - OrchardHashDomains::NoteCommit => pallas::Affine::from_xy( - pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap(), - pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap(), - ) - .unwrap(), - OrchardHashDomains::MerkleCrh => pallas::Affine::from_xy( - pallas::Base::from_bytes(&Q_MERKLE_CRH.0).unwrap(), - pallas::Base::from_bytes(&Q_MERKLE_CRH.1).unwrap(), - ) - .unwrap(), - } - } -} - -#[derive(Clone, Debug, Eq, PartialEq)] -pub enum OrchardCommitDomains { - NoteCommit, - CommitIvk, -} - -impl CommitDomains for OrchardCommitDomains { - fn r(&self) -> OrchardFixedBasesFull { - match self { - Self::NoteCommit => OrchardFixedBasesFull::NoteCommitR, - Self::CommitIvk => OrchardFixedBasesFull::CommitIvkR, - } - } - - fn hash_domain(&self) -> OrchardHashDomains { - match self { - Self::NoteCommit => OrchardHashDomains::NoteCommit, - Self::CommitIvk => OrchardHashDomains::CommitIvk, - } - } -} - -#[cfg(test)] -// Test that Lagrange interpolation coefficients reproduce the correct x-coordinate -// for each fixed-base multiple in each window. -fn test_lagrange_coeffs(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 = util::evaluate::(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..(1 << FIXED_BASE_WINDOW_SIZE) { - // Interpolate the x-coordinate using the last window's coefficients - let interpolated_x = util::evaluate::(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)] -// 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(base: pallas::Affine, 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 = pallas::Base::from_repr(*u).unwrap(); - assert_eq!(pallas::Base::from(*z) + y, u * u); // allow either square root - assert!(bool::from((pallas::Base::from(*z) - y).sqrt().is_none())); - } - } -} - #[cfg(test)] mod tests { use ff::PrimeField; use pasta_curves::{arithmetic::FieldExt, pallas}; - #[test] - // Nodes in the Merkle tree are Pallas base field elements. - fn l_orchard_merkle() { - assert_eq!(super::L_ORCHARD_MERKLE, pallas::Base::NUM_BITS as usize); - } - #[test] // Orchard uses the Pallas base field as its base field. fn l_orchard_base() { diff --git a/src/constants/fixed_bases.rs b/src/constants/fixed_bases.rs new file mode 100644 index 00000000..e7b70ee3 --- /dev/null +++ b/src/constants/fixed_bases.rs @@ -0,0 +1,355 @@ +//! 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, +}; + +pub mod commit_ivk_r; +pub mod note_commit_r; +pub mod nullifier_k; +pub mod spend_auth_g; +pub mod value_commit_r; +pub mod value_commit_v; + +/// SWU hash-to-curve personalization for the spending key base point and +/// the nullifier base point K^Orchard +pub const ORCHARD_PERSONALIZATION: &str = "z.cash:Orchard"; + +/// SWU hash-to-curve personalization for the value commitment generator +pub const VALUE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-cv"; + +/// SWU hash-to-curve value for the value commitment generator +pub const VALUE_COMMITMENT_V_BYTES: [u8; 1] = *b"v"; + +/// SWU hash-to-curve value for the value commitment generator +pub const VALUE_COMMITMENT_R_BYTES: [u8; 1] = *b"r"; + +/// SWU hash-to-curve personalization for the note commitment generator +pub const NOTE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-NoteCommit"; + +/// SWU hash-to-curve personalization for the IVK commitment generator +pub const COMMIT_IVK_PERSONALIZATION: &str = "z.cash:Orchard-CommitIvk"; + +/// 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 = + (L_ORCHARD_SCALAR + FIXED_BASE_WINDOW_SIZE - 1) / FIXED_BASE_WINDOW_SIZE; + +/// Number of windows for a short signed scalar +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(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::>() + .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::>() + .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(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::>() + .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(base: C, num_windows: usize) -> Option> { + // 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::>>() + .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. +pub enum OrchardFixedBases { + Full(OrchardFixedBasesFull), + NullifierK, + ValueCommitV, +} + +impl From for OrchardFixedBases { + fn from(full_width_base: OrchardFixedBasesFull) -> Self { + Self::Full(full_width_base) + } +} + +impl From for OrchardFixedBases { + fn from(_value_commit_v: ValueCommitV) -> Self { + Self::ValueCommitV + } +} + +impl From for OrchardFixedBases { + fn from(_nullifier_k: NullifierK) -> Self { + Self::NullifierK + } +} + +/// The Orchard fixed bases used in scalar mul with full-width scalars. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub enum OrchardFixedBasesFull { + CommitIvkR, + NoteCommitR, + ValueCommitR, + SpendAuthG, +} + +/// NullifierK is used in scalar mul with a base field element. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct NullifierK; + +/// ValueCommitV is used in scalar mul with a short signed scalar. +#[derive(Copy, Clone, Debug, Eq, PartialEq)] +pub struct ValueCommitV; + +impl FixedPoints for OrchardFixedBases { + type FullScalar = OrchardFixedBasesFull; + type Base = NullifierK; + type ShortScalar = ValueCommitV; +} + +impl FixedPoint for OrchardFixedBasesFull { + fn generator(&self) -> pallas::Affine { + match self { + Self::CommitIvkR => commit_ivk_r::generator(), + Self::NoteCommitR => note_commit_r::generator(), + Self::ValueCommitR => value_commit_r::generator(), + Self::SpendAuthG => spend_auth_g::generator(), + } + } + + fn u(&self) -> Vec<[[u8; 32]; H]> { + match self { + Self::CommitIvkR => commit_ivk_r::U.to_vec(), + Self::NoteCommitR => note_commit_r::U.to_vec(), + Self::ValueCommitR => value_commit_r::U.to_vec(), + Self::SpendAuthG => spend_auth_g::U.to_vec(), + } + } + + fn z(&self) -> Vec { + match self { + Self::CommitIvkR => commit_ivk_r::Z.to_vec(), + Self::NoteCommitR => note_commit_r::Z.to_vec(), + Self::ValueCommitR => value_commit_r::Z.to_vec(), + 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 for NullifierK { + fn generator(&self) -> pallas::Affine { + nullifier_k::generator() + } + + fn u(&self) -> Vec<[[u8; 32]; H]> { + nullifier_k::U.to_vec() + } + + fn z(&self) -> Vec { + nullifier_k::Z.to_vec() + } + + fn lagrange_coeffs(&self) -> Vec<[pallas::Base; H]> { + compute_lagrange_coeffs(self.generator(), NUM_WINDOWS) + } +} + +impl FixedPoint for ValueCommitV { + fn generator(&self) -> pallas::Affine { + value_commit_v::generator() + } + + fn u(&self) -> Vec<[[u8; 32]; H]> { + value_commit_v::U_SHORT.to_vec() + } + + fn z(&self) -> Vec { + 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(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::(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::(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(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())); + } + } +} diff --git a/src/constants/commit_ivk_r.rs b/src/constants/fixed_bases/commit_ivk_r.rs similarity index 100% rename from src/constants/commit_ivk_r.rs rename to src/constants/fixed_bases/commit_ivk_r.rs diff --git a/src/constants/note_commit_r.rs b/src/constants/fixed_bases/note_commit_r.rs similarity index 100% rename from src/constants/note_commit_r.rs rename to src/constants/fixed_bases/note_commit_r.rs diff --git a/src/constants/nullifier_k.rs b/src/constants/fixed_bases/nullifier_k.rs similarity index 100% rename from src/constants/nullifier_k.rs rename to src/constants/fixed_bases/nullifier_k.rs diff --git a/src/constants/spend_auth_g.rs b/src/constants/fixed_bases/spend_auth_g.rs similarity index 100% rename from src/constants/spend_auth_g.rs rename to src/constants/fixed_bases/spend_auth_g.rs diff --git a/src/constants/value_commit_r.rs b/src/constants/fixed_bases/value_commit_r.rs similarity index 100% rename from src/constants/value_commit_r.rs rename to src/constants/fixed_bases/value_commit_r.rs diff --git a/src/constants/value_commit_v.rs b/src/constants/fixed_bases/value_commit_v.rs similarity index 100% rename from src/constants/value_commit_v.rs rename to src/constants/fixed_bases/value_commit_v.rs diff --git a/src/constants/sinsemilla.rs b/src/constants/sinsemilla.rs new file mode 100644 index 00000000..4c0e8a1e --- /dev/null +++ b/src/constants/sinsemilla.rs @@ -0,0 +1,255 @@ +//! Sinsemilla generators +use super::{OrchardFixedBases, OrchardFixedBasesFull}; +use crate::circuit::gadget::sinsemilla::{CommitDomains, HashDomains}; +use crate::spec::i2lebsp; + +use pasta_curves::{ + arithmetic::{CurveAffine, FieldExt}, + pallas, +}; + +/// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$ +pub const K: usize = 10; + +/// $\frac{1}{2^K}$ +pub const INV_TWO_POW_K: [u8; 32] = [ + 1, 0, 192, 196, 160, 229, 70, 82, 221, 165, 74, 202, 85, 7, 62, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 240, 63, +]; + +/// The largest integer such that $2^c \leq (r_P - 1) / 2$, where $r_P$ is the order +/// of Pallas. +pub const C: usize = 253; + +/// $\ell^\mathsf{Orchard}_\mathsf{Merkle}$ +pub(crate) const L_ORCHARD_MERKLE: usize = 255; + +/// SWU hash-to-curve personalization for the Merkle CRH generator +pub const MERKLE_CRH_PERSONALIZATION: &str = "z.cash:Orchard-MerkleCRH"; + +// Sinsemilla Q generators + +/// SWU hash-to-curve personalization for Sinsemilla $Q$ generators. +pub const Q_PERSONALIZATION: &str = "z.cash:SinsemillaQ"; + +// Sinsemilla S generators + +/// SWU hash-to-curve personalization for Sinsemilla $S$ generators. +pub const S_PERSONALIZATION: &str = "z.cash:SinsemillaS"; + +/// Generator used in SinsemillaHashToPoint for note commitment +pub const Q_NOTE_COMMITMENT_M_GENERATOR: ([u8; 32], [u8; 32]) = ( + [ + 93, 116, 168, 64, 9, 186, 14, 50, 42, 221, 70, 253, 90, 15, 150, 197, 93, 237, 176, 121, + 180, 242, 159, 247, 13, 205, 251, 86, 160, 7, 128, 23, + ], + [ + 99, 172, 73, 115, 90, 10, 39, 135, 158, 94, 219, 129, 136, 18, 34, 136, 44, 201, 244, 110, + 217, 194, 190, 78, 131, 112, 198, 138, 147, 88, 160, 50, + ], +); + +/// Generator used in SinsemillaHashToPoint for IVK commitment +pub const Q_COMMIT_IVK_M_GENERATOR: ([u8; 32], [u8; 32]) = ( + [ + 242, 130, 15, 121, 146, 47, 203, 107, 50, 162, 40, 81, 36, 204, 27, 66, 250, 65, 162, 90, + 184, 129, 204, 125, 17, 200, 169, 74, 241, 12, 188, 5, + ], + [ + 190, 222, 173, 207, 206, 229, 90, 190, 241, 165, 109, 201, 29, 53, 196, 70, 75, 5, 222, 32, + 70, 7, 89, 239, 230, 190, 26, 212, 246, 76, 1, 27, + ], +); + +/// Generator used in SinsemillaHashToPoint for Merkle collision-resistant hash +pub const Q_MERKLE_CRH: ([u8; 32], [u8; 32]) = ( + [ + 160, 198, 41, 127, 249, 199, 185, 248, 112, 16, 141, 192, 85, 185, 190, 201, 153, 14, 137, + 239, 90, 54, 15, 160, 185, 24, 168, 99, 150, 210, 22, 22, + ], + [ + 98, 234, 242, 37, 206, 174, 233, 134, 150, 21, 116, 5, 234, 150, 28, 226, 121, 89, 163, 79, + 62, 242, 196, 45, 153, 32, 175, 227, 163, 66, 134, 53, + ], +); + +pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 { + assert!(bits.len() == K); + bits.iter() + .enumerate() + .fold(0u32, |acc, (i, b)| acc + if *b { 1 << i } else { 0 }) +} + +/// The sequence of K bits in little-endian order representing an integer +/// up to `2^K` - 1. +pub(crate) fn i2lebsp_k(int: usize) -> [bool; K] { + assert!(int < (1 << K)); + i2lebsp(int as u64) +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum OrchardHashDomains { + NoteCommit, + CommitIvk, + MerkleCrh, +} + +#[allow(non_snake_case)] +impl HashDomains for OrchardHashDomains { + fn Q(&self) -> pallas::Affine { + match self { + OrchardHashDomains::CommitIvk => pallas::Affine::from_xy( + pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.0).unwrap(), + pallas::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.1).unwrap(), + ) + .unwrap(), + OrchardHashDomains::NoteCommit => pallas::Affine::from_xy( + pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap(), + pallas::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap(), + ) + .unwrap(), + OrchardHashDomains::MerkleCrh => pallas::Affine::from_xy( + pallas::Base::from_bytes(&Q_MERKLE_CRH.0).unwrap(), + pallas::Base::from_bytes(&Q_MERKLE_CRH.1).unwrap(), + ) + .unwrap(), + } + } +} + +#[derive(Clone, Debug, Eq, PartialEq)] +pub enum OrchardCommitDomains { + NoteCommit, + CommitIvk, +} + +impl CommitDomains for OrchardCommitDomains { + fn r(&self) -> OrchardFixedBasesFull { + match self { + Self::NoteCommit => OrchardFixedBasesFull::NoteCommitR, + Self::CommitIvk => OrchardFixedBasesFull::CommitIvkR, + } + } + + fn hash_domain(&self) -> OrchardHashDomains { + match self { + Self::NoteCommit => OrchardHashDomains::NoteCommit, + Self::CommitIvk => OrchardHashDomains::CommitIvk, + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::constants::{ + fixed_bases::{COMMIT_IVK_PERSONALIZATION, NOTE_COMMITMENT_PERSONALIZATION}, + sinsemilla::MERKLE_CRH_PERSONALIZATION, + }; + use crate::primitives::sinsemilla::{CommitDomain, HashDomain}; + use group::{ff::PrimeField, Curve}; + use halo2::arithmetic::CurveAffine; + use halo2::pasta::pallas; + use rand::{self, rngs::OsRng, Rng}; + + #[test] + // Nodes in the Merkle tree are Pallas base field elements. + fn l_orchard_merkle() { + assert_eq!(super::L_ORCHARD_MERKLE, pallas::Base::NUM_BITS as usize); + } + + #[test] + fn lebs2ip_k_round_trip() { + let mut rng = OsRng; + { + let int = rng.gen_range(0..(1 << K)); + assert_eq!(lebs2ip_k(&i2lebsp_k(int)) as usize, int); + } + + assert_eq!(lebs2ip_k(&i2lebsp_k(0)) as usize, 0); + assert_eq!(lebs2ip_k(&i2lebsp_k((1 << K) - 1)) as usize, (1 << K) - 1); + } + + #[test] + fn i2lebsp_k_round_trip() { + { + let bitstring = (0..K).map(|_| rand::random()).collect::>(); + assert_eq!( + i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(), + bitstring + ); + } + + { + let bitstring = [false; K]; + assert_eq!( + i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(), + bitstring + ); + } + + { + let bitstring = [true; K]; + assert_eq!( + i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(), + bitstring + ); + } + } + + #[test] + fn q_note_commitment_m() { + let domain = CommitDomain::new(NOTE_COMMITMENT_PERSONALIZATION); + let point = domain.Q(); + let coords = point.to_affine().coordinates().unwrap(); + + assert_eq!( + *coords.x(), + pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap() + ); + assert_eq!( + *coords.y(), + pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap() + ); + } + + #[test] + fn q_commit_ivk_m() { + let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION); + let point = domain.Q(); + let coords = point.to_affine().coordinates().unwrap(); + + assert_eq!( + *coords.x(), + pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.0).unwrap() + ); + assert_eq!( + *coords.y(), + pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.1).unwrap() + ); + } + + #[test] + fn q_merkle_crh() { + let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION); + let point = domain.Q(); + let coords = point.to_affine().coordinates().unwrap(); + + assert_eq!( + *coords.x(), + pallas::Base::from_repr(Q_MERKLE_CRH.0).unwrap() + ); + assert_eq!( + *coords.y(), + pallas::Base::from_repr(Q_MERKLE_CRH.1).unwrap() + ); + } + + #[test] + fn inv_two_pow_k() { + let two_pow_k = pallas::Base::from(1u64 << K); + let inv_two_pow_k = pallas::Base::from_repr(INV_TWO_POW_K).unwrap(); + + assert_eq!(two_pow_k * inv_two_pow_k, pallas::Base::one()); + } +} diff --git a/src/note/commitment.rs b/src/note/commitment.rs index a8109efb..05636dce 100644 --- a/src/note/commitment.rs +++ b/src/note/commitment.rs @@ -6,7 +6,7 @@ use pasta_curves::pallas; use subtle::{ConstantTimeEq, CtOption}; use crate::{ - constants::{L_ORCHARD_BASE, NOTE_COMMITMENT_PERSONALIZATION}, + constants::{fixed_bases::NOTE_COMMITMENT_PERSONALIZATION, L_ORCHARD_BASE}, primitives::sinsemilla, spec::extract_p, value::NoteValue, diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index aedc49b3..f6776b92 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -1,19 +1,37 @@ //! The Sinsemilla hash function and commitment scheme. -use group::Wnaf; +use group::{Curve, Wnaf}; use halo2::arithmetic::{CurveAffine, CurveExt}; use pasta_curves::pallas; use subtle::CtOption; -use crate::spec::{extract_p_bottom, i2lebsp}; - mod addition; use self::addition::IncompletePoint; - -mod constants; mod sinsemilla_s; -pub use constants::*; -pub(crate) use sinsemilla_s::*; +pub use sinsemilla_s::SINSEMILLA_S; + +/// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$ +pub const K: usize = 10; + +/// $\frac{1}{2^K}$ +pub const INV_TWO_POW_K: [u8; 32] = [ + 1, 0, 192, 196, 160, 229, 70, 82, 221, 165, 74, 202, 85, 7, 62, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 240, 63, +]; + +/// The largest integer such that $2^c \leq (r_P - 1) / 2$, where $r_P$ is the order +/// of Pallas. +pub const C: usize = 253; + +// Sinsemilla Q generators + +/// SWU hash-to-curve personalization for Sinsemilla $Q$ generators. +pub const Q_PERSONALIZATION: &str = "z.cash:SinsemillaQ"; + +// Sinsemilla S generators + +/// SWU hash-to-curve personalization for Sinsemilla $S$ generators. +pub const S_PERSONALIZATION: &str = "z.cash:SinsemillaS"; pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 { assert!(bits.len() == K); @@ -22,11 +40,18 @@ pub(crate) fn lebs2ip_k(bits: &[bool]) -> u32 { .fold(0u32, |acc, (i, b)| acc + if *b { 1 << i } else { 0 }) } -/// The sequence of K bits in little-endian order representing an integer -/// up to `2^K` - 1. -pub(crate) fn i2lebsp_k(int: usize) -> [bool; K] { - assert!(int < (1 << K)); - i2lebsp(int as u64) +/// Coordinate extractor for Pallas. +/// +/// Defined in [Zcash Protocol Spec ยง 5.4.9.7: Coordinate Extractor for Pallas][concreteextractorpallas]. +/// +/// [concreteextractorpallas]: https://zips.z.cash/protocol/nu5.pdf#concreteextractorpallas +fn extract_p_bottom(point: CtOption) -> CtOption { + point.map(|p| { + p.to_affine() + .coordinates() + .map(|c| *c.x()) + .unwrap_or_else(pallas::Base::zero) + }) } /// Pads the given iterator (which MUST have length $\leq K * C$) with zero-bits to a @@ -196,12 +221,19 @@ impl CommitDomain { pub(crate) fn R(&self) -> pallas::Point { self.R } + + /// Returns the Sinsemilla $Q$ constant for this domain. + #[cfg(test)] + #[allow(non_snake_case)] + pub(crate) fn Q(&self) -> pallas::Point { + self.M.Q + } } #[cfg(test)] mod tests { - use super::{i2lebsp_k, lebs2ip_k, Pad, K}; - use rand::{self, rngs::OsRng, Rng}; + use super::{Pad, K}; + use pasta_curves::{arithmetic::CurveExt, pallas}; #[test] fn pad() { @@ -242,41 +274,20 @@ mod tests { } #[test] - fn lebs2ip_k_round_trip() { - let mut rng = OsRng; - { - let int = rng.gen_range(0..(1 << K)); - assert_eq!(lebs2ip_k(&i2lebsp_k(int)) as usize, int); - } + fn sinsemilla_s() { + use super::sinsemilla_s::SINSEMILLA_S; + use group::Curve; + use pasta_curves::arithmetic::CurveAffine; - assert_eq!(lebs2ip_k(&i2lebsp_k(0)) as usize, 0); - assert_eq!(lebs2ip_k(&i2lebsp_k((1 << K) - 1)) as usize, (1 << K) - 1); - } + let hasher = pallas::Point::hash_to_curve(super::S_PERSONALIZATION); - #[test] - fn i2lebsp_k_round_trip() { - { - let bitstring = (0..K).map(|_| rand::random()).collect::>(); - assert_eq!( - i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(), - bitstring - ); - } - - { - let bitstring = [false; K]; - assert_eq!( - i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(), - bitstring - ); - } - - { - let bitstring = [true; K]; - assert_eq!( - i2lebsp_k(lebs2ip_k(&bitstring) as usize).to_vec(), - bitstring - ); + for j in 0..(1u32 << K) { + let computed = { + let point = hasher(&j.to_le_bytes()).to_affine().coordinates().unwrap(); + (*point.x(), *point.y()) + }; + let actual = SINSEMILLA_S[j as usize]; + assert_eq!(computed, actual); } } } diff --git a/src/primitives/sinsemilla/constants.rs b/src/primitives/sinsemilla/constants.rs deleted file mode 100644 index d09c283b..00000000 --- a/src/primitives/sinsemilla/constants.rs +++ /dev/null @@ -1,143 +0,0 @@ -//! Sinsemilla generators - -/// Number of bits of each message piece in $\mathsf{SinsemillaHashToPoint}$ -pub const K: usize = 10; - -/// $\frac{1}{2^K}$ -pub const INV_TWO_POW_K: [u8; 32] = [ - 1, 0, 192, 196, 160, 229, 70, 82, 221, 165, 74, 202, 85, 7, 62, 34, 0, 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 240, 63, -]; - -/// The largest integer such that $2^c \leq (r_P - 1) / 2$, where $r_P$ is the order -/// of Pallas. -pub const C: usize = 253; - -// Sinsemilla Q generators - -/// SWU hash-to-curve personalization for Sinsemilla $Q$ generators. -pub const Q_PERSONALIZATION: &str = "z.cash:SinsemillaQ"; - -/// Generator used in SinsemillaHashToPoint for note commitment -pub const Q_NOTE_COMMITMENT_M_GENERATOR: ([u8; 32], [u8; 32]) = ( - [ - 93, 116, 168, 64, 9, 186, 14, 50, 42, 221, 70, 253, 90, 15, 150, 197, 93, 237, 176, 121, - 180, 242, 159, 247, 13, 205, 251, 86, 160, 7, 128, 23, - ], - [ - 99, 172, 73, 115, 90, 10, 39, 135, 158, 94, 219, 129, 136, 18, 34, 136, 44, 201, 244, 110, - 217, 194, 190, 78, 131, 112, 198, 138, 147, 88, 160, 50, - ], -); - -/// Generator used in SinsemillaHashToPoint for IVK commitment -pub const Q_COMMIT_IVK_M_GENERATOR: ([u8; 32], [u8; 32]) = ( - [ - 242, 130, 15, 121, 146, 47, 203, 107, 50, 162, 40, 81, 36, 204, 27, 66, 250, 65, 162, 90, - 184, 129, 204, 125, 17, 200, 169, 74, 241, 12, 188, 5, - ], - [ - 190, 222, 173, 207, 206, 229, 90, 190, 241, 165, 109, 201, 29, 53, 196, 70, 75, 5, 222, 32, - 70, 7, 89, 239, 230, 190, 26, 212, 246, 76, 1, 27, - ], -); - -/// Generator used in SinsemillaHashToPoint for Merkle collision-resistant hash -pub const Q_MERKLE_CRH: ([u8; 32], [u8; 32]) = ( - [ - 160, 198, 41, 127, 249, 199, 185, 248, 112, 16, 141, 192, 85, 185, 190, 201, 153, 14, 137, - 239, 90, 54, 15, 160, 185, 24, 168, 99, 150, 210, 22, 22, - ], - [ - 98, 234, 242, 37, 206, 174, 233, 134, 150, 21, 116, 5, 234, 150, 28, 226, 121, 89, 163, 79, - 62, 242, 196, 45, 153, 32, 175, 227, 163, 66, 134, 53, - ], -); - -// Sinsemilla S generators - -/// SWU hash-to-curve personalization for Sinsemilla $S$ generators. -pub const S_PERSONALIZATION: &str = "z.cash:SinsemillaS"; - -#[cfg(test)] -mod tests { - use super::super::{CommitDomain, HashDomain}; - use super::*; - use crate::constants::{ - COMMIT_IVK_PERSONALIZATION, MERKLE_CRH_PERSONALIZATION, NOTE_COMMITMENT_PERSONALIZATION, - }; - use group::{ff::PrimeField, Curve}; - use halo2::arithmetic::{CurveAffine, CurveExt}; - use halo2::pasta::pallas; - - #[test] - fn sinsemilla_s() { - use super::super::sinsemilla_s::SINSEMILLA_S; - let hasher = pallas::Point::hash_to_curve(S_PERSONALIZATION); - - for j in 0..(1u32 << K) { - let computed = { - let point = hasher(&j.to_le_bytes()).to_affine().coordinates().unwrap(); - (*point.x(), *point.y()) - }; - let actual = SINSEMILLA_S[j as usize]; - assert_eq!(computed, actual); - } - } - - #[test] - fn q_note_commitment_m() { - let domain = CommitDomain::new(NOTE_COMMITMENT_PERSONALIZATION); - let point = domain.M.Q; - let coords = point.to_affine().coordinates().unwrap(); - - assert_eq!( - *coords.x(), - pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap() - ); - assert_eq!( - *coords.y(), - pallas::Base::from_repr(Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap() - ); - } - - #[test] - fn q_commit_ivk_m() { - let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION); - let point = domain.M.Q; - let coords = point.to_affine().coordinates().unwrap(); - - assert_eq!( - *coords.x(), - pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.0).unwrap() - ); - assert_eq!( - *coords.y(), - pallas::Base::from_repr(Q_COMMIT_IVK_M_GENERATOR.1).unwrap() - ); - } - - #[test] - fn q_merkle_crh() { - let domain = HashDomain::new(MERKLE_CRH_PERSONALIZATION); - let point = domain.Q; - let coords = point.to_affine().coordinates().unwrap(); - - assert_eq!( - *coords.x(), - pallas::Base::from_repr(Q_MERKLE_CRH.0).unwrap() - ); - assert_eq!( - *coords.y(), - pallas::Base::from_repr(Q_MERKLE_CRH.1).unwrap() - ); - } - - #[test] - fn inv_two_pow_k() { - let two_pow_k = pallas::Base::from(1u64 << K); - let inv_two_pow_k = pallas::Base::from_repr(INV_TWO_POW_K).unwrap(); - - assert_eq!(two_pow_k * inv_two_pow_k, pallas::Base::one()); - } -} diff --git a/src/spec.rs b/src/spec.rs index d94605ed..7e4f3385 100644 --- a/src/spec.rs +++ b/src/spec.rs @@ -12,8 +12,8 @@ use subtle::{ConditionallySelectable, CtOption}; use crate::{ constants::{ - util::gen_const_array, COMMIT_IVK_PERSONALIZATION, KEY_DIVERSIFICATION_PERSONALIZATION, - L_ORCHARD_BASE, + fixed_bases::COMMIT_IVK_PERSONALIZATION, util::gen_const_array, + KEY_DIVERSIFICATION_PERSONALIZATION, L_ORCHARD_BASE, }, primitives::{poseidon, sinsemilla}, }; diff --git a/src/tree.rs b/src/tree.rs index 84d921ff..f4c6b461 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -2,11 +2,12 @@ use crate::{ constants::{ - util::gen_const_array_with_default, L_ORCHARD_MERKLE, MERKLE_CRH_PERSONALIZATION, + sinsemilla::{i2lebsp_k, L_ORCHARD_MERKLE, MERKLE_CRH_PERSONALIZATION}, + util::gen_const_array_with_default, MERKLE_DEPTH_ORCHARD, }, note::commitment::ExtractedNoteCommitment, - primitives::sinsemilla::{i2lebsp_k, HashDomain}, + primitives::sinsemilla::HashDomain, }; use incrementalmerkletree::{Altitude, Hashable}; use pasta_curves::pallas; diff --git a/src/value.rs b/src/value.rs index 77857727..1568a5ae 100644 --- a/src/value.rs +++ b/src/value.rs @@ -51,7 +51,7 @@ use rand::RngCore; use subtle::CtOption; use crate::{ - constants::{ + constants::fixed_bases::{ VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES, VALUE_COMMITMENT_V_BYTES, }, primitives::redpallas::{self, Binding},