From 21ad1ea5cbc2b767ffe525a4675257e488bd8976 Mon Sep 17 00:00:00 2001 From: Steven Smith Date: Tue, 17 May 2022 19:20:10 -0700 Subject: [PATCH 1/5] Add exception text --- COPYING | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/COPYING b/COPYING index 5236692e..35d86449 100644 --- a/COPYING +++ b/COPYING @@ -6,14 +6,19 @@ the file ./LICENSE-BOSL for the terms of the Bootstrap Open Source Licence, version 1.0. Only if this Original Work is included as part of the distribution of one of the -following projects ("the Project"): +following (each, the "Project"): -- The Zcash projects published by the Electric Coin Company, -- The Zebra project published by the Zcash Foundation, +- The Zcash projects published by the Electric Coin Company; +- The Zebra project published by the Zcash Foundation; +- A project that is designed to integrate with Zcash and provides additional + functionality or utility to the Zcash network and holders of the ZEC coin; or +- A blockchain that descends from the Zcash blockchain and that is forked + within 100 blocks of the current block height of the Zcash blockchain at the + time of the code fork; -then License is granted to use this package under the BOSL as modified by the -following clarification and special exception. This exception applies only to -the Original Work when linked or combined with the Project and not to the +then License is granted to use the Original Work under the BOSL as modified by +the following clarification and special exception. This exception applies only +to the Original Work when linked or combined with the Project and not to the Original Work when linked, combined, or included in or with any other software or project or on a standalone basis. From 07239e50a884683f8703d802261f0844cd2f6957 Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Fri, 29 Apr 2022 22:39:03 +0000 Subject: [PATCH 2/5] Add protocol rule links for the Orchard circuit constraints Part of zcash/zcash#3957. --- book/src/IDENTIFIERS.json | 19 +++++++++++++ book/src/design/circuit/commit-ivk.md | 4 +-- book/src/design/circuit/note-commit.md | 24 ++++++++-------- src/circuit.rs | 22 +++++++-------- src/circuit/commit_ivk.rs | 17 ++++++++++- src/circuit/note_commit.rs | 39 ++++++++++++++++++++++++++ 6 files changed, 99 insertions(+), 26 deletions(-) create mode 100644 book/src/IDENTIFIERS.json diff --git a/book/src/IDENTIFIERS.json b/book/src/IDENTIFIERS.json new file mode 100644 index 00000000..8080c0ec --- /dev/null +++ b/book/src/IDENTIFIERS.json @@ -0,0 +1,19 @@ +{ + "commit-ivk-bit-lengths": "design/circuit/commit-ivk.html#bit-length-constraints", + "commit-ivk-canonicity-ak": "design/circuit/commit-ivk.html#canonicity-ak", + "commit-ivk-canonicity-nk": "design/circuit/commit-ivk.html#canonicity-nk", + "commit-ivk-decompositions": "design/circuit/commit-ivk.html#constrain-bit-lengths", + "commit-ivk-region-layout": "design/circuit/commit-ivk.html#region-layout", + "note-commit-canonicity-g_d": "design/circuit/note-commit.html#canonicity-g_d", + "note-commit-canonicity-pk_d": "design/circuit/note-commit.html#canonicity-pk_d", + "note-commit-canonicity-psi": "design/circuit/note-commit.html#canonicity-psi", + "note-commit-canonicity-rho": "design/circuit/note-commit.html#canonicity-rho", + "note-commit-canonicity-v": "design/circuit/note-commit.html#canonicity-v", + "note-commit-canonicity-y": "design/circuit/note-commit.html#canonicity-y", + "note-commit-decomposition-b": "design/circuit/note-commit.html#decomposition-b", + "note-commit-decomposition-d": "design/circuit/note-commit.html#decomposition-d", + "note-commit-decomposition-e": "design/circuit/note-commit.html#decomposition-e", + "note-commit-decomposition-g": "design/circuit/note-commit.html#decomposition-g", + "note-commit-decomposition-h": "design/circuit/note-commit.html#decomposition-h", + "note-commit-decomposition-y": "design/circuit/note-commit.html#decomposition-y" +} \ No newline at end of file diff --git a/book/src/design/circuit/commit-ivk.md b/book/src/design/circuit/commit-ivk.md index 26c33cfa..dd8c588d 100644 --- a/book/src/design/circuit/commit-ivk.md +++ b/book/src/design/circuit/commit-ivk.md @@ -146,7 +146,7 @@ $\NullifierKey$). > - Let $x' = x + t' - t$. > - Enforce $0 \leq x' < t'$. -### $\AuthSignPublic$ with $b_1 = 1 \implies \AuthSignPublic \geq 2^{254}$ +### $\AuthSignPublic$ with $b_1 = 1 \implies \AuthSignPublic \geq 2^{254}$ In these cases, we check that $\textsf{ak}_{0..=253} < t_\mathbb{P}$: @@ -181,7 +181,7 @@ $$ \end{array} $$ -### $\NullifierKey$ with $d_1 = 1 \implies \NullifierKey \geq 2^{254}$ +### $\NullifierKey$ with $d_1 = 1 \implies \NullifierKey \geq 2^{254}$ In these cases, we check that $\textsf{nk}_{0..=253} < t_\mathbb{P}$: diff --git a/book/src/design/circuit/note-commit.md b/book/src/design/circuit/note-commit.md index e3e04fc6..5e8b3c9f 100644 --- a/book/src/design/circuit/note-commit.md +++ b/book/src/design/circuit/note-commit.md @@ -104,7 +104,7 @@ The following helper gates are defined: - $\ShortLookupRangeCheck{}$ is a [short lookup range check](../decomposition.md#short-range-check). -### $b = b_0 \bconcat b_1 \bconcat b_2 \bconcat b_3$ +### $b = b_0 \bconcat b_1 \bconcat b_2 \bconcat b_3$ $b$ has been constrained to be $10$ bits by the Sinsemilla hash. #### Region layout @@ -132,7 +132,7 @@ Outside this gate, we have constrained: - $\ShortLookupRangeCheck{b_0, 4}$ - $\ShortLookupRangeCheck{b_3, 4}$ -### $d = d_0 \bconcat d_1 \bconcat d_2 \bconcat d_3$ +### $d = d_0 \bconcat d_1 \bconcat d_2 \bconcat d_3$ $d$ has been constrained to be $60$ bits by the $\SinsemillaHash$. #### Region layout @@ -161,7 +161,7 @@ Outside this gate, we have constrained: - $d_3$ is equality-constrained to $z_{d,1}$, where the latter is the index-1 running sum output of $\SinsemillaHash(d),$ constrained by the hash to be $50$ bits. -### $e = e_0 \bconcat e_1$ +### $e = e_0 \bconcat e_1$ $e$ has been constrained to be $10$ bits by the $\SinsemillaHash$. #### Region layout @@ -186,7 +186,7 @@ Outside this gate, we have constrained: - $\ShortLookupRangeCheck{e_0, 6}$ - $\ShortLookupRangeCheck{e_1, 4}$ -### $g = g_0 \bconcat g_1 \bconcat g_2$ +### $g = g_0 \bconcat g_1 \bconcat g_2$ $g$ has been constrained to be $250$ bits by the $\SinsemillaHash$. #### Region layout @@ -214,7 +214,7 @@ Outside this gate, we have constrained: - $g_2$ is equality-constrained to $z_{g,1}$, where the latter is the index-1 running sum output of $\SinsemillaHash(g),$ constrained by the hash to be 240 bits. -### $h = h_0 \bconcat h_1 \bconcat h_2$ +### $h = h_0 \bconcat h_1 \bconcat h_2$ $h$ has been constrained to be $10$ bits by the $\SinsemillaHash$. #### Region layout @@ -280,7 +280,7 @@ below are enforced if and only if the corresponding top bit is set to 1. > - Let $x' = x + t' - t$. > - Enforce $0 \leq x' < t'$. -### $x(\mathsf{g_d})$ with $b_1 = 1 \implies x(\mathsf{g_d}) \geq 2^{254}$ +### $x(\mathsf{g_d})$ with $b_1 = 1 \implies x(\mathsf{g_d}) \geq 2^{254}$ Recall that $x(\mathsf{g_d}) = a + 2^{250} \cdot b_0 + 2^{254} \cdot b_1$. When the top bit $b_1$ is set, we check that $x(\mathsf{g_d})_{0..=253} < t_\mathbb{P}$: @@ -327,7 +327,7 @@ $$ \end{array} $$ -### $x(\mathsf{pk_d})$ with $d_0 = 1 \implies x(\mathsf{pk_d}) \geq 2^{254}$ +### $x(\mathsf{pk_d})$ with $d_0 = 1 \implies x(\mathsf{pk_d}) \geq 2^{254}$ Recall that $x(\mathsf{pk_d}) = b_3 + 2^4 \cdot c + 2^{254} \cdot d_0$. When the top bit $d_0$ is set, we check that $x(\mathsf{pk_d})_{0..=253} < t_\mathbb{P}$: @@ -368,7 +368,7 @@ $$ \end{array} $$ -### $\mathsf{v} = d_2 + 2^8 \cdot d_3 + 2^{58} \cdot e_0$ +### $\mathsf{v} = d_2 + 2^8 \cdot d_3 + 2^{58} \cdot e_0$ #### Region layout $$ @@ -388,7 +388,7 @@ $$ \end{array} $$ -### $\rho$ with $g_0 = 1 \implies \rho \geq 2^{254}$ +### $\rho$ with $g_0 = 1 \implies \rho \geq 2^{254}$ Recall that $\rho = e_1 + 2^4 \cdot f + 2^{254} \cdot g_0$. When the top bit $g_0$ is set, we check that $\rho_{0..=253} < t_\mathbb{P}$: @@ -429,7 +429,7 @@ $$ \end{array} $$ -### $\psi$ with $h_1 = 1 \implies \psi \geq 2^{254}$ +### $\psi$ with $h_1 = 1 \implies \psi \geq 2^{254}$ Recall that $\psi = g_1 + 2^9 \cdot g_2 + 2^{249} \cdot h_0 + 2^{254} \cdot h_1$. When the top bit $h_1$ is set, we check that $\psi_{0..=253} < t_\mathbb{P}$: @@ -476,7 +476,7 @@ $$ \end{array} $$ -### $y$-coordinate checks +### $y$-coordinate checks Note that only the $ỹ$ LSB of the $y$-coordinates $y(\mathsf{g_d}), y(\mathsf{pk_d})$ was input to the hash, while the other bits of the $y$-coordinate were unused. However, we @@ -523,7 +523,7 @@ $$ \end{array} $$ -### $y(\mathsf{g_d})$ with $k_3 = 1 \implies y(\mathsf{g_d}) \geq 2^{254}$ +### $y(\mathsf{g_d})$ with $k_3 = 1 \implies y(\mathsf{g_d}) \geq 2^{254}$ In these cases, we check that $y(\mathsf{g_d})_{0..=253} < t_\mathbb{P}$: diff --git a/src/circuit.rs b/src/circuit.rs index a64188fa..f1742a8d 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -143,10 +143,10 @@ impl plonk::Circuit for Circuit { meta.advice_column(), ]; - // Constrain v_old - v_new = magnitude * sign - // Either v_old = 0, or calculated root = anchor - // Constrain v_old = 0 or enable_spends = 1. - // Constrain v_new = 0 or enable_outputs = 1. + // Constrain v_old - v_new = magnitude * sign (https://p.z.cash/ZKS:action-cv-net-integrity?partial). + // Either v_old = 0, or calculated root = anchor (https://p.z.cash/ZKS:action-merkle-path-validity?partial). + // Constrain v_old = 0 or enable_spends = 1 (https://p.z.cash/ZKS:action-enable-spend). + // Constrain v_new = 0 or enable_outputs = 1 (https://p.z.cash/ZKS:action-enable-output). let q_orchard = meta.selector(); meta.create_gate("Orchard circuit checks", |meta| { let q_orchard = meta.query_selector(q_orchard); @@ -389,7 +389,7 @@ impl plonk::Circuit for Circuit { (psi_old, rho_old, cm_old, g_d_old, ak_P, nk, v_old, v_new) }; - // Merkle path validity check. + // Merkle path validity check (https://p.z.cash/ZKS:action-merkle-path-validity?partial). let root = { let path = self .path @@ -404,7 +404,7 @@ impl plonk::Circuit for Circuit { merkle_inputs.calculate_root(layouter.namespace(|| "Merkle path"), leaf)? }; - // Value commitment integrity. + // Value commitment integrity (https://p.z.cash/ZKS:action-cv-net-integrity?partial). let v_net_magnitude_sign = { // Witness the magnitude and sign of v_net = v_old - v_new let v_net_magnitude_sign = { @@ -462,7 +462,7 @@ impl plonk::Circuit for Circuit { v_net_magnitude_sign }; - // Nullifier integrity + // Nullifier integrity (https://p.z.cash/ZKS:action-nullifier-integrity). let nf_old = { let nf_old = gadget::derive_nullifier( layouter.namespace(|| "nf_old = DeriveNullifier_nk(rho_old, psi_old, cm_old)"), @@ -481,7 +481,7 @@ impl plonk::Circuit for Circuit { nf_old }; - // Spend authority + // Spend authority (https://p.z.cash/ZKS:action-spend-authority) { let alpha = ScalarFixed::new(ecc_chip.clone(), layouter.namespace(|| "alpha"), self.alpha)?; @@ -501,7 +501,7 @@ impl plonk::Circuit for Circuit { layouter.constrain_instance(rk.inner().y().cell(), config.primary, RK_Y)?; } - // Diversified address integrity. + // Diversified address integrity (https://p.z.cash/ZKS:action-addr-integrity?partial). let pk_d_old = { let ivk = { let ak = ak_P.extract_p().inner().clone(); @@ -547,7 +547,7 @@ impl plonk::Circuit for Circuit { pk_d_old }; - // Old note commitment integrity. + // Old note commitment integrity (https://p.z.cash/ZKS:action-cm-old-integrity?partial). { let rcm_old = ScalarFixed::new( ecc_chip.clone(), @@ -575,7 +575,7 @@ impl plonk::Circuit for Circuit { derived_cm_old.constrain_equal(layouter.namespace(|| "cm_old equality"), &cm_old)?; } - // New note commitment integrity. + // New note commitment integrity (https://p.z.cash/ZKS:action-cmx-new-integrity?partial). { // Witness g_d_new let g_d_new = { diff --git a/src/circuit/commit_ivk.rs b/src/circuit/commit_ivk.rs index 2bee762f..1460c9d8 100644 --- a/src/circuit/commit_ivk.rs +++ b/src/circuit/commit_ivk.rs @@ -52,6 +52,8 @@ impl CommitIvkChip { // - c: 240 bits, // - d: 10 bits // + // https://p.z.cash/orchard-0.1:commit-ivk-decompositions + // https://p.z.cash/orchard-0.1:commit-ivk-region-layout?partial /* The pieces are laid out in this configuration: @@ -106,6 +108,7 @@ impl CommitIvkChip { let d_decomposition_check = d_whole - (d_0.clone() + d_1.clone() * two_pow_9); // Check `b_1` and `d_1` are each a single-bit value. + // https://p.z.cash/orchard-0.1:commit-ivk-bit-lengths?partial let b1_bool_check = bool_check(b_1.clone()); let d1_bool_check = bool_check(d_1.clone()); @@ -126,6 +129,7 @@ impl CommitIvkChip { // ak = a (250 bits) || b_0 (4 bits) || b_1 (1 bit) // The `ak` canonicity checks are enforced if and only if `b_1` = 1. + // https://p.z.cash/orchard-0.1:commit-ivk-canonicity-ak?partial let ak_canonicity_checks = { // b_1 = 1 => b_0 = 0 let b0_canon_check = b_1.clone() * b_0; @@ -163,6 +167,7 @@ impl CommitIvkChip { // nk = b_2 (5 bits) || c (240 bits) || d_0 (9 bits) || d_1 (1 bit) // The `nk` canonicity checks are enforced if and only if `d_1` = 1. + // https://p.z.cash/orchard-0.1:commit-ivk-canonicity-nk?partial let nk_canonicity_checks = { // d_1 = 1 => d_0 = 0 let c0_canon_check = d_1.clone() * d_0; @@ -257,6 +262,8 @@ pub(in crate::circuit) mod gadgets { // // We start by witnessing all of the individual pieces, and range-constraining // the short pieces b_0, b_2, and d_0. + // + // https://p.z.cash/orchard-0.1:commit-ivk-bit-lengths?partial // `a` = bits 0..=249 of `ak` let a = MessagePiece::from_subpieces( @@ -327,6 +334,8 @@ pub(in crate::circuit) mod gadgets { // `ivk = ⊥` is handled internally to `CommitDomain::short_commit`: incomplete // addition constraints allows ⊥ to occur, and then during synthesis it detects // these edge cases and raises an error (aborting proof creation). + // + // https://p.z.cash/ZKS:action-addr-integrity?partial let (ivk, zs) = { let message = Message::from_pieces( sinsemilla_chip.clone(), @@ -385,6 +394,8 @@ pub(in crate::circuit) mod gadgets { } /// Witnesses and decomposes the `a'` value we need to check the canonicity of `ak`. + /// + /// [Specification](https://p.z.cash/orchard-0.1:commit-ivk-canonicity-ak?partial). #[allow(clippy::type_complexity)] fn ak_canonicity( lookup_config: &LookupRangeCheckConfig, @@ -424,6 +435,8 @@ pub(in crate::circuit) mod gadgets { } /// Witnesses and decomposes the `b2c'` value we need to check the canonicity of `nk`. + /// + /// [Specification](https://p.z.cash/orchard-0.1:commit-ivk-canonicity-nk?partial). #[allow(clippy::type_complexity)] fn nk_canonicity( lookup_config: &LookupRangeCheckConfig, @@ -468,7 +481,9 @@ pub(in crate::circuit) mod gadgets { } impl CommitIvkConfig { - /// Assign cells for the canonicity gate. + /// Assign cells for the [canonicity gate]. + /// + /// [canonicity gate]: https://p.z.cash/orchard-0.1:commit-ivk-region-layout?partial /* The pieces are laid out in this configuration: diff --git a/src/circuit/note_commit.rs b/src/circuit/note_commit.rs index d2a27adf..b7163285 100644 --- a/src/circuit/note_commit.rs +++ b/src/circuit/note_commit.rs @@ -59,6 +59,8 @@ type CanonicityBounds = ( /// ------------------------------------ /// | b | b_0 | b_1 | 1 | /// | | b_2 | b_3 | 0 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-decomposition-b?partial #[derive(Clone, Debug)] struct DecomposeB { q_notecommit_b: Selector, @@ -207,6 +209,8 @@ impl DecomposeB { /// ------------------------------------ /// | d | d_0 | d_1 | 1 | /// | | d_2 | d_3 | 0 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-decomposition-d?partial #[derive(Clone, Debug)] struct DecomposeD { q_notecommit_d: Selector, @@ -346,6 +350,8 @@ impl DecomposeD { /// | A_6 | A_7 | A_8 | q_notecommit_e | /// ------------------------------------ /// | e | e_0 | e_1 | 1 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-decomposition-e?partial #[derive(Clone, Debug)] struct DecomposeE { q_notecommit_e: Selector, @@ -463,6 +469,8 @@ impl DecomposeE { /// ------------------------------ /// | g | g_0 | 1 | /// | g_1 | g_2 | 0 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-decomposition-g?partial #[derive(Clone, Debug)] struct DecomposeG { q_notecommit_g: Selector, @@ -588,6 +596,8 @@ impl DecomposeG { /// | A_6 | A_7 | A_8 | q_notecommit_h | /// ------------------------------------ /// | h | h_0 | h_1 | 1 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-decomposition-h?partial #[derive(Clone, Debug)] struct DecomposeH { q_notecommit_h: Selector, @@ -708,6 +718,8 @@ impl DecomposeH { /// ----------------------------------------------------------- /// | x(g_d) | b_0 | a | z13_a | 1 | /// | | b_1 | a_prime | z13_a_prime | 0 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-canonicity-g_d?partial #[derive(Clone, Debug)] struct GdCanonicity { q_notecommit_g_d: Selector, @@ -823,6 +835,8 @@ impl GdCanonicity { /// ------------------------------------------------------------------- /// | x(pk_d) | b_3 | c | z13_c | 1 | /// | | d_0 | b3_c_prime | z14_b3_c_prime | 0 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-canonicity-pk_d?partial #[derive(Clone, Debug)] struct PkdCanonicity { q_notecommit_pk_d: Selector, @@ -937,6 +951,8 @@ impl PkdCanonicity { /// | A_6 | A_7 | A_8 | A_9 | q_notecommit_value | /// ------------------------------------------------ /// | value | d_2 | d_3 | e_0 | 1 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-canonicity-v?partial #[derive(Clone, Debug)] struct ValueCanonicity { q_notecommit_value: Selector, @@ -1013,6 +1029,8 @@ impl ValueCanonicity { /// -------------------------------------------------------------- /// | rho | e_1 | f | z13_f | 1 | /// | | g_0 | e1_f_prime | z14_e1_f_prime | 0 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-canonicity-rho?partial #[derive(Clone, Debug)] struct RhoCanonicity { q_notecommit_rho: Selector, @@ -1126,6 +1144,8 @@ impl RhoCanonicity { /// ---------------------------------------------------------------- /// | psi | g_1 | g_2 | z13_g | 1 | /// | h_0 | h_1 | g1_g2_prime | z13_g1_g2_prime | 0 | +/// +/// https://p.z.cash/orchard-0.1:note-commit-canonicity-psi?partial #[derive(Clone, Debug)] struct PsiCanonicity { q_notecommit_psi: Selector, @@ -1296,6 +1316,7 @@ impl YCanonicity { let z13_j_prime = meta.query_advice(advices[9], Rotation::next()); // Decomposition checks + // https://p.z.cash/orchard-0.1:note-commit-decomposition-y?partial let decomposition_checks = { // Check that k_3 is boolean let k3_check = bool_check(k_3.clone()); @@ -1316,6 +1337,7 @@ impl YCanonicity { }; // Canonicity checks. These are enforced if and only if k_3 = 1. + // https://p.z.cash/orchard-0.1:note-commit-canonicity-y?partial let canonicity_checks = { iter::empty() .chain(Some(("k_3 = 1 => k_2 = 0", k_2))) @@ -1652,6 +1674,9 @@ pub(in crate::circuit) mod gadgets { // `cm = ⊥` is handled internally to `CommitDomain::commit`: incomplete addition // constraints allows ⊥ to occur, and then during synthesis it detects these edge // cases and raises an error (aborting proof creation). + // + // https://p.z.cash/ZKS:action-cm-old-integrity?partial + // https://p.z.cash/ZKS:action-cmx-new-integrity?partial let (cm, zs) = { let message = Message::from_pieces( chip.clone(), @@ -1774,6 +1799,10 @@ pub(in crate::circuit) mod gadgets { } /// A canonicity check helper used in checking x(g_d), y(g_d), and y(pk_d). + /// + /// Specifications: + /// - [`g_d` canonicity](https://p.z.cash/orchard-0.1:note-commit-canonicity-g_d?partial) + /// - [`y` canonicity](https://p.z.cash/orchard-0.1:note-commit-canonicity-y?partial) fn canon_bitshift_130( lookup_config: &LookupRangeCheckConfig, mut layouter: impl Layouter, @@ -1806,6 +1835,8 @@ pub(in crate::circuit) mod gadgets { } /// Check canonicity of `x(pk_d)` encoding. + /// + /// [Specification](https://p.z.cash/orchard-0.1:note-commit-canonicity-pk_d?partial). fn pkd_x_canonicity( lookup_config: &LookupRangeCheckConfig, mut layouter: impl Layouter, @@ -1845,6 +1876,8 @@ pub(in crate::circuit) mod gadgets { } /// Check canonicity of `rho` encoding. + /// + /// [Specification](https://p.z.cash/orchard-0.1:note-commit-canonicity-rho?partial). fn rho_canonicity( lookup_config: &LookupRangeCheckConfig, mut layouter: impl Layouter, @@ -1884,6 +1917,8 @@ pub(in crate::circuit) mod gadgets { } /// Check canonicity of `psi` encoding. + /// + /// [Specification](https://p.z.cash/orchard-0.1:note-commit-canonicity-psi?partial). fn psi_canonicity( lookup_config: &LookupRangeCheckConfig, mut layouter: impl Layouter, @@ -1922,6 +1957,10 @@ pub(in crate::circuit) mod gadgets { /// Check canonicity of y-coordinate given its LSB as a value. /// Also, witness the LSB and return the witnessed cell. + /// + /// Specifications: + /// - [`y` decomposition](https://p.z.cash/orchard-0.1:note-commit-decomposition-y?partial) + /// - [`y` canonicity](https://p.z.cash/orchard-0.1:note-commit-canonicity-y?partial) fn y_canonicity( lookup_config: &LookupRangeCheckConfig, y_canon: &YCanonicity, From ce91df1699ebc160afc7ca0362c54c8889e5b127 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 3/5] 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 769f2f7b9ced795fb939a8a924f9f14c04e5b473 Mon Sep 17 00:00:00 2001 From: Daniel Benarroch Date: Tue, 14 Jun 2022 17:33:34 +0200 Subject: [PATCH 4/5] 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 8a6f59927eee1e456c6476c46ff7f6e6fb6fe497 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 5/5] 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 c1a45b76..e07e31f1 100644 --- a/src/builder.rs +++ b/src/builder.rs @@ -8,6 +8,7 @@ use nonempty::NonEmpty; use pasta_curves::pallas; use rand::{prelude::SliceRandom, CryptoRng, RngCore}; +use crate::note::NoteType; use crate::{ action::Action, address::Address, @@ -140,7 +141,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(); @@ -150,8 +151,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(); @@ -361,7 +369,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 5421dadc..e0c928f0 100644 --- a/src/bundle.rs +++ b/src/bundle.rs @@ -9,6 +9,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, @@ -376,6 +377,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 f1742a8d..9e5634e7 100644 --- a/src/circuit.rs +++ b/src/circuit.rs @@ -882,6 +882,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, @@ -905,7 +906,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 7aed1831..c9cef083 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)) } @@ -151,6 +153,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) @@ -316,6 +319,7 @@ mod tests { }; use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption}; + use crate::note::NoteType; use crate::{ action::Action, keys::{ @@ -369,7 +373,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); } } }