From ee1f66c70140328d39ed21e59deaa68a36894e1f Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 19 May 2022 15:43:51 -0700 Subject: [PATCH 01/18] Change copyright attribution for the BOSL license fixes #330 --- LICENSE-BOSL | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/LICENSE-BOSL b/LICENSE-BOSL index d73baa19..49211971 100644 --- a/LICENSE-BOSL +++ b/LICENSE-BOSL @@ -161,8 +161,7 @@ of such entity. restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. -16. **Modification of This License.** This License is Copyright © 2007 Zooko -Wilcox-O'Hearn. Permission is granted to copy, distribute, or communicate this +16. **Modification of This License.** This License is Copyright © 2021-2022 Electric Coin Company. Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate @@ -173,4 +172,4 @@ Licence" or "BOSL" and you may not use those names in the name of your Modified License; and (ii) You must replace the notice specified in the first paragraph above with the notice "Licensed under " or with a notice of your own that is not confusingly similar to the notice in this -License. \ No newline at end of file +License. From 6fd7ecfca8fb6a3819fbf111d8ba0daf098488d1 Mon Sep 17 00:00:00 2001 From: Daira Hopwood Date: Thu, 19 May 2022 15:45:33 -0700 Subject: [PATCH 02/18] Line wrapping --- LICENSE-BOSL | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/LICENSE-BOSL b/LICENSE-BOSL index 49211971..7abc6ba2 100644 --- a/LICENSE-BOSL +++ b/LICENSE-BOSL @@ -161,7 +161,8 @@ of such entity. restricted or conditioned by this License or by law, and Licensor promises not to interfere with or be responsible for such uses by You. -16. **Modification of This License.** This License is Copyright © 2021-2022 Electric Coin Company. Permission is granted to copy, distribute, or communicate this +16. **Modification of This License.** This License is Copyright © 2021-2022 Electric Coin Company. +Permission is granted to copy, distribute, or communicate this License without modification. Nothing in this License permits You to modify this License as applied to the Original Work or to Derivative Works. However, You may modify the text of this License and copy, distribute or communicate From 4e12b4e90a68010b303d40b41e07fc99be375b03 Mon Sep 17 00:00:00 2001 From: Hazel OHearn Date: Wed, 22 Jun 2022 17:34:34 -0300 Subject: [PATCH 03/18] Add OrchardDomain::for_nullifier and CompactAction::from_parts --- src/note_encryption.rs | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/note_encryption.rs b/src/note_encryption.rs index 7aed1831..9ef73c26 100644 --- a/src/note_encryption.rs +++ b/src/note_encryption.rs @@ -92,6 +92,11 @@ impl OrchardDomain { rho: *act.nullifier(), } } + + /// Constructs a domain from a nullifier. + pub fn for_nullifier(nullifier: Nullifier) -> Self { + OrchardDomain { rho: nullifier } + } } impl Domain for OrchardDomain { @@ -307,6 +312,23 @@ impl ShieldedOutput for CompactAction { } } +impl CompactAction { + /// Create a CompactAction from its constituent parts + pub fn from_parts( + nullifier: Nullifier, + cmx: ExtractedNoteCommitment, + ephemeral_key: EphemeralKeyBytes, + enc_ciphertext: [u8; 52], + ) -> Self { + Self { + nullifier, + cmx, + ephemeral_key, + enc_ciphertext, + } + } +} + #[cfg(test)] mod tests { use rand::rngs::OsRng; From 6956c18d31606a795074fba50eccba57571ab137 Mon Sep 17 00:00:00 2001 From: Hazel OHearn Date: Thu, 23 Jun 2022 16:06:37 -0300 Subject: [PATCH 04/18] Add CompactAction::nullifier getter fn --- src/note_encryption.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/note_encryption.rs b/src/note_encryption.rs index 9ef73c26..2da2583a 100644 --- a/src/note_encryption.rs +++ b/src/note_encryption.rs @@ -327,6 +327,11 @@ impl CompactAction { enc_ciphertext, } } + + ///Returns the nullifier of the note being spent. + pub fn nullifier(&self) -> Nullifier { + self.nullifier + } } #[cfg(test)] From be69324b9cab8179a61b2b8ee2a51a146665cd8d Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 9 Jun 2022 00:01:10 +0000 Subject: [PATCH 05/18] Migrate to `halo2_proofs 0.2.0` --- CHANGELOG.md | 2 + Cargo.toml | 6 +- src/builder.rs | 39 ++-- src/circuit.rs | 124 ++++++------- src/circuit/commit_ivk.rs | 120 ++++++------- src/circuit/gadget.rs | 13 +- src/circuit/gadget/add_chip.rs | 7 +- src/circuit/note_commit.rs | 316 ++++++++++++++------------------- 8 files changed, 288 insertions(+), 339 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 41f3e8d5..e7dc93fd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Changed +- Migrated to `halo2_proofs 0.2`. ## [0.1.0] - 2022-05-10 ### Changed diff --git a/Cargo.toml b/Cargo.toml index b50a0299..6dc303ba 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -29,8 +29,8 @@ blake2b_simd = "1" ff = "0.12" fpe = "0.5" group = "0.12" -halo2_gadgets = "0.1" -halo2_proofs = "0.1" +halo2_gadgets = "0.2" +halo2_proofs = "0.2" hex = "0.4" lazy_static = "1" memuse = { version = "0.2", features = ["nonempty"] } @@ -49,7 +49,7 @@ plotters = { version = "0.3.0", optional = true } [dev-dependencies] criterion = "0.3" -halo2_gadgets = { version = "0.1", features = ["test-dependencies"] } +halo2_gadgets = { version = "0.2", features = ["test-dependencies"] } hex = "0.4" proptest = "1.0.0" zcash_note_encryption = { version = "0.1", features = ["pre-zip-212"] } diff --git a/src/builder.rs b/src/builder.rs index c1a45b76..8ccc6274 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -4,6 +4,7 @@ use core::fmt; use core::iter; use ff::Field; +use halo2_proofs::circuit::Value; use nonempty::NonEmpty; use pasta_curves::pallas; use rand::{prelude::SliceRandom, CryptoRng, RngCore}; @@ -188,25 +189,25 @@ impl ActionInfo { }, ), Circuit { - path: Some(self.spend.merkle_path.auth_path()), - pos: Some(self.spend.merkle_path.position()), - g_d_old: Some(sender_address.g_d()), - pk_d_old: Some(*sender_address.pk_d()), - v_old: Some(self.spend.note.value()), - rho_old: Some(rho_old), - psi_old: Some(psi_old), - rcm_old: Some(rcm_old), - cm_old: Some(self.spend.note.commitment()), - alpha: Some(alpha), - ak: Some(ak), - nk: Some(*self.spend.fvk.nk()), - rivk: Some(self.spend.fvk.rivk(self.spend.scope)), - g_d_new: Some(note.recipient().g_d()), - pk_d_new: Some(*note.recipient().pk_d()), - v_new: Some(note.value()), - psi_new: Some(note.rseed().psi(¬e.rho())), - rcm_new: Some(note.rseed().rcm(¬e.rho())), - rcv: Some(self.rcv), + path: Value::known(self.spend.merkle_path.auth_path()), + pos: Value::known(self.spend.merkle_path.position()), + g_d_old: Value::known(sender_address.g_d()), + pk_d_old: Value::known(*sender_address.pk_d()), + v_old: Value::known(self.spend.note.value()), + rho_old: Value::known(rho_old), + psi_old: Value::known(psi_old), + rcm_old: Value::known(rcm_old), + cm_old: Value::known(self.spend.note.commitment()), + alpha: Value::known(alpha), + ak: Value::known(ak), + nk: Value::known(*self.spend.fvk.nk()), + rivk: Value::known(self.spend.fvk.rivk(self.spend.scope)), + g_d_new: Value::known(note.recipient().g_d()), + pk_d_new: Value::known(*note.recipient().pk_d()), + v_new: Value::known(note.value()), + psi_new: Value::known(note.rseed().psi(¬e.rho())), + rcm_new: Value::known(note.rseed().rcm(¬e.rho())), + rcv: Value::known(self.rcv), }, ) } diff --git a/src/circuit.rs b/src/circuit.rs index f1742a8d..7bcc31d6 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -4,7 +4,7 @@ use core::fmt; use group::{Curve, GroupEncoding}; use halo2_proofs::{ - circuit::{floor_planner, Layouter}, + circuit::{floor_planner, Layouter, Value}, plonk::{ self, Advice, Column, Constraints, Expression, Instance as InstanceColumn, Selector, SingleVerifier, @@ -99,25 +99,25 @@ pub struct Config { /// The Orchard Action circuit. #[derive(Clone, Debug, Default)] pub struct Circuit { - pub(crate) path: Option<[MerkleHashOrchard; MERKLE_DEPTH_ORCHARD]>, - pub(crate) pos: Option, - pub(crate) g_d_old: Option, - pub(crate) pk_d_old: Option, - pub(crate) v_old: Option, - pub(crate) rho_old: Option, - pub(crate) psi_old: Option, - pub(crate) rcm_old: Option, - pub(crate) cm_old: Option, - pub(crate) alpha: Option, - pub(crate) ak: Option, - pub(crate) nk: Option, - pub(crate) rivk: Option, - pub(crate) g_d_new: Option, - pub(crate) pk_d_new: Option, - pub(crate) v_new: Option, - pub(crate) psi_new: Option, - pub(crate) rcm_new: Option, - pub(crate) rcv: Option, + pub(crate) path: Value<[MerkleHashOrchard; MERKLE_DEPTH_ORCHARD]>, + pub(crate) pos: Value, + pub(crate) g_d_old: Value, + pub(crate) pk_d_old: Value, + pub(crate) v_old: Value, + pub(crate) rho_old: Value, + pub(crate) psi_old: Value, + pub(crate) rcm_old: Value, + pub(crate) cm_old: Value, + pub(crate) alpha: Value, + pub(crate) ak: Value, + pub(crate) nk: Value, + pub(crate) rivk: Value, + pub(crate) g_d_new: Value, + pub(crate) pk_d_new: Value, + pub(crate) v_new: Value, + pub(crate) psi_new: Value, + pub(crate) rcm_new: Value, + pub(crate) rcv: Value, } impl plonk::Circuit for Circuit { @@ -358,7 +358,7 @@ impl plonk::Circuit for Circuit { )?; // Witness ak_P. - let ak_P: Option = self.ak.as_ref().map(|ak| ak.into()); + let ak_P: Value = self.ak.as_ref().map(|ak| ak.into()); let ak_P = NonIdentityPoint::new( ecc_chip.clone(), layouter.namespace(|| "witness ak_P"), @@ -408,8 +408,8 @@ impl plonk::Circuit for Circuit { let v_net_magnitude_sign = { // Witness the magnitude and sign of v_net = v_old - v_new let v_net_magnitude_sign = { - let magnitude_sign = self.v_old.zip(self.v_new).map(|(v_old, v_new)| { - let v_net = v_old - v_new; + let v_net = self.v_old - self.v_new; + let magnitude_sign = v_net.map(|v_net| { let (magnitude, sign) = v_net.magnitude_sign(); ( @@ -877,7 +877,7 @@ mod tests { use core::iter; use ff::Field; - use halo2_proofs::dev::MockProver; + use halo2_proofs::{circuit::Value, dev::MockProver}; use pasta_curves::pallas; use rand::{rngs::OsRng, RngCore}; @@ -912,25 +912,25 @@ mod tests { ( Circuit { - path: Some(path.auth_path()), - pos: Some(path.position()), - g_d_old: Some(sender_address.g_d()), - pk_d_old: Some(*sender_address.pk_d()), - v_old: Some(spent_note.value()), - rho_old: Some(spent_note.rho()), - psi_old: Some(spent_note.rseed().psi(&spent_note.rho())), - rcm_old: Some(spent_note.rseed().rcm(&spent_note.rho())), - cm_old: Some(spent_note.commitment()), - alpha: Some(alpha), - ak: Some(ak), - nk: Some(nk), - rivk: Some(rivk), - g_d_new: Some(output_note.recipient().g_d()), - pk_d_new: Some(*output_note.recipient().pk_d()), - v_new: Some(output_note.value()), - psi_new: Some(output_note.rseed().psi(&output_note.rho())), - rcm_new: Some(output_note.rseed().rcm(&output_note.rho())), - rcv: Some(rcv), + path: Value::known(path.auth_path()), + pos: Value::known(path.position()), + g_d_old: Value::known(sender_address.g_d()), + pk_d_old: Value::known(*sender_address.pk_d()), + v_old: Value::known(spent_note.value()), + rho_old: Value::known(spent_note.rho()), + psi_old: Value::known(spent_note.rseed().psi(&spent_note.rho())), + rcm_old: Value::known(spent_note.rseed().rcm(&spent_note.rho())), + cm_old: Value::known(spent_note.commitment()), + alpha: Value::known(alpha), + ak: Value::known(ak), + nk: Value::known(nk), + rivk: Value::known(rivk), + g_d_new: Value::known(output_note.recipient().g_d()), + pk_d_new: Value::known(*output_note.recipient().pk_d()), + v_new: Value::known(output_note.value()), + psi_new: Value::known(output_note.rseed().psi(&output_note.rho())), + rcm_new: Value::known(output_note.rseed().rcm(&output_note.rho())), + rcv: Value::known(rcv), }, Instance { anchor, @@ -1098,25 +1098,25 @@ mod tests { .unwrap(); let circuit = Circuit { - path: None, - pos: None, - g_d_old: None, - pk_d_old: None, - v_old: None, - rho_old: None, - psi_old: None, - rcm_old: None, - cm_old: None, - alpha: None, - ak: None, - nk: None, - rivk: None, - g_d_new: None, - pk_d_new: None, - v_new: None, - psi_new: None, - rcm_new: None, - rcv: None, + path: Value::unknown(), + pos: Value::unknown(), + g_d_old: Value::unknown(), + pk_d_old: Value::unknown(), + v_old: Value::unknown(), + rho_old: Value::unknown(), + psi_old: Value::unknown(), + rcm_old: Value::unknown(), + cm_old: Value::unknown(), + alpha: Value::unknown(), + ak: Value::unknown(), + nk: Value::unknown(), + rivk: Value::unknown(), + g_d_new: Value::unknown(), + pk_d_new: Value::unknown(), + v_new: Value::unknown(), + psi_new: Value::unknown(), + rcm_new: Value::unknown(), + rcv: Value::unknown(), }; halo2_proofs::dev::CircuitLayout::default() .show_labels(false) diff --git a/src/circuit/commit_ivk.rs b/src/circuit/commit_ivk.rs index 1460c9d8..8001a0ac 100644 --- a/src/circuit/commit_ivk.rs +++ b/src/circuit/commit_ivk.rs @@ -1,7 +1,7 @@ use core::iter; use halo2_proofs::{ - circuit::{AssignedCell, Layouter}, + circuit::{AssignedCell, Layouter, Value}, plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; @@ -417,11 +417,11 @@ pub(in crate::circuit) mod gadgets { // Decompose the low 130 bits of a_prime = a + 2^130 - t_P, and output // the running sum at the end of it. If a_prime < 2^130, the running sum // will be 0. - let a_prime = a.value().map(|a| { - let two_pow_130 = pallas::Base::from_u128(1u128 << 65).square(); - let t_p = pallas::Base::from_u128(T_P); - a + two_pow_130 - t_p - }); + let a_prime = { + let two_pow_130 = Value::known(pallas::Base::from_u128(1u128 << 65).square()); + let t_p = Value::known(pallas::Base::from_u128(T_P)); + a.value() + two_pow_130 - t_p + }; let zs = lookup_config.witness_check( layouter.namespace(|| "Decompose low 130 bits of (a + 2^130 - t_P)"), a_prime, @@ -461,12 +461,12 @@ pub(in crate::circuit) mod gadgets { // Decompose the low 140 bits of b2_c_prime = b_2 + c * 2^5 + 2^140 - t_P, and output // the running sum at the end of it. If b2_c_prime < 2^140, the running sum will be 0. - let b2_c_prime = b_2.inner().value().zip(c.value()).map(|(b_2, c)| { - let two_pow_5 = pallas::Base::from(1 << 5); - let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square(); - let t_p = pallas::Base::from_u128(T_P); - b_2 + c * two_pow_5 + two_pow_140 - t_p - }); + let b2_c_prime = { + let two_pow_5 = Value::known(pallas::Base::from(1 << 5)); + let two_pow_140 = Value::known(pallas::Base::from_u128(1u128 << 70).square()); + let t_p = Value::known(pallas::Base::from_u128(T_P)); + b_2.inner().value() + c.value() * two_pow_5 + two_pow_140 - t_p + }; let zs = lookup_config.witness_check( layouter.namespace(|| "Decompose low 140 bits of (b_2 + c * 2^5 + 2^140 - t_P)"), b2_c_prime, @@ -535,7 +535,7 @@ impl CommitIvkConfig { || "Witness b_1", self.advices[4], offset, - || gate_cells.b_1.inner().ok_or(Error::Synthesis), + || *gate_cells.b_1.inner(), )?; // Copy in `b_2` @@ -603,7 +603,7 @@ impl CommitIvkConfig { || "Witness d_1", self.advices[4], offset, - || gate_cells.d_1.inner().ok_or(Error::Synthesis), + || *gate_cells.d_1.inner(), )?; // Copy in z13_c @@ -646,10 +646,10 @@ struct GateCells { ak: AssignedCell, nk: AssignedCell, b_0: RangeConstrained>, - b_1: RangeConstrained>, + b_1: RangeConstrained>, b_2: RangeConstrained>, d_0: RangeConstrained>, - d_1: RangeConstrained>, + d_1: RangeConstrained>, z13_a: AssignedCell, a_prime: AssignedCell, z13_a_prime: AssignedCell, @@ -680,7 +680,7 @@ mod tests { utilities::{lookup_range_check::LookupRangeCheckConfig, UtilitiesInstructions}, }; use halo2_proofs::{ - circuit::{AssignedCell, Layouter, SimpleFloorPlanner}, + circuit::{AssignedCell, Layouter, SimpleFloorPlanner, Value}, dev::MockProver, plonk::{Circuit, ConstraintSystem, Error}, }; @@ -691,8 +691,8 @@ mod tests { fn commit_ivk() { #[derive(Default)] struct MyCircuit { - ak: Option, - nk: Option, + ak: Value, + nk: Value, } impl UtilitiesInstructions for MyCircuit { @@ -809,8 +809,11 @@ mod tests { // Use a random scalar for rivk let rivk = pallas::Scalar::random(OsRng); - let rivk_gadget = - ScalarFixed::new(ecc_chip.clone(), layouter.namespace(|| "rivk"), Some(rivk))?; + let rivk_gadget = ScalarFixed::new( + ecc_chip.clone(), + layouter.namespace(|| "rivk"), + Value::known(rivk), + )?; let ivk = gadgets::commit_ivk( sinsemilla_chip, @@ -822,34 +825,29 @@ mod tests { rivk_gadget, )?; - let expected_ivk = { - let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION); - // Hash ak || nk - domain - .short_commit( - iter::empty() - .chain( - self.ak - .unwrap() - .to_le_bits() - .iter() - .by_vals() - .take(L_ORCHARD_BASE), + self.ak + .zip(self.nk) + .zip(ivk.inner().value()) + .assert_if_known(|((ak, nk), ivk)| { + let expected_ivk = { + let domain = CommitDomain::new(COMMIT_IVK_PERSONALIZATION); + // Hash ak || nk + domain + .short_commit( + iter::empty() + .chain( + ak.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE), + ) + .chain( + nk.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE), + ), + &rivk, ) - .chain( - self.nk - .unwrap() - .to_le_bits() - .iter() - .by_vals() - .take(L_ORCHARD_BASE), - ), - &rivk, - ) - .unwrap() - }; + .unwrap() + }; - assert_eq!(&expected_ivk, ivk.inner().value().unwrap()); + &&expected_ivk == ivk + }); Ok(()) } @@ -860,38 +858,38 @@ mod tests { let circuits = [ // `ak` = 0, `nk` = 0 MyCircuit { - ak: Some(pallas::Base::zero()), - nk: Some(pallas::Base::zero()), + ak: Value::known(pallas::Base::zero()), + nk: Value::known(pallas::Base::zero()), }, // `ak` = T_Q - 1, `nk` = T_Q - 1 MyCircuit { - ak: Some(pallas::Base::from_u128(T_Q - 1)), - nk: Some(pallas::Base::from_u128(T_Q - 1)), + ak: Value::known(pallas::Base::from_u128(T_Q - 1)), + nk: Value::known(pallas::Base::from_u128(T_Q - 1)), }, // `ak` = T_Q, `nk` = T_Q MyCircuit { - ak: Some(pallas::Base::from_u128(T_Q)), - nk: Some(pallas::Base::from_u128(T_Q)), + ak: Value::known(pallas::Base::from_u128(T_Q)), + nk: Value::known(pallas::Base::from_u128(T_Q)), }, // `ak` = 2^127 - 1, `nk` = 2^127 - 1 MyCircuit { - ak: Some(pallas::Base::from_u128((1 << 127) - 1)), - nk: Some(pallas::Base::from_u128((1 << 127) - 1)), + ak: Value::known(pallas::Base::from_u128((1 << 127) - 1)), + nk: Value::known(pallas::Base::from_u128((1 << 127) - 1)), }, // `ak` = 2^127, `nk` = 2^127 MyCircuit { - ak: Some(pallas::Base::from_u128(1 << 127)), - nk: Some(pallas::Base::from_u128(1 << 127)), + ak: Value::known(pallas::Base::from_u128(1 << 127)), + nk: Value::known(pallas::Base::from_u128(1 << 127)), }, // `ak` = 2^254 - 1, `nk` = 2^254 - 1 MyCircuit { - ak: Some(two_pow_254 - pallas::Base::one()), - nk: Some(two_pow_254 - pallas::Base::one()), + ak: Value::known(two_pow_254 - pallas::Base::one()), + nk: Value::known(two_pow_254 - pallas::Base::one()), }, // `ak` = 2^254, `nk` = 2^254 MyCircuit { - ak: Some(two_pow_254), - nk: Some(two_pow_254), + ak: Value::known(two_pow_254), + nk: Value::known(two_pow_254), }, ]; diff --git a/src/circuit/gadget.rs b/src/circuit/gadget.rs index 7fa52ecd..21d24333 100644 --- a/src/circuit/gadget.rs +++ b/src/circuit/gadget.rs @@ -21,7 +21,7 @@ use halo2_gadgets::{ }; use halo2_proofs::{ arithmetic::FieldExt, - circuit::{AssignedCell, Chip, Layouter}, + circuit::{AssignedCell, Chip, Layouter, Value}, plonk::{self, Advice, Assigned, Column}, }; @@ -96,21 +96,14 @@ pub(in crate::circuit) trait AddInstruction: Chip { pub(in crate::circuit) fn assign_free_advice( mut layouter: impl Layouter, column: Column, - value: Option, + value: Value, ) -> Result, plonk::Error> where for<'v> Assigned: From<&'v V>, { layouter.assign_region( || "load private", - |mut region| { - region.assign_advice( - || "load private", - column, - 0, - || value.ok_or(plonk::Error::Synthesis), - ) - }, + |mut region| region.assign_advice(|| "load private", column, 0, || value), ) } diff --git a/src/circuit/gadget/add_chip.rs b/src/circuit/gadget/add_chip.rs index bf013528..0973a3f3 100644 --- a/src/circuit/gadget/add_chip.rs +++ b/src/circuit/gadget/add_chip.rs @@ -74,12 +74,7 @@ impl AddInstruction for AddChip { b.copy_advice(|| "copy b", &mut region, self.config.b, 0)?; let scalar_val = a.value().zip(b.value()).map(|(a, b)| a + b); - region.assign_advice( - || "c", - self.config.c, - 0, - || scalar_val.ok_or(plonk::Error::Synthesis), - ) + region.assign_advice(|| "c", self.config.c, 0, || scalar_val) }, ) } diff --git a/src/circuit/note_commit.rs b/src/circuit/note_commit.rs index b7163285..1f4a3cec 100644 --- a/src/circuit/note_commit.rs +++ b/src/circuit/note_commit.rs @@ -1,7 +1,7 @@ use core::iter; use halo2_proofs::{ - circuit::{AssignedCell, Layouter}, + circuit::{AssignedCell, Layouter, Value}, plonk::{Advice, Column, ConstraintSystem, Constraints, Error, Expression, Selector}, poly::Rotation, }; @@ -128,8 +128,8 @@ impl DecomposeB { ( NoteCommitPiece, RangeConstrained>, - RangeConstrained>, - RangeConstrained>, + RangeConstrained>, + RangeConstrained>, RangeConstrained>, ), Error, @@ -170,7 +170,7 @@ impl DecomposeB { layouter: &mut impl Layouter, b: NoteCommitPiece, b_0: RangeConstrained>, - b_1: RangeConstrained>, + b_1: RangeConstrained>, b_2: RangeConstrained>, b_3: RangeConstrained>, ) -> Result, Error> { @@ -184,12 +184,7 @@ impl DecomposeB { .copy_advice(|| "b", &mut region, self.col_l, 0)?; b_0.inner() .copy_advice(|| "b_0", &mut region, self.col_m, 0)?; - let b_1 = region.assign_advice( - || "b_1", - self.col_r, - 0, - || b_1.inner().ok_or(Error::Synthesis), - )?; + let b_1 = region.assign_advice(|| "b_1", self.col_r, 0, || *b_1.inner())?; b_2.inner() .copy_advice(|| "b_2", &mut region, self.col_m, 1)?; @@ -277,8 +272,8 @@ impl DecomposeD { ) -> Result< ( NoteCommitPiece, - RangeConstrained>, - RangeConstrained>, + RangeConstrained>, + RangeConstrained>, RangeConstrained>, ), Error, @@ -313,7 +308,7 @@ impl DecomposeD { &self, layouter: &mut impl Layouter, d: NoteCommitPiece, - d_0: RangeConstrained>, + d_0: RangeConstrained>, d_1: RangeConstrained>, d_2: RangeConstrained>, z1_d: AssignedCell, @@ -326,12 +321,7 @@ impl DecomposeD { d.inner() .cell_value() .copy_advice(|| "d", &mut region, self.col_l, 0)?; - let d_0 = region.assign_advice( - || "d_0", - self.col_m, - 0, - || d_0.inner().ok_or(Error::Synthesis), - )?; + let d_0 = region.assign_advice(|| "d_0", self.col_m, 0, || *d_0.inner())?; d_1.inner() .copy_advice(|| "d_1", &mut region, self.col_r, 0)?; @@ -529,7 +519,7 @@ impl DecomposeG { ) -> Result< ( NoteCommitPiece, - RangeConstrained>, + RangeConstrained>, RangeConstrained>, ), Error, @@ -561,7 +551,7 @@ impl DecomposeG { &self, layouter: &mut impl Layouter, g: NoteCommitPiece, - g_0: RangeConstrained>, + g_0: RangeConstrained>, g_1: RangeConstrained>, z1_g: AssignedCell, ) -> Result, Error> { @@ -573,12 +563,7 @@ impl DecomposeG { g.inner() .cell_value() .copy_advice(|| "g", &mut region, self.col_l, 0)?; - let g_0 = region.assign_advice( - || "g_0", - self.col_m, - 0, - || g_0.inner().ok_or(Error::Synthesis), - )?; + let g_0 = region.assign_advice(|| "g_0", self.col_m, 0, || *g_0.inner())?; g_1.inner() .copy_advice(|| "g_1", &mut region, self.col_l, 1)?; @@ -656,7 +641,7 @@ impl DecomposeH { ( NoteCommitPiece, RangeConstrained>, - RangeConstrained>, + RangeConstrained>, ), Error, > { @@ -677,7 +662,7 @@ impl DecomposeH { [ h_0.value(), h_1, - RangeConstrained::bitrange_of(Some(&pallas::Base::zero()), 0..4), + RangeConstrained::bitrange_of(Value::known(&pallas::Base::zero()), 0..4), ], )?; @@ -689,7 +674,7 @@ impl DecomposeH { layouter: &mut impl Layouter, h: NoteCommitPiece, h_0: RangeConstrained>, - h_1: RangeConstrained>, + h_1: RangeConstrained>, ) -> Result, Error> { layouter.assign_region( || "NoteCommit MessagePiece h", @@ -701,12 +686,7 @@ impl DecomposeH { .copy_advice(|| "h", &mut region, self.col_l, 0)?; h_0.inner() .copy_advice(|| "h_0", &mut region, self.col_m, 0)?; - let h_1 = region.assign_advice( - || "h_1", - self.col_r, - 0, - || h_1.inner().ok_or(Error::Synthesis), - )?; + let h_1 = region.assign_advice(|| "h_1", self.col_r, 0, || *h_1.inner())?; Ok(h_1) }, @@ -1357,10 +1337,10 @@ impl YCanonicity { &self, layouter: &mut impl Layouter, y: AssignedCell, - lsb: RangeConstrained>, + lsb: RangeConstrained>, k_0: RangeConstrained>, k_2: RangeConstrained>, - k_3: RangeConstrained>, + k_3: RangeConstrained>, j: AssignedCell, z1_j: AssignedCell, z13_j: AssignedCell, @@ -1381,12 +1361,7 @@ impl YCanonicity { y.copy_advice(|| "copy y", &mut region, self.advices[5], offset)?; // Witness LSB. let lsb = region - .assign_advice( - || "witness LSB", - self.advices[6], - offset, - || lsb.inner().ok_or(Error::Synthesis), - ) + .assign_advice(|| "witness LSB", self.advices[6], offset, || *lsb.inner()) // SAFETY: This is sound because we just assigned this cell from a // range-constrained value. .map(|cell| RangeConstrained::unsound_unchecked(cell, lsb.num_bits()))?; @@ -1401,7 +1376,7 @@ impl YCanonicity { || "witness k_3", self.advices[9], offset, - || k_3.inner().ok_or(Error::Synthesis), + || *k_3.inner(), )?; lsb @@ -1586,7 +1561,7 @@ impl NoteCommitChip { } pub(in crate::circuit) mod gadgets { - use halo2_proofs::circuit::Chip; + use halo2_proofs::circuit::{Chip, Value}; use super::*; @@ -1817,11 +1792,11 @@ pub(in crate::circuit) mod gadgets { // Decompose the low 130 bits of a_prime = a + 2^130 - t_P, and output // the running sum at the end of it. If a_prime < 2^130, the running sum // will be 0. - let a_prime = a.value().map(|a| { - let two_pow_130 = pallas::Base::from_u128(1u128 << 65).square(); - let t_p = pallas::Base::from_u128(T_P); - a + two_pow_130 - t_p - }); + let a_prime = { + let two_pow_130 = Value::known(pallas::Base::from_u128(1u128 << 65).square()); + let t_p = Value::known(pallas::Base::from_u128(T_P)); + a.value() + two_pow_130 - t_p + }; let zs = lookup_config.witness_check( layouter.namespace(|| "Decompose low 130 bits of (a + 2^130 - t_P)"), a_prime, @@ -1856,12 +1831,12 @@ pub(in crate::circuit) mod gadgets { // Decompose the low 140 bits of b3_c_prime = b_3 + 2^4 c + 2^140 - t_P, // and output the running sum at the end of it. // If b3_c_prime < 2^140, the running sum will be 0. - let b3_c_prime = b_3.inner().value().zip(c.value()).map(|(b_3, c)| { - let two_pow_4 = pallas::Base::from(1u64 << 4); - let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square(); - let t_p = pallas::Base::from_u128(T_P); - b_3 + (two_pow_4 * c) + two_pow_140 - t_p - }); + let b3_c_prime = { + let two_pow_4 = Value::known(pallas::Base::from(1u64 << 4)); + let two_pow_140 = Value::known(pallas::Base::from_u128(1u128 << 70).square()); + let t_p = Value::known(pallas::Base::from_u128(T_P)); + b_3.inner().value() + (two_pow_4 * c.value()) + two_pow_140 - t_p + }; let zs = lookup_config.witness_check( layouter.namespace(|| "Decompose low 140 bits of (b_3 + 2^4 c + 2^140 - t_P)"), @@ -1894,12 +1869,12 @@ pub(in crate::circuit) mod gadgets { // to 130 bits. z13_f == 0 is directly checked in the gate. // - 0 ≤ e_1 + 2^4 f + 2^140 - t_P < 2^140 (14 ten-bit lookups) - let e1_f_prime = e_1.inner().value().zip(f.value()).map(|(e_1, f)| { - let two_pow_4 = pallas::Base::from(1u64 << 4); - let two_pow_140 = pallas::Base::from_u128(1u128 << 70).square(); - let t_p = pallas::Base::from_u128(T_P); - e_1 + (two_pow_4 * f) + two_pow_140 - t_p - }); + let e1_f_prime = { + let two_pow_4 = Value::known(pallas::Base::from(1u64 << 4)); + let two_pow_140 = Value::known(pallas::Base::from_u128(1u128 << 70).square()); + let t_p = Value::known(pallas::Base::from_u128(T_P)); + e_1.inner().value() + (two_pow_4 * f.value()) + two_pow_140 - t_p + }; // Decompose the low 140 bits of e1_f_prime = e_1 + 2^4 f + 2^140 - t_P, // and output the running sum at the end of it. @@ -1936,12 +1911,12 @@ pub(in crate::circuit) mod gadgets { // Decompose the low 130 bits of g1_g2_prime = g_1 + (2^9)g_2 + 2^130 - t_P, // and output the running sum at the end of it. // If g1_g2_prime < 2^130, the running sum will be 0. - let g1_g2_prime = g_1.inner().value().zip(g_2.value()).map(|(g_1, g_2)| { - let two_pow_9 = pallas::Base::from(1u64 << 9); - let two_pow_130 = pallas::Base::from_u128(1u128 << 65).square(); - let t_p = pallas::Base::from_u128(T_P); - g_1 + (two_pow_9 * g_2) + two_pow_130 - t_p - }); + let g1_g2_prime = { + let two_pow_9 = Value::known(pallas::Base::from(1u64 << 9)); + let two_pow_130 = Value::known(pallas::Base::from_u128(1u128 << 65).square()); + let t_p = Value::known(pallas::Base::from_u128(T_P)); + g_1.inner().value() + (two_pow_9 * g_2.value()) + two_pow_130 - t_p + }; let zs = lookup_config.witness_check( layouter.namespace(|| "Decompose low 130 bits of (g_1 + (2^9)g_2 + 2^130 - t_P)"), @@ -1966,7 +1941,7 @@ pub(in crate::circuit) mod gadgets { y_canon: &YCanonicity, mut layouter: impl Layouter, y: AssignedCell, - lsb: RangeConstrained>, + lsb: RangeConstrained>, ) -> Result>, Error> { // Decompose the field element @@ -1997,16 +1972,11 @@ pub(in crate::circuit) mod gadgets { // Decompose j = LSB + (2)k_0 + (2^10)k_1 using 25 ten-bit lookups. let (j, z1_j, z13_j) = { - let j = lsb - .inner() - .value() - .zip(k_0.inner().value()) - .zip(k_1.inner().value()) - .map(|((lsb, k_0), k_1)| { - let two = pallas::Base::from(2); - let two_pow_10 = pallas::Base::from(1 << 10); - lsb + two * k_0 + two_pow_10 * k_1 - }); + let j = { + let two = Value::known(pallas::Base::from(2)); + let two_pow_10 = Value::known(pallas::Base::from(1 << 10)); + lsb.inner().value() + two * k_0.inner().value() + two_pow_10 * k_1.inner().value() + }; let zs = lookup_config.witness_check( layouter.namespace(|| "Decompose j = LSB + (2)k_0 + (2^10)k_1"), j, @@ -2069,7 +2039,7 @@ mod tests { use ff::{Field, PrimeField, PrimeFieldBits}; use group::Curve; use halo2_proofs::{ - circuit::{Layouter, SimpleFloorPlanner}, + circuit::{Layouter, SimpleFloorPlanner, Value}, dev::MockProver, plonk::{Circuit, ConstraintSystem, Error}, }; @@ -2084,12 +2054,12 @@ mod tests { fn note_commit() { #[derive(Default)] struct MyCircuit { - gd_x: Option, - gd_y_lsb: Option, - pkd_x: Option, - pkd_y_lsb: Option, - rho: Option, - psi: Option, + gd_x: Value, + gd_y_lsb: Value, + pkd_x: Value, + pkd_y_lsb: Value, + rho: Value, + psi: Value, } impl Circuit for MyCircuit { @@ -2235,7 +2205,7 @@ mod tests { assign_free_advice( layouter.namespace(|| "witness value"), note_commit_config.advices[0], - Some(value), + Value::known(value), )? }; @@ -2254,8 +2224,11 @@ mod tests { )?; let rcm = pallas::Scalar::random(OsRng); - let rcm_gadget = - ScalarFixed::new(ecc_chip.clone(), layouter.namespace(|| "rcm"), Some(rcm))?; + let rcm_gadget = ScalarFixed::new( + ecc_chip.clone(), + layouter.namespace(|| "rcm"), + Value::known(rcm), + )?; let cm = gadgets::note_commit( layouter.namespace(|| "Hash NoteCommit pieces"), @@ -2273,53 +2246,40 @@ mod tests { let domain = CommitDomain::new(NOTE_COMMITMENT_PERSONALIZATION); // Hash g★_d || pk★_d || i2lebsp_{64}(v) || rho || psi let lsb = |y_lsb: pallas::Base| y_lsb == pallas::Base::one(); - let point = domain - .commit( - iter::empty() - .chain( - self.gd_x - .unwrap() - .to_le_bits() - .iter() - .by_vals() - .take(L_ORCHARD_BASE), + let point = self + .gd_x + .zip(self.gd_y_lsb) + .zip(self.pkd_x.zip(self.pkd_y_lsb)) + .zip(self.rho.zip(self.psi)) + .map(|(((gd_x, gd_y_lsb), (pkd_x, pkd_y_lsb)), (rho, psi))| { + domain + .commit( + iter::empty() + .chain( + gd_x.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE), + ) + .chain(Some(lsb(gd_y_lsb))) + .chain( + pkd_x + .to_le_bits() + .iter() + .by_vals() + .take(L_ORCHARD_BASE), + ) + .chain(Some(lsb(pkd_y_lsb))) + .chain(value.to_le_bits().iter().by_vals().take(L_VALUE)) + .chain( + rho.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE), + ) + .chain( + psi.to_le_bits().iter().by_vals().take(L_ORCHARD_BASE), + ), + &rcm, ) - .chain(Some(lsb(self.gd_y_lsb.unwrap()))) - .chain( - self.pkd_x - .unwrap() - .to_le_bits() - .iter() - .by_vals() - .take(L_ORCHARD_BASE), - ) - .chain(Some(lsb(self.pkd_y_lsb.unwrap()))) - .chain(value.to_le_bits().iter().by_vals().take(L_VALUE)) - .chain( - self.rho - .unwrap() - .to_le_bits() - .iter() - .by_vals() - .take(L_ORCHARD_BASE), - ) - .chain( - self.psi - .unwrap() - .to_le_bits() - .iter() - .by_vals() - .take(L_ORCHARD_BASE), - ), - &rcm, - ) - .unwrap() - .to_affine(); - NonIdentityPoint::new( - ecc_chip, - layouter.namespace(|| "witness cm"), - Some(point), - )? + .unwrap() + .to_affine() + }); + NonIdentityPoint::new(ecc_chip, layouter.namespace(|| "witness cm"), point)? }; cm.constrain_equal(layouter.namespace(|| "cm == expected cm"), &expected_cm) } @@ -2331,66 +2291,66 @@ mod tests { // `gd_x` = -1, `pkd_x` = -1 (these have to be x-coordinates of curve points) // `rho` = 0, `psi` = 0 MyCircuit { - gd_x: Some(-pallas::Base::one()), - gd_y_lsb: Some(pallas::Base::one()), - pkd_x: Some(-pallas::Base::one()), - pkd_y_lsb: Some(pallas::Base::one()), - rho: Some(pallas::Base::zero()), - psi: Some(pallas::Base::zero()), + gd_x: Value::known(-pallas::Base::one()), + gd_y_lsb: Value::known(pallas::Base::one()), + pkd_x: Value::known(-pallas::Base::one()), + pkd_y_lsb: Value::known(pallas::Base::one()), + rho: Value::known(pallas::Base::zero()), + psi: Value::known(pallas::Base::zero()), }, // `rho` = T_Q - 1, `psi` = T_Q - 1 MyCircuit { - gd_x: Some(-pallas::Base::one()), - gd_y_lsb: Some(pallas::Base::zero()), - pkd_x: Some(-pallas::Base::one()), - pkd_y_lsb: Some(pallas::Base::zero()), - rho: Some(pallas::Base::from_u128(T_Q - 1)), - psi: Some(pallas::Base::from_u128(T_Q - 1)), + gd_x: Value::known(-pallas::Base::one()), + gd_y_lsb: Value::known(pallas::Base::zero()), + pkd_x: Value::known(-pallas::Base::one()), + pkd_y_lsb: Value::known(pallas::Base::zero()), + rho: Value::known(pallas::Base::from_u128(T_Q - 1)), + psi: Value::known(pallas::Base::from_u128(T_Q - 1)), }, // `rho` = T_Q, `psi` = T_Q MyCircuit { - gd_x: Some(-pallas::Base::one()), - gd_y_lsb: Some(pallas::Base::one()), - pkd_x: Some(-pallas::Base::one()), - pkd_y_lsb: Some(pallas::Base::zero()), - rho: Some(pallas::Base::from_u128(T_Q)), - psi: Some(pallas::Base::from_u128(T_Q)), + gd_x: Value::known(-pallas::Base::one()), + gd_y_lsb: Value::known(pallas::Base::one()), + pkd_x: Value::known(-pallas::Base::one()), + pkd_y_lsb: Value::known(pallas::Base::zero()), + rho: Value::known(pallas::Base::from_u128(T_Q)), + psi: Value::known(pallas::Base::from_u128(T_Q)), }, // `rho` = 2^127 - 1, `psi` = 2^127 - 1 MyCircuit { - gd_x: Some(-pallas::Base::one()), - gd_y_lsb: Some(pallas::Base::zero()), - pkd_x: Some(-pallas::Base::one()), - pkd_y_lsb: Some(pallas::Base::one()), - rho: Some(pallas::Base::from_u128((1 << 127) - 1)), - psi: Some(pallas::Base::from_u128((1 << 127) - 1)), + gd_x: Value::known(-pallas::Base::one()), + gd_y_lsb: Value::known(pallas::Base::zero()), + pkd_x: Value::known(-pallas::Base::one()), + pkd_y_lsb: Value::known(pallas::Base::one()), + rho: Value::known(pallas::Base::from_u128((1 << 127) - 1)), + psi: Value::known(pallas::Base::from_u128((1 << 127) - 1)), }, // `rho` = 2^127, `psi` = 2^127 MyCircuit { - gd_x: Some(-pallas::Base::one()), - gd_y_lsb: Some(pallas::Base::zero()), - pkd_x: Some(-pallas::Base::one()), - pkd_y_lsb: Some(pallas::Base::zero()), - rho: Some(pallas::Base::from_u128(1 << 127)), - psi: Some(pallas::Base::from_u128(1 << 127)), + gd_x: Value::known(-pallas::Base::one()), + gd_y_lsb: Value::known(pallas::Base::zero()), + pkd_x: Value::known(-pallas::Base::one()), + pkd_y_lsb: Value::known(pallas::Base::zero()), + rho: Value::known(pallas::Base::from_u128(1 << 127)), + psi: Value::known(pallas::Base::from_u128(1 << 127)), }, // `rho` = 2^254 - 1, `psi` = 2^254 - 1 MyCircuit { - gd_x: Some(-pallas::Base::one()), - gd_y_lsb: Some(pallas::Base::one()), - pkd_x: Some(-pallas::Base::one()), - pkd_y_lsb: Some(pallas::Base::one()), - rho: Some(two_pow_254 - pallas::Base::one()), - psi: Some(two_pow_254 - pallas::Base::one()), + gd_x: Value::known(-pallas::Base::one()), + gd_y_lsb: Value::known(pallas::Base::one()), + pkd_x: Value::known(-pallas::Base::one()), + pkd_y_lsb: Value::known(pallas::Base::one()), + rho: Value::known(two_pow_254 - pallas::Base::one()), + psi: Value::known(two_pow_254 - pallas::Base::one()), }, // `rho` = 2^254, `psi` = 2^254 MyCircuit { - gd_x: Some(-pallas::Base::one()), - gd_y_lsb: Some(pallas::Base::one()), - pkd_x: Some(-pallas::Base::one()), - pkd_y_lsb: Some(pallas::Base::zero()), - rho: Some(two_pow_254), - psi: Some(two_pow_254), + gd_x: Value::known(-pallas::Base::one()), + gd_y_lsb: Value::known(pallas::Base::one()), + pkd_x: Value::known(-pallas::Base::one()), + pkd_y_lsb: Value::known(pallas::Base::zero()), + rho: Value::known(two_pow_254), + psi: Value::known(two_pow_254), }, ]; From 22b77c028f054d11455b60ac600787c3cdf74597 Mon Sep 17 00:00:00 2001 From: Hazel OHearn Date: Thu, 23 Jun 2022 16:29:55 -0300 Subject: [PATCH 06/18] Remove unneeded generic type from for_nullifier --- src/note_encryption.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/note_encryption.rs b/src/note_encryption.rs index 2da2583a..5113cf7e 100644 --- a/src/note_encryption.rs +++ b/src/note_encryption.rs @@ -94,7 +94,7 @@ impl OrchardDomain { } /// Constructs a domain from a nullifier. - pub fn for_nullifier(nullifier: Nullifier) -> Self { + pub fn for_nullifier(nullifier: Nullifier) -> Self { OrchardDomain { rho: nullifier } } } From 35a76f03b8bb6e48bf9b0a23266aee2db4e41068 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 21 Jun 2022 02:39:34 +0000 Subject: [PATCH 07/18] Add `orchard::bundle::BatchValidator` Adapted from the `BatchValidator` in `zcashd`, that only handles RedPallas signatures. --- CHANGELOG.md | 3 ++ Cargo.toml | 3 ++ src/bundle.rs | 3 ++ src/bundle/batch.rs | 74 +++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 83 insertions(+) create mode 100644 src/bundle/batch.rs diff --git a/CHANGELOG.md b/CHANGELOG.md index e7dc93fd..b527d10c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this project adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +### Added +- `orchard::bundle::BatchValidator` + ### Changed - Migrated to `halo2_proofs 0.2`. diff --git a/Cargo.toml b/Cargo.toml index 6dc303ba..7f1cd8f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -44,6 +44,9 @@ subtle = "2.3" zcash_note_encryption = "0.1" incrementalmerkletree = "0.3" +# Logging +tracing = "0.1" + # Developer tooling dependencies plotters = { version = "0.3.0", optional = true } diff --git a/src/bundle.rs b/src/bundle.rs index 5421dadc..c70ca635 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -1,7 +1,10 @@ //! Structs related to bundles of Orchard actions. +mod batch; pub mod commitments; +pub use batch::BatchValidator; + use core::fmt; use blake2b_simd::Hash as Blake2bHash; diff --git a/src/bundle/batch.rs b/src/bundle/batch.rs new file mode 100644 index 00000000..5bb462ac --- /dev/null +++ b/src/bundle/batch.rs @@ -0,0 +1,74 @@ +use rand::{CryptoRng, RngCore}; +use tracing::debug; + +use super::{Authorized, Bundle}; +use crate::primitives::redpallas::{self, Binding, SpendAuth}; + +/// A signature within an authorized Orchard bundle. +#[derive(Debug)] +struct BundleSignature { + /// The signature item for validation. + signature: redpallas::batch::Item, +} + +/// Batch validation context for Orchard. +/// +/// This batch-validates RedPallas signatures. +#[derive(Debug, Default)] +pub struct BatchValidator { + signatures: Vec, +} + +impl BatchValidator { + /// Constructs a new batch validation context. + pub fn new() -> Self { + BatchValidator { signatures: vec![] } + } + + /// Adds the RedPallas signatures from the given bundle to the validator. + pub fn add_bundle>( + &mut self, + bundle: &Bundle, + sighash: [u8; 32], + ) { + for action in bundle.actions().iter() { + self.signatures.push(BundleSignature { + signature: action + .rk() + .create_batch_item(action.authorization().clone(), &sighash), + }); + } + + self.signatures.push(BundleSignature { + signature: bundle + .binding_validating_key() + .create_batch_item(bundle.authorization().binding_signature().clone(), &sighash), + }); + } + + /// Batch-validates the accumulated bundles. + /// + /// Returns `true` if every signature in every bundle added to the batch validator is + /// valid, or `false` if one or more are invalid. No attempt is made to figure out + /// which of the accumulated bundles might be invalid; if that information is desired, + /// construct separate [`BatchValidator`]s for sub-batches of the bundles. + pub fn validate(&self, rng: R) -> bool { + if self.signatures.is_empty() { + // An empty batch is always valid, but is not free to run; skip it. + return true; + } + + let mut validator = redpallas::batch::Verifier::new(); + for sig in self.signatures.iter() { + validator.queue(sig.signature.clone()); + } + + match validator.verify(rng) { + Ok(()) => true, + Err(e) => { + debug!("RedPallas batch validation failed: {}", e); + false + } + } + } +} From 81626b3b288ddba3a65a3006ab4e5b678f55e085 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Tue, 21 Jun 2022 15:07:31 +0000 Subject: [PATCH 08/18] Add batch-verification of proofs to `orchard::bundle::BatchValidator` --- src/bundle/batch.rs | 35 +++++++++++++++++++++++++---------- src/circuit.rs | 26 ++++++++++++++++++++++---- 2 files changed, 47 insertions(+), 14 deletions(-) diff --git a/src/bundle/batch.rs b/src/bundle/batch.rs index 5bb462ac..4fee9590 100644 --- a/src/bundle/batch.rs +++ b/src/bundle/batch.rs @@ -1,8 +1,13 @@ +use halo2_proofs::plonk; +use pasta_curves::vesta; use rand::{CryptoRng, RngCore}; use tracing::debug; use super::{Authorized, Bundle}; -use crate::primitives::redpallas::{self, Binding, SpendAuth}; +use crate::{ + circuit::VerifyingKey, + primitives::redpallas::{self, Binding, SpendAuth}, +}; /// A signature within an authorized Orchard bundle. #[derive(Debug)] @@ -13,19 +18,23 @@ struct BundleSignature { /// Batch validation context for Orchard. /// -/// This batch-validates RedPallas signatures. +/// This batch-validates proofs and RedPallas signatures. #[derive(Debug, Default)] pub struct BatchValidator { + proofs: plonk::BatchVerifier, signatures: Vec, } impl BatchValidator { /// Constructs a new batch validation context. pub fn new() -> Self { - BatchValidator { signatures: vec![] } + BatchValidator { + proofs: plonk::BatchVerifier::new(), + signatures: vec![], + } } - /// Adds the RedPallas signatures from the given bundle to the validator. + /// Adds the proof and RedPallas signatures from the given bundle to the validator. pub fn add_bundle>( &mut self, bundle: &Bundle, @@ -44,15 +53,20 @@ impl BatchValidator { .binding_validating_key() .create_batch_item(bundle.authorization().binding_signature().clone(), &sighash), }); + + bundle + .authorization() + .proof() + .add_to_batch(&mut self.proofs, bundle.to_instances()); } /// Batch-validates the accumulated bundles. /// - /// Returns `true` if every signature in every bundle added to the batch validator is - /// valid, or `false` if one or more are invalid. No attempt is made to figure out - /// which of the accumulated bundles might be invalid; if that information is desired, - /// construct separate [`BatchValidator`]s for sub-batches of the bundles. - pub fn validate(&self, rng: R) -> bool { + /// Returns `true` if every proof and signature in every bundle added to the batch + /// validator is valid, or `false` if one or more are invalid. No attempt is made to + /// figure out which of the accumulated bundles might be invalid; if that information + /// is desired, construct separate [`BatchValidator`]s for sub-batches of the bundles. + pub fn validate(self, vk: &VerifyingKey, rng: R) -> bool { if self.signatures.is_empty() { // An empty batch is always valid, but is not free to run; skip it. return true; @@ -64,7 +78,8 @@ impl BatchValidator { } match validator.verify(rng) { - Ok(()) => true, + // If signatures are valid, check the proofs. + Ok(()) => self.proofs.finalize(&vk.params, &vk.vk), Err(e) => { debug!("RedPallas batch validation failed: {}", e); false diff --git a/src/circuit.rs b/src/circuit.rs index 7bcc31d6..f40a34a0 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -6,8 +6,8 @@ use group::{Curve, GroupEncoding}; use halo2_proofs::{ circuit::{floor_planner, Layouter, Value}, plonk::{ - self, Advice, Column, Constraints, Expression, Instance as InstanceColumn, Selector, - SingleVerifier, + self, Advice, BatchVerifier, Column, Constraints, Expression, Instance as InstanceColumn, + Selector, SingleVerifier, }, poly::Rotation, transcript::{Blake2bRead, Blake2bWrite}, @@ -690,8 +690,8 @@ impl plonk::Circuit for Circuit { /// The verifying key for the Orchard Action circuit. #[derive(Debug)] pub struct VerifyingKey { - params: halo2_proofs::poly::commitment::Params, - vk: plonk::VerifyingKey, + pub(crate) params: halo2_proofs::poly::commitment::Params, + pub(crate) vk: plonk::VerifyingKey, } impl VerifyingKey { @@ -866,6 +866,24 @@ impl Proof { plonk::verify_proof(&vk.params, &vk.vk, strategy, &instances, &mut transcript) } + pub(crate) fn add_to_batch( + &self, + batch: &mut BatchVerifier, + instances: Vec, + ) { + let instances = instances + .iter() + .map(|i| { + i.to_halo2_instance() + .into_iter() + .map(|c| c.into_iter().collect()) + .collect() + }) + .collect(); + + batch.add_proof(instances, self.0.clone()); + } + /// Constructs a new Proof value. pub fn new(bytes: Vec) -> Self { Proof(bytes) From 4bed67218aaeb0e28bc4e40910f27cc64fc68f42 Mon Sep 17 00:00:00 2001 From: str4d Date: Thu, 23 Jun 2022 22:33:40 +0100 Subject: [PATCH 09/18] Add note about relationship between signatures and proofs Co-authored-by: Daira Hopwood --- src/bundle/batch.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/bundle/batch.rs b/src/bundle/batch.rs index 4fee9590..c60d0cd5 100644 --- a/src/bundle/batch.rs +++ b/src/bundle/batch.rs @@ -69,6 +69,8 @@ impl BatchValidator { pub fn validate(self, vk: &VerifyingKey, rng: R) -> bool { if self.signatures.is_empty() { // An empty batch is always valid, but is not free to run; skip it. + // Note that a transaction has at least a binding signature, so if + // there are no signatures, there are also no proofs. return true; } From da7358a48c3b957b085b6d6e113977e37cbe5639 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 23 Jun 2022 22:33:10 +0000 Subject: [PATCH 10/18] Fix incorrect namespaces in circuit debug code Closes zcash/orchard#329. --- src/circuit.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/circuit.rs b/src/circuit.rs index f40a34a0..b61ccb29 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -507,7 +507,7 @@ impl plonk::Circuit for Circuit { let ak = ak_P.extract_p().inner().clone(); let rivk = ScalarFixed::new( ecc_chip.clone(), - layouter.namespace(|| "rcv"), + layouter.namespace(|| "rivk"), self.rivk.map(|rivk| rivk.inner()), )?; @@ -609,7 +609,7 @@ impl plonk::Circuit for Circuit { let rcm_new = ScalarFixed::new( ecc_chip, - layouter.namespace(|| "rcm_old"), + layouter.namespace(|| "rcm_new"), self.rcm_new.as_ref().map(|rcm_new| rcm_new.inner()), )?; From 1a1c3c30b0ad55eb2bee6e3c32aba3d5e7ef3960 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 23 Jun 2022 22:46:45 +0000 Subject: [PATCH 11/18] book: Finish incomplete sentence on nullifier page Closes zcash/orchard#195. --- book/src/design/nullifiers.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/book/src/design/nullifiers.md b/book/src/design/nullifiers.md index f84a5145..f9a11bad 100644 --- a/book/src/design/nullifiers.md +++ b/book/src/design/nullifiers.md @@ -246,7 +246,8 @@ The $\mathit{Commit}^{\mathsf{nf}}$ variants were considered to avoid directly d $\mathsf{cm}$ (which in its native type is a base field element, not a group element). We decided instead to follow Sapling by defining an intermediate representation of $\mathsf{cm}$ as a group element, that is only used in nullifier computation. The circuit -already needs to compute $\mathsf{cm}$, so this improves performance by removing +already needs to compute $\mathsf{cm}$, so this improves performance by removing an +additional commitment calculation from the circuit. We also considered variants that used a choice of fixed bases $\mathcal{G_v}$ to provide domain separation for zero-valued notes. The most performant design (similar to the chosen From e76a91adff185d495ddcf4ad34ab2318901835db Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 23 Jun 2022 22:51:05 +0000 Subject: [PATCH 12/18] Document how to obtain inputs for `Builder::add_spend` Closes zcash/orchard#244. --- src/builder.rs | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/src/builder.rs b/src/builder.rs index 8ccc6274..fd1e9fd5 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -236,8 +236,16 @@ impl Builder { /// Adds a note to be spent in this transaction. /// + /// - `note` is a spendable note, obtained by trial-decrypting an [`Action`] using the + /// [`zcash_note_encryption`] crate instantiated with [`OrchardDomain`]. + /// - `merkle_path` can be obtained using the [`incrementalmerkletree`] crate + /// instantiated with [`MerkleHashOrchard`]. + /// /// Returns an error if the given Merkle path does not have the required anchor for /// the given note. + /// + /// [`OrchardDomain`]: crate::note_encryption::OrchardDomain + /// [`MerkleHashOrchard`]: crate::tree::MerkleHashOrchard pub fn add_spend( &mut self, fvk: FullViewingKey, From 5f7f1afd46cddebb1cf3a601dcbfe905ee2d6e0f Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 24 Jun 2022 15:36:12 +0000 Subject: [PATCH 13/18] Update changelog --- CHANGELOG.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index b527d10c..9513343a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,10 @@ and this project adheres to Rust's notion of ## [Unreleased] ### Added - `orchard::bundle::BatchValidator` +- `orchard::note_encryption`: + - `CompactAction::from_parts` + - `CompactAction::nullifier` + - `OrchardDomain::for_nullifier` ### Changed - Migrated to `halo2_proofs 0.2`. From 9cc142ea3b224c032c485082489df15224bbab8e Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 24 Jun 2022 15:36:19 +0000 Subject: [PATCH 14/18] pprof 0.9 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 7f1cd8f5..b83d6687 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -58,7 +58,7 @@ proptest = "1.0.0" zcash_note_encryption = { version = "0.1", features = ["pre-zip-212"] } [target.'cfg(unix)'.dev-dependencies] -pprof = { version = "0.8", features = ["criterion", "flamegraph"] } # MSRV 1.56 +pprof = { version = "0.9", features = ["criterion", "flamegraph"] } # MSRV 1.56 [lib] bench = false From 597f37a869d2d03bb40b8b8108b88fba984b0842 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 24 Jun 2022 15:37:35 +0000 Subject: [PATCH 15/18] orchard 0.2.0 --- CHANGELOG.md | 2 ++ Cargo.toml | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9513343a..5077477b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,8 @@ and this project adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] + +## [0.2.0] - 2022-06-24 ### Added - `orchard::bundle::BatchValidator` - `orchard::note_encryption`: diff --git a/Cargo.toml b/Cargo.toml index b83d6687..692cbd58 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "orchard" -version = "0.1.0" +version = "0.2.0" authors = [ "Sean Bowe ", "Jack Grigg ", From 33a205b94c0328bc7df8f9826c5b97f655683028 Mon Sep 17 00:00:00 2001 From: Paul <3682187+PaulLaux@users.noreply.github.com> Date: Wed, 30 Mar 2022 19:10:55 +0300 Subject: [PATCH 16/18] Circleci project setup (#1) * Added .circleci/config.yml --- .circleci/config.yml | 29 +++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 .circleci/config.yml diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 00000000..153b5513 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,29 @@ +# Use the latest 2.1 version of CircleCI pipeline process engine. +# See: https://circleci.com/docs/2.0/configuration-reference +version: 2.1 + +# Define a job to be invoked later in a workflow. +# See: https://circleci.com/docs/2.0/configuration-reference/#jobs +jobs: + cargo-test: + # Specify the execution environment. You can specify an image from Dockerhub or use one of our Convenience Images from CircleCI's Developer Hub. + # See: https://circleci.com/docs/2.0/configuration-reference/#docker-machine-macos-windows-executor + docker: + - image: cimg/rust:1.59.0 + # Add steps to the job + # See: https://circleci.com/docs/2.0/configuration-reference/#steps + steps: + - checkout + - run: + name: "cargo test" + command: | + cargo version; + cargo test; + + +# Invoke jobs via workflows +# See: https://circleci.com/docs/2.0/configuration-reference/#workflows +workflows: + build-and-test: + jobs: + - cargo-test From 71565ee93306e955c2d0ab4c79ebb6657f20f212 Mon Sep 17 00:00:00 2001 From: Daniel Benarroch Date: Tue, 14 Jun 2022 17:33:34 +0200 Subject: [PATCH 17/18] issuer keys implementation (#5) Implements the issuer keys as IssuerAuthorizingKey -> isk IssuerVerifyingKey -> ik Test vectors generated with zcash_test_vectors repo --- src/keys.rs | 113 ++++++++++++++++++++++++++++++++++----- src/spec/prf_expand.rs | 2 + src/test_vectors/keys.rs | 102 +++++++++++++++++++++++++++++++++++ 3 files changed, 205 insertions(+), 12 deletions(-) diff --git a/src/keys.rs b/src/keys.rs index 0504dfa4..3f5b4bd8 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -18,7 +18,7 @@ use zcash_note_encryption::EphemeralKeyBytes; use crate::{ address::Address, - primitives::redpallas::{self, SpendAuth}, + primitives::redpallas::{self, SpendAuth, VerificationKey}, spec::{ commit_ivk, diversify_hash, extract_p, ka_orchard, prf_nf, to_base, to_scalar, NonIdentityPallasPoint, NonZeroPallasBase, NonZeroPallasScalar, PrfExpand, @@ -188,21 +188,104 @@ impl SpendValidatingKey { pub(crate) fn from_bytes(bytes: &[u8]) -> Option { <[u8; 32]>::try_from(bytes) .ok() - .and_then(|b| { - // Structural validity checks for ak_P: - // - The point must not be the identity - // (which for Pallas is canonically encoded as all-zeroes). - // - The sign of the y-coordinate must be positive. - if b != [0; 32] && b[31] & 0x80 == 0 { - >::try_from(b).ok() - } else { - None - } - }) + .and_then(check_structural_validity) .map(SpendValidatingKey) } } +/// An issuer authorizing key, used to create issuer authorization signatures. +/// This type enforces that the corresponding public point (ik^ℙ) has ỹ = 0. +/// +/// $\mathsf{isk}$ as defined in +/// [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. +/// +/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents +#[derive(Clone, Debug)] +pub struct IssuerAuthorizingKey(redpallas::SigningKey); + +impl IssuerAuthorizingKey { + /// Derives isk from sk. Internal use only, does not enforce all constraints. + fn derive_inner(sk: &SpendingKey) -> pallas::Scalar { + to_scalar(PrfExpand::ZsaIsk.expand(&sk.0)) + } +} + +impl From<&SpendingKey> for IssuerAuthorizingKey { + fn from(sk: &SpendingKey) -> Self { + let isk = Self::derive_inner(sk); + // IssuerSigningKey cannot be constructed such that this assertion would fail. + assert!(!bool::from(isk.is_zero())); + let ret = IssuerAuthorizingKey(isk.to_repr().try_into().unwrap()); + // If the last bit of repr_P(ik) is 1, negate isk. + if (<[u8; 32]>::from(IssuerValidatingKey::from(&ret).0)[31] >> 7) == 1 { + IssuerAuthorizingKey((-isk).to_repr().try_into().unwrap()) + } else { + ret + } + } +} + +/// A key used to validate issuer authorization signatures. +/// +/// Defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. +/// Note that this is $\mathsf{ik}^\mathbb{P}$, which by construction is equivalent to +/// $\mathsf{ik}$ but stored here as a RedPallas verification key. +/// +/// [orchardkeycomponents]: https://zips.z.cash/protocol/nu5.pdf#orchardkeycomponents +#[derive(Debug, Clone, PartialOrd, Ord)] +pub struct IssuerValidatingKey(redpallas::VerificationKey); +impl From<&IssuerAuthorizingKey> for IssuerValidatingKey { + fn from(isk: &IssuerAuthorizingKey) -> Self { + IssuerValidatingKey((&isk.0).into()) + } +} + +impl From<&IssuerValidatingKey> for pallas::Point { + fn from(issuer_validating_key: &IssuerValidatingKey) -> pallas::Point { + pallas::Point::from_bytes(&(&issuer_validating_key.0).into()).unwrap() + } +} + +impl PartialEq for IssuerValidatingKey { + fn eq(&self, other: &Self) -> bool { + <[u8; 32]>::from(&self.0).eq(&<[u8; 32]>::from(&other.0)) + } +} + +impl Eq for IssuerValidatingKey {} + +impl IssuerValidatingKey { + /// Converts this spend validating key to its serialized form, + /// I2LEOSP_256(ik). + pub(crate) fn to_bytes(&self) -> [u8; 32] { + // This is correct because the wrapped point must have ỹ = 0, and + // so the point repr is the same as I2LEOSP of its x-coordinate. + <[u8; 32]>::from(&self.0) + } + + pub(crate) fn from_bytes(bytes: &[u8]) -> Option { + <[u8; 32]>::try_from(bytes) + .ok() + .and_then(check_structural_validity) + .map(IssuerValidatingKey) + } +} + +/// A function to check structural validity of the validating keys for authorizing transfers and +/// issuing assets +/// Structural validity checks for ik_P: +/// - The point must not be the identity (which for Pallas is canonically encoded as all-zeroes). +/// - The sign of the y-coordinate must be positive. +fn check_structural_validity( + verification_key_bytes: [u8; 32], +) -> Option> { + if verification_key_bytes != [0; 32] && verification_key_bytes[31] & 0x80 == 0 { + >::try_from(verification_key_bytes).ok() + } else { + None + } +} + /// A key used to derive [`Nullifier`]s from [`Note`]s. /// /// $\mathsf{nk}$ as defined in [Zcash Protocol Spec § 4.2.3: Orchard Key Components][orchardkeycomponents]. @@ -1021,9 +1104,15 @@ mod tests { let ask: SpendAuthorizingKey = (&sk).into(); assert_eq!(<[u8; 32]>::from(&ask.0), tv.ask); + let isk: IssuerAuthorizingKey = (&sk).into(); + assert_eq!(<[u8; 32]>::from(&isk.0), tv.isk); + let ak: SpendValidatingKey = (&ask).into(); assert_eq!(<[u8; 32]>::from(ak.0), tv.ak); + let ik: IssuerValidatingKey = (&isk).into(); + assert_eq!(<[u8; 32]>::from(ik.0), tv.ik); + let nk: NullifierDerivingKey = (&sk).into(); assert_eq!(nk.0.to_repr(), tv.nk); diff --git a/src/spec/prf_expand.rs b/src/spec/prf_expand.rs index e2f95e7f..fa788188 100644 --- a/src/spec/prf_expand.rs +++ b/src/spec/prf_expand.rs @@ -10,6 +10,7 @@ pub(crate) enum PrfExpand { OrchardNk, OrchardRivk, Psi, + ZsaIsk, OrchardZip32Child, OrchardDkOvk, OrchardRivkInternal, @@ -24,6 +25,7 @@ impl PrfExpand { Self::OrchardNk => 0x07, Self::OrchardRivk => 0x08, Self::Psi => 0x09, + Self::ZsaIsk => 0x0a, Self::OrchardZip32Child => 0x81, Self::OrchardDkOvk => 0x82, Self::OrchardRivkInternal => 0x83, diff --git a/src/test_vectors/keys.rs b/src/test_vectors/keys.rs index 39d7dc00..35cc1acb 100644 --- a/src/test_vectors/keys.rs +++ b/src/test_vectors/keys.rs @@ -4,6 +4,8 @@ pub(crate) struct TestVector { pub(crate) sk: [u8; 32], pub(crate) ask: [u8; 32], pub(crate) ak: [u8; 32], + pub(crate) isk: [u8; 32], + pub(crate) ik: [u8; 32], pub(crate) nk: [u8; 32], pub(crate) rivk: [u8; 32], pub(crate) ivk: [u8; 32], @@ -41,6 +43,16 @@ pub(crate) fn test_vectors() -> Vec { 0x12, 0x8b, 0x9a, 0x14, 0x0d, 0x5e, 0x07, 0xc1, 0x51, 0x72, 0x1d, 0xc1, 0x6d, 0x25, 0xd4, 0xe2, 0x0f, 0x15, ], + isk: [ + 0x95, 0x4a, 0x86, 0xc7, 0xa7, 0x15, 0x53, 0xfa, 0x6c, 0x8b, 0x67, 0x58, 0x54, 0x26, + 0x8e, 0xa5, 0x4c, 0x51, 0xfb, 0x17, 0xd8, 0x3d, 0x80, 0xee, 0x71, 0xd4, 0xae, 0x42, + 0xa1, 0xf8, 0xc8, 0x16, + ], + ik: [ + 0x2e, 0x4f, 0xd4, 0xa6, 0xec, 0x39, 0x94, 0x87, 0xd3, 0x78, 0xb4, 0xc7, 0x25, 0xfb, + 0x9b, 0xaf, 0xbc, 0x01, 0xa5, 0xe2, 0xb7, 0xf3, 0x68, 0x2e, 0xf4, 0x53, 0x95, 0x91, + 0xbc, 0xf0, 0x59, 0x02, + ], nk: [ 0x9f, 0x2f, 0x82, 0x67, 0x38, 0x94, 0x5a, 0xd0, 0x1f, 0x47, 0xf7, 0x0d, 0xb0, 0xc3, 0x67, 0xc2, 0x46, 0xc2, 0x0c, 0x61, 0xff, 0x55, 0x83, 0x94, 0x8c, 0x39, 0xde, 0xa9, @@ -132,6 +144,16 @@ pub(crate) fn test_vectors() -> Vec { 0x2a, 0xd6, 0x43, 0x23, 0x62, 0x9c, 0xfe, 0xd1, 0xe3, 0xaa, 0x24, 0xef, 0x05, 0x2f, 0x56, 0xe4, 0x00, 0x2a, ], + isk: [ + 0xee, 0xf5, 0xe9, 0x1b, 0x36, 0xb8, 0x06, 0x86, 0x72, 0x3d, 0x14, 0xdc, 0xc7, 0x04, + 0xad, 0x59, 0x67, 0x08, 0x0b, 0x7d, 0x6e, 0x49, 0xaf, 0x97, 0x03, 0x0e, 0x4f, 0xa0, + 0xbf, 0x5a, 0xd9, 0x0b, + ], + ik: [ + 0x2e, 0xde, 0xfb, 0x15, 0x8e, 0xa4, 0x48, 0x82, 0x57, 0x2b, 0xcd, 0xb2, 0x35, 0xca, + 0x36, 0xab, 0x39, 0xc7, 0x47, 0xbb, 0x71, 0xfe, 0x0f, 0x10, 0xfa, 0xa3, 0x9b, 0xfd, + 0x62, 0x0a, 0xcc, 0x04, + ], nk: [ 0xa8, 0xb7, 0x3d, 0x97, 0x9b, 0x6e, 0xaa, 0xda, 0x89, 0x24, 0xbc, 0xbd, 0xc6, 0x3a, 0x9e, 0xf4, 0xe8, 0x73, 0x46, 0xf2, 0x30, 0xab, 0xa6, 0xbb, 0xe1, 0xe2, 0xb4, 0x3c, @@ -223,6 +245,16 @@ pub(crate) fn test_vectors() -> Vec { 0xed, 0xb4, 0x26, 0x65, 0x7b, 0x2d, 0x07, 0x40, 0x66, 0x64, 0xd8, 0x95, 0x31, 0x2e, 0xa1, 0xc3, 0xb3, 0x34, ], + isk: [ + 0x5b, 0x1f, 0xc4, 0x57, 0xae, 0x71, 0x38, 0x3c, 0x53, 0xf4, 0x69, 0x41, 0xb7, 0xcb, + 0x4c, 0xec, 0x3d, 0xea, 0xc0, 0xc6, 0x03, 0xe2, 0xcd, 0xd0, 0xd1, 0x8d, 0x94, 0x01, + 0x9e, 0x43, 0xe2, 0x07, + ], + ik: [ + 0x4f, 0x43, 0xeb, 0x7d, 0x9e, 0x03, 0x6f, 0xa6, 0x15, 0xfd, 0x04, 0xa5, 0xef, 0x6a, + 0xeb, 0x21, 0x6e, 0x06, 0x9b, 0xe9, 0x2d, 0x30, 0xe8, 0xf7, 0x16, 0x3e, 0xe3, 0x15, + 0x11, 0x6f, 0x18, 0x32, + ], nk: [ 0x04, 0x51, 0x4e, 0xa0, 0x48, 0xb9, 0x43, 0x63, 0xde, 0xa7, 0xcb, 0x3b, 0xe8, 0xd6, 0x25, 0x82, 0xac, 0x52, 0x92, 0x2e, 0x08, 0x65, 0xf6, 0x62, 0x74, 0x3b, 0x05, 0xea, @@ -314,6 +346,16 @@ pub(crate) fn test_vectors() -> Vec { 0xe7, 0x2c, 0x3b, 0x64, 0x00, 0x06, 0xff, 0x08, 0x50, 0x52, 0x80, 0xe4, 0xf0, 0x0f, 0xad, 0xf7, 0x63, 0x28, ], + isk: [ + 0x71, 0xd0, 0x64, 0xaa, 0xa0, 0x82, 0x63, 0xb8, 0xe4, 0xc3, 0xed, 0x70, 0x3c, 0x6f, + 0x54, 0x25, 0x4a, 0x88, 0x8c, 0x36, 0xec, 0x69, 0x86, 0x62, 0xf7, 0x1f, 0xbb, 0xf4, + 0x26, 0xd9, 0x09, 0x28, + ], + ik: [ + 0x12, 0xb3, 0xab, 0xff, 0x96, 0x75, 0x20, 0x9e, 0x94, 0x54, 0x07, 0x0c, 0x14, 0xac, + 0x15, 0x54, 0x65, 0xae, 0x83, 0xbe, 0x2c, 0x39, 0x46, 0x63, 0x5e, 0x38, 0x77, 0xba, + 0x67, 0xdf, 0x49, 0x12, + ], nk: [ 0xcf, 0x36, 0xad, 0x6a, 0x06, 0x6c, 0xd2, 0x13, 0xe1, 0xd7, 0x67, 0xab, 0x07, 0x1d, 0xc1, 0x16, 0x78, 0x85, 0xc4, 0x16, 0x8b, 0xc2, 0xe2, 0x17, 0x54, 0x48, 0x56, 0x3a, @@ -405,6 +447,16 @@ pub(crate) fn test_vectors() -> Vec { 0xf5, 0x6d, 0x83, 0x20, 0x09, 0xf7, 0x24, 0x2e, 0x1f, 0x7c, 0x77, 0x0a, 0x12, 0x24, 0x1d, 0xfa, 0x28, 0x07, ], + isk: [ + 0x44, 0x36, 0x1a, 0x7b, 0xa6, 0xa1, 0xaa, 0x17, 0x8e, 0x72, 0xaf, 0x47, 0xbd, 0xc1, + 0x60, 0x40, 0xce, 0x1c, 0x54, 0xdd, 0x4b, 0x56, 0x33, 0x21, 0x55, 0xba, 0x9d, 0x04, + 0x09, 0x71, 0xd0, 0x07, + ], + ik: [ + 0x1f, 0x17, 0x2d, 0x79, 0xae, 0xdc, 0xc2, 0x06, 0x8c, 0x3a, 0x09, 0x08, 0x93, 0xe1, + 0xa1, 0x75, 0xd9, 0xb5, 0x78, 0xf8, 0x91, 0xaf, 0x9a, 0xb6, 0x8d, 0x4f, 0xe1, 0xe9, + 0x05, 0xa3, 0xb2, 0x11, + ], nk: [ 0x51, 0xba, 0xf3, 0x33, 0xcf, 0xf1, 0xf2, 0xd0, 0xc7, 0xe3, 0xcf, 0xf4, 0xd3, 0x01, 0x29, 0x9d, 0xc1, 0xef, 0xe9, 0x83, 0x00, 0x31, 0x4a, 0x54, 0x19, 0x38, 0x02, 0x9b, @@ -496,6 +548,16 @@ pub(crate) fn test_vectors() -> Vec { 0xdf, 0x28, 0xbb, 0x0f, 0x10, 0x21, 0xea, 0x84, 0x3f, 0x86, 0x7f, 0x8a, 0x17, 0x0f, 0x5c, 0x33, 0x90, 0x1f, ], + isk: [ + 0xdc, 0x93, 0x72, 0x9f, 0x3f, 0x28, 0x30, 0xed, 0x79, 0x1c, 0x21, 0xbe, 0xbe, 0x45, + 0x0f, 0xcf, 0x1f, 0x8f, 0xef, 0x49, 0x81, 0x39, 0xc7, 0x99, 0xd1, 0x63, 0x66, 0x5a, + 0x8c, 0x51, 0xe5, 0x2d, + ], + ik: [ + 0x1d, 0xb6, 0x1c, 0x29, 0x3e, 0x3a, 0x93, 0x34, 0x5d, 0x06, 0xb9, 0x0b, 0xd7, 0x1f, + 0xd3, 0x21, 0x5c, 0x2c, 0x1c, 0x29, 0x53, 0x5a, 0x10, 0xde, 0x9d, 0x31, 0x40, 0xb7, + 0x4d, 0xb6, 0x1d, 0x07, + ], nk: [ 0x9e, 0x99, 0x7d, 0x9d, 0x26, 0x97, 0x87, 0x26, 0x8e, 0x09, 0x2a, 0x7c, 0x85, 0x41, 0x7d, 0xa5, 0x30, 0xea, 0x42, 0xfa, 0xc6, 0x68, 0xa7, 0x49, 0xaf, 0x55, 0xdf, 0xb7, @@ -587,6 +649,16 @@ pub(crate) fn test_vectors() -> Vec { 0x65, 0x43, 0x46, 0x2a, 0x13, 0x7f, 0xfe, 0xa3, 0x7b, 0xaf, 0x41, 0xef, 0x28, 0x6b, 0xb7, 0x32, 0xbe, 0x2c, ], + isk: [ + 0xf2, 0x34, 0x52, 0x32, 0xc9, 0x19, 0xc1, 0x29, 0xe0, 0x4b, 0x0c, 0x46, 0xac, 0x2c, + 0xa8, 0x50, 0x65, 0xd9, 0x54, 0x85, 0xb9, 0x02, 0xab, 0x0f, 0x98, 0xf9, 0x3a, 0xee, + 0x59, 0x4b, 0x5f, 0x02, + ], + ik: [ + 0x2c, 0x83, 0xd9, 0x20, 0xe9, 0xf6, 0x6d, 0xa5, 0x04, 0x86, 0x37, 0xad, 0x9a, 0xa2, + 0xcc, 0xe6, 0xe1, 0x6e, 0xf4, 0x8f, 0x86, 0x50, 0xea, 0x00, 0xd8, 0xc2, 0xd7, 0x68, + 0x61, 0x8a, 0xe3, 0x36, + ], nk: [ 0xfd, 0x31, 0x64, 0xc6, 0x32, 0xbe, 0xc9, 0x4c, 0xe9, 0xfb, 0x2f, 0x30, 0x22, 0x63, 0xb8, 0x84, 0xab, 0xb9, 0xc1, 0x0e, 0x55, 0xe4, 0x48, 0x64, 0x7f, 0x67, 0x98, 0x49, @@ -678,6 +750,16 @@ pub(crate) fn test_vectors() -> Vec { 0xb3, 0x65, 0x1f, 0xfa, 0x1c, 0x69, 0x69, 0x15, 0xac, 0x00, 0xa2, 0x5e, 0xa3, 0xac, 0x7d, 0xff, 0x99, 0x01, ], + isk: [ + 0x3d, 0xa9, 0x08, 0x88, 0xba, 0x18, 0x24, 0xc8, 0x16, 0x29, 0x2d, 0x7f, 0x17, 0x33, + 0xac, 0x4c, 0xbe, 0x72, 0x2c, 0x6a, 0x12, 0x1c, 0xc7, 0x80, 0x17, 0x06, 0x26, 0xb7, + 0x0a, 0x26, 0x95, 0x28, + ], + ik: [ + 0x16, 0xa8, 0xff, 0x29, 0xb5, 0x17, 0xb5, 0xa8, 0xf7, 0xd0, 0x9b, 0x4e, 0x5e, 0x71, + 0x3a, 0x9a, 0x78, 0x4c, 0x74, 0x04, 0xd6, 0x1e, 0x3a, 0xf5, 0x30, 0x19, 0xc3, 0x47, + 0x0e, 0x90, 0x95, 0x22, + ], nk: [ 0x02, 0xab, 0x99, 0x5c, 0xe9, 0x8f, 0x63, 0x02, 0x5f, 0xb6, 0x24, 0x28, 0xa0, 0xfb, 0xf5, 0x2f, 0x25, 0x22, 0xe6, 0xa2, 0x72, 0x61, 0x07, 0x8a, 0x9f, 0x4d, 0x6a, 0x36, @@ -769,6 +851,16 @@ pub(crate) fn test_vectors() -> Vec { 0x3b, 0x02, 0xd2, 0x5c, 0xc1, 0x0c, 0x90, 0x71, 0xfc, 0x02, 0x19, 0xe9, 0x7f, 0x93, 0x92, 0xd0, 0x67, 0x0c, ], + isk: [ + 0x01, 0x65, 0x33, 0x68, 0x4f, 0xb9, 0x81, 0x15, 0xa4, 0x05, 0xc9, 0xc7, 0xad, 0x47, + 0x72, 0x76, 0xab, 0x7c, 0x72, 0xfd, 0x67, 0x1a, 0x27, 0xe3, 0x6c, 0x0a, 0x7a, 0xbe, + 0x0a, 0x76, 0x90, 0x09, + ], + ik: [ + 0xff, 0xd7, 0x5f, 0x6f, 0x9e, 0xf4, 0x27, 0xf3, 0x26, 0xcd, 0xbf, 0x3a, 0x98, 0xbc, + 0xb5, 0x93, 0x63, 0x5a, 0x2c, 0x1a, 0xd7, 0x2b, 0x39, 0x99, 0x12, 0x61, 0xe2, 0x75, + 0xa9, 0xec, 0x6f, 0x10, + ], nk: [ 0x25, 0x91, 0xed, 0xf7, 0xef, 0x4c, 0xf2, 0x18, 0x4c, 0x34, 0xbe, 0x93, 0xfc, 0xf6, 0x12, 0x91, 0x50, 0x42, 0xf1, 0x5a, 0xb5, 0x08, 0x4b, 0x14, 0xe1, 0x66, 0x79, 0x5b, @@ -860,6 +952,16 @@ pub(crate) fn test_vectors() -> Vec { 0x8d, 0x4b, 0x02, 0x5f, 0x8c, 0xc1, 0x60, 0xe1, 0xf4, 0xe9, 0x5f, 0x0a, 0x85, 0x3e, 0xbc, 0x41, 0x6a, 0x2b, ], + isk: [ + 0x76, 0x08, 0x32, 0x9d, 0xfa, 0x77, 0xc4, 0x2c, 0x4f, 0xc7, 0x6a, 0xc2, 0x95, 0x94, + 0xa2, 0x72, 0x83, 0x93, 0x4f, 0x5a, 0x93, 0x40, 0x71, 0xb9, 0xf8, 0xcd, 0x34, 0x4e, + 0x1f, 0x98, 0x45, 0x0e, + ], + ik: [ + 0x72, 0xa0, 0xac, 0x97, 0x8a, 0x2d, 0xa1, 0x61, 0xf4, 0x1f, 0x5b, 0x7a, 0x40, 0xbd, + 0x83, 0xc0, 0x58, 0x41, 0xf8, 0x1b, 0xc5, 0x11, 0x40, 0x67, 0xb8, 0x85, 0x98, 0x7f, + 0x48, 0xca, 0x52, 0x2d, + ], nk: [ 0x3e, 0x88, 0xf2, 0x07, 0x1f, 0xd9, 0xa2, 0xbb, 0x26, 0xcd, 0xa2, 0xea, 0x85, 0x6a, 0xa0, 0xfb, 0x3a, 0x80, 0xa8, 0x7d, 0x2f, 0xb6, 0x13, 0x6f, 0xab, 0x85, 0xe3, 0x6c, From 5ae51075dbfafed9afba428b98639e864eee62a5 Mon Sep 17 00:00:00 2001 From: Paul <3682187+PaulLaux@users.noreply.github.com> Date: Tue, 14 Jun 2022 21:23:03 +0300 Subject: [PATCH 18/18] Added NoteType to Notes (#2) * Added NoteType to Notes * Added NoteType to value commitment derivation --- .circleci/config.yml | 15 ++++++- src/action.rs | 7 +++- src/builder.rs | 18 +++++++-- src/bundle.rs | 2 + src/circuit.rs | 3 +- src/constants/fixed_bases.rs | 4 ++ src/keys.rs | 2 + src/note.rs | 18 +++++++++ src/note/note_type.rs | 77 ++++++++++++++++++++++++++++++++++++ src/note_encryption.rs | 8 +++- src/value.rs | 66 ++++++++++++++++++------------- 11 files changed, 182 insertions(+), 38 deletions(-) create mode 100644 src/note/note_type.rs diff --git a/.circleci/config.yml b/.circleci/config.yml index 153b5513..ba37a789 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -2,6 +2,9 @@ # See: https://circleci.com/docs/2.0/configuration-reference version: 2.1 +orbs: + slack: circleci/slack@4.1 + # Define a job to be invoked later in a workflow. # See: https://circleci.com/docs/2.0/configuration-reference/#jobs jobs: @@ -17,8 +20,15 @@ jobs: - run: name: "cargo test" command: | + sudo apt update && sudo apt-get install libfontconfig libfontconfig1-dev libfreetype6-dev; cargo version; - cargo test; + cargo test --all --all-features; + - slack/notify: + event: fail + template: basic_fail_1 + - slack/notify: + event: pass + template: basic_success_1 # Invoke jobs via workflows @@ -26,4 +36,5 @@ jobs: workflows: build-and-test: jobs: - - cargo-test + - cargo-test: + context: CI-Orchard-slack diff --git a/src/action.rs b/src/action.rs index d0b73f23..b6396ed9 100644 --- a/src/action.rs +++ b/src/action.rs @@ -126,6 +126,7 @@ pub(crate) mod testing { use proptest::prelude::*; + use crate::note::NoteType; use crate::{ note::{ commitment::ExtractedNoteCommitment, nullifier::testing::arb_nullifier, @@ -150,7 +151,8 @@ pub(crate) mod testing { let cmx = ExtractedNoteCommitment::from(note.commitment()); let cv_net = ValueCommitment::derive( spend_value - output_value, - ValueCommitTrapdoor::zero() + ValueCommitTrapdoor::zero(), + NoteType::native() ); // FIXME: make a real one from the note. let encrypted_note = TransmittedNoteCiphertext { @@ -181,7 +183,8 @@ pub(crate) mod testing { let cmx = ExtractedNoteCommitment::from(note.commitment()); let cv_net = ValueCommitment::derive( spend_value - output_value, - ValueCommitTrapdoor::zero() + ValueCommitTrapdoor::zero(), + NoteType::native() ); // FIXME: make a real one from the note. diff --git a/src/builder.rs b/src/builder.rs index fd1e9fd5..7e3f00de 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -9,6 +9,7 @@ use nonempty::NonEmpty; use pasta_curves::pallas; use rand::{prelude::SliceRandom, CryptoRng, RngCore}; +use crate::note::NoteType; use crate::{ action::Action, address::Address, @@ -141,7 +142,7 @@ impl ActionInfo { /// [orchardsend]: https://zips.z.cash/protocol/nu5.pdf#orchardsend fn build(self, mut rng: impl RngCore) -> (Action, Circuit) { let v_net = self.value_sum(); - let cv_net = ValueCommitment::derive(v_net, self.rcv.clone()); + let cv_net = ValueCommitment::derive(v_net, self.rcv, NoteType::native()); let nf_old = self.spend.note.nullifier(&self.spend.fvk); let sender_address = self.spend.note.recipient(); @@ -151,8 +152,15 @@ impl ActionInfo { let ak: SpendValidatingKey = self.spend.fvk.clone().into(); let alpha = pallas::Scalar::random(&mut rng); let rk = ak.randomize(&alpha); + let note_type = self.spend.note.note_type(); - let note = Note::new(self.output.recipient, self.output.value, nf_old, &mut rng); + let note = Note::new( + self.output.recipient, + self.output.value, + note_type, + nf_old, + &mut rng, + ); let cm_new = note.commitment(); let cmx = cm_new.into(); @@ -370,7 +378,11 @@ impl Builder { // Verify that bsk and bvk are consistent. let bvk = (actions.iter().map(|a| a.cv_net()).sum::() - - ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero())) + - ValueCommitment::derive( + value_balance, + ValueCommitTrapdoor::zero(), + NoteType::native(), + )) .into_bvk(); assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); diff --git a/src/bundle.rs b/src/bundle.rs index c70ca635..01b61d79 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -12,6 +12,7 @@ use memuse::DynamicUsage; use nonempty::NonEmpty; use zcash_note_encryption::{try_note_decryption, try_output_recovery_with_ovk}; +use crate::note::NoteType; use crate::{ action::Action, address::Address, @@ -379,6 +380,7 @@ impl> Bundle { - ValueCommitment::derive( ValueSum::from_raw(self.value_balance.into()), ValueCommitTrapdoor::zero(), + NoteType::native(), )) .into_bvk() } diff --git a/src/circuit.rs b/src/circuit.rs index b61ccb29..b9f64175 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -900,6 +900,7 @@ mod tests { use rand::{rngs::OsRng, RngCore}; use super::{Circuit, Instance, Proof, ProvingKey, VerifyingKey, K}; + use crate::note::NoteType; use crate::{ keys::SpendValidatingKey, note::Note, @@ -923,7 +924,7 @@ mod tests { let value = spent_note.value() - output_note.value(); let rcv = ValueCommitTrapdoor::random(&mut rng); - let cv_net = ValueCommitment::derive(value, rcv.clone()); + let cv_net = ValueCommitment::derive(value, rcv, NoteType::native()); let path = MerklePath::dummy(&mut rng); let anchor = path.root(spent_note.commitment().into()); diff --git a/src/constants/fixed_bases.rs b/src/constants/fixed_bases.rs index af11e335..7a86487d 100644 --- a/src/constants/fixed_bases.rs +++ b/src/constants/fixed_bases.rs @@ -19,8 +19,12 @@ pub mod value_commit_v; pub const ORCHARD_PERSONALIZATION: &str = "z.cash:Orchard"; /// SWU hash-to-curve personalization for the value commitment generator +/// TODO: should we change to "NOTE_TYPE_PERSONALIZATION"? pub const VALUE_COMMITMENT_PERSONALIZATION: &str = "z.cash:Orchard-cv"; +/// SWU hash-to-curve personalization for the note type generator +// pub const NOTE_TYPE_PERSONALIZATION: &str = "z.cash:Orchard-NoteType"; + /// SWU hash-to-curve value for the value commitment generator pub const VALUE_COMMITMENT_V_BYTES: [u8; 1] = *b"v"; diff --git a/src/keys.rs b/src/keys.rs index 3f5b4bd8..811e86b3 100644 --- a/src/keys.rs +++ b/src/keys.rs @@ -1045,6 +1045,7 @@ mod tests { testing::{arb_diversifier_index, arb_diversifier_key, arb_esk, arb_spending_key}, *, }; + use crate::note::NoteType; use crate::{ note::{ExtractedNoteCommitment, Nullifier, RandomSeed}, value::NoteValue, @@ -1136,6 +1137,7 @@ mod tests { let note = Note::from_parts( addr, NoteValue::from_raw(tv.note_v), + NoteType::native(), rho, RandomSeed::from_bytes(tv.note_rseed, &rho).unwrap(), ); diff --git a/src/note.rs b/src/note.rs index 6bd3f778..7c59e036 100644 --- a/src/note.rs +++ b/src/note.rs @@ -19,6 +19,9 @@ pub use self::commitment::{ExtractedNoteCommitment, NoteCommitment}; pub(crate) mod nullifier; pub use self::nullifier::Nullifier; +pub(crate) mod note_type; +pub use self::note_type::NoteType; + /// The ZIP 212 seed randomness for a note. #[derive(Copy, Clone, Debug)] pub(crate) struct RandomSeed([u8; 32]); @@ -86,6 +89,8 @@ pub struct Note { recipient: Address, /// The value of this note. value: NoteValue, + /// The type of this note. + note_type: NoteType, /// A unique creation ID for this note. /// /// This is set to the nullifier of the note that was spent in the [`Action`] that @@ -111,12 +116,14 @@ impl Note { pub(crate) fn from_parts( recipient: Address, value: NoteValue, + note_type: NoteType, rho: Nullifier, rseed: RandomSeed, ) -> Self { Note { recipient, value, + note_type, rho, rseed, } @@ -130,6 +137,7 @@ impl Note { pub(crate) fn new( recipient: Address, value: NoteValue, + note_type: NoteType, rho: Nullifier, mut rng: impl RngCore, ) -> Self { @@ -137,6 +145,7 @@ impl Note { let note = Note { recipient, value, + note_type, rho, rseed: RandomSeed::random(&mut rng, &rho), }; @@ -162,6 +171,7 @@ impl Note { let note = Note::new( recipient, NoteValue::zero(), + NoteType::native(), rho.unwrap_or_else(|| Nullifier::dummy(rng)), rng, ); @@ -179,6 +189,11 @@ impl Note { self.value } + /// Returns the note type + pub fn note_type(&self) -> NoteType { + self.note_type + } + /// Returns the rseed value of this note. pub(crate) fn rseed(&self) -> &RandomSeed { &self.rseed @@ -265,6 +280,7 @@ impl fmt::Debug for TransmittedNoteCiphertext { pub mod testing { use proptest::prelude::*; + use crate::note::note_type::testing::arb_note_type; use crate::{ address::testing::arb_address, note::nullifier::testing::arb_nullifier, value::NoteValue, }; @@ -284,10 +300,12 @@ pub mod testing { recipient in arb_address(), rho in arb_nullifier(), rseed in arb_rseed(), + note_type in arb_note_type(), ) -> Note { Note { recipient, value, + note_type, rho, rseed, } diff --git a/src/note/note_type.rs b/src/note/note_type.rs new file mode 100644 index 00000000..d0857d61 --- /dev/null +++ b/src/note/note_type.rs @@ -0,0 +1,77 @@ +use group::GroupEncoding; +use halo2_proofs::arithmetic::CurveExt; +use pasta_curves::pallas; +use subtle::CtOption; + +use crate::constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_V_BYTES}; +use crate::keys::IssuerValidatingKey; + +/// Note type identifier. +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct NoteType(pub(crate) pallas::Point); + +const MAX_ASSET_DESCRIPTION_SIZE: usize = 512; + +// the hasher used to derive the assetID +#[allow(non_snake_case)] +fn assetID_hasher(msg: Vec) -> pallas::Point { + // TODO(zsa) replace personalization, will require circuit change? + pallas::Point::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION)(&msg) +} + +impl NoteType { + /// Deserialize the note_type from a byte array. + pub fn from_bytes(bytes: &[u8; 32]) -> CtOption { + pallas::Point::from_bytes(bytes).map(NoteType) + } + + /// Serialize the note_type to its canonical byte representation. + pub fn to_bytes(self) -> [u8; 32] { + self.0.to_bytes() + } + + /// $DeriveNoteType$. + /// + /// Defined in [Zcash Protocol Spec § TBD: Note Types][notetypes]. + /// + /// [notetypes]: https://zips.z.cash/protocol/nu5.pdf#notetypes + #[allow(non_snake_case)] + pub fn derive(ik: &IssuerValidatingKey, assetDesc: Vec) -> Self { + assert!(assetDesc.len() < MAX_ASSET_DESCRIPTION_SIZE); + + let mut s = vec![]; + s.extend(ik.to_bytes()); + s.extend(assetDesc); + + NoteType(assetID_hasher(s)) + } + + /// Note type for the "native" currency (zec), maintains backward compatibility with Orchard untyped notes. + pub fn native() -> Self { + NoteType(assetID_hasher(VALUE_COMMITMENT_V_BYTES.to_vec())) + } +} + +/// Generators for property testing. +#[cfg(any(test, feature = "test-dependencies"))] +#[cfg_attr(docsrs, doc(cfg(feature = "test-dependencies")))] +pub mod testing { + use proptest::prelude::*; + + use super::NoteType; + + use crate::keys::{testing::arb_spending_key, IssuerAuthorizingKey, IssuerValidatingKey}; + + prop_compose! { + /// Generate a uniformly distributed note type + pub fn arb_note_type()( + sk in arb_spending_key(), + bytes32a in prop::array::uniform32(prop::num::u8::ANY), + bytes32b in prop::array::uniform32(prop::num::u8::ANY), + ) -> NoteType { + let bytes64 = [bytes32a, bytes32b].concat(); + let isk = IssuerAuthorizingKey::from(&sk); + NoteType::derive(&IssuerValidatingKey::from(&isk), bytes64) + } + } +} diff --git a/src/note_encryption.rs b/src/note_encryption.rs index 5113cf7e..e45ca1ec 100644 --- a/src/note_encryption.rs +++ b/src/note_encryption.rs @@ -10,6 +10,7 @@ use zcash_note_encryption::{ OUT_PLAINTEXT_SIZE, }; +use crate::note::NoteType; use crate::{ action::Action, keys::{ @@ -75,7 +76,8 @@ where let pk_d = get_validated_pk_d(&diversifier)?; let recipient = Address::from_parts(diversifier, pk_d); - let note = Note::from_parts(recipient, value, domain.rho, rseed); + // TODO: add note_type + let note = Note::from_parts(recipient, value, NoteType::native(), domain.rho, rseed); Some((note, recipient)) } @@ -156,6 +158,7 @@ impl Domain for OrchardDomain { np[0] = 0x02; np[1..12].copy_from_slice(note.recipient().diversifier().as_array()); np[12..20].copy_from_slice(¬e.value().to_bytes()); + // todo: add note_type np[20..52].copy_from_slice(note.rseed().as_bytes()); np[52..].copy_from_slice(memo); NotePlaintextBytes(np) @@ -343,6 +346,7 @@ mod tests { }; use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption}; + use crate::note::NoteType; use crate::{ action::Action, keys::{ @@ -396,7 +400,7 @@ mod tests { assert_eq!(ock.as_ref(), tv.ock); let recipient = Address::from_parts(d, pk_d); - let note = Note::from_parts(recipient, value, rho, rseed); + let note = Note::from_parts(recipient, value, NoteType::native(), rho, rseed); assert_eq!(ExtractedNoteCommitment::from(note.commitment()), cmx); let action = Action::from_parts( diff --git a/src/value.rs b/src/value.rs index a760e77b..abb287bc 100644 --- a/src/value.rs +++ b/src/value.rs @@ -50,10 +50,9 @@ use pasta_curves::{ use rand::RngCore; use subtle::CtOption; +use crate::note::NoteType; use crate::{ - constants::fixed_bases::{ - VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES, VALUE_COMMITMENT_V_BYTES, - }, + constants::fixed_bases::{VALUE_COMMITMENT_PERSONALIZATION, VALUE_COMMITMENT_R_BYTES}, primitives::redpallas::{self, Binding}, }; @@ -209,7 +208,7 @@ impl TryFrom for i64 { } /// The blinding factor for a [`ValueCommitment`]. -#[derive(Clone, Debug)] +#[derive(Clone, Copy, Debug)] pub struct ValueCommitTrapdoor(pallas::Scalar); impl ValueCommitTrapdoor { @@ -292,9 +291,8 @@ impl ValueCommitment { /// /// [concretehomomorphiccommit]: https://zips.z.cash/protocol/nu5.pdf#concretehomomorphiccommit #[allow(non_snake_case)] - pub(crate) fn derive(value: ValueSum, rcv: ValueCommitTrapdoor) -> Self { + pub(crate) fn derive(value: ValueSum, rcv: ValueCommitTrapdoor, note_type: NoteType) -> Self { let hasher = pallas::Point::hash_to_curve(VALUE_COMMITMENT_PERSONALIZATION); - let V = hasher(&VALUE_COMMITMENT_V_BYTES); let R = hasher(&VALUE_COMMITMENT_R_BYTES); let abs_value = u64::try_from(value.0.abs()).expect("value must be in valid range"); @@ -304,7 +302,9 @@ impl ValueCommitment { pallas::Scalar::from(abs_value) }; - ValueCommitment(V * value + R * rcv.0) + let V_zsa = note_type.0; + + ValueCommitment(V_zsa * value + R * rcv.0) } pub(crate) fn into_bvk(self) -> redpallas::VerificationKey { @@ -407,6 +407,8 @@ pub mod testing { #[cfg(test)] mod tests { + use crate::note::note_type::testing::arb_note_type; + use crate::note::NoteType; use proptest::prelude::*; use super::{ @@ -415,6 +417,29 @@ mod tests { }; use crate::primitives::redpallas; + fn _bsk_consistent_with_bvk(values: &[(ValueSum, ValueCommitTrapdoor)], note_type: NoteType) { + let value_balance = values + .iter() + .map(|(value, _)| value) + .sum::>() + .expect("we generate values that won't overflow"); + + let bsk = values + .iter() + .map(|(_, rcv)| rcv) + .sum::() + .into_bsk(); + + let bvk = (values + .iter() + .map(|(value, rcv)| ValueCommitment::derive(*value, *rcv, note_type)) + .sum::() + - ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero(), note_type)) + .into_bvk(); + + assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); + } + proptest! { #[test] fn bsk_consistent_with_bvk( @@ -422,28 +447,13 @@ mod tests { arb_note_value_bounded(MAX_NOTE_VALUE / n_values as u64).prop_flat_map(move |bound| prop::collection::vec((arb_value_sum_bounded(bound), arb_trapdoor()), n_values) ) - ) + ), + arb_note_type in arb_note_type(), ) { - let value_balance = values - .iter() - .map(|(value, _)| value) - .sum::>() - .expect("we generate values that won't overflow"); - - let bsk = values - .iter() - .map(|(_, rcv)| rcv) - .sum::() - .into_bsk(); - - let bvk = (values - .into_iter() - .map(|(value, rcv)| ValueCommitment::derive(value, rcv)) - .sum::() - - ValueCommitment::derive(value_balance, ValueCommitTrapdoor::zero())) - .into_bvk(); - - assert_eq!(redpallas::VerificationKey::from(&bsk), bvk); + // Test with native note type (zec) + _bsk_consistent_with_bvk(&values, NoteType::native()); + // Test with arbitrary note type + _bsk_consistent_with_bvk(&values, arb_note_type); } } }