From 374882b7bccbdf8a2e60873a0569ebd6174dc544 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 17 Nov 2023 20:39:14 -0700 Subject: [PATCH] Move `zcash_note_encryption` component to https://github.com/zcash/zcash_note_encryption The `zcash_note_encryption` component crate has been factored out to its own repository, to avoid circular crate dependencies involving https://github.com/zcash/librustzcash and the https://github.com/zcash/orchard and https://github.com/zcash/sapling-crypto repositories. --- Cargo.lock | 19 +- Cargo.toml | 1 - components/zcash_note_encryption/CHANGELOG.md | 50 -- components/zcash_note_encryption/Cargo.toml | 34 - .../zcash_note_encryption/LICENSE-APACHE | 202 ----- components/zcash_note_encryption/LICENSE-MIT | 21 - components/zcash_note_encryption/README.md | 30 - components/zcash_note_encryption/src/batch.rs | 86 --- components/zcash_note_encryption/src/lib.rs | 696 ------------------ 9 files changed, 4 insertions(+), 1135 deletions(-) delete mode 100644 components/zcash_note_encryption/CHANGELOG.md delete mode 100644 components/zcash_note_encryption/Cargo.toml delete mode 100644 components/zcash_note_encryption/LICENSE-APACHE delete mode 100644 components/zcash_note_encryption/LICENSE-MIT delete mode 100644 components/zcash_note_encryption/README.md delete mode 100644 components/zcash_note_encryption/src/batch.rs delete mode 100644 components/zcash_note_encryption/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 7a0df7006..4b8b0cb74 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1484,7 +1484,7 @@ dependencies = [ "serde", "subtle", "tracing", - "zcash_note_encryption 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_note_encryption", ] [[package]] @@ -2990,7 +2990,7 @@ dependencies = [ "which", "zcash_address", "zcash_encoding", - "zcash_note_encryption 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_note_encryption", "zcash_primitives", "zcash_proofs", ] @@ -3023,7 +3023,7 @@ dependencies = [ "zcash_address", "zcash_client_backend", "zcash_encoding", - "zcash_note_encryption 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_note_encryption", "zcash_primitives", "zcash_proofs", ] @@ -3060,17 +3060,6 @@ dependencies = [ "proptest", ] -[[package]] -name = "zcash_note_encryption" -version = "0.4.0" -dependencies = [ - "chacha20", - "chacha20poly1305", - "cipher", - "rand_core", - "subtle", -] - [[package]] name = "zcash_note_encryption" version = "0.4.0" @@ -3124,7 +3113,7 @@ dependencies = [ "tracing", "zcash_address", "zcash_encoding", - "zcash_note_encryption 0.4.0 (registry+https://github.com/rust-lang/crates.io-index)", + "zcash_note_encryption", ] [[package]] diff --git a/Cargo.toml b/Cargo.toml index c51b93b61..b66c8c36b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,7 +4,6 @@ members = [ "components/f4jumble", "components/zcash_address", "components/zcash_encoding", - "components/zcash_note_encryption", "zcash_client_backend", "zcash_client_sqlite", "zcash_extensions", diff --git a/components/zcash_note_encryption/CHANGELOG.md b/components/zcash_note_encryption/CHANGELOG.md deleted file mode 100644 index cedc180c6..000000000 --- a/components/zcash_note_encryption/CHANGELOG.md +++ /dev/null @@ -1,50 +0,0 @@ -# Changelog -All notable changes to this library will be documented in this file. - -The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), -and this library adheres to Rust's notion of -[Semantic Versioning](https://semver.org/spec/v2.0.0.html). - -## [Unreleased] - -## [0.4.0] - 2023-06-06 -### Changed -- The `esk` and `ephemeral_key` arguments have been removed from - `Domain::parse_note_plaintext_without_memo_ovk`. It is therefore no longer - necessary (or possible) to ensure that `ephemeral_key` is derived from `esk` - and the diversifier within the note plaintext. We have analyzed the safety of - this change in the context of callers within `zcash_note_encryption` and - `orchard`. See https://github.com/zcash/librustzcash/pull/848 and the - associated issue https://github.com/zcash/librustzcash/issues/802 for - additional detail. - -## [0.3.0] - 2023-03-22 -### Changed -- The `recipient` parameter has been removed from `Domain::note_plaintext_bytes`. -- The `recipient` parameter has been removed from `NoteEncryption::new`. Since - the `Domain::Note` type is now expected to contain information about the - recipient of the note, there is no longer any need to pass this information - in via the encryption context. - -## [0.2.0] - 2022-10-13 -### Added -- `zcash_note_encryption::Domain`: - - `Domain::PreparedEphemeralPublicKey` associated type. - - `Domain::prepare_epk` method, which produces the above type. - -### Changed -- MSRV is now 1.56.1. -- `zcash_note_encryption::Domain` now requires `epk` to be converted to - `Domain::PreparedEphemeralPublicKey` before being passed to - `Domain::ka_agree_dec`. -- Changes to batch decryption APIs: - - The return types of `batch::try_note_decryption` and - `batch::try_compact_note_decryption` have changed. Now, instead of - returning entries corresponding to the cartesian product of the IVKs used for - decryption with the outputs being decrypted, this now returns a vector of - decryption results of the same length and in the same order as the `outputs` - argument to the function. Each successful result includes the index of the - entry in `ivks` used to decrypt the value. - -## [0.1.0] - 2021-12-17 -Initial release. diff --git a/components/zcash_note_encryption/Cargo.toml b/components/zcash_note_encryption/Cargo.toml deleted file mode 100644 index 34d359ef7..000000000 --- a/components/zcash_note_encryption/Cargo.toml +++ /dev/null @@ -1,34 +0,0 @@ -[package] -name = "zcash_note_encryption" -description = "Note encryption for Zcash transactions" -version = "0.4.0" -authors = [ - "Jack Grigg ", - "Kris Nuttycombe " -] -homepage = "https://github.com/zcash/librustzcash" -repository = "https://github.com/zcash/librustzcash" -readme = "README.md" -license = "MIT OR Apache-2.0" -edition = "2021" -rust-version = "1.56.1" -categories = ["cryptography::cryptocurrencies"] - -[package.metadata.docs.rs] -all-features = true -rustdoc-args = ["--cfg", "docsrs"] - -[dependencies] -cipher = { version = "0.4", default-features = false } -chacha20 = { version = "0.9", default-features = false } -chacha20poly1305 = { version = "0.10", default-features = false } -rand_core = { version = "0.6", default-features = false } -subtle = { version = "2.3", default-features = false } - -[features] -default = ["alloc"] -alloc = [] -pre-zip-212 = [] - -[lib] -bench = false diff --git a/components/zcash_note_encryption/LICENSE-APACHE b/components/zcash_note_encryption/LICENSE-APACHE deleted file mode 100644 index 1e5006dc1..000000000 --- a/components/zcash_note_encryption/LICENSE-APACHE +++ /dev/null @@ -1,202 +0,0 @@ - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - -TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - -1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - -2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - -3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - -4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - -5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - -6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - -7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - -8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - -9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - -END OF TERMS AND CONDITIONS - -APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - -Copyright [yyyy] [name of copyright owner] - -Licensed under the Apache License, Version 2.0 (the "License"); -you may not use this file except in compliance with the License. -You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - -Unless required by applicable law or agreed to in writing, software -distributed under the License is distributed on an "AS IS" BASIS, -WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -See the License for the specific language governing permissions and -limitations under the License. - diff --git a/components/zcash_note_encryption/LICENSE-MIT b/components/zcash_note_encryption/LICENSE-MIT deleted file mode 100644 index 9500c140c..000000000 --- a/components/zcash_note_encryption/LICENSE-MIT +++ /dev/null @@ -1,21 +0,0 @@ -The MIT License (MIT) - -Copyright (c) 2021 Electric Coin Company - -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in -all copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN -THE SOFTWARE. diff --git a/components/zcash_note_encryption/README.md b/components/zcash_note_encryption/README.md deleted file mode 100644 index 612b7a64f..000000000 --- a/components/zcash_note_encryption/README.md +++ /dev/null @@ -1,30 +0,0 @@ -# zcash_note_encryption - -This crate implements the [in-band secret distribution scheme] for the Sapling and -Orchard protocols. It provides reusable methods that implement common note encryption -and trial decryption logic, and enforce protocol-agnostic verification requirements. - -Protocol-specific logic is handled via the `Domain` trait. Implementations of this -trait are provided in the [`zcash_primitives`] (for Sapling) and [`orchard`] crates; -users with their own existing types can similarly implement the trait themselves. - -[in-band secret distribution scheme]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband -[`zcash_primitives`]: https://crates.io/crates/zcash_primitives -[`orchard`]: https://crates.io/crates/orchard - -## License - -Licensed under either of - - * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - http://www.apache.org/licenses/LICENSE-2.0) - * MIT license ([LICENSE-MIT](LICENSE-MIT) or http://opensource.org/licenses/MIT) - -at your option. - -### Contribution - -Unless you explicitly state otherwise, any contribution intentionally -submitted for inclusion in the work by you, as defined in the Apache-2.0 -license, shall be dual licensed as above, without any additional terms or -conditions. diff --git a/components/zcash_note_encryption/src/batch.rs b/components/zcash_note_encryption/src/batch.rs deleted file mode 100644 index ad704167c..000000000 --- a/components/zcash_note_encryption/src/batch.rs +++ /dev/null @@ -1,86 +0,0 @@ -//! APIs for batch trial decryption. - -use alloc::vec::Vec; // module is alloc only - -use crate::{ - try_compact_note_decryption_inner, try_note_decryption_inner, BatchDomain, EphemeralKeyBytes, - ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, -}; - -/// Trial decryption of a batch of notes with a set of recipients. -/// -/// This is the batched version of [`crate::try_note_decryption`]. -/// -/// Returns a vector containing the decrypted result for each output, -/// with the same length and in the same order as the outputs were -/// provided, along with the index in the `ivks` slice associated with -/// the IVK that successfully decrypted the output. -#[allow(clippy::type_complexity)] -pub fn try_note_decryption>( - ivks: &[D::IncomingViewingKey], - outputs: &[(D, Output)], -) -> Vec> { - batch_note_decryption(ivks, outputs, try_note_decryption_inner) -} - -/// Trial decryption of a batch of notes for light clients with a set of recipients. -/// -/// This is the batched version of [`crate::try_compact_note_decryption`]. -/// -/// Returns a vector containing the decrypted result for each output, -/// with the same length and in the same order as the outputs were -/// provided, along with the index in the `ivks` slice associated with -/// the IVK that successfully decrypted the output. -#[allow(clippy::type_complexity)] -pub fn try_compact_note_decryption>( - ivks: &[D::IncomingViewingKey], - outputs: &[(D, Output)], -) -> Vec> { - batch_note_decryption(ivks, outputs, try_compact_note_decryption_inner) -} - -fn batch_note_decryption, F, FR, const CS: usize>( - ivks: &[D::IncomingViewingKey], - outputs: &[(D, Output)], - decrypt_inner: F, -) -> Vec> -where - F: Fn(&D, &D::IncomingViewingKey, &EphemeralKeyBytes, &Output, &D::SymmetricKey) -> Option, -{ - if ivks.is_empty() { - return (0..outputs.len()).map(|_| None).collect(); - }; - - // Fetch the ephemeral keys for each output, and batch-parse and prepare them. - let ephemeral_keys = D::batch_epk(outputs.iter().map(|(_, output)| output.ephemeral_key())); - - // Derive the shared secrets for all combinations of (ivk, output). - // The scalar multiplications cannot benefit from batching. - let items = ephemeral_keys.iter().flat_map(|(epk, ephemeral_key)| { - ivks.iter().map(move |ivk| { - ( - epk.as_ref().map(|epk| D::ka_agree_dec(ivk, epk)), - ephemeral_key, - ) - }) - }); - - // Run the batch-KDF to obtain the symmetric keys from the shared secrets. - let keys = D::batch_kdf(items); - - // Finish the trial decryption! - keys.chunks(ivks.len()) - .zip(ephemeral_keys.iter().zip(outputs.iter())) - .map(|(key_chunk, ((_, ephemeral_key), (domain, output)))| { - key_chunk - .iter() - .zip(ivks.iter().enumerate()) - .filter_map(|(key, (i, ivk))| { - key.as_ref() - .and_then(|key| decrypt_inner(domain, ivk, ephemeral_key, output, key)) - .map(|out| (out, i)) - }) - .next() - }) - .collect::>>() -} diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs deleted file mode 100644 index 16c089bba..000000000 --- a/components/zcash_note_encryption/src/lib.rs +++ /dev/null @@ -1,696 +0,0 @@ -//! Note encryption for Zcash transactions. -//! -//! This crate implements the [in-band secret distribution scheme] for the Sapling and -//! Orchard protocols. It provides reusable methods that implement common note encryption -//! and trial decryption logic, and enforce protocol-agnostic verification requirements. -//! -//! Protocol-specific logic is handled via the [`Domain`] trait. Implementations of this -//! trait are provided in the [`zcash_primitives`] (for Sapling) and [`orchard`] crates; -//! users with their own existing types can similarly implement the trait themselves. -//! -//! [in-band secret distribution scheme]: https://zips.z.cash/protocol/protocol.pdf#saplingandorchardinband -//! [`zcash_primitives`]: https://crates.io/crates/zcash_primitives -//! [`orchard`]: https://crates.io/crates/orchard - -#![no_std] -#![cfg_attr(docsrs, feature(doc_cfg))] -// Catch documentation errors caused by code changes. -#![deny(rustdoc::broken_intra_doc_links)] -#![deny(unsafe_code)] -// TODO: #![deny(missing_docs)] - -use core::fmt::{self, Write}; - -#[cfg(feature = "alloc")] -extern crate alloc; -#[cfg(feature = "alloc")] -use alloc::vec::Vec; - -use chacha20::{ - cipher::{StreamCipher, StreamCipherSeek}, - ChaCha20, -}; -use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305, KeyInit}; -use cipher::KeyIvInit; - -use rand_core::RngCore; -use subtle::{Choice, ConstantTimeEq}; - -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub mod batch; - -/// The size of a compact note. -pub const COMPACT_NOTE_SIZE: usize = 1 + // version - 11 + // diversifier - 8 + // value - 32; // rseed (or rcm prior to ZIP 212) -/// The size of [`NotePlaintextBytes`]. -pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512; -/// The size of [`OutPlaintextBytes`]. -pub const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d - 32; // esk -const AEAD_TAG_SIZE: usize = 16; -/// The size of an encrypted note plaintext. -pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE; -/// The size of an encrypted outgoing plaintext. -pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + AEAD_TAG_SIZE; - -/// A symmetric key that can be used to recover a single Sapling or Orchard output. -pub struct OutgoingCipherKey(pub [u8; 32]); - -impl From<[u8; 32]> for OutgoingCipherKey { - fn from(ock: [u8; 32]) -> Self { - OutgoingCipherKey(ock) - } -} - -impl AsRef<[u8]> for OutgoingCipherKey { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -/// Newtype representing the byte encoding of an [`EphemeralPublicKey`]. -/// -/// [`EphemeralPublicKey`]: Domain::EphemeralPublicKey -#[derive(Clone)] -pub struct EphemeralKeyBytes(pub [u8; 32]); - -impl fmt::Debug for EphemeralKeyBytes { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - struct HexFmt<'b>(&'b [u8]); - impl<'b> fmt::Debug for HexFmt<'b> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_char('"')?; - for b in self.0 { - f.write_fmt(format_args!("{:02x}", b))?; - } - f.write_char('"') - } - } - - f.debug_tuple("EphemeralKeyBytes") - .field(&HexFmt(&self.0)) - .finish() - } -} - -impl AsRef<[u8]> for EphemeralKeyBytes { - fn as_ref(&self) -> &[u8] { - &self.0 - } -} - -impl From<[u8; 32]> for EphemeralKeyBytes { - fn from(value: [u8; 32]) -> EphemeralKeyBytes { - EphemeralKeyBytes(value) - } -} - -impl ConstantTimeEq for EphemeralKeyBytes { - fn ct_eq(&self, other: &Self) -> Choice { - self.0.ct_eq(&other.0) - } -} - -/// Newtype representing the byte encoding of a note plaintext. -pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]); -/// Newtype representing the byte encoding of a outgoing plaintext. -pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]); - -#[derive(Copy, Clone, PartialEq, Eq)] -enum NoteValidity { - Valid, - Invalid, -} - -/// Trait that encapsulates protocol-specific note encryption types and logic. -/// -/// This trait enables most of the note encryption logic to be shared between Sapling and -/// Orchard, as well as between different implementations of those protocols. -pub trait Domain { - type EphemeralSecretKey: ConstantTimeEq; - type EphemeralPublicKey; - type PreparedEphemeralPublicKey; - type SharedSecret; - type SymmetricKey: AsRef<[u8]>; - type Note; - type Recipient; - type DiversifiedTransmissionKey; - type IncomingViewingKey; - type OutgoingViewingKey; - type ValueCommitment; - type ExtractedCommitment; - type ExtractedCommitmentBytes: Eq + for<'a> From<&'a Self::ExtractedCommitment>; - type Memo; - - /// Derives the `EphemeralSecretKey` corresponding to this note. - /// - /// Returns `None` if the note was created prior to [ZIP 212], and doesn't have a - /// deterministic `EphemeralSecretKey`. - /// - /// [ZIP 212]: https://zips.z.cash/zip-0212 - fn derive_esk(note: &Self::Note) -> Option; - - /// Extracts the `DiversifiedTransmissionKey` from the note. - fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey; - - /// Prepare an ephemeral public key for more efficient scalar multiplication. - fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey; - - /// Derives `EphemeralPublicKey` from `esk` and the note's diversifier. - fn ka_derive_public( - note: &Self::Note, - esk: &Self::EphemeralSecretKey, - ) -> Self::EphemeralPublicKey; - - /// Derives the `SharedSecret` from the sender's information during note encryption. - fn ka_agree_enc( - esk: &Self::EphemeralSecretKey, - pk_d: &Self::DiversifiedTransmissionKey, - ) -> Self::SharedSecret; - - /// Derives the `SharedSecret` from the recipient's information during note trial - /// decryption. - fn ka_agree_dec( - ivk: &Self::IncomingViewingKey, - epk: &Self::PreparedEphemeralPublicKey, - ) -> Self::SharedSecret; - - /// Derives the `SymmetricKey` used to encrypt the note plaintext. - /// - /// `secret` is the `SharedSecret` obtained from [`Self::ka_agree_enc`] or - /// [`Self::ka_agree_dec`]. - /// - /// `ephemeral_key` is the byte encoding of the [`EphemeralPublicKey`] used to derive - /// `secret`. During encryption it is derived via [`Self::epk_bytes`]; during trial - /// decryption it is obtained from [`ShieldedOutput::ephemeral_key`]. - /// - /// [`EphemeralPublicKey`]: Self::EphemeralPublicKey - /// [`EphemeralSecretKey`]: Self::EphemeralSecretKey - fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey; - - /// Encodes the given `Note` and `Memo` as a note plaintext. - fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> NotePlaintextBytes; - - /// Derives the [`OutgoingCipherKey`] for an encrypted note, given the note-specific - /// public data and an `OutgoingViewingKey`. - fn derive_ock( - ovk: &Self::OutgoingViewingKey, - cv: &Self::ValueCommitment, - cmstar_bytes: &Self::ExtractedCommitmentBytes, - ephemeral_key: &EphemeralKeyBytes, - ) -> OutgoingCipherKey; - - /// Encodes the outgoing plaintext for the given note. - fn outgoing_plaintext_bytes( - note: &Self::Note, - esk: &Self::EphemeralSecretKey, - ) -> OutPlaintextBytes; - - /// Returns the byte encoding of the given `EphemeralPublicKey`. - fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes; - - /// Attempts to parse `ephemeral_key` as an `EphemeralPublicKey`. - /// - /// Returns `None` if `ephemeral_key` is not a valid byte encoding of an - /// `EphemeralPublicKey`. - fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option; - - /// Derives the `ExtractedCommitment` for this note. - fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment; - - /// Parses the given note plaintext from the recipient's perspective. - /// - /// The implementation of this method must check that: - /// - The note plaintext version is valid (for the given decryption domain's context, - /// which may be passed via `self`). - /// - The note plaintext contains valid encodings of its various fields. - /// - Any domain-specific requirements are satisfied. - /// - /// `&self` is passed here to enable the implementation to enforce contextual checks, - /// such as rules like [ZIP 212] that become active at a specific block height. - /// - /// [ZIP 212]: https://zips.z.cash/zip-0212 - /// - /// # Panics - /// - /// Panics if `plaintext` is shorter than [`COMPACT_NOTE_SIZE`]. - fn parse_note_plaintext_without_memo_ivk( - &self, - ivk: &Self::IncomingViewingKey, - plaintext: &[u8], - ) -> Option<(Self::Note, Self::Recipient)>; - - /// Parses the given note plaintext from the sender's perspective. - /// - /// The implementation of this method must check that: - /// - The note plaintext version is valid (for the given decryption domain's context, - /// which may be passed via `self`). - /// - The note plaintext contains valid encodings of its various fields. - /// - Any domain-specific requirements are satisfied. - /// - /// `&self` is passed here to enable the implementation to enforce contextual checks, - /// such as rules like [ZIP 212] that become active at a specific block height. - /// - /// [ZIP 212]: https://zips.z.cash/zip-0212 - fn parse_note_plaintext_without_memo_ovk( - &self, - pk_d: &Self::DiversifiedTransmissionKey, - plaintext: &NotePlaintextBytes, - ) -> Option<(Self::Note, Self::Recipient)>; - - /// Extracts the memo field from the given note plaintext. - /// - /// # Compatibility - /// - /// `&self` is passed here in anticipation of future changes to memo handling, where - /// the memos may no longer be part of the note plaintext. - fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo; - - /// Parses the `DiversifiedTransmissionKey` field of the outgoing plaintext. - /// - /// Returns `None` if `out_plaintext` does not contain a valid byte encoding of a - /// `DiversifiedTransmissionKey`. - fn extract_pk_d(out_plaintext: &OutPlaintextBytes) -> Option; - - /// Parses the `EphemeralSecretKey` field of the outgoing plaintext. - /// - /// Returns `None` if `out_plaintext` does not contain a valid byte encoding of an - /// `EphemeralSecretKey`. - fn extract_esk(out_plaintext: &OutPlaintextBytes) -> Option; -} - -/// Trait that encapsulates protocol-specific batch trial decryption logic. -/// -/// Each batchable operation has a default implementation that calls through to the -/// non-batched implementation. Domains can override whichever operations benefit from -/// batched logic. -#[cfg(feature = "alloc")] -#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))] -pub trait BatchDomain: Domain { - /// Computes `Self::kdf` on a batch of items. - /// - /// For each item in the batch, if the shared secret is `None`, this returns `None` at - /// that position. - fn batch_kdf<'a>( - items: impl Iterator, &'a EphemeralKeyBytes)>, - ) -> Vec> { - // Default implementation: do the non-batched thing. - items - .map(|(secret, ephemeral_key)| secret.map(|secret| Self::kdf(secret, ephemeral_key))) - .collect() - } - - /// Computes `Self::epk` on a batch of ephemeral keys. - /// - /// This is useful for protocols where the underlying curve requires an inversion to - /// parse an encoded point. - /// - /// For usability, this returns tuples of the ephemeral keys and the result of parsing - /// them. - fn batch_epk( - ephemeral_keys: impl Iterator, - ) -> Vec<(Option, EphemeralKeyBytes)> { - // Default implementation: do the non-batched thing. - ephemeral_keys - .map(|ephemeral_key| { - ( - Self::epk(&ephemeral_key).map(Self::prepare_epk), - ephemeral_key, - ) - }) - .collect() - } -} - -/// Trait that provides access to the components of an encrypted transaction output. -/// -/// Implementations of this trait are required to define the length of their ciphertext -/// field. In order to use the trial decryption APIs in this crate, the length must be -/// either [`ENC_CIPHERTEXT_SIZE`] or [`COMPACT_NOTE_SIZE`]. -pub trait ShieldedOutput { - /// Exposes the `ephemeral_key` field of the output. - fn ephemeral_key(&self) -> EphemeralKeyBytes; - - /// Exposes the `cmu_bytes` or `cmx_bytes` field of the output. - fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes; - - /// Exposes the note ciphertext of the output. - fn enc_ciphertext(&self) -> &[u8; CIPHERTEXT_SIZE]; -} - -/// A struct containing context required for encrypting Sapling and Orchard notes. -/// -/// This struct provides a safe API for encrypting Sapling and Orchard notes. In particular, it -/// enforces that fresh ephemeral keys are used for every note, and that the ciphertexts are -/// consistent with each other. -/// -/// Implements section 4.19 of the -/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#saplingandorchardinband) -pub struct NoteEncryption { - epk: D::EphemeralPublicKey, - esk: D::EphemeralSecretKey, - note: D::Note, - memo: D::Memo, - /// `None` represents the `ovk = ⊥` case. - ovk: Option, -} - -impl NoteEncryption { - /// Construct a new note encryption context for the specified note, - /// recipient, and memo. - pub fn new(ovk: Option, note: D::Note, memo: D::Memo) -> Self { - let esk = D::derive_esk(¬e).expect("ZIP 212 is active."); - NoteEncryption { - epk: D::ka_derive_public(¬e, &esk), - esk, - note, - memo, - ovk, - } - } - - /// For use only with Sapling. This method is preserved in order that test code - /// be able to generate pre-ZIP-212 ciphertexts so that tests can continue to - /// cover pre-ZIP-212 transaction decryption. - #[cfg(feature = "pre-zip-212")] - #[cfg_attr(docsrs, doc(cfg(feature = "pre-zip-212")))] - pub fn new_with_esk( - esk: D::EphemeralSecretKey, - ovk: Option, - note: D::Note, - memo: D::Memo, - ) -> Self { - NoteEncryption { - epk: D::ka_derive_public(¬e, &esk), - esk, - note, - memo, - ovk, - } - } - - /// Exposes the ephemeral secret key being used to encrypt this note. - pub fn esk(&self) -> &D::EphemeralSecretKey { - &self.esk - } - - /// Exposes the encoding of the ephemeral public key being used to encrypt this note. - pub fn epk(&self) -> &D::EphemeralPublicKey { - &self.epk - } - - /// Generates `encCiphertext` for this note. - pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { - let pk_d = D::get_pk_d(&self.note); - let shared_secret = D::ka_agree_enc(&self.esk, &pk_d); - let key = D::kdf(shared_secret, &D::epk_bytes(&self.epk)); - let input = D::note_plaintext_bytes(&self.note, &self.memo); - - let mut output = [0u8; ENC_CIPHERTEXT_SIZE]; - output[..NOTE_PLAINTEXT_SIZE].copy_from_slice(&input.0); - let tag = ChaCha20Poly1305::new(key.as_ref().into()) - .encrypt_in_place_detached( - [0u8; 12][..].into(), - &[], - &mut output[..NOTE_PLAINTEXT_SIZE], - ) - .unwrap(); - output[NOTE_PLAINTEXT_SIZE..].copy_from_slice(&tag); - - output - } - - /// Generates `outCiphertext` for this note. - pub fn encrypt_outgoing_plaintext( - &self, - cv: &D::ValueCommitment, - cmstar: &D::ExtractedCommitment, - rng: &mut R, - ) -> [u8; OUT_CIPHERTEXT_SIZE] { - let (ock, input) = if let Some(ovk) = &self.ovk { - let ock = D::derive_ock(ovk, cv, &cmstar.into(), &D::epk_bytes(&self.epk)); - let input = D::outgoing_plaintext_bytes(&self.note, &self.esk); - - (ock, input) - } else { - // ovk = ⊥ - let mut ock = OutgoingCipherKey([0; 32]); - let mut input = [0u8; OUT_PLAINTEXT_SIZE]; - - rng.fill_bytes(&mut ock.0); - rng.fill_bytes(&mut input); - - (ock, OutPlaintextBytes(input)) - }; - - let mut output = [0u8; OUT_CIPHERTEXT_SIZE]; - output[..OUT_PLAINTEXT_SIZE].copy_from_slice(&input.0); - let tag = ChaCha20Poly1305::new(ock.as_ref().into()) - .encrypt_in_place_detached([0u8; 12][..].into(), &[], &mut output[..OUT_PLAINTEXT_SIZE]) - .unwrap(); - output[OUT_PLAINTEXT_SIZE..].copy_from_slice(&tag); - - output - } -} - -/// Trial decryption of the full note plaintext by the recipient. -/// -/// Attempts to decrypt and validate the given shielded output using the given `ivk`. -/// If successful, the corresponding note and memo are returned, along with the address to -/// which the note was sent. -/// -/// Implements section 4.19.2 of the -/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk). -pub fn try_note_decryption>( - domain: &D, - ivk: &D::IncomingViewingKey, - output: &Output, -) -> Option<(D::Note, D::Recipient, D::Memo)> { - let ephemeral_key = output.ephemeral_key(); - - let epk = D::prepare_epk(D::epk(&ephemeral_key)?); - let shared_secret = D::ka_agree_dec(ivk, &epk); - let key = D::kdf(shared_secret, &ephemeral_key); - - try_note_decryption_inner(domain, ivk, &ephemeral_key, output, &key) -} - -fn try_note_decryption_inner>( - domain: &D, - ivk: &D::IncomingViewingKey, - ephemeral_key: &EphemeralKeyBytes, - output: &Output, - key: &D::SymmetricKey, -) -> Option<(D::Note, D::Recipient, D::Memo)> { - let enc_ciphertext = output.enc_ciphertext(); - - let mut plaintext = - NotePlaintextBytes(enc_ciphertext[..NOTE_PLAINTEXT_SIZE].try_into().unwrap()); - - ChaCha20Poly1305::new(key.as_ref().into()) - .decrypt_in_place_detached( - [0u8; 12][..].into(), - &[], - &mut plaintext.0, - enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(), - ) - .ok()?; - - let (note, to) = parse_note_plaintext_without_memo_ivk( - domain, - ivk, - ephemeral_key, - &output.cmstar_bytes(), - &plaintext.0, - )?; - let memo = domain.extract_memo(&plaintext); - - Some((note, to, memo)) -} - -fn parse_note_plaintext_without_memo_ivk( - domain: &D, - ivk: &D::IncomingViewingKey, - ephemeral_key: &EphemeralKeyBytes, - cmstar_bytes: &D::ExtractedCommitmentBytes, - plaintext: &[u8], -) -> Option<(D::Note, D::Recipient)> { - let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, plaintext)?; - - if let NoteValidity::Valid = check_note_validity::(¬e, ephemeral_key, cmstar_bytes) { - Some((note, to)) - } else { - None - } -} - -fn check_note_validity( - note: &D::Note, - ephemeral_key: &EphemeralKeyBytes, - cmstar_bytes: &D::ExtractedCommitmentBytes, -) -> NoteValidity { - if &D::ExtractedCommitmentBytes::from(&D::cmstar(note)) == cmstar_bytes { - // In the case corresponding to specification section 4.19.3, we check that `esk` is equal - // to `D::derive_esk(note)` prior to calling this method. - if let Some(derived_esk) = D::derive_esk(note) { - if D::epk_bytes(&D::ka_derive_public(note, &derived_esk)) - .ct_eq(ephemeral_key) - .into() - { - NoteValidity::Valid - } else { - NoteValidity::Invalid - } - } else { - // Before ZIP 212 - NoteValidity::Valid - } - } else { - // Published commitment doesn't match calculated commitment - NoteValidity::Invalid - } -} - -/// Trial decryption of the compact note plaintext by the recipient for light clients. -/// -/// Attempts to decrypt and validate the given compact shielded output using the -/// given `ivk`. If successful, the corresponding note is returned, along with the address -/// to which the note was sent. -/// -/// Implements the procedure specified in [`ZIP 307`]. -/// -/// [`ZIP 307`]: https://zips.z.cash/zip-0307 -pub fn try_compact_note_decryption>( - domain: &D, - ivk: &D::IncomingViewingKey, - output: &Output, -) -> Option<(D::Note, D::Recipient)> { - let ephemeral_key = output.ephemeral_key(); - - let epk = D::prepare_epk(D::epk(&ephemeral_key)?); - let shared_secret = D::ka_agree_dec(ivk, &epk); - let key = D::kdf(shared_secret, &ephemeral_key); - - try_compact_note_decryption_inner(domain, ivk, &ephemeral_key, output, &key) -} - -fn try_compact_note_decryption_inner>( - domain: &D, - ivk: &D::IncomingViewingKey, - ephemeral_key: &EphemeralKeyBytes, - output: &Output, - key: &D::SymmetricKey, -) -> Option<(D::Note, D::Recipient)> { - // Start from block 1 to skip over Poly1305 keying output - let mut plaintext = [0; COMPACT_NOTE_SIZE]; - plaintext.copy_from_slice(output.enc_ciphertext()); - let mut keystream = ChaCha20::new(key.as_ref().into(), [0u8; 12][..].into()); - keystream.seek(64); - keystream.apply_keystream(&mut plaintext); - - parse_note_plaintext_without_memo_ivk( - domain, - ivk, - ephemeral_key, - &output.cmstar_bytes(), - &plaintext, - ) -} - -/// Recovery of the full note plaintext by the sender. -/// -/// Attempts to decrypt and validate the given shielded output using the given `ovk`. -/// If successful, the corresponding note and memo are returned, along with the address to -/// which the note was sent. -/// -/// Implements [Zcash Protocol Specification section 4.19.3][decryptovk]. -/// -/// [decryptovk]: https://zips.z.cash/protocol/nu5.pdf#decryptovk -pub fn try_output_recovery_with_ovk>( - domain: &D, - ovk: &D::OutgoingViewingKey, - output: &Output, - cv: &D::ValueCommitment, - out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE], -) -> Option<(D::Note, D::Recipient, D::Memo)> { - let ock = D::derive_ock(ovk, cv, &output.cmstar_bytes(), &output.ephemeral_key()); - try_output_recovery_with_ock(domain, &ock, output, out_ciphertext) -} - -/// Recovery of the full note plaintext by the sender. -/// -/// Attempts to decrypt and validate the given shielded output using the given `ock`. -/// If successful, the corresponding note and memo are returned, along with the address to -/// which the note was sent. -/// -/// Implements part of section 4.19.3 of the -/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptovk). -/// For decryption using a Full Viewing Key see [`try_output_recovery_with_ovk`]. -pub fn try_output_recovery_with_ock>( - domain: &D, - ock: &OutgoingCipherKey, - output: &Output, - out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE], -) -> Option<(D::Note, D::Recipient, D::Memo)> { - let enc_ciphertext = output.enc_ciphertext(); - - let mut op = OutPlaintextBytes([0; OUT_PLAINTEXT_SIZE]); - op.0.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]); - - ChaCha20Poly1305::new(ock.as_ref().into()) - .decrypt_in_place_detached( - [0u8; 12][..].into(), - &[], - &mut op.0, - out_ciphertext[OUT_PLAINTEXT_SIZE..].into(), - ) - .ok()?; - - let pk_d = D::extract_pk_d(&op)?; - let esk = D::extract_esk(&op)?; - - let ephemeral_key = output.ephemeral_key(); - let shared_secret = D::ka_agree_enc(&esk, &pk_d); - // The small-order point check at the point of output parsing rejects - // non-canonical encodings, so reencoding here for the KDF should - // be okay. - let key = D::kdf(shared_secret, &ephemeral_key); - - let mut plaintext = NotePlaintextBytes([0; NOTE_PLAINTEXT_SIZE]); - plaintext - .0 - .copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]); - - ChaCha20Poly1305::new(key.as_ref().into()) - .decrypt_in_place_detached( - [0u8; 12][..].into(), - &[], - &mut plaintext.0, - enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(), - ) - .ok()?; - - let (note, to) = domain.parse_note_plaintext_without_memo_ovk(&pk_d, &plaintext)?; - let memo = domain.extract_memo(&plaintext); - - // ZIP 212: Check that the esk provided to this function is consistent with the esk we can - // derive from the note. This check corresponds to `ToScalar(PRF^{expand}_{rseed}([4]) = esk` - // in https://zips.z.cash/protocol/protocol.pdf#decryptovk. (`ρ^opt = []` for Sapling.) - if let Some(derived_esk) = D::derive_esk(¬e) { - if (!derived_esk.ct_eq(&esk)).into() { - return None; - } - } - - if let NoteValidity::Valid = - check_note_validity::(¬e, &ephemeral_key, &output.cmstar_bytes()) - { - Some((note, to, memo)) - } else { - None - } -}