From 6f4b5b03408d6ceffcdc076a474b2c2dded7d7e3 Mon Sep 17 00:00:00 2001 From: therealyingtong Date: Tue, 20 Jul 2021 00:42:22 +0800 Subject: [PATCH] circuit.rs: Constrain derived circuit values to equal public inputs. --- src/circuit.rs | 218 ++++++++++++++++++++++++++++-------------- src/keys.rs | 8 ++ src/note/nullifier.rs | 8 ++ src/tree.rs | 8 ++ src/value.rs | 22 ++++- 5 files changed, 191 insertions(+), 73 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index f45cae14..acebfde2 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -9,7 +9,10 @@ use halo2::{ poly::Rotation, transcript::{Blake2bRead, Blake2bWrite}, }; -use pasta_curves::{arithmetic::FieldExt, pallas, vesta}; +use pasta_curves::{ + arithmetic::{CurveAffine, FieldExt}, + pallas, vesta, +}; use crate::{ constants::{ @@ -66,12 +69,23 @@ pub(crate) mod gadget; // FIXME: This circuit should fit within 2^11 rows. const K: u32 = 12; +// Absolute offsets for public inputs. +const ZERO: usize = 0; +const ANCHOR: usize = 1; +const NF_OLD: usize = 2; +const CV_NET_X: usize = 3; +const CV_NET_Y: usize = 4; +const RK_X: usize = 5; +const RK_Y: usize = 6; +const CMX: usize = 7; +const ENABLE_SPEND: usize = 8; +const ENABLE_OUTPUT: usize = 9; + /// Configuration needed to use the Orchard Action circuit. #[derive(Clone, Debug)] pub struct Config { - q_primary: Selector, primary: Column, - q_v_net: Selector, + q_orchard: Selector, advices: [Column; 10], enable_flag_config: EnableFlagConfig, ecc_config: EccConfig, @@ -138,15 +152,29 @@ impl plonk::Circuit for Circuit { ]; // Constrain v_old - v_new = magnitude * sign - let q_v_net = meta.selector(); - meta.create_gate("v_old - v_new = magnitude * sign", |meta| { - let q_v_net = meta.query_selector(q_v_net); + // Either v_old = 0, or anchor equals public input + let q_orchard = meta.selector(); + meta.create_gate("Orchard circuit checks", |meta| { + let q_orchard = meta.query_selector(q_orchard); let v_old = meta.query_advice(advices[0], Rotation::cur()); let v_new = meta.query_advice(advices[1], Rotation::cur()); let magnitude = meta.query_advice(advices[2], Rotation::cur()); let sign = meta.query_advice(advices[3], Rotation::cur()); - vec![q_v_net * (v_old - v_new - magnitude * sign)] + let anchor = meta.query_advice(advices[4], Rotation::cur()); + let pub_input_anchor = meta.query_advice(advices[5], Rotation::cur()); + + std::array::IntoIter::new([ + ( + "v_old - v_new = magnitude * sign", + v_old.clone() - v_new - magnitude * sign, + ), + ( + "Either v_old = 0, or anchor equals public input", + v_old * (anchor - pub_input_anchor), + ), + ]) + .map(move |(name, poly)| (name, q_orchard.clone() * poly)) }); // Fixed columns for the Sinsemilla generator lookup table @@ -173,6 +201,11 @@ impl plonk::Circuit for Circuit { meta.fixed_column(), ]; + // Instance column. + let primary = meta.instance_column(); + + meta.enable_equality(primary.into()); + // Permutation over all advice columns and `constants` columns. // TODO: Replace `*_constants` with public inputs API. for advice in advices.iter() { @@ -254,27 +287,9 @@ impl plonk::Circuit for Circuit { let new_note_commit_config = NoteCommitConfig::configure(meta, advices, sinsemilla_config_2.clone()); - // TODO: Infrastructure to handle public inputs. - let q_primary = meta.selector(); - let primary = meta.instance_column(); - - // Placeholder gate so there is something for the prover to operate on. - // We need a selector so that the gate is disabled by default, and doesn't - // interfere with the blinding factors. - let advice = meta.advice_column(); - let selector = meta.selector(); - - meta.create_gate("TODO", |meta| { - let a = meta.query_advice(advice, Rotation::cur()); - let s = meta.query_selector(selector); - - vec![s * a] - }); - Config { - q_primary, primary, - q_v_net, + q_orchard, advices, enable_flag_config, ecc_config, @@ -366,8 +381,7 @@ impl plonk::Circuit for Circuit { }; // Merkle path validity check. - // TODO: constrain output to equal public input - let _anchor = { + let anchor = { let merkle_inputs = MerklePath { chip_1: config.merkle_chip_1(), chip_2: config.merkle_chip_2(), @@ -380,8 +394,7 @@ impl plonk::Circuit for Circuit { }; // Value commitment integrity. - // TODO: constrain to equal public input cv_net - let _cv_net = { + let v_net = { // v_net = v_old - v_new let v_net = { let v_net_val = self.v_old.zip(self.v_new).map(|(v_old, v_new)| { @@ -426,26 +439,6 @@ impl plonk::Circuit for Circuit { (magnitude, sign) }; - // Constrain v_old - v_new = magnitude * sign - layouter.assign_region( - || "v_old - v_new = magnitude * sign", - |mut region| { - copy(&mut region, || "v_old", config.advices[0], 0, &v_old)?; - copy(&mut region, || "v_new", config.advices[1], 0, &v_new)?; - let (magnitude, sign) = v_net; - copy( - &mut region, - || "v_net magnitude", - config.advices[2], - 0, - &magnitude, - )?; - copy(&mut region, || "v_net sign", config.advices[3], 0, &sign)?; - - config.q_v_net.enable(&mut region, 0) - }, - )?; - // commitment = [v_net] ValueCommitV let (commitment, _) = { let value_commit_v = ValueCommitV::get(); @@ -464,11 +457,16 @@ impl plonk::Circuit for Circuit { }; // [v_net] ValueCommitV + [rcv] ValueCommitR - commitment.add(layouter.namespace(|| "cv_net"), &blind)? + let cv_net = commitment.add(layouter.namespace(|| "cv_net"), &blind)?; + + // Constrain cv_net to equal public input + layouter.constrain_instance(cv_net.inner().x().cell(), config.primary, CV_NET_X)?; + layouter.constrain_instance(cv_net.inner().y().cell(), config.primary, CV_NET_Y)?; + + v_net }; // Nullifier integrity - // TODO: constrain to equal public input nf_old let nf_old = { // nk_rho_old = poseidon_hash(nk, rho_old) let nk_rho_old = { @@ -548,14 +546,18 @@ impl plonk::Circuit for Circuit { // Add cm_old to multiplied fixed base to get nf_old // cm_old + [poseidon_output + psi_old] NullifierK - cm_old + let nf_old = cm_old .add(layouter.namespace(|| "nf_old"), &product)? - .extract_p() + .extract_p(); + + // Constrain nf_old to equal public input + layouter.constrain_instance(nf_old.inner().cell(), config.primary, NF_OLD)?; + + nf_old }; // Spend authority - // TODO: constrain to equal public input rk - let _rk = { + { // alpha_commitment = [alpha] SpendAuthG let (alpha_commitment, _) = { let spend_auth_g = OrchardFixedBasesFull::SpendAuthG; @@ -564,11 +566,15 @@ impl plonk::Circuit for Circuit { }; // [alpha] SpendAuthG + ak - alpha_commitment.add(layouter.namespace(|| "rk"), &ak)? - }; + let rk = alpha_commitment.add(layouter.namespace(|| "rk"), &ak)?; + + // Constrain rk to equal public input + layouter.constrain_instance(rk.inner().x().cell(), config.primary, RK_X)?; + layouter.constrain_instance(rk.inner().y().cell(), config.primary, RK_Y)?; + } // Diversified address integrity. - let (pk_d_old, _) = { + let pk_d_old = { let commit_ivk_config = config.commit_ivk_config.clone(); let ivk = { @@ -585,17 +591,31 @@ impl plonk::Circuit for Circuit { }; // [ivk] g_d_old - g_d_old.mul(layouter.namespace(|| "[ivk] g_d_old"), ivk.inner())? + let (derived_pk_d_old, _) = + g_d_old.mul(layouter.namespace(|| "[ivk] g_d_old"), ivk.inner())?; + + // Constrain derived pk_d_old to equal witnessed pk_d_old + // This addresses the case where ivk = ⊥ , since ⊥ maps to 0 and variable-base + // scalar multiplication maps [0]B to (0, 0). + let pk_d_old = Point::new( + ecc_chip.clone(), + layouter.namespace(|| "witness pk_d_old"), + self.pk_d_old.map(|pk_d_old| (*pk_d_old).to_affine()), + )?; + derived_pk_d_old + .constrain_equal(layouter.namespace(|| "pk_d_old equality"), &pk_d_old)?; + + pk_d_old }; // Old note commitment integrity. - let _cm_old = { + { let old_note_commit_config = config.old_note_commit_config.clone(); let rcm_old = self.rcm_old.as_ref().map(|rcm_old| **rcm_old); // g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi) - old_note_commit_config.assign_region( + let derived_cm_old = old_note_commit_config.assign_region( layouter.namespace(|| { "g★_d || pk★_d || i2lebsp_{64}(v) || i2lebsp_{255}(rho) || i2lebsp_{255}(psi)" }), @@ -607,11 +627,14 @@ impl plonk::Circuit for Circuit { rho_old, psi_old, rcm_old, - )? - }; + )?; - // new note commitment integrity. - let _cmx = { + // Constrain derived cm_old to equal witnessed cm_old + derived_cm_old.constrain_equal(layouter.namespace(|| "cm_old equality"), &cm_old)?; + } + + // New note commitment integrity. + { let new_note_commit_config = config.new_note_commit_config.clone(); // Witness g_d_new_star @@ -662,8 +685,41 @@ impl plonk::Circuit for Circuit { rcm_new, )?; - cm_new.extract_p() - }; + let cmx = cm_new.extract_p(); + + // Constrain cmx to equal public input + layouter.constrain_instance(cmx.inner().cell(), config.primary, CMX)?; + } + + // Constrain v_old - v_new = magnitude * sign + // Either v_old = 0, or anchor equals public input + layouter.assign_region( + || "v_old - v_new = magnitude * sign", + |mut region| { + copy(&mut region, || "v_old", config.advices[0], 0, &v_old)?; + copy(&mut region, || "v_new", config.advices[1], 0, &v_new)?; + let (magnitude, sign) = v_net; + copy( + &mut region, + || "v_net magnitude", + config.advices[2], + 0, + &magnitude, + )?; + copy(&mut region, || "v_net sign", config.advices[3], 0, &sign)?; + + copy(&mut region, || "anchor", config.advices[4], 0, &anchor)?; + region.assign_advice_from_instance( + || "pub input anchor", + config.primary, + ANCHOR, + config.advices[5], + 0, + )?; + + config.q_orchard.enable(&mut region, 0) + }, + )?; Ok(()) } @@ -721,9 +777,28 @@ pub struct Instance { } impl Instance { - fn to_halo2_instance(&self) -> [[vesta::Scalar; 0]; 1] { - // TODO - [[]] + fn to_halo2_instance(&self) -> [[vesta::Scalar; 10]; 1] { + let mut instance = [vesta::Scalar::zero(); 10]; + + // instance[0] is left as 0. + instance[ANCHOR] = *self.anchor; + instance[CV_NET_X] = self.cv_net.x(); + instance[CV_NET_Y] = self.cv_net.y(); + instance[NF_OLD] = *self.nf_old; + + let rk = pallas::Point::from_bytes(&self.rk.clone().into()) + .unwrap() + .to_affine() + .coordinates() + .unwrap(); + + instance[RK_X] = *rk.x(); + instance[RK_Y] = *rk.y(); + instance[CMX] = *self.cmx; + instance[ENABLE_SPEND] = vesta::Scalar::from_u64(self.enable_spend.into()); + instance[ENABLE_OUTPUT] = vesta::Scalar::from_u64(self.enable_output.into()); + + [instance] } } @@ -814,6 +889,7 @@ mod tests { let (circuits, instances): (Vec<_>, Vec<_>) = iter::once(()) .map(|()| { let (_, fvk, spent_note) = Note::dummy(&mut rng, None); + let sender_address = fvk.default_address(); let nk = *fvk.nk(); let rivk = *fvk.rivk(); diff --git a/src/keys.rs b/src/keys.rs index 57213af4..abcf0091 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -466,6 +466,14 @@ impl AsRef<[u8; 32]> for OutgoingViewingKey { #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub struct DiversifiedTransmissionKey(NonIdentityPallasPoint); +impl std::ops::Deref for DiversifiedTransmissionKey { + type Target = pallas::Point; + + fn deref(&self) -> &pallas::Point { + &(*self.0) + } +} + impl DiversifiedTransmissionKey { /// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. /// diff --git a/src/note/nullifier.rs b/src/note/nullifier.rs index 75d2c40b..0f98b850 100644 --- a/src/note/nullifier.rs +++ b/src/note/nullifier.rs @@ -14,6 +14,14 @@ use crate::{ #[derive(Clone, Copy, Debug)] pub struct Nullifier(pub(crate) pallas::Base); +impl std::ops::Deref for Nullifier { + type Target = pallas::Base; + + fn deref(&self) -> &pallas::Base { + &self.0 + } +} + impl Nullifier { /// Generates a dummy nullifier for use as $\rho$ in dummy spent notes. /// diff --git a/src/tree.rs b/src/tree.rs index 642fe64a..4a38e0f2 100644 --- a/src/tree.rs +++ b/src/tree.rs @@ -53,6 +53,14 @@ impl From for Anchor { } } +impl std::ops::Deref for Anchor { + type Target = pallas::Base; + + fn deref(&self) -> &pallas::Base { + &self.0 + } +} + impl Anchor { /// Parses an Orchard anchor from a byte encoding. pub fn from_bytes(bytes: [u8; 32]) -> Option { diff --git a/src/value.rs b/src/value.rs index fa8250bd..4bfc4817 100644 --- a/src/value.rs +++ b/src/value.rs @@ -21,9 +21,9 @@ use std::ops::{Add, Sub}; use bitvec::{array::BitArray, order::Lsb0}; use ff::{Field, PrimeField}; -use group::{Group, GroupEncoding}; +use group::{Curve, Group, GroupEncoding}; use pasta_curves::{ - arithmetic::{CurveExt, FieldExt}, + arithmetic::{CurveAffine, CurveExt, FieldExt}, pallas, }; use rand::RngCore; @@ -275,6 +275,24 @@ impl ValueCommitment { pub fn to_bytes(&self) -> [u8; 32] { self.0.to_bytes() } + + /// x-coordinate of this value commitment. + pub fn x(&self) -> pallas::Base { + if self.0 == pallas::Point::identity() { + pallas::Base::zero() + } else { + *self.0.to_affine().coordinates().unwrap().x() + } + } + + /// y-coordinate of this value commitment. + pub fn y(&self) -> pallas::Base { + if self.0 == pallas::Point::identity() { + pallas::Base::zero() + } else { + *self.0.to_affine().coordinates().unwrap().y() + } + } } /// Generators for property testing.