From 2ad868a9f3a7eb808fa89c48a79ce491770069de Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Wed, 21 Apr 2021 18:57:37 +0800 Subject: [PATCH] Fix Sinsemilla chip to work with config abstraction --- src/circuit/gadget/sinsemilla.rs | 237 ++++-- src/circuit/gadget/sinsemilla/chip.rs | 696 ++++++++++++++---- .../gadget/sinsemilla/chip/generator_table.rs | 179 +++-- src/primitives/sinsemilla.rs | 3 +- 4 files changed, 895 insertions(+), 220 deletions(-) diff --git a/src/circuit/gadget/sinsemilla.rs b/src/circuit/gadget/sinsemilla.rs index 455c03e17..af2e21f87 100644 --- a/src/circuit/gadget/sinsemilla.rs +++ b/src/circuit/gadget/sinsemilla.rs @@ -3,48 +3,39 @@ use crate::circuit::gadget::ecc::{self, EccInstructions}; use halo2::{arithmetic::CurveAffine, circuit::Layouter, plonk::Error}; use std::fmt; -mod chip; -// pub use chip::{SinsemillaChip, SinsemillaColumns, SinsemillaConfig}; - -/// Trait allowing circuit's Sinsemilla HashDomains to be enumerated. -pub trait HashDomains: Clone + fmt::Debug {} - -/// Trait allowing circuit's Sinsemilla CommitDomains to be enumerated. -pub trait CommitDomains, H: HashDomains>: - Clone + fmt::Debug -{ - /// Returns the fixed point corresponding to the R constant for this CommitDomain. - fn r(&self) -> F; - - /// Returns the HashDomain contained in this CommitDomain - fn hash_domain(&self) -> H; -} +pub mod chip; +pub use chip::{ + Message, SinsemillaChip, SinsemillaCommitDomains, SinsemillaConfigEnum, SinsemillaHashDomains, +}; /// The set of circuit instructions required to use the [`Sinsemilla`](https://zcash.github.io/halo2/design/gadgets/sinsemilla.html) gadget. pub trait SinsemillaInstructions: EccInstructions { - /// Witnessed message. - type Message: Clone + fmt::Debug; - /// Variable representing the set of CommitDomains in the circuit. + /// HashDomains used in this instruction. + type HashDomains: HashDomains; + /// CommitDomains used in this instruction. type CommitDomains: CommitDomains< C, >::FixedPoints, Self::HashDomains, >; - /// Variable representing the set of HashDomains in the circuit. - type HashDomains: HashDomains; /// Variable representing a Q fixed point for a HashDomain. type Q: Clone + fmt::Debug; + /// Witnessed message. + type Message: Clone + fmt::Debug; + /// Gets the Q constant for the given domain. #[allow(non_snake_case)] fn get_Q( - layouter: &mut impl Layouter, + &self, + layouter: &mut impl Layouter, domain: &Self::HashDomains, ) -> Result; /// Witnesses a message in the form of a bitstring. fn witness_message( - layouter: &mut impl Layouter, + &self, + layouter: &mut impl Layouter, message: Vec, ) -> Result; @@ -55,25 +46,33 @@ pub trait SinsemillaInstructions: EccInstructions { /// Hashes a message to an ECC curve point. #[allow(non_snake_case)] fn hash_to_point( - layouter: &mut impl Layouter, + &self, + layouter: &mut impl Layouter, Q: &Self::Q, message: Self::Message, ) -> Result; } +/// Trait allowing circuit's Sinsemilla HashDomains to be enumerated. +#[allow(non_snake_case)] +pub trait HashDomains: Clone + fmt::Debug { + fn Q(&self) -> C; +} + #[allow(non_snake_case)] pub struct HashDomain> { - Q: SinsemillaChip::Q, + pub Q: SinsemillaChip::Q, } impl> HashDomain { #[allow(non_snake_case)] /// Constructs a new `HashDomain` for the given domain. pub fn new( - mut layouter: impl Layouter, - domain: &SinsemillaChip::HashDomains, + chip: SinsemillaChip, + mut layouter: impl Layouter, + domain: &>::HashDomains, ) -> Result { - SinsemillaChip::get_Q(&mut layouter, domain).map(|Q| HashDomain { Q }) + chip.get_Q(&mut layouter, domain).map(|Q| HashDomain { Q }) } /// $\mathsf{SinsemillaHashToPoint}$ from [§ 5.4.1.9][concretesinsemillahash]. @@ -81,10 +80,13 @@ impl> HashDomain, - message: >::Message, + chip: SinsemillaChip, + mut layouter: impl Layouter, + message: Vec, ) -> Result, Error> { - SinsemillaChip::hash_to_point(&mut layouter, &self.Q, message).map(ecc::Point::from_inner) + let message = chip.witness_message(&mut layouter, message)?; + chip.hash_to_point(&mut layouter, &self.Q, message) + .map(ecc::Point::from_inner) } /// $\mathsf{SinsemillaHash}$ from [§ 5.4.1.9][concretesinsemillahash]. @@ -92,29 +94,48 @@ impl> HashDomain, - message: >::Message, + chip: SinsemillaChip, + layouter: impl Layouter, + message: Vec, ) -> Result, Error> { - let p = self.hash_to_point(layouter, message); + let p = self.hash_to_point(chip, layouter, message); p.map(|p| p.extract_p()) } } +/// Trait allowing circuit's Sinsemilla CommitDomains to be enumerated. +pub trait CommitDomains, H: HashDomains>: + Clone + fmt::Debug +{ + /// Returns the fixed point corresponding to the R constant for this CommitDomain. + fn r(&self) -> F; + + /// Returns the HashDomain contained in this CommitDomain + fn hash_domain(&self) -> H; +} + #[allow(non_snake_case)] pub struct CommitDomain> { M: HashDomain, R: ecc::FixedPoint, } -impl> CommitDomain { +impl> + CommitDomain +{ /// Constructs a new `CommitDomain` for the given domain. pub fn new( - mut layouter: impl Layouter, + chip: SinsemillaChip, + mut layouter: impl Layouter, domain: &SinsemillaChip::CommitDomains, ) -> Result { Ok(CommitDomain { - M: HashDomain::new(layouter.namespace(|| "M"), &domain.hash_domain())?, - R: ecc::FixedPoint::get(layouter.namespace(|| "R"), domain.r())?, + M: HashDomain::new( + chip.clone(), + layouter.namespace(|| "M"), + &domain.hash_domain(), + )?, + R: ecc::FixedPoint::get(chip, domain.r())?, }) } @@ -123,14 +144,17 @@ impl> CommitDomain, - message: >::Message, + chip: SinsemillaChip, + mut layouter: impl Layouter, + message: Vec, r: ecc::ScalarFixed, ) -> Result, Error> { - let blind = self.R.mul(layouter.namespace(|| "[r] R"), &r)?; + let blind = self + .R + .mul(chip.clone(), layouter.namespace(|| "[r] R"), &r)?; self.M - .hash_to_point(layouter.namespace(|| "M"), message)? - .add(layouter.namespace(|| "M + [r] R"), &blind) + .hash_to_point(chip.clone(), layouter.namespace(|| "M"), message)? + .add(chip, layouter.namespace(|| "M + [r] R"), &blind) } /// $\mathsf{SinsemillaShortCommit}$ from [§ 5.4.8.4][concretesinsemillacommit]. @@ -138,11 +162,132 @@ impl> CommitDomain, - message: >::Message, + chip: SinsemillaChip, + mut layouter: impl Layouter, + message: Vec, r: ecc::ScalarFixed, ) -> Result, Error> { - let p = self.commit(layouter.namespace(|| "commit"), message, r); + let p = self.commit(chip, layouter.namespace(|| "commit"), message, r); p.map(|p| p.extract_p()) } } + +#[cfg(test)] +mod tests { + use halo2::{ + arithmetic::{CurveAffine, FieldExt}, + circuit::{layouter::SingleChipLayouter, Chip, Layouter, Loaded}, + dev::MockProver, + pasta::pallas, + plonk::{Assignment, Circuit, ConstraintSystem, Error, Permutation}, + }; + use std::collections::BTreeMap; + + use super::{ + CommitDomain, HashDomain, SinsemillaChip, SinsemillaCommitDomains, SinsemillaConfigEnum, + SinsemillaHashDomains, + }; + use crate::circuit::gadget::ecc::ScalarFixed; + + struct MyCircuit { + _marker: std::marker::PhantomData, + } + + impl Circuit for MyCircuit { + type Config = SinsemillaConfigEnum; + + fn configure(meta: &mut ConstraintSystem) -> Self::Config { + let mut columns = BTreeMap::new(); + let bits = meta.advice_column(); + let x_p = meta.advice_column(); + let y_p = meta.advice_column(); + let x_a = meta.advice_column(); + let y_a = meta.advice_column(); + + columns.insert("bits", bits.into()); + columns.insert("u", meta.advice_column().into()); + + columns.insert("x_a", x_a.into()); + columns.insert("y_a", y_a.into()); + + columns.insert("x_p", x_p.into()); + columns.insert("y_p", y_p.into()); + + columns.insert("lambda1", meta.advice_column().into()); + columns.insert("lambda2", meta.advice_column().into()); + + columns.insert("inv_alpha", meta.advice_column().into()); + columns.insert("inv_beta", meta.advice_column().into()); + columns.insert("inv_gamma", meta.advice_column().into()); + columns.insert("inv_delta", meta.advice_column().into()); + + columns.insert("bool_a", meta.advice_column().into()); + columns.insert("bool_b", meta.advice_column().into()); + columns.insert("bool_c", meta.advice_column().into()); + columns.insert("bool_d", meta.advice_column().into()); + + let mut perms = BTreeMap::new(); + perms.insert("perm_bits", Permutation::new(meta, &[bits.into()])); + perms.insert( + "perm_sum", + Permutation::new(meta, &[x_p.into(), y_p.into(), x_a.into(), y_a.into()]), + ); + + let mut chip = SinsemillaChip::::new(); + chip.configure(meta, BTreeMap::new(), columns, perms) + } + + fn synthesize( + &self, + cs: &mut impl Assignment, + config: Self::Config, + ) -> Result<(), Error> { + let mut chip = SinsemillaChip::::construct( + config, + as Chip>::Loaded::empty(), + ); + let mut layouter = SingleChipLayouter::new(cs)?; + chip.load(&mut layouter)?; + + let merkle_crh = HashDomain::new( + chip.clone(), + layouter.namespace(|| "merkle_crh"), + &SinsemillaHashDomains::MerkleCrh, + )?; + merkle_crh.hash_to_point( + chip.clone(), + layouter.namespace(|| "hash_to_point"), + vec![true, true, false, false], + )?; + + let commit_ivk = CommitDomain::new( + chip.clone(), + layouter.namespace(|| "commit_ivk"), + &SinsemillaCommitDomains::CommitIvk, + )?; + let r = ScalarFixed::>::new( + chip.clone(), + layouter.namespace(|| "r"), + Some(C::Scalar::rand()), + )?; + commit_ivk.commit( + chip.clone(), + layouter.namespace(|| "commit"), + vec![true, true, false, false], + r, + )?; + + Ok(()) + } + } + + #[test] + fn sinsemilla_gadget() { + let k = 11; + let circuit = MyCircuit:: { + _marker: std::marker::PhantomData, + }; + let prover = MockProver::run(k, &circuit, vec![]).unwrap(); + assert_eq!(prover.verify(), Ok(())) + } +} diff --git a/src/circuit/gadget/sinsemilla/chip.rs b/src/circuit/gadget/sinsemilla/chip.rs index 3582e2859..8d0528cad 100644 --- a/src/circuit/gadget/sinsemilla/chip.rs +++ b/src/circuit/gadget/sinsemilla/chip.rs @@ -1,209 +1,645 @@ -use super::super::ecc::chip::{EccChip, EccConfig}; +use super::super::ecc::{ + chip::{EccChip, EccConfigEnum, EccPoint, OrchardFixedBases}, + EccInstructions, +}; use super::{CommitDomains, HashDomains, SinsemillaInstructions}; +use std::collections::BTreeMap; +use std::convert::TryFrom; -use crate::constants::OrchardFixedBases; -use crate::primitives::sinsemilla::K; +use crate::constants; +use crate::primitives::sinsemilla::{ + lebs2ip_k, C as SinsemillaC, K, Q_COMMIT_IVK_M_GENERATOR, Q_MERKLE_CRH, + Q_NOTE_COMMITMENT_M_GENERATOR, +}; use ff::Field; use group::Curve; use halo2::{ arithmetic::{CurveAffine, FieldExt}, - circuit::{Cell, Layouter}, - plonk::{Advice, Column, ConstraintSystem, Error, Expression, Selector}, + circuit::{CellValue, Chip, Config, Layouter, Loaded, Region}, + plonk::{Advice, Any, Column, ConstraintSystem, Error, Permutation, Selector}, poly::Rotation, }; mod generator_table; -use generator_table::*; - -/// A structure containing a cell and its assigned value. -#[derive(Clone, Debug)] -pub struct CellValue { - cell: Cell, - value: Option, -} +use generator_table::{get_s_by_idx, GeneratorTableChip, GeneratorTableConfigEnum}; /// A message to be hashed. #[derive(Clone, Debug)] -pub struct Message(Vec>); +pub struct Message(Vec>); -/// A domain in which $\mathsf{SinsemillaHashToPoint}$ and $\mathsf{SinsemillaHash}$ can -/// be used. +/// Configuration for the Sinsemilla hash chip #[derive(Clone, Debug)] #[allow(non_snake_case)] -pub struct HashDomain { - Q: C, -} - -#[derive(Clone, Debug)] -pub enum OrchardHashDomains { - NoteCommit(HashDomain), - CommitIvk(HashDomain), - MerkleCrh(HashDomain), +pub struct SinsemillaConfig { + bits: Column, + u: Column, + x_a: Column, + y_a: Column, + x_p: Column, + lambda1: Column, + lambda2: Column, + perm_bits: Permutation, + perm_sum: Permutation, + q_sinsemilla: Selector, + generator_table: GeneratorTableConfigEnum, + ecc_config: EccConfigEnum, } -impl HashDomains for OrchardHashDomains {} - -/// A domain in which $\mathsf{SinsemillaCommit}$ and $\mathsf{SinsemillaShortCommit}$ can -/// be used. +/// Enum for the Sinsemilla hash chip #[derive(Clone, Debug)] #[allow(non_snake_case)] -pub struct OrchardCommitDomain { - M: HashDomain, - R: OrchardFixedBases, +pub enum SinsemillaConfigEnum { + Empty, + Config(SinsemillaConfig), } -impl OrchardCommitDomain { - fn M(&self) -> &HashDomain { - &self.M - } - - fn R(&self) -> OrchardFixedBases { - self.R +impl Config for SinsemillaConfigEnum { + fn empty() -> Self { + Self::Empty } } #[derive(Clone, Debug)] -pub enum OrchardCommitDomains { - NoteCommit(OrchardCommitDomain), - CommitIvk(OrchardCommitDomain), +pub struct SinsemillaChip { + config: SinsemillaConfigEnum, + loaded: as Chip>::Loaded, } -impl From> for OrchardHashDomains { - fn from(commit_domain: OrchardCommitDomains) -> Self { - match commit_domain { - OrchardCommitDomains::NoteCommit(domain) => Self::NoteCommit(domain.M().clone()), - OrchardCommitDomains::CommitIvk(domain) => Self::CommitIvk(domain.M().clone()), - } +impl Chip for SinsemillaChip { + type Config = SinsemillaConfigEnum; + type Loaded = as Chip>::Loaded; + + fn config(&self) -> &Self::Config { + &self.config } -} -impl CommitDomains, OrchardHashDomains> - for OrchardCommitDomains -{ - fn r(&self) -> OrchardFixedBases { - match self { - Self::NoteCommit(domain) => domain.R(), - Self::CommitIvk(domain) => domain.R(), - _ => unreachable!(), - } + fn loaded(&self) -> &Self::Loaded { + &self.loaded } +} - fn hash_domain(&self) -> OrchardHashDomains { - match self { - Self::NoteCommit(_) => self.clone().into(), - Self::CommitIvk(_) => self.clone().into(), +impl SinsemillaChip { + pub fn new() -> Self { + Self { + config: >::Config::empty(), + loaded: >::Loaded::empty(), } } -} -/// Configuration for the ECC chip -#[derive(Clone, Debug)] -#[allow(non_snake_case)] -pub struct SinsemillaConfig { - ecc_config: EccConfig, - generator_table: GeneratorTable, - q_sinsemilla: Selector, -} + pub fn construct( + config: >::Config, + loaded: >::Loaded, + ) -> Self { + Self { config, loaded } + } -#[allow(non_snake_case)] -impl EccChip { - fn configure_sinsemilla( + #[allow(non_snake_case)] + pub fn configure( + &mut self, meta: &mut ConstraintSystem, - q_sinsemilla: Selector, - bits: Column, - u: Column, - A: (Column, Column), - P: (Column, Column), - lambda: (Column, Column), - add_complete_bool: [Column; 4], - add_complete_inv: [Column; 4], - ) -> SinsemillaConfig { - let ecc_config = EccChip::::configure( - meta, - bits, - u, - A, - P, - lambda, - add_complete_bool, - add_complete_inv, - ); + _selectors: BTreeMap<&str, Selector>, + columns: BTreeMap<&str, Column>, + perms: BTreeMap<&str, Permutation>, + ) -> >::Config { + // Sinsemilla selector + let q_sinsemilla = meta.selector(); + let mut selectors = BTreeMap::new(); + selectors.insert("q_sinsemilla", q_sinsemilla); - // Fixed column for Sinsemilla selector - let sinsemilla_cur = meta.query_selector(q_sinsemilla, Rotation::cur()); + let mut ecc_chip = EccChip::::new(); + let ecc_config = + ecc_chip.configure(meta, selectors.clone(), columns.clone(), perms.clone()); + + let mut generator_table_chip = GeneratorTableChip::::new(); + let generator_table = + generator_table_chip.configure(meta, selectors.clone(), columns.clone(), perms.clone()); - // m_i = z_{i + 1} - (z_i * 2^k) - let z_cur = meta.query_advice(ecc_config.bits, Rotation::cur()); - let z_next = meta.query_advice(ecc_config.bits, Rotation::next()); - let m = z_next - z_cur * C::Base::from_u64((1 << K) as u64); + let bits = *columns.get("bits").unwrap(); + let u = *columns.get("u").unwrap(); + let x_a = *columns.get("x_a").unwrap(); + let y_a = *columns.get("y_a").unwrap(); + let x_p = *columns.get("x_p").unwrap(); + let lambda1 = *columns.get("lambda1").unwrap(); + let lambda2 = *columns.get("lambda2").unwrap(); + + let perm_bits = perms.get("perm_bits").unwrap(); + let perm_sum = perms.get("perm_sum").unwrap(); + + let sinsemilla_cur = meta.query_selector(q_sinsemilla, Rotation::cur()); // y_a = (1/2) ⋅ (lambda1 + lambda2) ⋅ (x_a - (lambda1^2 - x_a - x_p)) - let lambda1_cur = meta.query_advice(ecc_config.lambda.0, Rotation::cur()); - let lambda2_cur = meta.query_advice(ecc_config.lambda.1, Rotation::cur()); - let x_a_cur = meta.query_advice(ecc_config.A.0, Rotation::cur()); - let x_p_cur = meta.query_advice(ecc_config.P.0, Rotation::cur()); + let lambda1_cur = meta.query_any(lambda1, Rotation::cur()); + let lambda2_cur = meta.query_any(lambda2, Rotation::cur()); + let x_a_cur = meta.query_any(x_a, Rotation::cur()); + let x_p_cur = meta.query_any(x_p, Rotation::cur()); let y_a_cur = (lambda1_cur.clone() + lambda2_cur.clone()) * (x_a_cur.clone() - (lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone())) * C::Base::TWO_INV; - // y_p = y_a - lambda1 ⋅ (x_a - x_p) - let y_p = y_a_cur.clone() - lambda1_cur.clone() * (x_a_cur.clone() - x_p_cur.clone()); + let lambda1_next = meta.query_any(lambda1, Rotation::next()); + let lambda2_next = meta.query_any(lambda2, Rotation::next()); + let x_a_next = meta.query_any(x_a, Rotation::next()); + let x_p_next = meta.query_any(x_p, Rotation::next()); + let y_a_next = (lambda1_next.clone() + lambda2_next) + * (x_a_next.clone() + - (lambda1_next.clone() * lambda1_next - x_a_next.clone() - x_p_next)) + * C::Base::TWO_INV; - let (x_p_init, y_p_init) = get_s_by_idx::(0).to_affine().get_xy().unwrap(); + // Sinsemilla expr1 gate + meta.create_gate("Sinsemilla expr1", |_| { + // λ_{2,i}^2 − x_{A,i+1} −(λ_{1,i}^2 − x_{A,i} − x_{P,i}) − x_{A,i} = 0 + let expr1 = lambda2_cur.clone() * lambda2_cur.clone() + - x_a_next.clone() + - (lambda1_cur.clone() * lambda1_cur) + + x_p_cur; - let generator_table = GeneratorTable::configure::( - meta, - sinsemilla_cur.clone() * m - + (Expression::Constant(C::Base::one()) - sinsemilla_cur.clone()) * C::Base::zero(), - sinsemilla_cur.clone() * x_p_cur.clone() - + (Expression::Constant(C::Base::one()) - sinsemilla_cur.clone()) * x_p_init, - sinsemilla_cur.clone() * y_p - + (Expression::Constant(C::Base::one()) - sinsemilla_cur.clone()) * y_p_init, - ); + sinsemilla_cur.clone() * expr1 + }); - // TODO: create gates + // Sinsemilla expr2 gate + meta.create_gate("Sinsemilla expr2", |_| { + // λ_{2,i}⋅(x_{A,i} − x_{A,i+1}) − y_{A,i} − y_{A,i+1} = 0 + let expr2 = lambda2_cur * (x_a_cur - x_a_next) - y_a_cur - y_a_next; - SinsemillaConfig { - ecc_config, + sinsemilla_cur.clone() * expr2 + }); + + let config = SinsemillaConfigEnum::Config(SinsemillaConfig { + bits: Column::::try_from(bits).unwrap(), + u: Column::::try_from(u).unwrap(), + x_a: Column::::try_from(x_a).unwrap(), + y_a: Column::::try_from(y_a).unwrap(), + x_p: Column::::try_from(x_p).unwrap(), + lambda1: Column::::try_from(lambda1).unwrap(), + lambda2: Column::::try_from(lambda2).unwrap(), + perm_bits: perm_bits.clone(), + perm_sum: perm_sum.clone(), generator_table, q_sinsemilla, + ecc_config, + }); + self.config = config.clone(); + config + } + + pub fn load( + &mut self, + layouter: &mut impl Layouter, + ) -> Result<>::Loaded, Error> { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config, + _ => unreachable!(), + }; + + // Load the lookup table. + let mut generator_table_chip = + GeneratorTableChip::::construct(config.generator_table.clone(), ()); + generator_table_chip.load(layouter)?; + + let mut ecc_chip = EccChip::::construct( + config.ecc_config.clone(), + as Chip>::Loaded::empty(), + ); + let loaded = ecc_chip.load(layouter)?; + self.loaded = loaded.clone(); + + Ok(loaded) + } +} + +#[derive(Clone, Debug)] +pub enum SinsemillaHashDomains { + NoteCommit, + CommitIvk, + MerkleCrh, +} + +impl HashDomains for SinsemillaHashDomains { + fn Q(&self) -> C { + match self { + SinsemillaHashDomains::CommitIvk => C::from_xy( + C::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.0).unwrap(), + C::Base::from_bytes(&Q_COMMIT_IVK_M_GENERATOR.1).unwrap(), + ) + .unwrap(), + SinsemillaHashDomains::NoteCommit => C::from_xy( + C::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.0).unwrap(), + C::Base::from_bytes(&Q_NOTE_COMMITMENT_M_GENERATOR.1).unwrap(), + ) + .unwrap(), + SinsemillaHashDomains::MerkleCrh => C::from_xy( + C::Base::from_bytes(&Q_MERKLE_CRH.0).unwrap(), + C::Base::from_bytes(&Q_MERKLE_CRH.1).unwrap(), + ) + .unwrap(), } } } -impl SinsemillaInstructions for EccChip { - type Message = Message; - type CommitDomains = OrchardCommitDomains; - type HashDomains = OrchardHashDomains; - type Q = Self::Point; +impl SinsemillaInstructions for SinsemillaChip { + type HashDomains = SinsemillaHashDomains; + type CommitDomains = SinsemillaCommitDomains; + + type Q = EccPoint; + + type Message = Message; #[allow(non_snake_case)] fn get_Q( - layouter: &mut impl Layouter, + &self, + layouter: &mut impl Layouter, domain: &Self::HashDomains, ) -> Result { - todo!() + let q: C = domain.Q(); + let q = q.coordinates().unwrap(); + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config, + _ => unreachable!(), + }; + layouter.assign_region( + || format!("{:?} Q", domain), + |mut region: Region<'_, C::Base>| { + let x = region.assign_advice(|| "x_q", config.x_a, 0, || Ok(*q.x()))?; + let x = CellValue::new(x, Some(*q.x())); + let y = region.assign_advice(|| "y_q", config.y_a, 0, || Ok(*q.y()))?; + let y = CellValue::new(y, Some(*q.y())); + Ok(EccPoint { x, y }) + }, + ) } fn witness_message( - layouter: &mut impl Layouter, + &self, + layouter: &mut impl Layouter, message: Vec, ) -> Result { - todo!() + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config, + _ => unreachable!(), + }; + + // Message must be at most `kc` bits + let max_len = K * SinsemillaC; + assert!(message.len() <= max_len); + + // Pad message to nearest multiple of `k`. + let pad_length = K - (message.len() % K); + let mut message = message.clone(); + message.extend_from_slice(&vec![false; pad_length]); + + // Chunk message into `k`-bit words + let words: Vec<_> = message.chunks_exact(K).collect(); + + // Parse each chunk of boolean values (little-endian bit order) into a u64. + let words: Vec = words.iter().map(|word| lebs2ip_k(word)).collect(); + + layouter.assign_region( + || "message", + |mut region: Region<'_, C::Base>| { + let mut result = Vec::with_capacity(words.len()); + for (idx, word) in words.iter().enumerate() { + let cell = region.assign_advice( + || format!("word {:?}", idx), + config.bits, + idx, + || Ok(C::Base::from_u64(*word as u64)), + )?; + result.push(CellValue::new(cell, Some(*word))); + } + Ok(Message(result)) + }, + ) } fn extract(point: &Self::Point) -> Self::X { - todo!() + point.x.clone() } #[allow(non_snake_case)] fn hash_to_point( - layouter: &mut impl Layouter, + &self, + layouter: &mut impl Layouter, Q: &Self::Q, message: Self::Message, ) -> Result { - todo!() + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config, + _ => unreachable!(), + }; + + // Get (x_p, y_p) for each word. We precompute this here so that we can use `batch_normalize()`. + let generators_projective: Vec<_> = message + .0 + .iter() + .map(|word| get_s_by_idx::(word.value.unwrap())) + .collect(); + let mut generators = vec![C::default(); generators_projective.len()]; + C::Curve::batch_normalize(&generators_projective, &mut generators); + let generators: Vec<(C::Base, C::Base)> = generators + .iter() + .map(|gen| { + let point = gen.coordinates().unwrap(); + (*point.x(), *point.y()) + }) + .collect(); + + // Initialize `(x_a, y_a)` to be `(x_q, y_q)` + + layouter.assign_region( + || "Assign message", + |mut region| { + // Copy message into this region. + { + for (idx, word) in message.0.iter().enumerate() { + let word_copy = region.assign_advice( + || format!("hash message word {:?}", idx), + config.bits, + idx, + || { + word.value + .map(|value| C::Base::from_u64(value.into())) + .ok_or(Error::SynthesisError) + }, + )?; + region.constrain_equal(&config.perm_bits, word.cell, word_copy)?; + } + } + + for row in 0..(message.0.len() - 1) { + // Enable `Sinsemilla` selector + config.q_sinsemilla.enable(&mut region, row)?; + } + + // Copy the `x`-coordinate of our starting `Q` base. + let x_q_cell = region.assign_advice( + || "x_q", + config.x_a, + 0, + || Q.x.value.ok_or(Error::SynthesisError), + )?; + region.constrain_equal(&config.perm_sum, Q.x.cell, x_q_cell)?; + + // Initialize `x_a`, `y_a` as `x_q`, `y_q`. + let mut x_a = Q.x.value; + let mut x_a_cell = Q.x.cell; + let mut y_a = Q.y.value; + + for row in 0..message.0.len() { + let gen = generators[row]; + let x_p = gen.0; + let y_p = gen.1; + + // Assign `x_p` + region.assign_advice(|| "x_p", config.x_p, row, || Ok(x_p))?; + + // Compute and assign `lambda1, lambda2` + let lambda1 = x_a + .zip(y_a) + .map(|(x_a, y_a)| (y_a - y_p) * (x_a - x_p).invert().unwrap()); + let x_r = lambda1 + .zip(x_a) + .map(|(lambda1, x_a)| lambda1 * lambda1 - x_a - x_p); + let lambda2 = + x_a.zip(y_a) + .zip(x_r) + .zip(lambda1) + .map(|(((x_a, y_a), x_r), lambda1)| { + C::Base::from_u64(2) * y_a * (x_a - x_r).invert().unwrap() - lambda1 + }); + region.assign_advice( + || "lambda1", + config.lambda1, + row, + || lambda1.ok_or(Error::SynthesisError), + )?; + region.assign_advice( + || "lambda2", + config.lambda2, + row, + || lambda2.ok_or(Error::SynthesisError), + )?; + + // Compute and assign `x_a` for the next row + let x_a_new = lambda2 + .zip(x_a) + .zip(x_r) + .map(|((lambda2, x_a), x_r)| lambda2 * lambda2 - x_a - x_r); + y_a = + lambda2.zip(x_a).zip(x_a_new).zip(y_a).map( + |(((lambda2, x_a), x_a_new), y_a)| lambda2 * (x_a - x_a_new) - y_a, + ); + + x_a_cell = region.assign_advice( + || "x_a", + config.x_a, + row + 1, + || x_a_new.ok_or(Error::SynthesisError), + )?; + + x_a = x_a_new; + } + + // Assign the final `y_a` + let y_a_cell = region.assign_advice( + || "y_a", + config.y_a, + message.0.len(), + || y_a.ok_or(Error::SynthesisError), + )?; + + let y_a = CellValue::new(y_a_cell, y_a); + let x_a = CellValue::new(x_a_cell, x_a); + + Ok(EccPoint { x: x_a, y: y_a }) + }, + ) + } +} + +#[derive(Clone, Debug)] +pub enum SinsemillaCommitDomains { + NoteCommit, + CommitIvk, +} + +impl CommitDomains, SinsemillaHashDomains> + for SinsemillaCommitDomains +{ + fn r(&self) -> OrchardFixedBases { + match self { + Self::NoteCommit => { + OrchardFixedBases::::NoteCommitR(constants::note_commit_r::generator()) + } + Self::CommitIvk => { + OrchardFixedBases::::CommitIvkR(constants::commit_ivk_r::generator()) + } + } + } + + fn hash_domain(&self) -> SinsemillaHashDomains { + match self { + Self::NoteCommit => SinsemillaHashDomains::NoteCommit, + Self::CommitIvk => SinsemillaHashDomains::CommitIvk, + } + } +} + +impl EccInstructions for SinsemillaChip { + type ScalarVar = as EccInstructions>::ScalarVar; + type ScalarFixed = as EccInstructions>::ScalarFixed; + type ScalarFixedShort = as EccInstructions>::ScalarFixedShort; + type Point = as EccInstructions>::Point; + type X = as EccInstructions>::X; + type FixedPoint = as EccInstructions>::FixedPoint; + type FixedPoints = as EccInstructions>::FixedPoints; + + fn witness_scalar_var( + &self, + layouter: &mut impl Layouter, + value: Option, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.witness_scalar_var(layouter, value) + } + + fn witness_scalar_fixed( + &self, + layouter: &mut impl Layouter, + value: Option, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.witness_scalar_fixed(layouter, value) + } + + fn witness_scalar_fixed_short( + &self, + layouter: &mut impl Layouter, + value: Option, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.witness_scalar_fixed_short(layouter, value) + } + + fn witness_point( + &self, + layouter: &mut impl Layouter, + value: Option, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.witness_point(layouter, value) + } + + fn extract_p(point: &Self::Point) -> &Self::X { + EccChip::::extract_p(point) + } + + fn get_fixed(&self, fixed_point: Self::FixedPoints) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.get_fixed(fixed_point) + } + + fn add( + &self, + layouter: &mut impl Layouter, + a: &Self::Point, + b: &Self::Point, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.add(layouter, a, b) + } + + fn add_complete( + &self, + layouter: &mut impl Layouter, + a: &Self::Point, + b: &Self::Point, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.add_complete(layouter, a, b) + } + + fn double( + &self, + layouter: &mut impl Layouter, + a: &Self::Point, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.double(layouter, a) + } + + fn mul( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarVar, + base: &Self::Point, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.mul(layouter, scalar, base) + } + + fn mul_fixed( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarFixed, + base: &Self::FixedPoint, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.mul_fixed(layouter, scalar, base) + } + + fn mul_fixed_short( + &self, + layouter: &mut impl Layouter, + scalar: &Self::ScalarFixedShort, + base: &Self::FixedPoint, + ) -> Result { + let config = match self.config() { + SinsemillaConfigEnum::Config(config) => config.ecc_config.clone(), + _ => unreachable!(), + }; + let ecc_chip = EccChip::::construct(config, self.loaded.clone()); + ecc_chip.mul_fixed_short(layouter, scalar, base) } } diff --git a/src/circuit/gadget/sinsemilla/chip/generator_table.rs b/src/circuit/gadget/sinsemilla/chip/generator_table.rs index 2952e83f7..6f96c6b0c 100644 --- a/src/circuit/gadget/sinsemilla/chip/generator_table.rs +++ b/src/circuit/gadget/sinsemilla/chip/generator_table.rs @@ -1,29 +1,82 @@ -use super::super::ecc::chip::EccChip; use crate::primitives::sinsemilla::{K, S_PERSONALIZATION}; use halo2::{ arithmetic::{CurveAffine, CurveExt, FieldExt}, - circuit::Layouter, - plonk::{Column, ConstraintSystem, Error, Expression, Fixed}, + circuit::{Chip, Config, Layouter}, + plonk::{Any, Column, ConstraintSystem, Error, Expression, Fixed, Permutation, Selector}, poly::Rotation, }; +use std::collections::BTreeMap; +use std::marker::PhantomData; +use ff::Field; use group::Curve; /// Table containing independent generators S[0..2^k] #[derive(Clone, Debug)] -pub(super) struct GeneratorTable { +pub(super) struct GeneratorTableConfig { table_idx: Column, table_x: Column, table_y: Column, } -impl GeneratorTable { - pub(super) fn configure( - meta: &mut ConstraintSystem, - m: Expression, - x_p: Expression, - y_p: Expression, +#[derive(Clone, Debug)] +pub(super) enum GeneratorTableConfigEnum { + Empty, + Config(GeneratorTableConfig), +} + +impl Config for GeneratorTableConfigEnum { + fn empty() -> Self { + Self::Empty + } +} + +pub(super) struct GeneratorTableChip { + config: GeneratorTableConfigEnum, + marker: PhantomData, +} + +impl Chip for GeneratorTableChip { + type Config = GeneratorTableConfigEnum; + type Loaded = (); + + fn config(&self) -> &Self::Config { + &self.config + } + + fn loaded(&self) -> &Self::Loaded { + &() + } +} + +impl GeneratorTableChip { + pub fn new() -> Self { + Self { + config: >::Config::empty(), + marker: PhantomData, + } + } + + pub fn construct( + config: >::Config, + _loaded: >::Loaded, ) -> Self { + Self { + config, + marker: PhantomData, + } + } + + pub fn configure( + &mut self, + meta: &mut ConstraintSystem, + selectors: BTreeMap<&str, Selector>, + columns: BTreeMap<&str, Column>, + _perms: BTreeMap<&str, Permutation>, + ) -> >::Config { + let q_sinsemilla = *selectors.get("q_sinsemilla").unwrap(); + let sinsemilla_cur = meta.query_selector(q_sinsemilla, Rotation::cur()); + let table_idx = meta.fixed_column(); let table_idx_cur = meta.query_fixed(table_idx, Rotation::cur()); let table_x = meta.fixed_column(); @@ -31,55 +84,68 @@ impl GeneratorTable { let table_y = meta.fixed_column(); let table_y_cur = meta.query_fixed(table_y, Rotation::cur()); - meta.lookup(&[m, x_p, y_p], &[table_idx_cur, table_x_cur, table_y_cur]); + let bits = *columns.get("bits").unwrap(); + let x_p = *columns.get("x_p").unwrap(); + let x_a = *columns.get("x_a").unwrap(); + let lambda1 = *columns.get("lambda1").unwrap(); + let lambda2 = *columns.get("lambda2").unwrap(); - GeneratorTable { - table_idx, - table_x, - table_y, - } - } + let bits = meta.query_any(bits, Rotation::cur()); + let x_a_cur = meta.query_any(x_a, Rotation::cur()); + let x_p_cur = meta.query_any(x_p, Rotation::cur()); + let lambda1_cur = meta.query_any(lambda1, Rotation::cur()); + let lambda2_cur = meta.query_any(lambda2, Rotation::cur()); + let y_a_cur = (lambda1_cur.clone() + lambda2_cur.clone()) + * (x_a_cur.clone() + - (lambda1_cur.clone() * lambda1_cur.clone() - x_a_cur.clone() - x_p_cur.clone())) + * C::Base::TWO_INV; - // Generates S[0..2^k] as 2^k independent, verifiably random generators of the group. - // Loads these generators into a lookup table along with their indices. - // Uses SWU hash-to-curve. - fn generate(&self) -> impl Iterator { - let (init_x, init_y) = get_s_by_idx::(0).to_affine().get_xy().unwrap(); + // y_p = y_a - lambda1 ⋅ (x_a - x_p) + let y_p = y_a_cur.clone() - lambda1_cur.clone() * (x_a_cur.clone() - x_p_cur.clone()); - (1..=(1 << K)).scan( - (C::Base::default(), init_x, init_y), - move |(idx, x, y), i| { - // We computed this table row in the previous iteration. - let res = (*idx, *x, *y); + let init_p = get_s_by_idx::(0).to_affine().coordinates().unwrap(); - // i holds the zero-indexed row number for the next table row. - *idx = C::Base::from_u64(i as u64); + // Lookup expressions default to the first entry when `q_sinsemilla` + // is not enabled. + let m = sinsemilla_cur.clone() * bits + + (Expression::Constant(C::Base::one()) - sinsemilla_cur.clone()) * C::Base::zero(); + let x_p = sinsemilla_cur.clone() * x_p_cur.clone() + + (Expression::Constant(C::Base::one()) - sinsemilla_cur.clone()) * *init_p.x(); + let y_p = sinsemilla_cur.clone() * y_p + + (Expression::Constant(C::Base::one()) - sinsemilla_cur.clone()) * *init_p.y(); - let (new_x, new_y) = get_s_by_idx::(i).to_affine().get_xy().unwrap(); + meta.lookup(&[m, x_p, y_p], &[table_idx_cur, table_x_cur, table_y_cur]); - *x = new_x; - *y = new_y; + let config = GeneratorTableConfigEnum::Config(GeneratorTableConfig { + table_idx, + table_x, + table_y, + }); - Some(res) - }, - ) + self.config = config.clone(); + config } - pub(super) fn load( - &self, - layouter: &mut impl Layouter>, - ) -> Result<(), Error> { + pub fn load( + &mut self, + layouter: &mut impl Layouter, + ) -> Result<>::Loaded, Error> { + let config = match self.config() { + GeneratorTableConfigEnum::Config(config) => config, + _ => unreachable!(), + }; + layouter.assign_region( || "generator_table", |mut gate| { // We generate the row values lazily (we only need them during keygen). - let mut rows = self.generate::(); + let mut rows = config.generate::(); for index in 0..(1 << K) { let mut row = None; gate.assign_fixed( || "table_idx", - self.table_idx, + config.table_idx, index, || { row = rows.next(); @@ -88,13 +154,13 @@ impl GeneratorTable { )?; gate.assign_fixed( || "table_x", - self.table_x, + config.table_x, index, || row.map(|(_, x, _)| x).ok_or(Error::SynthesisError), )?; gate.assign_fixed( || "table_y", - self.table_y, + config.table_y, index, || row.map(|(_, _, y)| y).ok_or(Error::SynthesisError), )?; @@ -105,6 +171,33 @@ impl GeneratorTable { } } +impl GeneratorTableConfig { + // Generates S[0..2^k] as 2^k independent, verifiably random generators of the group. + // Loads these generators into a lookup table along with their indices. + // Uses SWU hash-to-curve. + fn generate(&self) -> impl Iterator { + let init = get_s_by_idx::(0).to_affine().coordinates().unwrap(); + + (1..=(1 << K)).scan( + (C::Base::default(), *init.x(), *init.y()), + move |(idx, x, y), i| { + // We computed this table row in the previous iteration. + let res = (*idx, *x, *y); + + // i holds the zero-indexed row number for the next table row. + *idx = C::Base::from_u64(i as u64); + + let new = get_s_by_idx::(i).to_affine().coordinates().unwrap(); + + *x = *new.x(); + *y = *new.y(); + + Some(res) + }, + ) + } +} + /// Get generator S by index pub fn get_s_by_idx(idx: u32) -> C::Curve { let hash = C::CurveExt::hash_to_curve(S_PERSONALIZATION); diff --git a/src/primitives/sinsemilla.rs b/src/primitives/sinsemilla.rs index e0d3cfc1b..38f71fd22 100644 --- a/src/primitives/sinsemilla.rs +++ b/src/primitives/sinsemilla.rs @@ -12,7 +12,8 @@ use self::addition::IncompletePoint; mod constants; pub use constants::*; -fn lebs2ip_k(bits: &[bool]) -> u32 { +/// Convert `k`-bit little-endian boolean array to a u32 +pub fn lebs2ip_k(bits: &[bool]) -> u32 { assert!(bits.len() == K); bits.iter() .enumerate()