From 66c6bba84e1c967e53e376602d6c5c8c735acbe5 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 27 Jan 2024 09:22:01 -0700 Subject: [PATCH 01/18] Add stub for a `zcash_protocol` crate. --- Cargo.lock | 4 + Cargo.toml | 3 + components/zcash_protocol/CHANGELOG.md | 8 + components/zcash_protocol/Cargo.toml | 22 +++ components/zcash_protocol/LICENSE-APACHE | 202 +++++++++++++++++++++++ components/zcash_protocol/LICENSE-MIT | 21 +++ components/zcash_protocol/README.md | 20 +++ components/zcash_protocol/src/lib.rs | 1 + 8 files changed, 281 insertions(+) create mode 100644 components/zcash_protocol/CHANGELOG.md create mode 100644 components/zcash_protocol/Cargo.toml create mode 100644 components/zcash_protocol/LICENSE-APACHE create mode 100644 components/zcash_protocol/LICENSE-MIT create mode 100644 components/zcash_protocol/README.md create mode 100644 components/zcash_protocol/src/lib.rs diff --git a/Cargo.lock b/Cargo.lock index 417de7fe26..b519c219bb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3228,6 +3228,10 @@ dependencies = [ "zcash_primitives", ] +[[package]] +name = "zcash_protocol" +version = "0.0.0" + [[package]] name = "zcash_spec" version = "0.1.0" diff --git a/Cargo.toml b/Cargo.toml index 3049022446..df119c2430 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -4,6 +4,7 @@ members = [ "components/f4jumble", "components/zcash_address", "components/zcash_encoding", + "components/zcash_protocol", "zcash_client_backend", "zcash_client_sqlite", "zcash_extensions", @@ -32,6 +33,8 @@ zcash_address = { version = "0.3", path = "components/zcash_address" } zcash_client_backend = { version = "0.11", path = "zcash_client_backend" } zcash_encoding = { version = "0.2", path = "components/zcash_encoding" } zcash_keys = { version = "0.1", path = "zcash_keys" } +zcash_protocol = { version = "0.0", path = "components/zcash_protocol" } + zcash_note_encryption = "0.4" zcash_primitives = { version = "0.14", path = "zcash_primitives", default-features = false } zcash_proofs = { version = "0.14", path = "zcash_proofs", default-features = false } diff --git a/components/zcash_protocol/CHANGELOG.md b/components/zcash_protocol/CHANGELOG.md new file mode 100644 index 0000000000..d536350e63 --- /dev/null +++ b/components/zcash_protocol/CHANGELOG.md @@ -0,0 +1,8 @@ +# 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] diff --git a/components/zcash_protocol/Cargo.toml b/components/zcash_protocol/Cargo.toml new file mode 100644 index 0000000000..902a9648be --- /dev/null +++ b/components/zcash_protocol/Cargo.toml @@ -0,0 +1,22 @@ +[package] +name = "zcash_protocol" +description = "Zcash protocol network constants and value types." +version = "0.0.0" +authors = [ + "Jack Grigg ", + "Kris Nuttycombe ", +] +homepage = "https://github.com/zcash/librustzcash" +repository.workspace = true +readme = "README.md" +license.workspace = true +edition.workspace = true +rust-version.workspace = true +categories = ["cryptography::cryptocurrencies"] +keywords = ["zcash"] + +[package.metadata.docs.rs] +all-features = true +rustdoc-args = ["--cfg", "docsrs"] + +[dependencies] diff --git a/components/zcash_protocol/LICENSE-APACHE b/components/zcash_protocol/LICENSE-APACHE new file mode 100644 index 0000000000..1e5006dc14 --- /dev/null +++ b/components/zcash_protocol/LICENSE-APACHE @@ -0,0 +1,202 @@ + 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_protocol/LICENSE-MIT b/components/zcash_protocol/LICENSE-MIT new file mode 100644 index 0000000000..9500c140cc --- /dev/null +++ b/components/zcash_protocol/LICENSE-MIT @@ -0,0 +1,21 @@ +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_protocol/README.md b/components/zcash_protocol/README.md new file mode 100644 index 0000000000..862adf0a84 --- /dev/null +++ b/components/zcash_protocol/README.md @@ -0,0 +1,20 @@ +# zcash_protocol + +Zcash network constants and value types. + +## 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_protocol/src/lib.rs b/components/zcash_protocol/src/lib.rs new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/components/zcash_protocol/src/lib.rs @@ -0,0 +1 @@ + From bacfe3cb9d30c961a174ae4b30d041456c7e4d96 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 27 Jan 2024 09:55:33 -0700 Subject: [PATCH 02/18] Move `zcash_primitives::{consensus, constants}` to the `zcash_protocol` crate --- Cargo.lock | 8 ++++++ components/zcash_protocol/Cargo.toml | 26 +++++++++++++++++++ .../zcash_protocol}/src/consensus.rs | 22 ++-------------- .../zcash_protocol}/src/constants.rs | 0 .../zcash_protocol}/src/constants/mainnet.rs | 0 .../zcash_protocol}/src/constants/regtest.rs | 0 .../zcash_protocol}/src/constants/testnet.rs | 0 components/zcash_protocol/src/lib.rs | 18 +++++++++++++ zcash_client_backend/src/data_api/wallet.rs | 10 +++---- zcash_client_backend/src/decrypt.rs | 3 ++- zcash_client_backend/src/scanning.rs | 13 +++++----- zcash_client_sqlite/src/testing.rs | 9 +++---- .../init/migrations/receiving_key_scopes.rs | 9 ++++--- zcash_client_sqlite/src/wallet/sapling.rs | 6 ++--- zcash_primitives/CHANGELOG.md | 13 ++++++++++ zcash_primitives/Cargo.toml | 5 ++-- zcash_primitives/benches/note_decryption.rs | 6 ++--- zcash_primitives/src/lib.rs | 4 +-- zcash_primitives/src/transaction/builder.rs | 3 ++- .../src/transaction/components/sapling.rs | 18 +++++++++++++ 20 files changed, 121 insertions(+), 52 deletions(-) rename {zcash_primitives => components/zcash_protocol}/src/consensus.rs (97%) rename {zcash_primitives => components/zcash_protocol}/src/constants.rs (100%) rename {zcash_primitives => components/zcash_protocol}/src/constants/mainnet.rs (100%) rename {zcash_primitives => components/zcash_protocol}/src/constants/regtest.rs (100%) rename {zcash_primitives => components/zcash_protocol}/src/constants/testnet.rs (100%) diff --git a/Cargo.lock b/Cargo.lock index b519c219bb..aaadf4cc89 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3200,6 +3200,7 @@ dependencies = [ "zcash_address", "zcash_encoding", "zcash_note_encryption", + "zcash_protocol", "zcash_spec", "zip32", ] @@ -3231,6 +3232,13 @@ dependencies = [ [[package]] name = "zcash_protocol" version = "0.0.0" +dependencies = [ + "document-features", + "incrementalmerkletree", + "memuse", + "proptest", + "zcash_address", +] [[package]] name = "zcash_spec" diff --git a/components/zcash_protocol/Cargo.toml b/components/zcash_protocol/Cargo.toml index 902a9648be..f4ea79fc13 100644 --- a/components/zcash_protocol/Cargo.toml +++ b/components/zcash_protocol/Cargo.toml @@ -20,3 +20,29 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] +zcash_address.workspace = true + +# - Logging and metrics +memuse.workspace = true + +# Dependencies used internally: +# (Breaking upgrades to these are usually backwards-compatible, but check MSRVs.) +# - Documentation +document-features.workspace = true + +# - Test dependencies +proptest = { workspace = true, optional = true } +incrementalmerkletree = { workspace = true, optional = true } + +[dev-dependencies] +proptest.workspace = true + +[features] +## Exposes APIs that are useful for testing, such as `proptest` strategies. +test-dependencies = ["dep:proptest", "incrementalmerkletree/test-dependencies"] + +## Exposes the in-development NU6 features. +unstable-nu6 = [] + +## Exposes early in-development features that are not yet planned for any network upgrade. +zfuture = [] diff --git a/zcash_primitives/src/consensus.rs b/components/zcash_protocol/src/consensus.rs similarity index 97% rename from zcash_primitives/src/consensus.rs rename to components/zcash_protocol/src/consensus.rs index 8acd5c291b..11a70ec327 100644 --- a/zcash_primitives/src/consensus.rs +++ b/components/zcash_protocol/src/consensus.rs @@ -7,7 +7,7 @@ use std::fmt; use std::ops::{Add, Bound, RangeBounds, Sub}; use zcash_address; -use crate::{constants, sapling::note_encryption::Zip212Enforcement}; +use crate::constants; /// A wrapper type representing blockchain heights. /// @@ -632,25 +632,6 @@ impl BranchId { } } -/// Returns the enforcement policy for ZIP 212 at the given height. -pub fn sapling_zip212_enforcement( - params: &impl Parameters, - height: BlockHeight, -) -> Zip212Enforcement { - if params.is_nu_active(NetworkUpgrade::Canopy, height) { - let grace_period_end_height = - params.activation_height(NetworkUpgrade::Canopy).unwrap() + ZIP212_GRACE_PERIOD; - - if height < grace_period_end_height { - Zip212Enforcement::GracePeriod - } else { - Zip212Enforcement::On - } - } else { - Zip212Enforcement::Off - } -} - #[cfg(any(test, feature = "test-dependencies"))] pub mod testing { use proptest::sample::select; @@ -688,6 +669,7 @@ pub mod testing { }) } + #[cfg(feature = "test-dependencies")] impl incrementalmerkletree::testing::TestCheckpoint for BlockHeight { fn from_u64(value: u64) -> Self { BlockHeight(u32::try_from(value).expect("Test checkpoint ids do not exceed 32 bits")) diff --git a/zcash_primitives/src/constants.rs b/components/zcash_protocol/src/constants.rs similarity index 100% rename from zcash_primitives/src/constants.rs rename to components/zcash_protocol/src/constants.rs diff --git a/zcash_primitives/src/constants/mainnet.rs b/components/zcash_protocol/src/constants/mainnet.rs similarity index 100% rename from zcash_primitives/src/constants/mainnet.rs rename to components/zcash_protocol/src/constants/mainnet.rs diff --git a/zcash_primitives/src/constants/regtest.rs b/components/zcash_protocol/src/constants/regtest.rs similarity index 100% rename from zcash_primitives/src/constants/regtest.rs rename to components/zcash_protocol/src/constants/regtest.rs diff --git a/zcash_primitives/src/constants/testnet.rs b/components/zcash_protocol/src/constants/testnet.rs similarity index 100% rename from zcash_primitives/src/constants/testnet.rs rename to components/zcash_protocol/src/constants/testnet.rs diff --git a/components/zcash_protocol/src/lib.rs b/components/zcash_protocol/src/lib.rs index 8b13789179..7a4c918ecb 100644 --- a/components/zcash_protocol/src/lib.rs +++ b/components/zcash_protocol/src/lib.rs @@ -1 +1,19 @@ +//! *A crate for Zcash protocol constants and value types.* +//! +//! `zcash_protocol` contains Rust structs, traits and functions that provide the network constants +//! for the Zcash main and test networks, as well types for representing ZEC amounts and value +//! balances. +//! +//! ## Feature flags +#![doc = document_features::document_features!()] +//! +#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] +// Catch documentation errors caused by code changes. +#![deny(rustdoc::broken_intra_doc_links)] +// Temporary until we have addressed all Result cases. +#![allow(clippy::result_unit_err)] + +pub mod consensus; +pub mod constants; diff --git a/zcash_client_backend/src/data_api/wallet.rs b/zcash_client_backend/src/data_api/wallet.rs index 6648810fd8..dd3014fa98 100644 --- a/zcash_client_backend/src/data_api/wallet.rs +++ b/zcash_client_backend/src/data_api/wallet.rs @@ -41,7 +41,10 @@ use zcash_primitives::{ memo::MemoBytes, transaction::{ builder::{BuildConfig, BuildResult, Builder}, - components::amount::{Amount, NonNegativeAmount}, + components::{ + amount::{Amount, NonNegativeAmount}, + sapling::zip212_enforcement, + }, fees::{zip317::FeeError as Zip317FeeError, FeeRule, StandardFeeRule}, Transaction, TxId, }, @@ -1138,10 +1141,7 @@ where try_sapling_note_decryption( &sapling_internal_ivk, &bundle.shielded_outputs()[output_index], - consensus::sapling_zip212_enforcement( - params, - min_target_height, - ), + zip212_enforcement(params, min_target_height), ) .map(|(note, _, _)| Note::Sapling(note)) }) diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index e658b0658a..91a8e2a8ab 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -6,6 +6,7 @@ use sapling::note_encryption::{ use zcash_primitives::{ consensus::{self, BlockHeight}, memo::MemoBytes, + transaction::components::sapling::zip212_enforcement, transaction::Transaction, zip32::Scope, }; @@ -53,7 +54,7 @@ pub fn decrypt_transaction( tx: &Transaction, ufvks: &HashMap, ) -> Vec> { - let zip212_enforcement = consensus::sapling_zip212_enforcement(params, height); + let zip212_enforcement = zip212_enforcement(params, height); tx.sapling_bundle() .iter() .flat_map(|bundle| { diff --git a/zcash_client_backend/src/scanning.rs b/zcash_client_backend/src/scanning.rs index 8f132055ea..632c9808de 100644 --- a/zcash_client_backend/src/scanning.rs +++ b/zcash_client_backend/src/scanning.rs @@ -11,11 +11,12 @@ use sapling::{ SaplingIvk, }; use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; + use zcash_keys::keys::UnifiedFullViewingKey; use zcash_note_encryption::{batch, BatchDomain, Domain, ShieldedOutput, COMPACT_NOTE_SIZE}; use zcash_primitives::{ consensus::{self, BlockHeight, NetworkUpgrade}, - transaction::TxId, + transaction::{components::sapling::zip212_enforcement, TxId}, }; use zip32::Scope; @@ -594,7 +595,7 @@ where { let block_hash = block.hash(); let block_height = block.height(); - let zip212_enforcement = consensus::sapling_zip212_enforcement(params, block_height); + let zip212_enforcement = zip212_enforcement(params, block_height); for tx in block.vtx.into_iter() { let txid = tx.txid(); @@ -687,7 +688,7 @@ where let cur_height = block.height(); let cur_hash = block.hash(); - let zip212_enforcement = consensus::sapling_zip212_enforcement(params, cur_height); + let zip212_enforcement = zip212_enforcement(params, cur_height); let mut sapling_commitment_tree_size = prior_block_metadata .and_then(|m| m.sapling_tree_size()) @@ -1147,9 +1148,9 @@ mod tests { use zcash_note_encryption::{Domain, COMPACT_NOTE_SIZE}; use zcash_primitives::{ block::BlockHash, - consensus::{sapling_zip212_enforcement, BlockHeight, Network}, + consensus::{BlockHeight, Network}, memo::MemoBytes, - transaction::components::amount::NonNegativeAmount, + transaction::components::{amount::NonNegativeAmount, sapling::zip212_enforcement}, zip32::AccountId, }; @@ -1210,7 +1211,7 @@ mod tests { tx_after: bool, initial_tree_sizes: Option<(u32, u32)>, ) -> CompactBlock { - let zip212_enforcement = sapling_zip212_enforcement(&Network::TestNetwork, height); + let zip212_enforcement = zip212_enforcement(&Network::TestNetwork, height); let to = dfvk.default_address().1; // Create a fake Note for the account diff --git a/zcash_client_sqlite/src/testing.rs b/zcash_client_sqlite/src/testing.rs index d62401bf9b..76a38b0201 100644 --- a/zcash_client_sqlite/src/testing.rs +++ b/zcash_client_sqlite/src/testing.rs @@ -54,7 +54,7 @@ use zcash_primitives::{ consensus::{self, BlockHeight, Network, NetworkUpgrade, Parameters}, memo::{Memo, MemoBytes}, transaction::{ - components::amount::NonNegativeAmount, + components::{amount::NonNegativeAmount, sapling::zip212_enforcement}, fees::{zip317::FeeError as Zip317FeeError, FeeRule, StandardFeeRule}, Transaction, TxId, }, @@ -784,10 +784,7 @@ pub(crate) fn fake_compact_block( // Create a fake Note for the account let mut rng = OsRng; - let rseed = generate_random_rseed( - consensus::sapling_zip212_enforcement(params, height), - &mut rng, - ); + let rseed = generate_random_rseed(zip212_enforcement(params, height), &mut rng); let note = Note::from_parts(to, NoteValue::from(value), rseed); let encryptor = sapling_note_encryption( Some(dfvk.fvk().ovk), @@ -883,7 +880,7 @@ pub(crate) fn fake_compact_block_spending( value: NonNegativeAmount, initial_sapling_tree_size: u32, ) -> CompactBlock { - let zip212_enforcement = consensus::sapling_zip212_enforcement(params, height); + let zip212_enforcement = zip212_enforcement(params, height); let mut rng = OsRng; let rseed = generate_random_rseed(zip212_enforcement, &mut rng); diff --git a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs index 366d946d27..c28cd99ec8 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs @@ -18,8 +18,11 @@ use sapling::{ }; use zcash_client_backend::{data_api::SAPLING_SHARD_HEIGHT, keys::UnifiedFullViewingKey}; use zcash_primitives::{ - consensus::{self, sapling_zip212_enforcement, BlockHeight, BranchId}, - transaction::{components::amount::NonNegativeAmount, Transaction}, + consensus::{self, BlockHeight, BranchId}, + transaction::{ + components::{amount::NonNegativeAmount, sapling::zip212_enforcement}, + Transaction, + }, zip32::Scope, }; @@ -165,7 +168,7 @@ impl RusqliteMigration for Migration

{ // be mined under ZIP 212 enforcement rules, so we default to `On` Zip212Enforcement::On }, - |h| sapling_zip212_enforcement(&self.params, h), + |h| zip212_enforcement(&self.params, h), ); let ufvk_str: String = row.get(5)?; diff --git a/zcash_client_sqlite/src/wallet/sapling.rs b/zcash_client_sqlite/src/wallet/sapling.rs index 26a90f13a9..cc36a00236 100644 --- a/zcash_client_sqlite/src/wallet/sapling.rs +++ b/zcash_client_sqlite/src/wallet/sapling.rs @@ -484,11 +484,11 @@ pub(crate) mod tests { }; use zcash_primitives::{ block::BlockHash, - consensus::{sapling_zip212_enforcement, BranchId}, + consensus::BranchId, legacy::TransparentAddress, memo::{Memo, MemoBytes}, transaction::{ - components::{amount::NonNegativeAmount, Amount}, + components::{amount::NonNegativeAmount, sapling::zip212_enforcement, Amount}, fees::{ fixed::FeeRule as FixedFeeRule, zip317::FeeError as Zip317FeeError, StandardFeeRule, }, @@ -1261,7 +1261,7 @@ pub(crate) mod tests { let result = try_sapling_output_recovery( &dfvk.to_ovk(Scope::External), output, - sapling_zip212_enforcement(&st.network(), h1), + zip212_enforcement(&st.network(), h1), ); if result.is_some() { diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index e9afe9dc0a..2271ff431a 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -7,6 +7,19 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Added +- `zcash_primitives::transaction::components::sapling::zip212_enforcement` + +### Changed +- `zcash_primitives::consensus` is now a reexport of the + `zcash_protocol::consensus` module. +- `zcash_primitives::constants` is now a reexport of the + `zcash_protocol::constants` module. + +### Removed +- `zcash_primitives::consensus::sapling_zip212_enforcement` instead use + `zcash_primitives::transaction::components::sapling::zip212_enforcement`. + ## [0.14.0] - 2024-03-01 ### Added - Dependency on `bellman 0.14`, `sapling-crypto 0.1`. diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 95c1bdfe1b..c2da8b7d0a 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -22,6 +22,7 @@ rustdoc-args = ["--cfg", "docsrs"] equihash.workspace = true zcash_address.workspace = true zcash_encoding.workspace = true +zcash_protocol.workspace = true zip32.workspace = true # Dependencies exposed in a public API: @@ -127,10 +128,10 @@ test-dependencies = [ #! consensus rules! ## Exposes the in-development NU6 features. -unstable-nu6 = [] +unstable-nu6 = ["zcash_protocol/unstable-nu6"] ## Exposes early in-development features that are not yet planned for any network upgrade. -zfuture = [] +zfuture = ["zcash_protocol/zfuture"] ## Exposes support for working with a local consensus (e.g. regtest local-consensus = [] diff --git a/zcash_primitives/benches/note_decryption.rs b/zcash_primitives/benches/note_decryption.rs index 518f9623c3..99048cb843 100644 --- a/zcash_primitives/benches/note_decryption.rs +++ b/zcash_primitives/benches/note_decryption.rs @@ -15,8 +15,8 @@ use sapling::{ }; use zcash_note_encryption::batch; use zcash_primitives::{ - consensus::{sapling_zip212_enforcement, NetworkUpgrade::Canopy, Parameters, TEST_NETWORK}, - transaction::components::Amount, + consensus::{NetworkUpgrade::Canopy, Parameters, TEST_NETWORK}, + transaction::components::{sapling::zip212_enforcement, Amount}, }; #[cfg(unix)] @@ -25,7 +25,7 @@ use pprof::criterion::{Output, PProfProfiler}; fn bench_note_decryption(c: &mut Criterion) { let mut rng = OsRng; let height = TEST_NETWORK.activation_height(Canopy).unwrap(); - let zip212_enforcement = sapling_zip212_enforcement(&TEST_NETWORK, height); + let zip212_enforcement = zip212_enforcement(&TEST_NETWORK, height); let valid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); let invalid_ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index e32e2165bc..bd77312a80 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -18,8 +18,8 @@ #![allow(clippy::single_component_path_imports)] pub mod block; -pub mod consensus; -pub mod constants; +pub use zcash_protocol::consensus; +pub use zcash_protocol::constants; pub mod legacy; pub mod memo; pub mod merkle_tree; diff --git a/zcash_primitives/src/transaction/builder.rs b/zcash_primitives/src/transaction/builder.rs index 95e051536c..2fc016bd8f 100644 --- a/zcash_primitives/src/transaction/builder.rs +++ b/zcash_primitives/src/transaction/builder.rs @@ -48,6 +48,7 @@ use crate::{ }; use super::components::amount::NonNegativeAmount; +use super::components::sapling::zip212_enforcement; /// Since Blossom activation, the default transaction expiry delta should be 40 blocks. /// @@ -352,7 +353,7 @@ impl<'a, P: consensus::Parameters> Builder<'a, P, ()> { .sapling_builder_config() .map(|(bundle_type, anchor)| { sapling::builder::Builder::new( - consensus::sapling_zip212_enforcement(¶ms, target_height), + zip212_enforcement(¶ms, target_height), bundle_type, anchor, ) diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index 64f4764012..62e493865d 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -1,5 +1,7 @@ use ff::PrimeField; use redjubjub::SpendAuth; +use sapling::note_encryption::Zip212Enforcement; +use zcash_protocol::consensus::{BlockHeight, NetworkUpgrade, Parameters, ZIP212_GRACE_PERIOD}; use std::io::{self, Read, Write}; @@ -21,6 +23,22 @@ use crate::{ use super::{Amount, GROTH_PROOF_SIZE}; +/// Returns the enforcement policy for ZIP 212 at the given height. +pub fn zip212_enforcement(params: &impl Parameters, height: BlockHeight) -> Zip212Enforcement { + if params.is_nu_active(NetworkUpgrade::Canopy, height) { + let grace_period_end_height = + params.activation_height(NetworkUpgrade::Canopy).unwrap() + ZIP212_GRACE_PERIOD; + + if height < grace_period_end_height { + Zip212Enforcement::GracePeriod + } else { + Zip212Enforcement::On + } + } else { + Zip212Enforcement::Off + } +} + /// A map from one bundle authorization to another. /// /// For use with [`TransactionData::map_authorization`]. From 5e4d9abbcec251e0656b34b9026f26fc69692e5a Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 27 Jan 2024 11:46:06 -0700 Subject: [PATCH 03/18] Move `Amount` and `NonNegativeAmount` types to the `zcash_protocol` crate. --- components/zcash_protocol/CHANGELOG.md | 22 ++++++++++ components/zcash_protocol/src/lib.rs | 1 + .../zcash_protocol/src/value.rs | 44 ++++--------------- zcash_client_backend/src/fees/sapling.rs | 4 +- zcash_client_backend/src/scanning.rs | 2 +- zcash_client_backend/src/wallet.rs | 4 +- zcash_client_sqlite/src/testing.rs | 10 +++-- .../init/migrations/receiving_key_scopes.rs | 2 +- zcash_client_sqlite/src/wallet/sapling.rs | 9 ++-- zcash_primitives/CHANGELOG.md | 8 ++++ zcash_primitives/Cargo.toml | 1 + .../src/transaction/components.rs | 2 +- 12 files changed, 59 insertions(+), 50 deletions(-) rename zcash_primitives/src/transaction/components/amount.rs => components/zcash_protocol/src/value.rs (93%) diff --git a/components/zcash_protocol/CHANGELOG.md b/components/zcash_protocol/CHANGELOG.md index d536350e63..332b39bfb4 100644 --- a/components/zcash_protocol/CHANGELOG.md +++ b/components/zcash_protocol/CHANGELOG.md @@ -6,3 +6,25 @@ and this library adheres to Rust's notion of [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] +The entries below are relative to the `zcash_primitives` crate as of the tag +`zcash_primitives-0.14.0`. + +### Added +- The following modules have been extracted from `zcash_primitives` and + moved to this crate: + - `consensus` + - `constants` + - `zcash_protocol::value` replaces `zcash_primitives::transcation::components::amount` +- `zcash_protocol::value::Amount::into_u64` +- `impl TryFrom for zcash_protocol::value::NonNegativeAmount` +- Added in `zcash_protocol::value`: + - `NonNegativeAmount::into_u64` + - `TryFrom for NonNegativeAmount` + +### Removed +- The following conversions have been removed from `zcash_protocol::value`, as + `zcash_protocol` does not depend on the `orchard` or `sapling-crypto` crates. + - `From for orchard::NoteValue>` + - `TryFrom for Amount` + - `From for sapling::value::NoteValue>` + - `TryFrom for NonNegativeAmount` diff --git a/components/zcash_protocol/src/lib.rs b/components/zcash_protocol/src/lib.rs index 7a4c918ecb..a15d5e5881 100644 --- a/components/zcash_protocol/src/lib.rs +++ b/components/zcash_protocol/src/lib.rs @@ -17,3 +17,4 @@ pub mod consensus; pub mod constants; +pub mod value; diff --git a/zcash_primitives/src/transaction/components/amount.rs b/components/zcash_protocol/src/value.rs similarity index 93% rename from zcash_primitives/src/transaction/components/amount.rs rename to components/zcash_protocol/src/value.rs index 766368202c..b902c81460 100644 --- a/zcash_primitives/src/transaction/components/amount.rs +++ b/components/zcash_protocol/src/value.rs @@ -4,9 +4,6 @@ use std::iter::Sum; use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}; use memuse::DynamicUsage; -use orchard::value as orchard; - -use crate::sapling; pub const COIN: i64 = 1_0000_0000; pub const MAX_MONEY: i64 = 21_000_000 * COIN; @@ -235,14 +232,6 @@ impl Mul for Amount { } } -impl TryFrom for Amount { - type Error = (); - - fn try_from(v: orchard::ValueSum) -> Result { - i64::try_from(v).map_err(|_| ()).and_then(Amount::try_from) - } -} - /// A type-safe representation of some nonnegative amount of Zcash. /// /// A NonNegativeAmount can only be constructed from an integer that is within the valid monetary @@ -254,6 +243,11 @@ impl NonNegativeAmount { /// Returns the identity `NonNegativeAmount` pub const ZERO: Self = NonNegativeAmount(Amount(0)); + /// Returns this NonNegativeAmount as a u64. + pub fn into_u64(self) -> u64 { + self.0.try_into().unwrap() + } + /// Creates a NonNegativeAmount from a u64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. @@ -323,35 +317,15 @@ impl From<&NonNegativeAmount> for Amount { impl From for u64 { fn from(n: NonNegativeAmount) -> Self { - n.0.try_into().unwrap() - } -} - -impl From for sapling::value::NoteValue { - fn from(n: NonNegativeAmount) -> Self { - sapling::value::NoteValue::from_raw(n.into()) - } -} - -impl TryFrom for NonNegativeAmount { - type Error = (); - - fn try_from(value: sapling::value::NoteValue) -> Result { - Self::from_u64(value.inner()) - } -} - -impl From for orchard::NoteValue { - fn from(n: NonNegativeAmount) -> Self { - orchard::NoteValue::from_raw(n.into()) + n.into_u64() } } -impl TryFrom for NonNegativeAmount { +impl TryFrom for NonNegativeAmount { type Error = (); - fn try_from(value: orchard::NoteValue) -> Result { - Self::from_u64(value.inner()) + fn try_from(value: u64) -> Result { + NonNegativeAmount::from_u64(value) } } diff --git a/zcash_client_backend/src/fees/sapling.rs b/zcash_client_backend/src/fees/sapling.rs index 2653310a92..fa7ef6157e 100644 --- a/zcash_client_backend/src/fees/sapling.rs +++ b/zcash_client_backend/src/fees/sapling.rs @@ -67,7 +67,7 @@ impl InputView<()> for SpendInfo { } fn value(&self) -> NonNegativeAmount { - NonNegativeAmount::try_from(self.value()) + NonNegativeAmount::try_from(self.value().inner()) .expect("An existing note to be spent must have a valid amount value.") } } @@ -81,7 +81,7 @@ pub trait OutputView { impl OutputView for OutputInfo { fn value(&self) -> NonNegativeAmount { - NonNegativeAmount::try_from(self.value()) + NonNegativeAmount::try_from(self.value().inner()) .expect("Output values should be checked at construction.") } } diff --git a/zcash_client_backend/src/scanning.rs b/zcash_client_backend/src/scanning.rs index 632c9808de..4a99f87547 100644 --- a/zcash_client_backend/src/scanning.rs +++ b/zcash_client_backend/src/scanning.rs @@ -1217,7 +1217,7 @@ mod tests { // Create a fake Note for the account let mut rng = OsRng; let rseed = generate_random_rseed(zip212_enforcement, &mut rng); - let note = sapling::Note::from_parts(to, NoteValue::from(value), rseed); + let note = sapling::Note::from_parts(to, NoteValue::from_raw(value.into()), rseed); let encryptor = sapling_note_encryption( Some(dfvk.fvk().ovk), note.clone(), diff --git a/zcash_client_backend/src/wallet.rs b/zcash_client_backend/src/wallet.rs index b91ebfad0f..c65103a2e2 100644 --- a/zcash_client_backend/src/wallet.rs +++ b/zcash_client_backend/src/wallet.rs @@ -361,7 +361,7 @@ pub enum Note { impl Note { pub fn value(&self) -> NonNegativeAmount { match self { - Note::Sapling(n) => n.value().try_into().expect( + Note::Sapling(n) => n.value().inner().try_into().expect( "Sapling notes must have values in the range of valid non-negative ZEC values.", ), #[cfg(feature = "orchard")] @@ -461,6 +461,7 @@ impl sapling_fees::InputView for ReceivedNote NonNegativeAmount { self.note .value() + .inner() .try_into() .expect("Sapling note values are indirectly checked by consensus.") } @@ -475,6 +476,7 @@ impl orchard_fees::InputView for ReceivedNote NonNegativeAmount { self.note .value() + .inner() .try_into() .expect("Orchard note values are indirectly checked by consensus.") } diff --git a/zcash_client_sqlite/src/testing.rs b/zcash_client_sqlite/src/testing.rs index 76a38b0201..2a13e468d9 100644 --- a/zcash_client_sqlite/src/testing.rs +++ b/zcash_client_sqlite/src/testing.rs @@ -785,7 +785,7 @@ pub(crate) fn fake_compact_block( // Create a fake Note for the account let mut rng = OsRng; let rseed = generate_random_rseed(zip212_enforcement(params, height), &mut rng); - let note = Note::from_parts(to, NoteValue::from(value), rseed); + let note = Note::from_parts(to, NoteValue::from_raw(value.into_u64()), rseed); let encryptor = sapling_note_encryption( Some(dfvk.fvk().ovk), note.clone(), @@ -894,7 +894,11 @@ pub(crate) fn fake_compact_block_spending( // Create a fake Note for the payment ctx.outputs.push({ - let note = Note::from_parts(to, NoteValue::from(value), rseed); + let note = Note::from_parts( + to, + sapling::value::NoteValue::from_raw(value.into_u64()), + rseed, + ); let encryptor = sapling_note_encryption( Some(dfvk.fvk().ovk), note.clone(), @@ -918,7 +922,7 @@ pub(crate) fn fake_compact_block_spending( let rseed = generate_random_rseed(zip212_enforcement, &mut rng); let note = Note::from_parts( change_addr, - NoteValue::from((in_value - value).unwrap()), + NoteValue::from_raw((in_value - value).unwrap().into_u64()), rseed, ); let encryptor = sapling_note_encryption( diff --git a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs index c28cd99ec8..23cc0bc6ad 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs @@ -244,7 +244,7 @@ impl RusqliteMigration for Migration

{ &mut commitment_tree, dfvk, &diversifier, - ¬e_value.try_into().unwrap(), + &sapling::value::NoteValue::from_raw(note_value.into_u64()), &rseed, note_commitment_tree_position, )?; diff --git a/zcash_client_sqlite/src/wallet/sapling.rs b/zcash_client_sqlite/src/wallet/sapling.rs index cc36a00236..74270b160e 100644 --- a/zcash_client_sqlite/src/wallet/sapling.rs +++ b/zcash_client_sqlite/src/wallet/sapling.rs @@ -9,10 +9,7 @@ use sapling::{self, Diversifier, Nullifier, Rseed}; use zcash_primitives::{ consensus::{self, BlockHeight}, memo::MemoBytes, - transaction::{ - components::{amount::NonNegativeAmount, Amount}, - TxId, - }, + transaction::{components::Amount, TxId}, zip32::{AccountId, Scope}, }; @@ -115,7 +112,7 @@ fn to_spendable_note( Diversifier(tmp) }; - let note_value = NonNegativeAmount::from_nonnegative_i64(row.get(4)?).map_err(|_e| { + let note_value: u64 = row.get::<_, i64>(4)?.try_into().map_err(|_e| { SqliteClientError::CorruptedData("Note values must be nonnegative".to_string()) })?; @@ -164,7 +161,7 @@ fn to_spendable_note( output_index, Note::Sapling(sapling::Note::from_parts( recipient, - note_value.into(), + sapling::value::NoteValue::from_raw(note_value), rseed, )), spending_key_scope, diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index 2271ff431a..4f1a8373e2 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -15,10 +15,18 @@ and this library adheres to Rust's notion of `zcash_protocol::consensus` module. - `zcash_primitives::constants` is now a reexport of the `zcash_protocol::constants` module. +- `zcash_primitives::transaction::components::amount` is now a reexport of the + `zcash_protocol::value` module. ### Removed - `zcash_primitives::consensus::sapling_zip212_enforcement` instead use `zcash_primitives::transaction::components::sapling::zip212_enforcement`. +- From `zcash_primitive::components::transaction`: + - `impl From for u64` + - `impl TryFrom for NonNegativeAmount` + - `impl From for sapling::value::NoteValue` + - `impl TryFrom for Amount` + - `impl From for orchard::NoteValue` ## [0.14.0] - 2024-03-01 ### Added diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index c2da8b7d0a..15733b15e5 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -118,6 +118,7 @@ temporary-zcashd = [] ## Exposes APIs that are useful for testing, such as `proptest` strategies. test-dependencies = [ "dep:proptest", + "zcash_protocol/test-dependencies", "orchard/test-dependencies", "sapling/test-dependencies", ] diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index 24dfd724b5..f5a530cfe3 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -1,6 +1,6 @@ //! Structs representing the components within Zcash transactions. -pub mod amount; +pub use zcash_protocol::value as amount; pub mod orchard; pub mod sapling; pub mod sprout; From 71884822250e34f031e36bf82c6f31d02f1b536b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 27 Jan 2024 11:53:44 -0700 Subject: [PATCH 04/18] zcash_protocol: Rename value amounts to `Zatoshis` and `ZatBalance` --- components/zcash_protocol/CHANGELOG.md | 23 +- components/zcash_protocol/src/value.rs | 319 +++++++++--------- .../src/transaction/components.rs | 13 +- 3 files changed, 185 insertions(+), 170 deletions(-) diff --git a/components/zcash_protocol/CHANGELOG.md b/components/zcash_protocol/CHANGELOG.md index 332b39bfb4..d70071f34a 100644 --- a/components/zcash_protocol/CHANGELOG.md +++ b/components/zcash_protocol/CHANGELOG.md @@ -14,17 +14,18 @@ The entries below are relative to the `zcash_primitives` crate as of the tag moved to this crate: - `consensus` - `constants` - - `zcash_protocol::value` replaces `zcash_primitives::transcation::components::amount` -- `zcash_protocol::value::Amount::into_u64` -- `impl TryFrom for zcash_protocol::value::NonNegativeAmount` + - `zcash_protocol::value` replaces `zcash_primitives::transaction::components::amount` - Added in `zcash_protocol::value`: - - `NonNegativeAmount::into_u64` - - `TryFrom for NonNegativeAmount` + - `Zatoshis` + - `ZatBalance` ### Removed -- The following conversions have been removed from `zcash_protocol::value`, as - `zcash_protocol` does not depend on the `orchard` or `sapling-crypto` crates. - - `From for orchard::NoteValue>` - - `TryFrom for Amount` - - `From for sapling::value::NoteValue>` - - `TryFrom for NonNegativeAmount` +- From `zcash_protocol::value`: + - `NonNegativeAmount` (use `Zatoshis` instead.) + - `Amount` (use `ZatBalance` instead.) + - The following conversions have been removed relative to `zcash_primitives-0.14.0`, + as `zcash_protocol` does not depend on the `orchard` or `sapling-crypto` crates. + - `From for orchard::NoteValue>` + - `TryFrom for Amount` + - `From for sapling::value::NoteValue>` + - `TryFrom for NonNegativeAmount` diff --git a/components/zcash_protocol/src/value.rs b/components/zcash_protocol/src/value.rs index b902c81460..7767220c00 100644 --- a/components/zcash_protocol/src/value.rs +++ b/components/zcash_protocol/src/value.rs @@ -10,118 +10,118 @@ pub const MAX_MONEY: i64 = 21_000_000 * COIN; /// A type-safe representation of a Zcash value delta, in zatoshis. /// -/// An Amount can only be constructed from an integer that is within the valid monetary +/// An ZatBalance can only be constructed from an integer that is within the valid monetary /// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis). /// However, this range is not preserved as an invariant internally; it is possible to -/// add two valid Amounts together to obtain an invalid Amount. It is the user's -/// responsibility to handle the result of serializing potentially-invalid Amounts. In -/// particular, a [`Transaction`] containing serialized invalid Amounts will be rejected +/// add two valid ZatBalances together to obtain an invalid ZatBalance. It is the user's +/// responsibility to handle the result of serializing potentially-invalid ZatBalances. In +/// particular, a [`Transaction`] containing serialized invalid ZatBalances will be rejected /// by the network consensus rules. /// /// [`Transaction`]: crate::transaction::Transaction #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub struct Amount(i64); +pub struct ZatBalance(i64); -memuse::impl_no_dynamic_usage!(Amount); +memuse::impl_no_dynamic_usage!(ZatBalance); -impl Amount { - /// Returns a zero-valued Amount. +impl ZatBalance { + /// Returns a zero-valued ZatBalance. pub const fn zero() -> Self { - Amount(0) + ZatBalance(0) } - /// Creates a constant Amount from an i64. + /// Creates a constant ZatBalance from an i64. /// /// Panics: if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. pub const fn const_from_i64(amount: i64) -> Self { assert!(-MAX_MONEY <= amount && amount <= MAX_MONEY); // contains is not const - Amount(amount) + ZatBalance(amount) } - /// Creates a constant Amount from a u64. + /// Creates a constant ZatBalance from a u64. /// /// Panics: if the amount is outside the range `{0..MAX_MONEY}`. const fn const_from_u64(amount: u64) -> Self { assert!(amount <= (MAX_MONEY as u64)); // contains is not const - Amount(amount as i64) + ZatBalance(amount as i64) } - /// Creates an Amount from an i64. + /// Creates an ZatBalance from an i64. /// /// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. pub fn from_i64(amount: i64) -> Result { if (-MAX_MONEY..=MAX_MONEY).contains(&amount) { - Ok(Amount(amount)) + Ok(ZatBalance(amount)) } else { Err(()) } } - /// Creates a non-negative Amount from an i64. + /// Creates a non-negative ZatBalance from an i64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_nonnegative_i64(amount: i64) -> Result { if (0..=MAX_MONEY).contains(&amount) { - Ok(Amount(amount)) + Ok(ZatBalance(amount)) } else { Err(()) } } - /// Creates an Amount from a u64. + /// Creates an ZatBalance from a u64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_u64(amount: u64) -> Result { if amount <= MAX_MONEY as u64 { - Ok(Amount(amount as i64)) + Ok(ZatBalance(amount as i64)) } else { Err(()) } } - /// Reads an Amount from a signed 64-bit little-endian integer. + /// Reads an ZatBalance from a signed 64-bit little-endian integer. /// /// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result { let amount = i64::from_le_bytes(bytes); - Amount::from_i64(amount) + ZatBalance::from_i64(amount) } - /// Reads a non-negative Amount from a signed 64-bit little-endian integer. + /// Reads a non-negative ZatBalance from a signed 64-bit little-endian integer. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result { let amount = i64::from_le_bytes(bytes); - Amount::from_nonnegative_i64(amount) + ZatBalance::from_nonnegative_i64(amount) } - /// Reads an Amount from an unsigned 64-bit little-endian integer. + /// Reads an ZatBalance from an unsigned 64-bit little-endian integer. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result { let amount = u64::from_le_bytes(bytes); - Amount::from_u64(amount) + ZatBalance::from_u64(amount) } - /// Returns the Amount encoded as a signed 64-bit little-endian integer. + /// Returns the ZatBalance encoded as a signed 64-bit little-endian integer. pub fn to_i64_le_bytes(self) -> [u8; 8] { self.0.to_le_bytes() } - /// Returns `true` if `self` is positive and `false` if the Amount is zero or + /// Returns `true` if `self` is positive and `false` if the ZatBalance is zero or /// negative. pub const fn is_positive(self) -> bool { self.0.is_positive() } - /// Returns `true` if `self` is negative and `false` if the Amount is zero or + /// Returns `true` if `self` is negative and `false` if the ZatBalance is zero or /// positive. pub const fn is_negative(self) -> bool { self.0.is_negative() } - pub fn sum>(values: I) -> Option { - let mut result = Amount::zero(); + pub fn sum>(values: I) -> Option { + let mut result = ZatBalance::zero(); for value in values { result = (result + value)?; } @@ -129,147 +129,147 @@ impl Amount { } } -impl TryFrom for Amount { +impl TryFrom for ZatBalance { type Error = (); fn try_from(value: i64) -> Result { - Amount::from_i64(value) + ZatBalance::from_i64(value) } } -impl From for i64 { - fn from(amount: Amount) -> i64 { +impl From for i64 { + fn from(amount: ZatBalance) -> i64 { amount.0 } } -impl From<&Amount> for i64 { - fn from(amount: &Amount) -> i64 { +impl From<&ZatBalance> for i64 { + fn from(amount: &ZatBalance) -> i64 { amount.0 } } -impl TryFrom for u64 { +impl TryFrom for u64 { type Error = (); - fn try_from(value: Amount) -> Result { + fn try_from(value: ZatBalance) -> Result { value.0.try_into().map_err(|_| ()) } } -impl Add for Amount { - type Output = Option; +impl Add for ZatBalance { + type Output = Option; - fn add(self, rhs: Amount) -> Option { - Amount::from_i64(self.0 + rhs.0).ok() + fn add(self, rhs: ZatBalance) -> Option { + ZatBalance::from_i64(self.0 + rhs.0).ok() } } -impl Add for Option { +impl Add for Option { type Output = Self; - fn add(self, rhs: Amount) -> Option { + fn add(self, rhs: ZatBalance) -> Option { self.and_then(|lhs| lhs + rhs) } } -impl AddAssign for Amount { - fn add_assign(&mut self, rhs: Amount) { +impl AddAssign for ZatBalance { + fn add_assign(&mut self, rhs: ZatBalance) { *self = (*self + rhs).expect("Addition must produce a valid amount value.") } } -impl Sub for Amount { - type Output = Option; +impl Sub for ZatBalance { + type Output = Option; - fn sub(self, rhs: Amount) -> Option { - Amount::from_i64(self.0 - rhs.0).ok() + fn sub(self, rhs: ZatBalance) -> Option { + ZatBalance::from_i64(self.0 - rhs.0).ok() } } -impl Sub for Option { +impl Sub for Option { type Output = Self; - fn sub(self, rhs: Amount) -> Option { + fn sub(self, rhs: ZatBalance) -> Option { self.and_then(|lhs| lhs - rhs) } } -impl SubAssign for Amount { - fn sub_assign(&mut self, rhs: Amount) { +impl SubAssign for ZatBalance { + fn sub_assign(&mut self, rhs: ZatBalance) { *self = (*self - rhs).expect("Subtraction must produce a valid amount value.") } } -impl Sum for Option { - fn sum>(iter: I) -> Self { - iter.fold(Some(Amount::zero()), |acc, a| acc? + a) +impl Sum for Option { + fn sum>(iter: I) -> Self { + iter.fold(Some(ZatBalance::zero()), |acc, a| acc? + a) } } -impl<'a> Sum<&'a Amount> for Option { - fn sum>(iter: I) -> Self { - iter.fold(Some(Amount::zero()), |acc, a| acc? + *a) +impl<'a> Sum<&'a ZatBalance> for Option { + fn sum>(iter: I) -> Self { + iter.fold(Some(ZatBalance::zero()), |acc, a| acc? + *a) } } -impl Neg for Amount { +impl Neg for ZatBalance { type Output = Self; fn neg(self) -> Self { - Amount(-self.0) + ZatBalance(-self.0) } } -impl Mul for Amount { - type Output = Option; +impl Mul for ZatBalance { + type Output = Option; - fn mul(self, rhs: usize) -> Option { + fn mul(self, rhs: usize) -> Option { let rhs: i64 = rhs.try_into().ok()?; self.0 .checked_mul(rhs) - .and_then(|i| Amount::try_from(i).ok()) + .and_then(|i| ZatBalance::try_from(i).ok()) } } /// A type-safe representation of some nonnegative amount of Zcash. /// -/// A NonNegativeAmount can only be constructed from an integer that is within the valid monetary +/// A Zatoshis can only be constructed from an integer that is within the valid monetary /// range of `{0..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis). #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub struct NonNegativeAmount(Amount); +pub struct Zatoshis(ZatBalance); -impl NonNegativeAmount { - /// Returns the identity `NonNegativeAmount` - pub const ZERO: Self = NonNegativeAmount(Amount(0)); +impl Zatoshis { + /// Returns the identity `Zatoshis` + pub const ZERO: Self = Zatoshis(ZatBalance(0)); - /// Returns this NonNegativeAmount as a u64. + /// Returns this Zatoshis as a u64. pub fn into_u64(self) -> u64 { self.0.try_into().unwrap() } - /// Creates a NonNegativeAmount from a u64. + /// Creates a Zatoshis from a u64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_u64(amount: u64) -> Result { - Amount::from_u64(amount).map(NonNegativeAmount) + ZatBalance::from_u64(amount).map(Zatoshis) } - /// Creates a constant NonNegativeAmount from a u64. + /// Creates a constant Zatoshis from a u64. /// /// Panics: if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. pub const fn const_from_u64(amount: u64) -> Self { - NonNegativeAmount(Amount::const_from_u64(amount)) + Zatoshis(ZatBalance::const_from_u64(amount)) } - /// Creates a NonNegativeAmount from an i64. + /// Creates a Zatoshis from an i64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_nonnegative_i64(amount: i64) -> Result { - Amount::from_nonnegative_i64(amount).map(NonNegativeAmount) + ZatBalance::from_nonnegative_i64(amount).map(Zatoshis) } - /// Reads an NonNegativeAmount from an unsigned 64-bit little-endian integer. + /// Reads an Zatoshis from an unsigned 64-bit little-endian integer. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result { @@ -277,7 +277,7 @@ impl NonNegativeAmount { Self::from_u64(amount) } - /// Reads a NonNegativeAmount from a signed integer represented as a two's + /// Reads a Zatoshis from a signed integer represented as a two's /// complement 64-bit little-endian value. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. @@ -286,110 +286,110 @@ impl NonNegativeAmount { Self::from_nonnegative_i64(amount) } - /// Returns this NonNegativeAmount encoded as a signed two's complement 64-bit + /// Returns this Zatoshis encoded as a signed two's complement 64-bit /// little-endian value. pub fn to_i64_le_bytes(self) -> [u8; 8] { self.0.to_i64_le_bytes() } - /// Returns whether or not this `NonNegativeAmount` is the zero value. + /// Returns whether or not this `Zatoshis` is the zero value. pub fn is_zero(&self) -> bool { - self == &NonNegativeAmount::ZERO + self == &Zatoshis::ZERO } - /// Returns whether or not this `NonNegativeAmount` is positive. + /// Returns whether or not this `Zatoshis` is positive. pub fn is_positive(&self) -> bool { - self > &NonNegativeAmount::ZERO + self > &Zatoshis::ZERO } } -impl From for Amount { - fn from(n: NonNegativeAmount) -> Self { +impl From for ZatBalance { + fn from(n: Zatoshis) -> Self { n.0 } } -impl From<&NonNegativeAmount> for Amount { - fn from(n: &NonNegativeAmount) -> Self { +impl From<&Zatoshis> for ZatBalance { + fn from(n: &Zatoshis) -> Self { n.0 } } -impl From for u64 { - fn from(n: NonNegativeAmount) -> Self { +impl From for u64 { + fn from(n: Zatoshis) -> Self { n.into_u64() } } -impl TryFrom for NonNegativeAmount { +impl TryFrom for Zatoshis { type Error = (); fn try_from(value: u64) -> Result { - NonNegativeAmount::from_u64(value) + Zatoshis::from_u64(value) } } -impl TryFrom for NonNegativeAmount { +impl TryFrom for Zatoshis { type Error = (); - fn try_from(value: Amount) -> Result { + fn try_from(value: ZatBalance) -> Result { if value.is_negative() { Err(()) } else { - Ok(NonNegativeAmount(value)) + Ok(Zatoshis(value)) } } } -impl Add for NonNegativeAmount { - type Output = Option; +impl Add for Zatoshis { + type Output = Option; - fn add(self, rhs: NonNegativeAmount) -> Option { - (self.0 + rhs.0).map(NonNegativeAmount) + fn add(self, rhs: Zatoshis) -> Option { + (self.0 + rhs.0).map(Zatoshis) } } -impl Add for Option { +impl Add for Option { type Output = Self; - fn add(self, rhs: NonNegativeAmount) -> Option { + fn add(self, rhs: Zatoshis) -> Option { self.and_then(|lhs| lhs + rhs) } } -impl Sub for NonNegativeAmount { - type Output = Option; +impl Sub for Zatoshis { + type Output = Option; - fn sub(self, rhs: NonNegativeAmount) -> Option { - (self.0 - rhs.0).and_then(|amt| NonNegativeAmount::try_from(amt).ok()) + fn sub(self, rhs: Zatoshis) -> Option { + (self.0 - rhs.0).and_then(|amt| Zatoshis::try_from(amt).ok()) } } -impl Sub for Option { +impl Sub for Option { type Output = Self; - fn sub(self, rhs: NonNegativeAmount) -> Option { + fn sub(self, rhs: Zatoshis) -> Option { self.and_then(|lhs| lhs - rhs) } } -impl Mul for NonNegativeAmount { +impl Mul for Zatoshis { type Output = Option; - fn mul(self, rhs: usize) -> Option { - (self.0 * rhs).and_then(|v| NonNegativeAmount::try_from(v).ok()) + fn mul(self, rhs: usize) -> Option { + (self.0 * rhs).and_then(|v| Zatoshis::try_from(v).ok()) } } -impl Sum for Option { - fn sum>(iter: I) -> Self { - iter.fold(Some(NonNegativeAmount::ZERO), |acc, a| acc? + a) +impl Sum for Option { + fn sum>(iter: I) -> Self { + iter.fold(Some(Zatoshis::ZERO), |acc, a| acc? + a) } } -impl<'a> Sum<&'a NonNegativeAmount> for Option { - fn sum>(iter: I) -> Self { - iter.fold(Some(NonNegativeAmount::ZERO), |acc, a| acc? + *a) +impl<'a> Sum<&'a Zatoshis> for Option { + fn sum>(iter: I) -> Self { + iter.fold(Some(Zatoshis::ZERO), |acc, a| acc? + *a) } } @@ -409,12 +409,12 @@ impl std::fmt::Display for BalanceError { BalanceError::Overflow => { write!( f, - "Amount addition resulted in a value outside the valid range." + "ZatBalance addition resulted in a value outside the valid range." ) } BalanceError::Underflow => write!( f, - "Amount subtraction resulted in a value outside the valid range." + "ZatBalance subtraction resulted in a value outside the valid range." ), } } @@ -430,102 +430,105 @@ impl From for BalanceError { pub mod testing { use proptest::prelude::prop_compose; - use super::{Amount, NonNegativeAmount, MAX_MONEY}; + use super::{ZatBalance, Zatoshis, MAX_MONEY}; prop_compose! { - pub fn arb_amount()(amt in -MAX_MONEY..MAX_MONEY) -> Amount { - Amount::from_i64(amt).unwrap() + pub fn arb_zat_balance()(amt in -MAX_MONEY..MAX_MONEY) -> ZatBalance { + ZatBalance::from_i64(amt).unwrap() } } prop_compose! { - pub fn arb_nonnegative_amount()(amt in 0i64..MAX_MONEY) -> NonNegativeAmount { - NonNegativeAmount::from_u64(amt as u64).unwrap() + pub fn arb_positive_zat_balance()(amt in 1i64..MAX_MONEY) -> ZatBalance { + ZatBalance::from_i64(amt).unwrap() } } prop_compose! { - pub fn arb_positive_amount()(amt in 1i64..MAX_MONEY) -> Amount { - Amount::from_i64(amt).unwrap() + pub fn arb_zatoshis()(amt in 0i64..MAX_MONEY) -> Zatoshis { + Zatoshis::from_u64(amt as u64).unwrap() } } } #[cfg(test)] mod tests { - use super::{Amount, MAX_MONEY}; + use super::{ZatBalance, MAX_MONEY}; #[test] fn amount_in_range() { let zero = b"\x00\x00\x00\x00\x00\x00\x00\x00"; - assert_eq!(Amount::from_u64_le_bytes(*zero).unwrap(), Amount(0)); + assert_eq!(ZatBalance::from_u64_le_bytes(*zero).unwrap(), ZatBalance(0)); assert_eq!( - Amount::from_nonnegative_i64_le_bytes(*zero).unwrap(), - Amount(0) + ZatBalance::from_nonnegative_i64_le_bytes(*zero).unwrap(), + ZatBalance(0) ); - assert_eq!(Amount::from_i64_le_bytes(*zero).unwrap(), Amount(0)); + assert_eq!(ZatBalance::from_i64_le_bytes(*zero).unwrap(), ZatBalance(0)); let neg_one = b"\xff\xff\xff\xff\xff\xff\xff\xff"; - assert!(Amount::from_u64_le_bytes(*neg_one).is_err()); - assert!(Amount::from_nonnegative_i64_le_bytes(*neg_one).is_err()); - assert_eq!(Amount::from_i64_le_bytes(*neg_one).unwrap(), Amount(-1)); + assert!(ZatBalance::from_u64_le_bytes(*neg_one).is_err()); + assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_one).is_err()); + assert_eq!( + ZatBalance::from_i64_le_bytes(*neg_one).unwrap(), + ZatBalance(-1) + ); let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00"; assert_eq!( - Amount::from_u64_le_bytes(*max_money).unwrap(), - Amount(MAX_MONEY) + ZatBalance::from_u64_le_bytes(*max_money).unwrap(), + ZatBalance(MAX_MONEY) ); assert_eq!( - Amount::from_nonnegative_i64_le_bytes(*max_money).unwrap(), - Amount(MAX_MONEY) + ZatBalance::from_nonnegative_i64_le_bytes(*max_money).unwrap(), + ZatBalance(MAX_MONEY) ); assert_eq!( - Amount::from_i64_le_bytes(*max_money).unwrap(), - Amount(MAX_MONEY) + ZatBalance::from_i64_le_bytes(*max_money).unwrap(), + ZatBalance(MAX_MONEY) ); let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00"; - assert!(Amount::from_u64_le_bytes(*max_money_p1).is_err()); - assert!(Amount::from_nonnegative_i64_le_bytes(*max_money_p1).is_err()); - assert!(Amount::from_i64_le_bytes(*max_money_p1).is_err()); + assert!(ZatBalance::from_u64_le_bytes(*max_money_p1).is_err()); + assert!(ZatBalance::from_nonnegative_i64_le_bytes(*max_money_p1).is_err()); + assert!(ZatBalance::from_i64_le_bytes(*max_money_p1).is_err()); let neg_max_money = b"\x00\xc0\xf8\xa5\x0f\x8a\xf8\xff"; - assert!(Amount::from_u64_le_bytes(*neg_max_money).is_err()); - assert!(Amount::from_nonnegative_i64_le_bytes(*neg_max_money).is_err()); + assert!(ZatBalance::from_u64_le_bytes(*neg_max_money).is_err()); + assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money).is_err()); assert_eq!( - Amount::from_i64_le_bytes(*neg_max_money).unwrap(), - Amount(-MAX_MONEY) + ZatBalance::from_i64_le_bytes(*neg_max_money).unwrap(), + ZatBalance(-MAX_MONEY) ); let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff"; - assert!(Amount::from_u64_le_bytes(*neg_max_money_m1).is_err()); - assert!(Amount::from_nonnegative_i64_le_bytes(*neg_max_money_m1).is_err()); - assert!(Amount::from_i64_le_bytes(*neg_max_money_m1).is_err()); + assert!(ZatBalance::from_u64_le_bytes(*neg_max_money_m1).is_err()); + assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money_m1).is_err()); + assert!(ZatBalance::from_i64_le_bytes(*neg_max_money_m1).is_err()); } #[test] fn add_overflow() { - let v = Amount(MAX_MONEY); - assert_eq!(v + Amount(1), None) + let v = ZatBalance(MAX_MONEY); + assert_eq!(v + ZatBalance(1), None) } #[test] #[should_panic] fn add_assign_panics_on_overflow() { - let mut a = Amount(MAX_MONEY); - a += Amount(1); + let mut a = ZatBalance(MAX_MONEY); + a += ZatBalance(1); } #[test] fn sub_underflow() { - let v = Amount(-MAX_MONEY); - assert_eq!(v - Amount(1), None) + let v = ZatBalance(-MAX_MONEY); + assert_eq!(v - ZatBalance(1), None) } #[test] #[should_panic] fn sub_assign_panics_on_underflow() { - let mut a = Amount(-MAX_MONEY); - a -= Amount(1); + let mut a = ZatBalance(-MAX_MONEY); + a -= ZatBalance(1); } } diff --git a/zcash_primitives/src/transaction/components.rs b/zcash_primitives/src/transaction/components.rs index f5a530cfe3..bba5ddf951 100644 --- a/zcash_primitives/src/transaction/components.rs +++ b/zcash_primitives/src/transaction/components.rs @@ -1,6 +1,17 @@ //! Structs representing the components within Zcash transactions. +pub mod amount { + pub use zcash_protocol::value::{ + BalanceError, ZatBalance as Amount, Zatoshis as NonNegativeAmount, COIN, + }; -pub use zcash_protocol::value as amount; + #[cfg(feature = "test-dependencies")] + pub mod testing { + pub use zcash_protocol::value::testing::{ + arb_positive_zat_balance as arb_positive_amount, arb_zat_balance as arb_amount, + arb_zatoshis as arb_nonnegative_amount, + }; + } +} pub mod orchard; pub mod sapling; pub mod sprout; From eb3c7b479e0a032e16adf11176d046d37263604f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Sat, 27 Jan 2024 12:31:07 -0700 Subject: [PATCH 05/18] zcash_protocol: Modify `Zatoshis` to directly wrap a u64 --- components/zcash_protocol/CHANGELOG.md | 6 ++ components/zcash_protocol/src/value.rs | 98 ++++++++++++++------------ zcash_client_backend/CHANGELOG.md | 4 ++ zcash_client_backend/src/zip321.rs | 56 ++++++--------- 4 files changed, 82 insertions(+), 82 deletions(-) diff --git a/components/zcash_protocol/CHANGELOG.md b/components/zcash_protocol/CHANGELOG.md index d70071f34a..c29a7fd01a 100644 --- a/components/zcash_protocol/CHANGELOG.md +++ b/components/zcash_protocol/CHANGELOG.md @@ -18,6 +18,12 @@ The entries below are relative to the `zcash_primitives` crate as of the tag - Added in `zcash_protocol::value`: - `Zatoshis` - `ZatBalance` + - `MAX_BALANCE` has been added to replace previous instances where + `zcash_protocol::value::MAX_MONEY` was used as a signed value. + +### Changed +- `zcash_protocol::value::COIN` has been changed from an `i64` to a `u64` +- `zcash_protocol::value::MAX_MONEY` has been changed from an `i64` to a `u64` ### Removed - From `zcash_protocol::value`: diff --git a/components/zcash_protocol/src/value.rs b/components/zcash_protocol/src/value.rs index 7767220c00..34070a8a8e 100644 --- a/components/zcash_protocol/src/value.rs +++ b/components/zcash_protocol/src/value.rs @@ -5,8 +5,9 @@ use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}; use memuse::DynamicUsage; -pub const COIN: i64 = 1_0000_0000; -pub const MAX_MONEY: i64 = 21_000_000 * COIN; +pub const COIN: u64 = 1_0000_0000; +pub const MAX_MONEY: u64 = 21_000_000 * COIN; +pub const MAX_BALANCE: i64 = MAX_MONEY as i64; /// A type-safe representation of a Zcash value delta, in zatoshis. /// @@ -32,25 +33,25 @@ impl ZatBalance { /// Creates a constant ZatBalance from an i64. /// - /// Panics: if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. + /// Panics: if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. pub const fn const_from_i64(amount: i64) -> Self { - assert!(-MAX_MONEY <= amount && amount <= MAX_MONEY); // contains is not const + assert!(-MAX_BALANCE <= amount && amount <= MAX_BALANCE); // contains is not const ZatBalance(amount) } /// Creates a constant ZatBalance from a u64. /// - /// Panics: if the amount is outside the range `{0..MAX_MONEY}`. - const fn const_from_u64(amount: u64) -> Self { - assert!(amount <= (MAX_MONEY as u64)); // contains is not const + /// Panics: if the amount is outside the range `{0..MAX_BALANCE}`. + pub const fn const_from_u64(amount: u64) -> Self { + assert!(amount <= MAX_MONEY); // contains is not const ZatBalance(amount as i64) } /// Creates an ZatBalance from an i64. /// - /// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. + /// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. pub fn from_i64(amount: i64) -> Result { - if (-MAX_MONEY..=MAX_MONEY).contains(&amount) { + if (-MAX_BALANCE..=MAX_BALANCE).contains(&amount) { Ok(ZatBalance(amount)) } else { Err(()) @@ -59,9 +60,9 @@ impl ZatBalance { /// Creates a non-negative ZatBalance from an i64. /// - /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. + /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. pub fn from_nonnegative_i64(amount: i64) -> Result { - if (0..=MAX_MONEY).contains(&amount) { + if (0..=MAX_BALANCE).contains(&amount) { Ok(ZatBalance(amount)) } else { Err(()) @@ -72,7 +73,7 @@ impl ZatBalance { /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_u64(amount: u64) -> Result { - if amount <= MAX_MONEY as u64 { + if amount <= MAX_MONEY { Ok(ZatBalance(amount as i64)) } else { Err(()) @@ -81,7 +82,7 @@ impl ZatBalance { /// Reads an ZatBalance from a signed 64-bit little-endian integer. /// - /// Returns an error if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. + /// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result { let amount = i64::from_le_bytes(bytes); ZatBalance::from_i64(amount) @@ -89,7 +90,7 @@ impl ZatBalance { /// Reads a non-negative ZatBalance from a signed 64-bit little-endian integer. /// - /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. + /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result { let amount = i64::from_le_bytes(bytes); ZatBalance::from_nonnegative_i64(amount) @@ -97,7 +98,7 @@ impl ZatBalance { /// Reads an ZatBalance from an unsigned 64-bit little-endian integer. /// - /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. + /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result { let amount = u64::from_le_bytes(bytes); ZatBalance::from_u64(amount) @@ -237,36 +238,41 @@ impl Mul for ZatBalance { /// A Zatoshis can only be constructed from an integer that is within the valid monetary /// range of `{0..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis). #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] -pub struct Zatoshis(ZatBalance); +pub struct Zatoshis(u64); impl Zatoshis { /// Returns the identity `Zatoshis` - pub const ZERO: Self = Zatoshis(ZatBalance(0)); + pub const ZERO: Self = Zatoshis(0); /// Returns this Zatoshis as a u64. pub fn into_u64(self) -> u64 { - self.0.try_into().unwrap() + self.0 } /// Creates a Zatoshis from a u64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_u64(amount: u64) -> Result { - ZatBalance::from_u64(amount).map(Zatoshis) + if (0..=MAX_MONEY).contains(&amount) { + Ok(Zatoshis(amount)) + } else { + Err(()) + } } /// Creates a constant Zatoshis from a u64. /// - /// Panics: if the amount is outside the range `{-MAX_MONEY..MAX_MONEY}`. + /// Panics: if the amount is outside the range `{0..MAX_MONEY}`. pub const fn const_from_u64(amount: u64) -> Self { - Zatoshis(ZatBalance::const_from_u64(amount)) + assert!(amount <= MAX_MONEY); // contains is not const + Zatoshis(amount) } /// Creates a Zatoshis from an i64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_nonnegative_i64(amount: i64) -> Result { - ZatBalance::from_nonnegative_i64(amount).map(Zatoshis) + u64::try_from(amount).map(Zatoshis).map_err(|_| ()) } /// Reads an Zatoshis from an unsigned 64-bit little-endian integer. @@ -289,7 +295,7 @@ impl Zatoshis { /// Returns this Zatoshis encoded as a signed two's complement 64-bit /// little-endian value. pub fn to_i64_le_bytes(self) -> [u8; 8] { - self.0.to_i64_le_bytes() + (self.0 as i64).to_le_bytes() } /// Returns whether or not this `Zatoshis` is the zero value. @@ -305,13 +311,13 @@ impl Zatoshis { impl From for ZatBalance { fn from(n: Zatoshis) -> Self { - n.0 + ZatBalance(n.0 as i64) } } impl From<&Zatoshis> for ZatBalance { fn from(n: &Zatoshis) -> Self { - n.0 + ZatBalance(n.0 as i64) } } @@ -333,11 +339,7 @@ impl TryFrom for Zatoshis { type Error = (); fn try_from(value: ZatBalance) -> Result { - if value.is_negative() { - Err(()) - } else { - Ok(Zatoshis(value)) - } + Zatoshis::from_nonnegative_i64(value.0) } } @@ -345,7 +347,7 @@ impl Add for Zatoshis { type Output = Option; fn add(self, rhs: Zatoshis) -> Option { - (self.0 + rhs.0).map(Zatoshis) + Self::from_u64(self.0.checked_add(rhs.0)?).ok() } } @@ -361,7 +363,7 @@ impl Sub for Zatoshis { type Output = Option; fn sub(self, rhs: Zatoshis) -> Option { - (self.0 - rhs.0).and_then(|amt| Zatoshis::try_from(amt).ok()) + Zatoshis::from_u64(self.0.checked_sub(rhs.0)?).ok() } } @@ -377,7 +379,7 @@ impl Mul for Zatoshis { type Output = Option; fn mul(self, rhs: usize) -> Option { - (self.0 * rhs).and_then(|v| Zatoshis::try_from(v).ok()) + Zatoshis::from_u64(self.0.checked_mul(u64::try_from(rhs).ok()?)?).ok() } } @@ -430,30 +432,32 @@ impl From for BalanceError { pub mod testing { use proptest::prelude::prop_compose; - use super::{ZatBalance, Zatoshis, MAX_MONEY}; + use super::{ZatBalance, Zatoshis, MAX_BALANCE, MAX_MONEY}; prop_compose! { - pub fn arb_zat_balance()(amt in -MAX_MONEY..MAX_MONEY) -> ZatBalance { + pub fn arb_zat_balance()(amt in -MAX_BALANCE..MAX_BALANCE) -> ZatBalance { ZatBalance::from_i64(amt).unwrap() } } prop_compose! { - pub fn arb_positive_zat_balance()(amt in 1i64..MAX_MONEY) -> ZatBalance { + pub fn arb_positive_zat_balance()(amt in 1i64..MAX_BALANCE) -> ZatBalance { ZatBalance::from_i64(amt).unwrap() } } prop_compose! { - pub fn arb_zatoshis()(amt in 0i64..MAX_MONEY) -> Zatoshis { - Zatoshis::from_u64(amt as u64).unwrap() + pub fn arb_zatoshis()(amt in 0u64..MAX_MONEY) -> Zatoshis { + Zatoshis::from_u64(amt).unwrap() } } } #[cfg(test)] mod tests { - use super::{ZatBalance, MAX_MONEY}; + use crate::value::MAX_BALANCE; + + use super::ZatBalance; #[test] fn amount_in_range() { @@ -476,15 +480,15 @@ mod tests { let max_money = b"\x00\x40\x07\x5a\xf0\x75\x07\x00"; assert_eq!( ZatBalance::from_u64_le_bytes(*max_money).unwrap(), - ZatBalance(MAX_MONEY) + ZatBalance(MAX_BALANCE) ); assert_eq!( ZatBalance::from_nonnegative_i64_le_bytes(*max_money).unwrap(), - ZatBalance(MAX_MONEY) + ZatBalance(MAX_BALANCE) ); assert_eq!( ZatBalance::from_i64_le_bytes(*max_money).unwrap(), - ZatBalance(MAX_MONEY) + ZatBalance(MAX_BALANCE) ); let max_money_p1 = b"\x01\x40\x07\x5a\xf0\x75\x07\x00"; @@ -497,7 +501,7 @@ mod tests { assert!(ZatBalance::from_nonnegative_i64_le_bytes(*neg_max_money).is_err()); assert_eq!( ZatBalance::from_i64_le_bytes(*neg_max_money).unwrap(), - ZatBalance(-MAX_MONEY) + ZatBalance(-MAX_BALANCE) ); let neg_max_money_m1 = b"\xff\xbf\xf8\xa5\x0f\x8a\xf8\xff"; @@ -508,27 +512,27 @@ mod tests { #[test] fn add_overflow() { - let v = ZatBalance(MAX_MONEY); + let v = ZatBalance(MAX_BALANCE); assert_eq!(v + ZatBalance(1), None) } #[test] #[should_panic] fn add_assign_panics_on_overflow() { - let mut a = ZatBalance(MAX_MONEY); + let mut a = ZatBalance(MAX_BALANCE); a += ZatBalance(1); } #[test] fn sub_underflow() { - let v = ZatBalance(-MAX_MONEY); + let v = ZatBalance(-MAX_BALANCE); assert_eq!(v - ZatBalance(1), None) } #[test] #[should_panic] fn sub_assign_panics_on_underflow() { - let mut a = ZatBalance(-MAX_MONEY); + let mut a = ZatBalance(-MAX_BALANCE); a -= ZatBalance(1); } } diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index e6e34ffc45..52993169b4 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -53,6 +53,10 @@ and this library adheres to Rust's notion of - Added method `WalletRead::validate_seed` - `zcash_client_backend::fees`: - Arguments to `ChangeStrategy::compute_balance` have changed. +- `zcash_client_backend::zip321::render::amount_str` now takes a + `NonNegativeAmount` rather than a signed `Amount` as its argument. +- `zcash_client_backend::zip321::parse::parse_amount` now parses a + `NonNegativeAmount` rather than a signed `Amount`. ## [0.11.0] - 2024-03-01 diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index 9292206d06..39b0505663 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -358,9 +358,8 @@ mod render { use percent_encoding::{utf8_percent_encode, AsciiSet, CONTROLS}; use zcash_primitives::{ - consensus, + consensus, transaction::components::amount::NonNegativeAmount, transaction::components::amount::COIN, - transaction::components::{amount::NonNegativeAmount, Amount}, }; use super::{memo_to_base64, Address, MemoBytes}; @@ -413,10 +412,10 @@ mod render { /// Converts an [`Amount`] value to a correctly formatted decimal ZEC /// value for inclusion in a ZIP 321 URI. - pub fn amount_str(amount: Amount) -> Option { + pub fn amount_str(amount: NonNegativeAmount) -> Option { if amount.is_positive() { - let coins = i64::from(amount) / COIN; - let zats = i64::from(amount) % COIN; + let coins = u64::from(amount) / COIN; + let zats = u64::from(amount) % COIN; Some(if zats == 0 { format!("{}", coins) } else { @@ -432,7 +431,7 @@ mod render { /// Constructs an "amount" key/value pair containing the encoded ZEC amount /// at the specified parameter index. pub fn amount_param(amount: NonNegativeAmount, idx: Option) -> Option { - amount_str(amount.into()).map(|s| format!("amount{}={}", param_index(idx), s)) + amount_str(amount).map(|s| format!("amount{}={}", param_index(idx), s)) } /// Constructs a "memo" key/value pair containing the base64URI-encoded memo @@ -465,9 +464,8 @@ mod parse { }; use percent_encoding::percent_decode; use zcash_primitives::{ - consensus, + consensus, transaction::components::amount::NonNegativeAmount, transaction::components::amount::COIN, - transaction::components::{amount::NonNegativeAmount, Amount}, }; use crate::address::Address; @@ -641,7 +639,7 @@ mod parse { } /// Parses a value in decimal ZEC. - pub fn parse_amount(input: &str) -> IResult<&str, Amount> { + pub fn parse_amount(input: &str) -> IResult<&str, NonNegativeAmount> { map_res( tuple(( digit1, @@ -651,29 +649,24 @@ mod parse { )), )), |(whole_s, decimal_s): (&str, Option<&str>)| { - let coins: i64 = whole_s + let coins: u64 = whole_s .to_string() - .parse::() + .parse::() .map_err(|e| e.to_string())?; - let zats: i64 = match decimal_s { + let zats: u64 = match decimal_s { Some(d) => format!("{:0<8}", d) - .parse::() + .parse::() .map_err(|e| e.to_string())?, None => 0, }; - if coins >= 21000000 && (coins > 21000000 || zats > 0) { - return Err(format!( - "{} coins exceeds the maximum possible Zcash value.", - coins - )); - } - - let amt = coins * COIN + zats; - - Amount::from_nonnegative_i64(amt) - .map_err(|_| format!("Not a valid zat amount: {}", amt)) + coins + .checked_mul(COIN) + .and_then(|coin_zats| coin_zats.checked_add(zats)) + .ok_or(()) + .and_then(NonNegativeAmount::from_u64) + .map_err(|_| format!("Not a valid zat amount: {}.{}", coins, zats)) }, )(input) } @@ -693,11 +686,7 @@ mod parse { "amount" => parse_amount(value) .map_err(|e| e.to_string()) - .and_then(|(_, a)| { - NonNegativeAmount::try_from(a) - .map_err(|_| "Payment amount must be nonnegative.".to_owned()) - }) - .map(Param::Amount), + .map(|(_, amt)| Param::Amount(amt)), "label" => percent_decode(value.as_bytes()) .decode_utf8() @@ -832,9 +821,7 @@ mod tests { use zcash_primitives::{ consensus::{Parameters, TEST_NETWORK}, memo::Memo, - transaction::components::amount::{ - testing::arb_nonnegative_amount, Amount, NonNegativeAmount, - }, + transaction::components::amount::{testing::arb_nonnegative_amount, NonNegativeAmount}, }; #[cfg(feature = "local-consensus")] @@ -861,7 +848,7 @@ mod tests { let amounts = vec![1u64, 1000u64, 100000u64, 100000000u64, 100000000000u64]; for amt_u64 in amounts { - let amt = Amount::from_u64(amt_u64).unwrap(); + let amt = NonNegativeAmount::from_u64(amt_u64).unwrap(); let amt_str = amount_str(amt).unwrap(); assert_eq!(amt, parse_amount(&amt_str).unwrap().1); } @@ -1106,8 +1093,7 @@ mod tests { } #[test] - fn prop_zip321_roundtrip_amount(nn_amt in arb_nonnegative_amount()) { - let amt = Amount::from(nn_amt); + fn prop_zip321_roundtrip_amount(amt in arb_nonnegative_amount()) { let amt_str = amount_str(amt).unwrap(); assert_eq!(amt, parse_amount(&amt_str).unwrap().1); } From 85d1ca251acc1674b885476929d98a7c02ab12d1 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 29 Jan 2024 16:44:32 -0700 Subject: [PATCH 06/18] zcash_primitives: Move the `local-consensus` module to the `zcash_protocol` crate. --- components/zcash_protocol/Cargo.toml | 3 +++ components/zcash_protocol/src/lib.rs | 2 ++ .../zcash_protocol}/src/local_consensus.rs | 0 zcash_primitives/Cargo.toml | 3 --- zcash_primitives/src/lib.rs | 2 -- 5 files changed, 5 insertions(+), 5 deletions(-) rename {zcash_primitives => components/zcash_protocol}/src/local_consensus.rs (100%) diff --git a/components/zcash_protocol/Cargo.toml b/components/zcash_protocol/Cargo.toml index f4ea79fc13..03cc53b3b8 100644 --- a/components/zcash_protocol/Cargo.toml +++ b/components/zcash_protocol/Cargo.toml @@ -46,3 +46,6 @@ unstable-nu6 = [] ## Exposes early in-development features that are not yet planned for any network upgrade. zfuture = [] + +## Exposes support for working with a local consensus (e.g. regtest +local-consensus = [] diff --git a/components/zcash_protocol/src/lib.rs b/components/zcash_protocol/src/lib.rs index a15d5e5881..c597be43a0 100644 --- a/components/zcash_protocol/src/lib.rs +++ b/components/zcash_protocol/src/lib.rs @@ -17,4 +17,6 @@ pub mod consensus; pub mod constants; +#[cfg(feature = "local-consensus")] +pub mod local_consensus; pub mod value; diff --git a/zcash_primitives/src/local_consensus.rs b/components/zcash_protocol/src/local_consensus.rs similarity index 100% rename from zcash_primitives/src/local_consensus.rs rename to components/zcash_protocol/src/local_consensus.rs diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index 15733b15e5..f2fc208217 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -133,9 +133,6 @@ unstable-nu6 = ["zcash_protocol/unstable-nu6"] ## Exposes early in-development features that are not yet planned for any network upgrade. zfuture = ["zcash_protocol/zfuture"] - -## Exposes support for working with a local consensus (e.g. regtest -local-consensus = [] [lib] bench = false diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index bd77312a80..e539dfeea0 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -28,6 +28,4 @@ pub mod transaction; pub use zip32; #[cfg(feature = "zfuture")] pub mod extensions; -#[cfg(feature = "local-consensus")] -pub mod local_consensus; pub mod zip339; From a35ccfc10bd2a3926a15d65d0e1e1340ea1f952f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 2 Feb 2024 11:09:56 -0700 Subject: [PATCH 07/18] Fix broken intra-doc links after `zcash_protocol` crate extraction. --- components/zcash_protocol/src/consensus.rs | 2 +- components/zcash_protocol/src/constants/mainnet.rs | 10 +++++----- components/zcash_protocol/src/constants/regtest.rs | 10 +++++----- components/zcash_protocol/src/constants/testnet.rs | 10 +++++----- components/zcash_protocol/src/value.rs | 2 +- zcash_client_backend/src/zip321.rs | 2 +- 6 files changed, 18 insertions(+), 18 deletions(-) diff --git a/components/zcash_protocol/src/consensus.rs b/components/zcash_protocol/src/consensus.rs index 11a70ec327..c060b68f87 100644 --- a/components/zcash_protocol/src/consensus.rs +++ b/components/zcash_protocol/src/consensus.rs @@ -485,7 +485,7 @@ pub const ZIP212_GRACE_PERIOD: u32 = 32256; /// /// See [ZIP 200](https://zips.z.cash/zip-0200) for more details. /// -/// [`signature_hash`]: crate::transaction::sighash::signature_hash +/// [`signature_hash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/transaction/sighash/fn.signature_hash.html #[derive(Clone, Copy, Debug, PartialEq, Eq)] pub enum BranchId { /// The consensus rules at the launch of Zcash. diff --git a/components/zcash_protocol/src/constants/mainnet.rs b/components/zcash_protocol/src/constants/mainnet.rs index 3b099e1389..bd31a895eb 100644 --- a/components/zcash_protocol/src/constants/mainnet.rs +++ b/components/zcash_protocol/src/constants/mainnet.rs @@ -9,7 +9,7 @@ pub const COIN_TYPE: u32 = 133; /// /// Defined in [ZIP 32]. /// -/// [`ExtendedSpendingKey`]: crate::sapling::zip32::ExtendedSpendingKey +/// [`ExtendedSpendingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedSpendingKey.html /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-main"; @@ -17,7 +17,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-main"; /// /// Defined in [ZIP 32]. /// -/// [`ExtendedFullViewingKey`]: crate::sapling::zip32::ExtendedFullViewingKey +/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html# /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviews"; @@ -25,16 +25,16 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviews"; /// /// Defined in section 5.6.4 of the [Zcash Protocol Specification]. /// -/// [`PaymentAddress`]: crate::sapling::PaymentAddress +/// [`PaymentAddress`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/struct.PaymentAddress.html /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zs"; /// The prefix for a Base58Check-encoded mainnet [`PublicKeyHash`]. /// -/// [`PublicKeyHash`]: crate::legacy::TransparentAddress::PublicKeyHash +/// [`PublicKeyHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html pub const B58_PUBKEY_ADDRESS_PREFIX: [u8; 2] = [0x1c, 0xb8]; /// The prefix for a Base58Check-encoded mainnet [`ScriptHash`]. /// -/// [`ScriptHash`]: crate::legacy::TransparentAddress::ScriptHash +/// [`ScriptHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html pub const B58_SCRIPT_ADDRESS_PREFIX: [u8; 2] = [0x1c, 0xbd]; diff --git a/components/zcash_protocol/src/constants/regtest.rs b/components/zcash_protocol/src/constants/regtest.rs index c460ccbd41..7ae37dec53 100644 --- a/components/zcash_protocol/src/constants/regtest.rs +++ b/components/zcash_protocol/src/constants/regtest.rs @@ -13,7 +13,7 @@ pub const COIN_TYPE: u32 = 1; /// /// It is defined in [the `zcashd` codebase]. /// -/// [`ExtendedSpendingKey`]: crate::sapling::zip32::ExtendedSpendingKey +/// [`ExtendedSpendingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedSpendingKey.html /// [the `zcashd` codebase]: pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-regtest"; @@ -21,7 +21,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-regtest /// /// It is defined in [the `zcashd` codebase]. /// -/// [`ExtendedFullViewingKey`]: crate::sapling::zip32::ExtendedFullViewingKey +/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html# /// [the `zcashd` codebase]: pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewregtestsapling"; @@ -29,18 +29,18 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewregtestsapling"; /// /// It is defined in [the `zcashd` codebase]. /// -/// [`PaymentAddress`]: crate::sapling::PaymentAddress +/// [`PaymentAddress`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/struct.PaymentAddress.html /// [the `zcashd` codebase]: pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zregtestsapling"; /// The prefix for a Base58Check-encoded regtest transparent [`PublicKeyHash`]. /// Same as the testnet prefix. /// -/// [`PublicKeyHash`]: crate::legacy::TransparentAddress::PublicKeyHash +/// [`PublicKeyHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html pub const B58_PUBKEY_ADDRESS_PREFIX: [u8; 2] = [0x1d, 0x25]; /// The prefix for a Base58Check-encoded regtest transparent [`ScriptHash`]. /// Same as the testnet prefix. /// -/// [`ScriptHash`]: crate::legacy::TransparentAddress::ScriptHash +/// [`ScriptHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html pub const B58_SCRIPT_ADDRESS_PREFIX: [u8; 2] = [0x1c, 0xba]; diff --git a/components/zcash_protocol/src/constants/testnet.rs b/components/zcash_protocol/src/constants/testnet.rs index b61e188c36..dba86bb9c7 100644 --- a/components/zcash_protocol/src/constants/testnet.rs +++ b/components/zcash_protocol/src/constants/testnet.rs @@ -9,7 +9,7 @@ pub const COIN_TYPE: u32 = 1; /// /// Defined in [ZIP 32]. /// -/// [`ExtendedSpendingKey`]: crate::sapling::zip32::ExtendedSpendingKey +/// [`ExtendedSpendingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedSpendingKey.html /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-test"; @@ -17,7 +17,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-test"; /// /// Defined in [ZIP 32]. /// -/// [`ExtendedFullViewingKey`]: crate::sapling::zip32::ExtendedFullViewingKey +/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html# /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewtestsapling"; @@ -25,16 +25,16 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewtestsapling"; /// /// Defined in section 5.6.4 of the [Zcash Protocol Specification]. /// -/// [`PaymentAddress`]: crate::sapling::PaymentAddress +/// [`PaymentAddress`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/struct.PaymentAddress.html /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "ztestsapling"; /// The prefix for a Base58Check-encoded testnet transparent [`PublicKeyHash`]. /// -/// [`PublicKeyHash`]: crate::legacy::TransparentAddress::PublicKeyHash +/// [`PublicKeyHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html pub const B58_PUBKEY_ADDRESS_PREFIX: [u8; 2] = [0x1d, 0x25]; /// The prefix for a Base58Check-encoded testnet transparent [`ScriptHash`]. /// -/// [`ScriptHash`]: crate::legacy::TransparentAddress::ScriptHash +/// [`ScriptHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html pub const B58_SCRIPT_ADDRESS_PREFIX: [u8; 2] = [0x1c, 0xba]; diff --git a/components/zcash_protocol/src/value.rs b/components/zcash_protocol/src/value.rs index 34070a8a8e..65376fa8e0 100644 --- a/components/zcash_protocol/src/value.rs +++ b/components/zcash_protocol/src/value.rs @@ -19,7 +19,7 @@ pub const MAX_BALANCE: i64 = MAX_MONEY as i64; /// particular, a [`Transaction`] containing serialized invalid ZatBalances will be rejected /// by the network consensus rules. /// -/// [`Transaction`]: crate::transaction::Transaction +/// [`Transaction`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/transaction/struct.Transaction.html #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] pub struct ZatBalance(i64); diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index 39b0505663..a3e8e0093c 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -410,7 +410,7 @@ mod render { format!("address{}={}", param_index(idx), addr.encode(params)) } - /// Converts an [`Amount`] value to a correctly formatted decimal ZEC + /// Converts an [`NonNegativeAmount`] value to a correctly formatted decimal ZEC /// value for inclusion in a ZIP 321 URI. pub fn amount_str(amount: NonNegativeAmount) -> Option { if amount.is_positive() { From 64454100c50cd13a5b793095b60758015ef02cd9 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 2 Feb 2024 13:53:38 -0700 Subject: [PATCH 08/18] zcash_client_backend: Move the `ShieldedProtocol` and `PoolType` types to `zcash_protocol` --- Cargo.lock | 2 ++ components/zcash_protocol/src/lib.rs | 30 ++++++++++++++++ zcash_client_backend/CHANGELOG.md | 4 +++ zcash_client_backend/Cargo.toml | 1 + zcash_client_backend/src/lib.rs | 52 +--------------------------- zcash_client_backend/src/proposal.rs | 2 +- zcash_keys/CHANGELOG.md | 3 ++ zcash_keys/Cargo.toml | 1 + zcash_keys/src/address.rs | 28 +++++++++++++++ 9 files changed, 71 insertions(+), 52 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index aaadf4cc89..750ccdd26e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3046,6 +3046,7 @@ dependencies = [ "zcash_note_encryption", "zcash_primitives", "zcash_proofs", + "zcash_protocol", "zip32", ] @@ -3146,6 +3147,7 @@ dependencies = [ "zcash_address", "zcash_encoding", "zcash_primitives", + "zcash_protocol", "zip32", ] diff --git a/components/zcash_protocol/src/lib.rs b/components/zcash_protocol/src/lib.rs index c597be43a0..f752dc909d 100644 --- a/components/zcash_protocol/src/lib.rs +++ b/components/zcash_protocol/src/lib.rs @@ -15,8 +15,38 @@ // Temporary until we have addressed all Result cases. #![allow(clippy::result_unit_err)] +use core::fmt; + pub mod consensus; pub mod constants; #[cfg(feature = "local-consensus")] pub mod local_consensus; pub mod value; + +/// A Zcash shielded transfer protocol. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum ShieldedProtocol { + /// The Sapling protocol + Sapling, + /// The Orchard protocol + Orchard, +} + +/// A value pool in the Zcash protocol. +#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum PoolType { + /// The transparent value pool + Transparent, + /// A shielded value pool. + Shielded(ShieldedProtocol), +} + +impl fmt::Display for PoolType { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + PoolType::Transparent => f.write_str("Transparent"), + PoolType::Shielded(ShieldedProtocol::Sapling) => f.write_str("Sapling"), + PoolType::Shielded(ShieldedProtocol::Orchard) => f.write_str("Orchard"), + } + } +} diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index 52993169b4..17f58112f5 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -58,6 +58,10 @@ and this library adheres to Rust's notion of - `zcash_client_backend::zip321::parse::parse_amount` now parses a `NonNegativeAmount` rather than a signed `Amount`. +### Removed +- `zcash_client_backend::PoolType::is_receiver` use + `zcash_keys::Address::has_receiver` instead. + ## [0.11.0] - 2024-03-01 ### Added diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 43605960a5..a9dce2f686 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -30,6 +30,7 @@ zcash_encoding.workspace = true zcash_keys = { workspace = true, features = ["sapling"] } zcash_note_encryption.workspace = true zcash_primitives.workspace = true +zcash_protocol.workspace = true zip32.workspace = true # Dependencies exposed in a public API: diff --git a/zcash_client_backend/src/lib.rs b/zcash_client_backend/src/lib.rs index a4d88cee7b..7c5d32cef1 100644 --- a/zcash_client_backend/src/lib.rs +++ b/zcash_client_backend/src/lib.rs @@ -64,7 +64,6 @@ pub use zcash_keys::address; pub mod data_api; mod decrypt; -use zcash_keys::address::Address; pub use zcash_keys::encoding; pub mod fees; pub use zcash_keys::keys; @@ -78,9 +77,8 @@ pub mod zip321; #[cfg(feature = "unstable-serialization")] pub mod serialization; -use std::fmt; - pub use decrypt::{decrypt_transaction, DecryptedOutput, TransferType}; +pub use zcash_protocol::{PoolType, ShieldedProtocol}; #[cfg(test)] #[macro_use] @@ -90,51 +88,3 @@ extern crate assert_matches; core::compile_error!( "The `orchard` feature flag requires the `zcash_unstable=\"orchard\"` RUSTFLAG." ); - -/// A shielded transfer protocol known to the wallet. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum ShieldedProtocol { - /// The Sapling protocol - Sapling, - /// The Orchard protocol - Orchard, -} - -/// A value pool to which the wallet supports sending transaction outputs. -#[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord)] -pub enum PoolType { - /// The transparent value pool - Transparent, - /// A shielded value pool. - Shielded(ShieldedProtocol), -} - -impl PoolType { - pub fn is_receiver(&self, addr: &Address) -> bool { - match addr { - Address::Sapling(_) => matches!(self, PoolType::Shielded(ShieldedProtocol::Sapling)), - Address::Transparent(_) => matches!(self, PoolType::Transparent), - Address::Unified(ua) => match self { - PoolType::Transparent => ua.transparent().is_some(), - PoolType::Shielded(ShieldedProtocol::Sapling) => ua.sapling().is_some(), - PoolType::Shielded(ShieldedProtocol::Orchard) => { - #[cfg(feature = "orchard")] - return ua.orchard().is_some(); - - #[cfg(not(feature = "orchard"))] - return false; - } - }, - } - } -} - -impl fmt::Display for PoolType { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self { - PoolType::Transparent => f.write_str("Transparent"), - PoolType::Shielded(ShieldedProtocol::Sapling) => f.write_str("Sapling"), - PoolType::Shielded(ShieldedProtocol::Orchard) => f.write_str("Orchard"), - } - } -} diff --git a/zcash_client_backend/src/proposal.rs b/zcash_client_backend/src/proposal.rs index 759aaa578a..cb5e004f86 100644 --- a/zcash_client_backend/src/proposal.rs +++ b/zcash_client_backend/src/proposal.rs @@ -377,7 +377,7 @@ impl Step { .payments() .get(idx) .iter() - .any(|payment| pool.is_receiver(&payment.recipient_address)) + .any(|payment| payment.recipient_address.has_receiver(*pool)) { return Err(ProposalError::PaymentPoolsMismatch); } diff --git a/zcash_keys/CHANGELOG.md b/zcash_keys/CHANGELOG.md index 4ebf3c1c38..1db7830c5f 100644 --- a/zcash_keys/CHANGELOG.md +++ b/zcash_keys/CHANGELOG.md @@ -6,6 +6,9 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Added +- `zcash_keys::address::Address::has_receiver` + ## [0.1.1] - 2024-03-04 ### Added diff --git a/zcash_keys/Cargo.toml b/zcash_keys/Cargo.toml index 5d3f8cdd91..dd08483f8d 100644 --- a/zcash_keys/Cargo.toml +++ b/zcash_keys/Cargo.toml @@ -22,6 +22,7 @@ rustdoc-args = ["--cfg", "docsrs"] zcash_address.workspace = true zcash_encoding.workspace = true zcash_primitives.workspace = true +zcash_protocol.workspace = true zip32.workspace = true # Dependencies exposed in a public API: diff --git a/zcash_keys/src/address.rs b/zcash_keys/src/address.rs index c0990c24ba..2dfed4fa9f 100644 --- a/zcash_keys/src/address.rs +++ b/zcash_keys/src/address.rs @@ -8,6 +8,7 @@ use zcash_primitives::{consensus, legacy::TransparentAddress}; #[cfg(feature = "sapling")] use sapling::PaymentAddress; +use zcash_protocol::{PoolType, ShieldedProtocol}; /// A Unified Address. #[derive(Clone, Debug, PartialEq, Eq)] @@ -313,6 +314,33 @@ impl Address { } .to_string() } + + pub fn has_receiver(&self, pool_type: PoolType) -> bool { + match self { + #[cfg(feature = "sapling")] + Address::Sapling(_) => { + matches!(pool_type, PoolType::Shielded(ShieldedProtocol::Sapling)) + } + Address::Transparent(_) => matches!(pool_type, PoolType::Transparent), + Address::Unified(ua) => match pool_type { + PoolType::Transparent => ua.transparent().is_some(), + PoolType::Shielded(ShieldedProtocol::Sapling) => { + #[cfg(feature = "sapling")] + return ua.sapling().is_some(); + + #[cfg(not(feature = "sapling"))] + return false; + } + PoolType::Shielded(ShieldedProtocol::Orchard) => { + #[cfg(feature = "orchard")] + return ua.orchard().is_some(); + + #[cfg(not(feature = "orchard"))] + return false; + } + }, + } + } } #[cfg(any(test, feature = "test-dependencies"))] From 4b18426fcd3bb8e297f28d1601ef09fcd905dca9 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Fri, 2 Feb 2024 17:28:14 -0700 Subject: [PATCH 09/18] zcash_address: Use `zcash_protocol::consensus::NetworkType` This inverts the dependency relationship between `zcash_protocol` and `zcash_address`, permitting the network constants (primarily the HRPs) defined in `zcash_protocol` to be used directly in `zcash_address` instead of being duplicated. --- Cargo.lock | 2 +- components/zcash_address/CHANGELOG.md | 6 + components/zcash_address/Cargo.toml | 3 +- components/zcash_address/src/encoding.rs | 85 +++-- components/zcash_address/src/kind.rs | 6 - components/zcash_address/src/kind/p2pkh.rs | 5 - components/zcash_address/src/kind/p2sh.rs | 5 - components/zcash_address/src/kind/sapling.rs | 20 -- components/zcash_address/src/kind/sprout.rs | 13 - components/zcash_address/src/lib.rs | 15 +- components/zcash_protocol/CHANGELOG.md | 9 + components/zcash_protocol/Cargo.toml | 2 - components/zcash_protocol/src/consensus.rs | 300 ++++++++++-------- .../zcash_protocol/src/constants/mainnet.rs | 7 + .../zcash_protocol/src/constants/regtest.rs | 8 + .../zcash_protocol/src/constants/testnet.rs | 7 + .../zcash_protocol/src/local_consensus.rs | 88 ++--- zcash_client_backend/src/zip321.rs | 10 +- zcash_client_sqlite/src/lib.rs | 8 +- zcash_client_sqlite/src/wallet/init.rs | 22 +- zcash_extensions/src/transparent/demo.rs | 32 +- zcash_keys/src/address.rs | 15 +- zcash_keys/src/encoding.rs | 47 +-- zcash_keys/src/keys.rs | 14 +- zcash_primitives/src/legacy/keys.rs | 9 +- 25 files changed, 340 insertions(+), 398 deletions(-) delete mode 100644 components/zcash_address/src/kind/p2pkh.rs delete mode 100644 components/zcash_address/src/kind/p2sh.rs delete mode 100644 components/zcash_address/src/kind/sapling.rs delete mode 100644 components/zcash_address/src/kind/sprout.rs diff --git a/Cargo.lock b/Cargo.lock index 750ccdd26e..d88d1524b8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3002,6 +3002,7 @@ dependencies = [ "f4jumble", "proptest", "zcash_encoding", + "zcash_protocol", ] [[package]] @@ -3239,7 +3240,6 @@ dependencies = [ "incrementalmerkletree", "memuse", "proptest", - "zcash_address", ] [[package]] diff --git a/components/zcash_address/CHANGELOG.md b/components/zcash_address/CHANGELOG.md index 780712c60c..4189e18852 100644 --- a/components/zcash_address/CHANGELOG.md +++ b/components/zcash_address/CHANGELOG.md @@ -7,6 +7,12 @@ and this library adheres to Rust's notion of ## [Unreleased] +### Removed +- `zcash_address::kind::p2pkh` - use constants from `zcash_protocol` instead. +- `zcash_address::kind::p2sh` - use constants from `zcash_protocol` instead. +- `zcash_address::kind::sapling` - use constants from `zcash_protocol` instead. +- `zcash_address::kind::sprout` - use constants from `zcash_protocol` instead. + ## [0.3.1] - 2024-01-12 ### Fixed - Stubs for `zcash_address::convert` traits that are created by `rust-analyzer` diff --git a/components/zcash_address/Cargo.toml b/components/zcash_address/Cargo.toml index b797ae22d8..e8ed075a56 100644 --- a/components/zcash_address/Cargo.toml +++ b/components/zcash_address/Cargo.toml @@ -22,7 +22,8 @@ rustdoc-args = ["--cfg", "docsrs"] bech32 = "0.9" bs58 = { version = "0.5", features = ["check"] } f4jumble = { version = "0.1", path = "../f4jumble" } -zcash_encoding = { version = "0.2", path = "../zcash_encoding" } +zcash_protocol.workspace = true +zcash_encoding.workspace = true [dev-dependencies] assert_matches = "1.3.0" diff --git a/components/zcash_address/src/encoding.rs b/components/zcash_address/src/encoding.rs index 9e5e422ce6..2f5bf8445f 100644 --- a/components/zcash_address/src/encoding.rs +++ b/components/zcash_address/src/encoding.rs @@ -1,9 +1,11 @@ use std::{convert::TryInto, error::Error, fmt, str::FromStr}; use bech32::{self, FromBase32, ToBase32, Variant}; +use zcash_protocol::consensus::{NetworkConstants, NetworkType}; +use zcash_protocol::constants::{mainnet, regtest, testnet}; use crate::kind::unified::Encoding; -use crate::{kind::*, AddressKind, Network, ZcashAddress}; +use crate::{kind::*, AddressKind, ZcashAddress}; /// An error while attempting to parse a string as a Zcash address. #[derive(Debug, PartialEq, Eq)] @@ -68,9 +70,9 @@ impl FromStr for ZcashAddress { let data = Vec::::from_base32(&data).map_err(|_| ParseError::InvalidEncoding)?; let net = match hrp.as_str() { - sapling::MAINNET => Network::Main, - sapling::TESTNET => Network::Test, - sapling::REGTEST => Network::Regtest, + mainnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Main, + testnet::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Test, + regtest::HRP_SAPLING_PAYMENT_ADDRESS => NetworkType::Regtest, // We will not define new Bech32 address encodings. _ => { return Err(ParseError::NotZcash); @@ -86,23 +88,33 @@ impl FromStr for ZcashAddress { // The rest use Base58Check. if let Ok(decoded) = bs58::decode(s).with_check(None).into_vec() { - let net = match decoded[..2].try_into().unwrap() { - sprout::MAINNET | p2pkh::MAINNET | p2sh::MAINNET => Network::Main, - sprout::TESTNET | p2pkh::TESTNET | p2sh::TESTNET => Network::Test, - // We will not define new Base58Check address encodings. - _ => return Err(ParseError::NotZcash), - }; + if decoded.len() >= 2 { + let (prefix, net) = match decoded[..2].try_into().unwrap() { + prefix @ (mainnet::B58_PUBKEY_ADDRESS_PREFIX + | mainnet::B58_SCRIPT_ADDRESS_PREFIX + | mainnet::B58_SPROUT_ADDRESS_PREFIX) => (prefix, NetworkType::Main), + prefix @ (testnet::B58_PUBKEY_ADDRESS_PREFIX + | testnet::B58_SCRIPT_ADDRESS_PREFIX + | testnet::B58_SPROUT_ADDRESS_PREFIX) => (prefix, NetworkType::Test), + // We will not define new Base58Check address encodings. + _ => return Err(ParseError::NotZcash), + }; - return match decoded[..2].try_into().unwrap() { - sprout::MAINNET | sprout::TESTNET => { - decoded[2..].try_into().map(AddressKind::Sprout) + return match prefix { + mainnet::B58_SPROUT_ADDRESS_PREFIX | testnet::B58_SPROUT_ADDRESS_PREFIX => { + decoded[2..].try_into().map(AddressKind::Sprout) + } + mainnet::B58_PUBKEY_ADDRESS_PREFIX | testnet::B58_PUBKEY_ADDRESS_PREFIX => { + decoded[2..].try_into().map(AddressKind::P2pkh) + } + mainnet::B58_SCRIPT_ADDRESS_PREFIX | testnet::B58_SCRIPT_ADDRESS_PREFIX => { + decoded[2..].try_into().map(AddressKind::P2sh) + } + _ => unreachable!(), } - p2pkh::MAINNET | p2pkh::TESTNET => decoded[2..].try_into().map(AddressKind::P2pkh), - p2sh::MAINNET | p2sh::TESTNET => decoded[2..].try_into().map(AddressKind::P2sh), - _ => unreachable!(), + .map_err(|_| ParseError::InvalidEncoding) + .map(|kind| ZcashAddress { kind, net }); } - .map_err(|_| ParseError::InvalidEncoding) - .map(|kind| ZcashAddress { kind, net }); }; // If it's not valid Bech32, Bech32m, or Base58Check, it's not a Zcash address. @@ -124,36 +136,13 @@ fn encode_b58(prefix: [u8; 2], data: &[u8]) -> String { impl fmt::Display for ZcashAddress { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { let encoded = match &self.kind { - AddressKind::Sprout(data) => encode_b58( - match self.net { - Network::Main => sprout::MAINNET, - Network::Test | Network::Regtest => sprout::TESTNET, - }, - data, - ), - AddressKind::Sapling(data) => encode_bech32( - match self.net { - Network::Main => sapling::MAINNET, - Network::Test => sapling::TESTNET, - Network::Regtest => sapling::REGTEST, - }, - data, - ), + AddressKind::Sprout(data) => encode_b58(self.net.b58_sprout_address_prefix(), data), + AddressKind::Sapling(data) => { + encode_bech32(self.net.hrp_sapling_payment_address(), data) + } AddressKind::Unified(addr) => addr.encode(&self.net), - AddressKind::P2pkh(data) => encode_b58( - match self.net { - Network::Main => p2pkh::MAINNET, - Network::Test | Network::Regtest => p2pkh::TESTNET, - }, - data, - ), - AddressKind::P2sh(data) => encode_b58( - match self.net { - Network::Main => p2sh::MAINNET, - Network::Test | Network::Regtest => p2sh::TESTNET, - }, - data, - ), + AddressKind::P2pkh(data) => encode_b58(self.net.b58_pubkey_address_prefix(), data), + AddressKind::P2sh(data) => encode_b58(self.net.b58_script_address_prefix(), data), }; write!(f, "{}", encoded) } @@ -162,7 +151,7 @@ impl fmt::Display for ZcashAddress { #[cfg(test)] mod tests { use super::*; - use crate::kind::unified; + use crate::{kind::unified, Network}; fn encoding(encoded: &str, decoded: ZcashAddress) { assert_eq!(decoded.to_string(), encoded); diff --git a/components/zcash_address/src/kind.rs b/components/zcash_address/src/kind.rs index 5397c027f8..38b4557a6e 100644 --- a/components/zcash_address/src/kind.rs +++ b/components/zcash_address/src/kind.rs @@ -1,7 +1 @@ pub mod unified; - -pub(crate) mod sapling; -pub(crate) mod sprout; - -pub(crate) mod p2pkh; -pub(crate) mod p2sh; diff --git a/components/zcash_address/src/kind/p2pkh.rs b/components/zcash_address/src/kind/p2pkh.rs deleted file mode 100644 index a37377d3c5..0000000000 --- a/components/zcash_address/src/kind/p2pkh.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// The prefix for a Base58Check-encoded mainnet transparent P2PKH address. -pub(crate) const MAINNET: [u8; 2] = [0x1c, 0xb8]; - -/// The prefix for a Base58Check-encoded testnet transparent P2PKH address. -pub(crate) const TESTNET: [u8; 2] = [0x1d, 0x25]; diff --git a/components/zcash_address/src/kind/p2sh.rs b/components/zcash_address/src/kind/p2sh.rs deleted file mode 100644 index 1ebef52ab1..0000000000 --- a/components/zcash_address/src/kind/p2sh.rs +++ /dev/null @@ -1,5 +0,0 @@ -/// The prefix for a Base58Check-encoded mainnet transparent P2SH address. -pub(crate) const MAINNET: [u8; 2] = [0x1c, 0xbd]; - -/// The prefix for a Base58Check-encoded testnet transparent P2SH address. -pub(crate) const TESTNET: [u8; 2] = [0x1c, 0xba]; diff --git a/components/zcash_address/src/kind/sapling.rs b/components/zcash_address/src/kind/sapling.rs deleted file mode 100644 index 6f2e945b94..0000000000 --- a/components/zcash_address/src/kind/sapling.rs +++ /dev/null @@ -1,20 +0,0 @@ -/// The HRP for a Bech32-encoded mainnet Sapling address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.4][saplingpaymentaddrencoding]. -/// -/// [saplingpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding -pub(crate) const MAINNET: &str = "zs"; - -/// The HRP for a Bech32-encoded testnet Sapling address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.4][saplingpaymentaddrencoding]. -/// -/// [saplingpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#saplingpaymentaddrencoding -pub(crate) const TESTNET: &str = "ztestsapling"; - -/// The HRP for a Bech32-encoded regtest Sapling address. -/// -/// It is defined in [the `zcashd` codebase]. -/// -/// [the `zcashd` codebase]: https://github.com/zcash/zcash/blob/128d863fb8be39ee294fda397c1ce3ba3b889cb2/src/chainparams.cpp#L493 -pub(crate) const REGTEST: &str = "zregtestsapling"; diff --git a/components/zcash_address/src/kind/sprout.rs b/components/zcash_address/src/kind/sprout.rs deleted file mode 100644 index 06a8a03c79..0000000000 --- a/components/zcash_address/src/kind/sprout.rs +++ /dev/null @@ -1,13 +0,0 @@ -/// The prefix for a Base58Check-encoded mainnet Sprout address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. -/// -/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding -pub(crate) const MAINNET: [u8; 2] = [0x16, 0x9a]; - -/// The prefix for a Base58Check-encoded testnet Sprout address. -/// -/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. -/// -/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding -pub(crate) const TESTNET: [u8; 2] = [0x16, 0xb6]; diff --git a/components/zcash_address/src/lib.rs b/components/zcash_address/src/lib.rs index a16281943e..6f516a941e 100644 --- a/components/zcash_address/src/lib.rs +++ b/components/zcash_address/src/lib.rs @@ -141,6 +141,7 @@ pub use convert::{ }; pub use encoding::ParseError; pub use kind::unified; +pub use zcash_protocol::consensus::NetworkType as Network; /// A Zcash address. #[derive(Clone, Debug, PartialEq, Eq, Hash)] @@ -149,20 +150,6 @@ pub struct ZcashAddress { kind: AddressKind, } -/// The Zcash network for which an address is encoded. -#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] -pub enum Network { - /// Zcash Mainnet. - Main, - /// Zcash Testnet. - Test, - /// Private integration / regression testing, used in `zcashd`. - /// - /// For some address types there is no distinction between test and regtest encodings; - /// those will always be parsed as `Network::Test`. - Regtest, -} - /// Known kinds of Zcash addresses. #[derive(Clone, Debug, PartialEq, Eq, Hash)] enum AddressKind { diff --git a/components/zcash_protocol/CHANGELOG.md b/components/zcash_protocol/CHANGELOG.md index c29a7fd01a..f6b80a9c73 100644 --- a/components/zcash_protocol/CHANGELOG.md +++ b/components/zcash_protocol/CHANGELOG.md @@ -15,6 +15,11 @@ The entries below are relative to the `zcash_primitives` crate as of the tag - `consensus` - `constants` - `zcash_protocol::value` replaces `zcash_primitives::transaction::components::amount` +- `zcash_protocol::consensus`: + - `NetworkConstants` has been extracted from the `Parameters` trait. + - `NetworkType` + - `Parameters::b58_sprout_address_prefix` +- `zcash_protocol::constants::{mainnet, testnet}::B58_SPROUT_ADDRESS_PREFIX` - Added in `zcash_protocol::value`: - `Zatoshis` - `ZatBalance` @@ -35,3 +40,7 @@ The entries below are relative to the `zcash_primitives` crate as of the tag - `TryFrom for Amount` - `From for sapling::value::NoteValue>` - `TryFrom for NonNegativeAmount` +- `zcash_protocol::consensus::Parameters` has been split into two traits, with + the `NetworkConstants` trait providing all network constant accessors. Also, + the `address_network` method has been replaced with a new `network_type` + method that serves the same purpose. diff --git a/components/zcash_protocol/Cargo.toml b/components/zcash_protocol/Cargo.toml index 03cc53b3b8..b5c8f651d4 100644 --- a/components/zcash_protocol/Cargo.toml +++ b/components/zcash_protocol/Cargo.toml @@ -20,8 +20,6 @@ all-features = true rustdoc-args = ["--cfg", "docsrs"] [dependencies] -zcash_address.workspace = true - # - Logging and metrics memuse.workspace = true diff --git a/components/zcash_protocol/src/consensus.rs b/components/zcash_protocol/src/consensus.rs index c060b68f87..aabcebcde0 100644 --- a/components/zcash_protocol/src/consensus.rs +++ b/components/zcash_protocol/src/consensus.rs @@ -5,9 +5,11 @@ use std::cmp::{Ord, Ordering}; use std::convert::TryFrom; use std::fmt; use std::ops::{Add, Bound, RangeBounds, Sub}; -use zcash_address; -use crate::constants; +use crate::constants::{mainnet, regtest, testnet}; + +#[cfg(feature = "local-consensus")] +use crate::local_consensus::LocalNetwork; /// A wrapper type representing blockchain heights. /// @@ -136,68 +138,183 @@ impl Sub for BlockHeight { } } -/// Zcash consensus parameters. -pub trait Parameters: Clone { - /// Returns the activation height for a particular network upgrade, - /// if an activation height has been set. - fn activation_height(&self, nu: NetworkUpgrade) -> Option; - - /// Determines whether the specified network upgrade is active as of the - /// provided block height on the network to which this Parameters value applies. - fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool { - self.activation_height(nu).map_or(false, |h| h <= height) - } - +/// Constants associated with a given Zcash network. +pub trait NetworkConstants: Clone { /// The coin type for ZEC, as defined by [SLIP 44]. /// /// [SLIP 44]: https://github.com/satoshilabs/slips/blob/master/slip-0044.md fn coin_type(&self) -> u32; - /// Returns the standard network constant for address encoding. Returns - /// 'None' for nonstandard networks. - fn address_network(&self) -> Option; - /// Returns the human-readable prefix for Bech32-encoded Sapling extended spending keys - /// the network to which this Parameters value applies. + /// the network to which this NetworkConstants value applies. /// /// Defined in [ZIP 32]. /// /// [`ExtendedSpendingKey`]: zcash_primitives::zip32::ExtendedSpendingKey /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst - fn hrp_sapling_extended_spending_key(&self) -> &str; + fn hrp_sapling_extended_spending_key(&self) -> &'static str; /// Returns the human-readable prefix for Bech32-encoded Sapling extended full - /// viewing keys for the network to which this Parameters value applies. + /// viewing keys for the network to which this NetworkConstants value applies. /// /// Defined in [ZIP 32]. /// /// [`ExtendedFullViewingKey`]: zcash_primitives::zip32::ExtendedFullViewingKey /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst - fn hrp_sapling_extended_full_viewing_key(&self) -> &str; + fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str; /// Returns the Bech32-encoded human-readable prefix for Sapling payment addresses - /// viewing keys for the network to which this Parameters value applies. + /// viewing keys for the network to which this NetworkConstants value applies. /// /// Defined in section 5.6.4 of the [Zcash Protocol Specification]. /// /// [`PaymentAddress`]: zcash_primitives::primitives::PaymentAddress /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf - fn hrp_sapling_payment_address(&self) -> &str; + fn hrp_sapling_payment_address(&self) -> &'static str; + + /// Returns the human-readable prefix for Base58Check-encoded Sprout + /// payment addresses for the network to which this NetworkConstants value + /// applies. + /// + /// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. + /// + /// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding + fn b58_sprout_address_prefix(&self) -> [u8; 2]; /// Returns the human-readable prefix for Base58Check-encoded transparent - /// pay-to-public-key-hash payment addresses for the network to which this Parameters value + /// pay-to-public-key-hash payment addresses for the network to which this NetworkConstants value /// applies. /// /// [`TransparentAddress::PublicKey`]: zcash_primitives::legacy::TransparentAddress::PublicKey fn b58_pubkey_address_prefix(&self) -> [u8; 2]; /// Returns the human-readable prefix for Base58Check-encoded transparent pay-to-script-hash - /// payment addresses for the network to which this Parameters value applies. + /// payment addresses for the network to which this NetworkConstants value applies. /// /// [`TransparentAddress::Script`]: zcash_primitives::legacy::TransparentAddress::Script fn b58_script_address_prefix(&self) -> [u8; 2]; } +/// The enumeration of known Zcash network types. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] +pub enum NetworkType { + /// Zcash Mainnet. + Main, + /// Zcash Testnet. + Test, + /// Private integration / regression testing, used in `zcashd`. + /// + /// For some address types there is no distinction between test and regtest encodings; + /// those will always be parsed as `Network::Test`. + Regtest, +} + +memuse::impl_no_dynamic_usage!(NetworkType); + +impl NetworkConstants for NetworkType { + fn coin_type(&self) -> u32 { + match self { + NetworkType::Main => mainnet::COIN_TYPE, + NetworkType::Test => testnet::COIN_TYPE, + NetworkType::Regtest => regtest::COIN_TYPE, + } + } + + fn hrp_sapling_extended_spending_key(&self) -> &'static str { + match self { + NetworkType::Main => mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, + NetworkType::Test => testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY, + NetworkType::Regtest => regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY, + } + } + + fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str { + match self { + NetworkType::Main => mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + NetworkType::Test => testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + NetworkType::Regtest => regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY, + } + } + + fn hrp_sapling_payment_address(&self) -> &'static str { + match self { + NetworkType::Main => mainnet::HRP_SAPLING_PAYMENT_ADDRESS, + NetworkType::Test => testnet::HRP_SAPLING_PAYMENT_ADDRESS, + NetworkType::Regtest => regtest::HRP_SAPLING_PAYMENT_ADDRESS, + } + } + + fn b58_sprout_address_prefix(&self) -> [u8; 2] { + match self { + NetworkType::Main => mainnet::B58_SPROUT_ADDRESS_PREFIX, + NetworkType::Test => testnet::B58_SPROUT_ADDRESS_PREFIX, + NetworkType::Regtest => regtest::B58_SPROUT_ADDRESS_PREFIX, + } + } + + fn b58_pubkey_address_prefix(&self) -> [u8; 2] { + match self { + NetworkType::Main => mainnet::B58_PUBKEY_ADDRESS_PREFIX, + NetworkType::Test => testnet::B58_PUBKEY_ADDRESS_PREFIX, + NetworkType::Regtest => regtest::B58_PUBKEY_ADDRESS_PREFIX, + } + } + + fn b58_script_address_prefix(&self) -> [u8; 2] { + match self { + NetworkType::Main => mainnet::B58_SCRIPT_ADDRESS_PREFIX, + NetworkType::Test => testnet::B58_SCRIPT_ADDRESS_PREFIX, + NetworkType::Regtest => regtest::B58_SCRIPT_ADDRESS_PREFIX, + } + } +} + +/// Zcash consensus parameters. +pub trait Parameters: Clone { + /// Returns the type of network configured by this set of consensus parameters. + fn network_type(&self) -> NetworkType; + + /// Returns the activation height for a particular network upgrade, + /// if an activation height has been set. + fn activation_height(&self, nu: NetworkUpgrade) -> Option; + + /// Determines whether the specified network upgrade is active as of the + /// provided block height on the network to which this Parameters value applies. + fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool { + self.activation_height(nu).map_or(false, |h| h <= height) + } +} + +impl NetworkConstants for P { + fn coin_type(&self) -> u32 { + self.network_type().coin_type() + } + + fn hrp_sapling_extended_spending_key(&self) -> &'static str { + self.network_type().hrp_sapling_extended_spending_key() + } + + fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str { + self.network_type().hrp_sapling_extended_full_viewing_key() + } + + fn hrp_sapling_payment_address(&self) -> &'static str { + self.network_type().hrp_sapling_payment_address() + } + + fn b58_sprout_address_prefix(&self) -> [u8; 2] { + self.network_type().b58_sprout_address_prefix() + } + + fn b58_pubkey_address_prefix(&self) -> [u8; 2] { + self.network_type().b58_pubkey_address_prefix() + } + + fn b58_script_address_prefix(&self) -> [u8; 2] { + self.network_type().b58_script_address_prefix() + } +} + /// Marker struct for the production network. #[derive(PartialEq, Eq, Copy, Clone, Debug)] pub struct MainNetwork; @@ -208,6 +325,10 @@ memuse::impl_no_dynamic_usage!(MainNetwork); pub const MAIN_NETWORK: MainNetwork = MainNetwork; impl Parameters for MainNetwork { + fn network_type(&self) -> NetworkType { + NetworkType::Main + } + fn activation_height(&self, nu: NetworkUpgrade) -> Option { match nu { NetworkUpgrade::Overwinter => Some(BlockHeight(347_500)), @@ -222,34 +343,6 @@ impl Parameters for MainNetwork { NetworkUpgrade::ZFuture => None, } } - - fn coin_type(&self) -> u32 { - constants::mainnet::COIN_TYPE - } - - fn address_network(&self) -> Option { - Some(zcash_address::Network::Main) - } - - fn hrp_sapling_extended_spending_key(&self) -> &str { - constants::mainnet::HRP_SAPLING_EXTENDED_SPENDING_KEY - } - - fn hrp_sapling_extended_full_viewing_key(&self) -> &str { - constants::mainnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY - } - - fn hrp_sapling_payment_address(&self) -> &str { - constants::mainnet::HRP_SAPLING_PAYMENT_ADDRESS - } - - fn b58_pubkey_address_prefix(&self) -> [u8; 2] { - constants::mainnet::B58_PUBKEY_ADDRESS_PREFIX - } - - fn b58_script_address_prefix(&self) -> [u8; 2] { - constants::mainnet::B58_SCRIPT_ADDRESS_PREFIX - } } /// Marker struct for the test network. @@ -262,6 +355,10 @@ memuse::impl_no_dynamic_usage!(TestNetwork); pub const TEST_NETWORK: TestNetwork = TestNetwork; impl Parameters for TestNetwork { + fn network_type(&self) -> NetworkType { + NetworkType::Test + } + fn activation_height(&self, nu: NetworkUpgrade) -> Option { match nu { NetworkUpgrade::Overwinter => Some(BlockHeight(207_500)), @@ -276,99 +373,38 @@ impl Parameters for TestNetwork { NetworkUpgrade::ZFuture => None, } } - - fn coin_type(&self) -> u32 { - constants::testnet::COIN_TYPE - } - - fn address_network(&self) -> Option { - Some(zcash_address::Network::Test) - } - - fn hrp_sapling_extended_spending_key(&self) -> &str { - constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY - } - - fn hrp_sapling_extended_full_viewing_key(&self) -> &str { - constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY - } - - fn hrp_sapling_payment_address(&self) -> &str { - constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS - } - - fn b58_pubkey_address_prefix(&self) -> [u8; 2] { - constants::testnet::B58_PUBKEY_ADDRESS_PREFIX - } - - fn b58_script_address_prefix(&self) -> [u8; 2] { - constants::testnet::B58_SCRIPT_ADDRESS_PREFIX - } } -/// Marker enum for the deployed Zcash consensus networks. -#[derive(PartialEq, Eq, Copy, Clone, Debug)] +/// The enumeration of known Zcash networks. +#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)] pub enum Network { + /// Zcash Mainnet. MainNetwork, + /// Zcash Testnet. TestNetwork, + /// Private integration / regression testing, used in `zcashd`. + #[cfg(feature = "local-consensus")] + Regtest(LocalNetwork), } memuse::impl_no_dynamic_usage!(Network); impl Parameters for Network { - fn activation_height(&self, nu: NetworkUpgrade) -> Option { - match self { - Network::MainNetwork => MAIN_NETWORK.activation_height(nu), - Network::TestNetwork => TEST_NETWORK.activation_height(nu), - } - } - - fn coin_type(&self) -> u32 { - match self { - Network::MainNetwork => MAIN_NETWORK.coin_type(), - Network::TestNetwork => TEST_NETWORK.coin_type(), - } - } - - fn address_network(&self) -> Option { - match self { - Network::MainNetwork => Some(zcash_address::Network::Main), - Network::TestNetwork => Some(zcash_address::Network::Test), - } - } - - fn hrp_sapling_extended_spending_key(&self) -> &str { - match self { - Network::MainNetwork => MAIN_NETWORK.hrp_sapling_extended_spending_key(), - Network::TestNetwork => TEST_NETWORK.hrp_sapling_extended_spending_key(), - } - } - - fn hrp_sapling_extended_full_viewing_key(&self) -> &str { - match self { - Network::MainNetwork => MAIN_NETWORK.hrp_sapling_extended_full_viewing_key(), - Network::TestNetwork => TEST_NETWORK.hrp_sapling_extended_full_viewing_key(), - } - } - - fn hrp_sapling_payment_address(&self) -> &str { - match self { - Network::MainNetwork => MAIN_NETWORK.hrp_sapling_payment_address(), - Network::TestNetwork => TEST_NETWORK.hrp_sapling_payment_address(), - } - } - - fn b58_pubkey_address_prefix(&self) -> [u8; 2] { + fn network_type(&self) -> NetworkType { match self { - Network::MainNetwork => MAIN_NETWORK.b58_pubkey_address_prefix(), - Network::TestNetwork => TEST_NETWORK.b58_pubkey_address_prefix(), + Network::MainNetwork => NetworkType::Main, + Network::TestNetwork => NetworkType::Test, + #[cfg(feature = "local-consensus")] + Network::Regtest(_) => NetworkType::Regtest, } } - fn b58_script_address_prefix(&self) -> [u8; 2] { + fn activation_height(&self, nu: NetworkUpgrade) -> Option { match self { - Network::MainNetwork => MAIN_NETWORK.b58_script_address_prefix(), - Network::TestNetwork => TEST_NETWORK.b58_script_address_prefix(), + Network::MainNetwork => MAIN_NETWORK.activation_height(nu), + Network::TestNetwork => TEST_NETWORK.activation_height(nu), + #[cfg(feature = "local-consensus")] + Network::Regtest(network_params) => network_params.activation_height(nu), } } } diff --git a/components/zcash_protocol/src/constants/mainnet.rs b/components/zcash_protocol/src/constants/mainnet.rs index bd31a895eb..467f22df5a 100644 --- a/components/zcash_protocol/src/constants/mainnet.rs +++ b/components/zcash_protocol/src/constants/mainnet.rs @@ -29,6 +29,13 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviews"; /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zs"; +/// The prefix for a Base58Check-encoded mainnet Sprout address +/// +/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. +/// +/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding +pub const B58_SPROUT_ADDRESS_PREFIX: [u8; 2] = [0x16, 0x9a]; + /// The prefix for a Base58Check-encoded mainnet [`PublicKeyHash`]. /// /// [`PublicKeyHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html diff --git a/components/zcash_protocol/src/constants/regtest.rs b/components/zcash_protocol/src/constants/regtest.rs index 7ae37dec53..cd793f6c7c 100644 --- a/components/zcash_protocol/src/constants/regtest.rs +++ b/components/zcash_protocol/src/constants/regtest.rs @@ -33,6 +33,14 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewregtestsapling"; /// [the `zcashd` codebase]: pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zregtestsapling"; +/// The prefix for a Base58Check-encoded regtest Sprout address +/// +/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. +/// Same as the testnet prefix. +/// +/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding +pub const B58_SPROUT_ADDRESS_PREFIX: [u8; 2] = [0x16, 0xb6]; + /// The prefix for a Base58Check-encoded regtest transparent [`PublicKeyHash`]. /// Same as the testnet prefix. /// diff --git a/components/zcash_protocol/src/constants/testnet.rs b/components/zcash_protocol/src/constants/testnet.rs index dba86bb9c7..7bf3309691 100644 --- a/components/zcash_protocol/src/constants/testnet.rs +++ b/components/zcash_protocol/src/constants/testnet.rs @@ -29,6 +29,13 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewtestsapling"; /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "ztestsapling"; +/// The prefix for a Base58Check-encoded testnet Sprout address +/// +/// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. +/// +/// [sproutpaymentaddrencoding]: https://zips.z.cash/protocol/protocol.pdf#sproutpaymentaddrencoding +pub const B58_SPROUT_ADDRESS_PREFIX: [u8; 2] = [0x16, 0xb6]; + /// The prefix for a Base58Check-encoded testnet transparent [`PublicKeyHash`]. /// /// [`PublicKeyHash`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/legacy/enum.TransparentAddress.html diff --git a/components/zcash_protocol/src/local_consensus.rs b/components/zcash_protocol/src/local_consensus.rs index 8c4b647cc1..cf4dffe606 100644 --- a/components/zcash_protocol/src/local_consensus.rs +++ b/components/zcash_protocol/src/local_consensus.rs @@ -1,7 +1,4 @@ -use crate::{ - consensus::{BlockHeight, NetworkUpgrade, Parameters}, - constants, -}; +use crate::consensus::{BlockHeight, NetworkType, NetworkUpgrade, Parameters}; /// a `LocalNetwork` setup should define the activation heights /// of network upgrades. `None` is considered as "not activated" @@ -36,7 +33,7 @@ use crate::{ /// }; /// ``` /// -#[derive(Clone, PartialEq, Eq, Copy, Debug)] +#[derive(Clone, PartialEq, Eq, Copy, Debug, Hash)] pub struct LocalNetwork { pub overwinter: Option, pub sapling: Option, @@ -44,18 +41,18 @@ pub struct LocalNetwork { pub heartwood: Option, pub canopy: Option, pub nu5: Option, + #[cfg(feature = "unstable-nu6")] pub nu6: Option, #[cfg(feature = "zfuture")] pub z_future: Option, } -/// Parameters default implementation for `LocalNetwork` -/// Important note: -/// The functions `coin_type()`, `address_network()`, -/// `hrp_sapling_extended_spending_key()`, `hrp_sapling_extended_full_viewing_key()`, -/// `hrp_sapling_payment_address()`, `b58_script_address_prefix()` return -/// `constants::regtest` values +/// Parameters implementation for `LocalNetwork` impl Parameters for LocalNetwork { + fn network_type(&self) -> NetworkType { + NetworkType::Regtest + } + fn activation_height(&self, nu: NetworkUpgrade) -> Option { match nu { NetworkUpgrade::Overwinter => self.overwinter, @@ -70,44 +67,12 @@ impl Parameters for LocalNetwork { NetworkUpgrade::ZFuture => self.z_future, } } - - fn coin_type(&self) -> u32 { - constants::regtest::COIN_TYPE - } - - fn address_network(&self) -> Option { - Some(zcash_address::Network::Regtest) - } - - fn hrp_sapling_extended_spending_key(&self) -> &str { - constants::regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY - } - - fn hrp_sapling_extended_full_viewing_key(&self) -> &str { - constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY - } - - fn hrp_sapling_payment_address(&self) -> &str { - constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS - } - - fn b58_pubkey_address_prefix(&self) -> [u8; 2] { - constants::regtest::B58_PUBKEY_ADDRESS_PREFIX - } - - fn b58_script_address_prefix(&self) -> [u8; 2] { - constants::regtest::B58_SCRIPT_ADDRESS_PREFIX - } - - fn is_nu_active(&self, nu: NetworkUpgrade, height: BlockHeight) -> bool { - self.activation_height(nu).map_or(false, |h| h <= height) - } } #[cfg(test)] mod tests { use crate::{ - consensus::{BlockHeight, NetworkUpgrade, Parameters}, + consensus::{BlockHeight, NetworkConstants, NetworkUpgrade, Parameters}, constants, local_consensus::LocalNetwork, }; @@ -148,24 +113,6 @@ mod tests { assert!(regtest.is_nu_active(NetworkUpgrade::Nu6, expected_nu6)); #[cfg(feature = "zfuture")] assert!(!regtest.is_nu_active(NetworkUpgrade::ZFuture, expected_nu5)); - - assert_eq!(regtest.coin_type(), constants::regtest::COIN_TYPE); - assert_eq!( - regtest.hrp_sapling_extended_spending_key(), - constants::regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY - ); - assert_eq!( - regtest.hrp_sapling_extended_full_viewing_key(), - constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY - ); - assert_eq!( - regtest.hrp_sapling_payment_address(), - constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS - ); - assert_eq!( - regtest.b58_pubkey_address_prefix(), - constants::regtest::B58_PUBKEY_ADDRESS_PREFIX - ); } #[test] @@ -251,25 +198,30 @@ mod tests { z_future: Some(expected_z_future), }; - assert_eq!(regtest.coin_type(), constants::regtest::COIN_TYPE); assert_eq!( - regtest.hrp_sapling_extended_spending_key(), + regtest.network_type().coin_type(), + constants::regtest::COIN_TYPE + ); + assert_eq!( + regtest.network_type().hrp_sapling_extended_spending_key(), constants::regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY ); assert_eq!( - regtest.hrp_sapling_extended_full_viewing_key(), + regtest + .network_type() + .hrp_sapling_extended_full_viewing_key(), constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY ); assert_eq!( - regtest.hrp_sapling_payment_address(), + regtest.network_type().hrp_sapling_payment_address(), constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS ); assert_eq!( - regtest.b58_pubkey_address_prefix(), + regtest.network_type().b58_pubkey_address_prefix(), constants::regtest::B58_PUBKEY_ADDRESS_PREFIX ); assert_eq!( - regtest.b58_script_address_prefix(), + regtest.network_type().b58_script_address_prefix(), constants::regtest::B58_SCRIPT_ADDRESS_PREFIX ); } diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index a3e8e0093c..82b68b9f15 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -16,10 +16,10 @@ use nom::{ sequence::preceded, }; use zcash_primitives::{ - consensus, memo::{self, MemoBytes}, transaction::components::amount::NonNegativeAmount, }; +use zcash_protocol::consensus; use crate::address::Address; @@ -819,10 +819,10 @@ mod tests { use zcash_keys::address::testing::arb_addr; use zcash_primitives::{ - consensus::{Parameters, TEST_NETWORK}, memo::Memo, transaction::components::amount::{testing::arb_nonnegative_amount, NonNegativeAmount}, }; + use zcash_protocol::consensus::{NetworkConstants, NetworkType, TEST_NETWORK}; #[cfg(feature = "local-consensus")] use zcash_primitives::{local_consensus::LocalNetwork, BlockHeight}; @@ -870,7 +870,7 @@ mod tests { let expected = TransactionRequest::new( vec![ Payment { - recipient_address: Address::Sapling(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()), + recipient_address: Address::Sapling(decode_payment_address(NetworkType::Test.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()), amount: NonNegativeAmount::const_from_u64(376876902796286), memo: None, label: None, @@ -891,7 +891,7 @@ mod tests { let expected = TransactionRequest::new( vec![ Payment { - recipient_address: Address::Sapling(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()), + recipient_address: Address::Sapling(decode_payment_address(NetworkType::Test.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()), amount: NonNegativeAmount::ZERO, memo: None, label: None, @@ -909,7 +909,7 @@ mod tests { let req = TransactionRequest::new( vec![ Payment { - recipient_address: Address::Sapling(decode_payment_address(TEST_NETWORK.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()), + recipient_address: Address::Sapling(decode_payment_address(NetworkType::Test.hrp_sapling_payment_address(), "ztestsapling1n65uaftvs2g7075q2x2a04shfk066u3lldzxsrprfrqtzxnhc9ps73v4lhx4l9yfxj46sl0q90k").unwrap()), amount: NonNegativeAmount::ZERO, memo: None, label: None, diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 54f311a584..75cada3f17 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -1330,6 +1330,8 @@ mod tests { #[cfg(feature = "unstable")] #[test] pub(crate) fn fsblockdb_api() { + use zcash_primitives::consensus::NetworkConstants; + let mut st = TestBuilder::new().with_fs_block_cache().build(); // The BlockMeta DB starts off empty. @@ -1338,7 +1340,11 @@ mod tests { // Generate some fake CompactBlocks. let seed = [0u8; 32]; let account = AccountId::ZERO; - let extsk = sapling::spending_key(&seed, st.wallet().params.coin_type(), account); + let extsk = sapling::spending_key( + &seed, + st.wallet().params.network_type().coin_type(), + account, + ); let dfvk = extsk.to_diversifiable_full_viewing_key(); let (h1, meta1, _) = st.generate_next_block( &dfvk, diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 14bf46d30f..9493149593 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -176,7 +176,9 @@ mod tests { use ::sapling::zip32::ExtendedFullViewingKey; use zcash_primitives::{ - consensus::{self, BlockHeight, BranchId, Network, NetworkUpgrade, Parameters}, + consensus::{ + self, BlockHeight, BranchId, Network, NetworkConstants, NetworkUpgrade, Parameters, + }, transaction::{TransactionData, TxVersion}, zip32::AccountId, }; @@ -698,11 +700,13 @@ mod tests { )?; let address = encode_payment_address( - wdb.params.hrp_sapling_payment_address(), + wdb.params.network_type().hrp_sapling_payment_address(), &extfvk.default_address().1, ); let extfvk = encode_extended_full_viewing_key( - wdb.params.hrp_sapling_extended_full_viewing_key(), + wdb.params + .network_type() + .hrp_sapling_extended_full_viewing_key(), extfvk, ); wdb.conn.execute( @@ -723,7 +727,8 @@ mod tests { let seed = [0xab; 32]; let account = AccountId::ZERO; - let secret_key = sapling::spending_key(&seed, db_data.params.coin_type(), account); + let secret_key = + sapling::spending_key(&seed, db_data.params.network_type().coin_type(), account); let extfvk = secret_key.to_extended_full_viewing_key(); init_0_3_0(&mut db_data, &extfvk, account).unwrap(); @@ -835,11 +840,13 @@ mod tests { )?; let address = encode_payment_address( - wdb.params.hrp_sapling_payment_address(), + wdb.params.network_type().hrp_sapling_payment_address(), &extfvk.default_address().1, ); let extfvk = encode_extended_full_viewing_key( - wdb.params.hrp_sapling_extended_full_viewing_key(), + wdb.params + .network_type() + .hrp_sapling_extended_full_viewing_key(), extfvk, ); wdb.conn.execute( @@ -894,7 +901,8 @@ mod tests { let seed = [0xab; 32]; let account = AccountId::ZERO; - let secret_key = sapling::spending_key(&seed, db_data.params.coin_type(), account); + let secret_key = + sapling::spending_key(&seed, db_data.params.network_type().coin_type(), account); let extfvk = secret_key.to_extended_full_viewing_key(); init_autoshielding(&mut db_data, &extfvk, account).unwrap(); diff --git a/zcash_extensions/src/transparent/demo.rs b/zcash_extensions/src/transparent/demo.rs index 96f2d9f6fa..6f26629932 100644 --- a/zcash_extensions/src/transparent/demo.rs +++ b/zcash_extensions/src/transparent/demo.rs @@ -484,8 +484,7 @@ mod tests { use sapling::{zip32::ExtendedSpendingKey, Node, Rseed}; use zcash_primitives::{ - consensus::{BlockHeight, BranchId, NetworkUpgrade, Parameters}, - constants, + consensus::{BlockHeight, BranchId, NetworkType, NetworkUpgrade, Parameters}, extensions::transparent::{self as tze, Extension, FromPayload, ToPayload}, legacy::TransparentAddress, transaction::{ @@ -520,34 +519,11 @@ mod tests { } } - fn address_network(&self) -> Option { - None - } - - fn coin_type(&self) -> u32 { - constants::testnet::COIN_TYPE - } - - fn hrp_sapling_extended_spending_key(&self) -> &str { - constants::testnet::HRP_SAPLING_EXTENDED_SPENDING_KEY - } - - fn hrp_sapling_extended_full_viewing_key(&self) -> &str { - constants::testnet::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY - } - - fn hrp_sapling_payment_address(&self) -> &str { - constants::testnet::HRP_SAPLING_PAYMENT_ADDRESS - } - - fn b58_pubkey_address_prefix(&self) -> [u8; 2] { - constants::testnet::B58_PUBKEY_ADDRESS_PREFIX - } - - fn b58_script_address_prefix(&self) -> [u8; 2] { - constants::testnet::B58_SCRIPT_ADDRESS_PREFIX + fn network_type(&self) -> NetworkType { + NetworkType::Test } } + fn demo_hashes(preimage_1: &[u8; 32], preimage_2: &[u8; 32]) -> ([u8; 32], [u8; 32]) { let hash_2 = { let mut hash = [0; 32]; diff --git a/zcash_keys/src/address.rs b/zcash_keys/src/address.rs index 2dfed4fa9f..dd1fb9ac43 100644 --- a/zcash_keys/src/address.rs +++ b/zcash_keys/src/address.rs @@ -2,9 +2,10 @@ use zcash_address::{ unified::{self, Container, Encoding, Typecode}, - ConversionError, Network, ToAddress, TryFromRawAddress, ZcashAddress, + ConversionError, ToAddress, TryFromRawAddress, ZcashAddress, }; -use zcash_primitives::{consensus, legacy::TransparentAddress}; +use zcash_primitives::legacy::TransparentAddress; +use zcash_protocol::consensus::{self, NetworkType}; #[cfg(feature = "sapling")] use sapling::PaymentAddress; @@ -172,7 +173,7 @@ impl UnifiedAddress { &self.unknown } - fn to_address(&self, net: Network) -> ZcashAddress { + fn to_address(&self, net: NetworkType) -> ZcashAddress { let items = self .unknown .iter() @@ -209,8 +210,7 @@ impl UnifiedAddress { /// Returns the string encoding of this `UnifiedAddress` for the given network. pub fn encode(&self, params: &P) -> String { - self.to_address(params.address_network().expect("Unrecognized network")) - .to_string() + self.to_address(params.network_type()).to_string() } /// Returns the set of receiver typecodes. @@ -292,12 +292,11 @@ impl TryFromRawAddress for Address { impl Address { pub fn decode(params: &P, s: &str) -> Option { let addr = ZcashAddress::try_from_encoded(s).ok()?; - addr.convert_if_network(params.address_network().expect("Unrecognized network")) - .ok() + addr.convert_if_network(params.network_type()).ok() } pub fn encode(&self, params: &P) -> String { - let net = params.address_network().expect("Unrecognized network"); + let net = params.network_type(); match self { #[cfg(feature = "sapling")] diff --git a/zcash_keys/src/encoding.rs b/zcash_keys/src/encoding.rs index 7a280178c6..cb1fef8763 100644 --- a/zcash_keys/src/encoding.rs +++ b/zcash_keys/src/encoding.rs @@ -6,6 +6,7 @@ use crate::address::UnifiedAddress; use bs58::{self, decode::Error as Bs58Error}; use std::fmt; +use zcash_primitives::consensus::NetworkConstants; use zcash_address::unified::{self, Encoding}; use zcash_primitives::{consensus, legacy::TransparentAddress}; @@ -130,16 +131,16 @@ impl AddressCodec

for TransparentAddress { fn encode(&self, params: &P) -> String { encode_transparent_address( - ¶ms.b58_pubkey_address_prefix(), - ¶ms.b58_script_address_prefix(), + ¶ms.network_type().b58_pubkey_address_prefix(), + ¶ms.network_type().b58_script_address_prefix(), self, ) } fn decode(params: &P, address: &str) -> Result { decode_transparent_address( - ¶ms.b58_pubkey_address_prefix(), - ¶ms.b58_script_address_prefix(), + ¶ms.network_type().b58_pubkey_address_prefix(), + ¶ms.network_type().b58_script_address_prefix(), address, ) .map_err(TransparentCodecError::Base58) @@ -154,11 +155,11 @@ impl AddressCodec

for sapling::PaymentAddress { type Error = Bech32DecodeError; fn encode(&self, params: &P) -> String { - encode_payment_address(params.hrp_sapling_payment_address(), self) + encode_payment_address(params.network_type().hrp_sapling_payment_address(), self) } fn decode(params: &P, address: &str) -> Result { - decode_payment_address(params.hrp_sapling_payment_address(), address) + decode_payment_address(params.network_type().hrp_sapling_payment_address(), address) } } @@ -173,7 +174,7 @@ impl AddressCodec

for UnifiedAddress { unified::Address::decode(address) .map_err(|e| format!("{}", e)) .and_then(|(network, addr)| { - if params.address_network() == Some(network) { + if params.network_type() == network { UnifiedAddress::try_from(addr).map_err(|e| e.to_owned()) } else { Err(format!( @@ -298,7 +299,7 @@ pub fn encode_payment_address_p( params: &P, addr: &sapling::PaymentAddress, ) -> String { - encode_payment_address(params.hrp_sapling_payment_address(), addr) + encode_payment_address(params.network_type().hrp_sapling_payment_address(), addr) } /// Decodes a [`PaymentAddress`] from a Bech32-encoded string. @@ -312,7 +313,7 @@ pub fn encode_payment_address_p( /// encoding::decode_payment_address, /// }; /// use zcash_primitives::{ -/// consensus::{TEST_NETWORK, Parameters}, +/// consensus::{TEST_NETWORK, NetworkConstants, Parameters}, /// }; /// /// let pa = PaymentAddress::from_bytes(&[ @@ -325,7 +326,7 @@ pub fn encode_payment_address_p( /// /// assert_eq!( /// decode_payment_address( -/// TEST_NETWORK.hrp_sapling_payment_address(), +/// TEST_NETWORK.network_type().hrp_sapling_payment_address(), /// "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk", /// ), /// Ok(pa), @@ -357,14 +358,14 @@ pub fn decode_payment_address( /// encoding::encode_transparent_address, /// }; /// use zcash_primitives::{ -/// consensus::{TEST_NETWORK, Parameters}, +/// consensus::{TEST_NETWORK, NetworkConstants, Parameters}, /// legacy::TransparentAddress, /// }; /// /// assert_eq!( /// encode_transparent_address( -/// &TEST_NETWORK.b58_pubkey_address_prefix(), -/// &TEST_NETWORK.b58_script_address_prefix(), +/// &TEST_NETWORK.network_type().b58_pubkey_address_prefix(), +/// &TEST_NETWORK.network_type().b58_script_address_prefix(), /// &TransparentAddress::PublicKeyHash([0; 20]), /// ), /// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma", @@ -372,8 +373,8 @@ pub fn decode_payment_address( /// /// assert_eq!( /// encode_transparent_address( -/// &TEST_NETWORK.b58_pubkey_address_prefix(), -/// &TEST_NETWORK.b58_script_address_prefix(), +/// &TEST_NETWORK.network_type().b58_pubkey_address_prefix(), +/// &TEST_NETWORK.network_type().b58_script_address_prefix(), /// &TransparentAddress::ScriptHash([0; 20]), /// ), /// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2", @@ -410,8 +411,8 @@ pub fn encode_transparent_address_p( addr: &TransparentAddress, ) -> String { encode_transparent_address( - ¶ms.b58_pubkey_address_prefix(), - ¶ms.b58_script_address_prefix(), + ¶ms.network_type().b58_pubkey_address_prefix(), + ¶ms.network_type().b58_script_address_prefix(), addr, ) } @@ -422,17 +423,17 @@ pub fn encode_transparent_address_p( /// /// ``` /// use zcash_primitives::{ -/// consensus::{TEST_NETWORK, Parameters}, +/// consensus::{TEST_NETWORK, NetworkConstants, Parameters}, +/// legacy::TransparentAddress, /// }; /// use zcash_keys::{ /// encoding::decode_transparent_address, /// }; -/// use zcash_primitives::legacy::TransparentAddress; /// /// assert_eq!( /// decode_transparent_address( -/// &TEST_NETWORK.b58_pubkey_address_prefix(), -/// &TEST_NETWORK.b58_script_address_prefix(), +/// &TEST_NETWORK.network_type().b58_pubkey_address_prefix(), +/// &TEST_NETWORK.network_type().b58_script_address_prefix(), /// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma", /// ), /// Ok(Some(TransparentAddress::PublicKeyHash([0; 20]))), @@ -440,8 +441,8 @@ pub fn encode_transparent_address_p( /// /// assert_eq!( /// decode_transparent_address( -/// &TEST_NETWORK.b58_pubkey_address_prefix(), -/// &TEST_NETWORK.b58_script_address_prefix(), +/// &TEST_NETWORK.network_type().b58_pubkey_address_prefix(), +/// &TEST_NETWORK.network_type().b58_script_address_prefix(), /// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2", /// ), /// Ok(Some(TransparentAddress::ScriptHash([0; 20]))), diff --git a/zcash_keys/src/keys.rs b/zcash_keys/src/keys.rs index 3515f4cbf9..6995f8c8be 100644 --- a/zcash_keys/src/keys.rs +++ b/zcash_keys/src/keys.rs @@ -1,9 +1,7 @@ //! Helper functions for managing light client key material. use zcash_address::unified::{self, Container, Encoding, Typecode}; -use zcash_primitives::{ - consensus, - zip32::{AccountId, DiversifierIndex}, -}; +use zcash_protocol::consensus::{self, NetworkConstants}; +use zip32::{AccountId, DiversifierIndex}; use crate::address::UnifiedAddress; @@ -190,11 +188,11 @@ impl UnifiedSpendingKey { transparent: legacy::AccountPrivKey::from_seed(_params, seed, _account) .map_err(DerivationError::Transparent)?, #[cfg(feature = "sapling")] - sapling: sapling::spending_key(seed, _params.coin_type(), _account), + sapling: sapling::spending_key(seed, _params.network_type().coin_type(), _account), #[cfg(feature = "orchard")] orchard: orchard::keys::SpendingKey::from_zip32_seed( seed, - _params.coin_type(), + _params.network_type().coin_type(), _account, ) .map_err(DerivationError::Orchard)?, @@ -554,7 +552,7 @@ impl UnifiedFullViewingKey { /// [ZIP 316]: https://zips.z.cash/zip-0316 pub fn decode(params: &P, encoding: &str) -> Result { let (net, ufvk) = unified::Ufvk::decode(encoding).map_err(|e| e.to_string())?; - let expected_net = params.address_network().expect("Unrecognized network"); + let expected_net = params.network_type(); if net != expected_net { return Err(format!( "UFVK is for network {:?} but we expected {:?}", @@ -663,7 +661,7 @@ impl UnifiedFullViewingKey { let ufvk = unified::Ufvk::try_from_items(items.collect()) .expect("UnifiedFullViewingKey should only be constructed safely"); - ufvk.encode(¶ms.address_network().expect("Unrecognized network")) + ufvk.encode(¶ms.network_type()) } /// Returns the transparent component of the unified key at the diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index ff4f7e946a..19a12e59f7 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -7,9 +7,10 @@ use hdwallet::{ use secp256k1::PublicKey; use sha2::{Digest, Sha256}; use subtle::{Choice, ConstantTimeEq}; -use zcash_spec::PrfExpand; -use crate::{consensus, zip32::AccountId}; +use zcash_protocol::consensus::{self, NetworkConstants}; +use zcash_spec::PrfExpand; +use zip32::AccountId; use super::TransparentAddress; @@ -119,7 +120,9 @@ impl AccountPrivKey { ) -> Result { ExtendedPrivKey::with_seed(seed)? .derive_private_key(KeyIndex::hardened_from_normalize_index(44)?)? - .derive_private_key(KeyIndex::hardened_from_normalize_index(params.coin_type())?)? + .derive_private_key(KeyIndex::hardened_from_normalize_index( + params.network_type().coin_type(), + )?)? .derive_private_key(KeyIndex::hardened_from_normalize_index(account.into())?) .map(AccountPrivKey) } From a823ed776f51f3e903a7841f79532629a72369f7 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Mon, 5 Feb 2024 17:59:55 -0700 Subject: [PATCH 10/18] Expose address generation errors when constructing default addresses --- zcash_client_backend/src/zip321.rs | 8 +- zcash_client_sqlite/CHANGELOG.md | 6 ++ zcash_client_sqlite/src/error.rs | 14 +++- zcash_client_sqlite/src/lib.rs | 28 ++++--- zcash_client_sqlite/src/wallet.rs | 4 +- zcash_client_sqlite/src/wallet/init.rs | 9 ++- .../init/migrations/add_transaction_views.rs | 12 +-- .../wallet/init/migrations/addresses_table.rs | 24 +++--- .../wallet/init/migrations/ufvk_support.rs | 10 ++- zcash_keys/CHANGELOG.md | 9 +++ zcash_keys/src/keys.rs | 74 ++++++++++++++++--- 11 files changed, 151 insertions(+), 47 deletions(-) diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index 82b68b9f15..1faf3ce2a8 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -306,8 +306,8 @@ impl TransactionRequest { /// Parse the provided URI to a payment request value. pub fn from_uri(params: &P, uri: &str) -> Result { // Parse the leading zcash:

- let (rest, primary_addr_param) = - parse::lead_addr(params)(uri).map_err(|e| Zip321Error::ParseError(e.to_string()))?; + let (rest, primary_addr_param) = parse::lead_addr(params)(uri) + .map_err(|e| Zip321Error::ParseError(format!("Error parsing lead address: {}", e)))?; // Parse the remaining parameters as an undifferentiated list let (_, xs) = if rest.is_empty() { @@ -317,7 +317,9 @@ impl TransactionRequest { char('?'), separated_list0(char('&'), parse::zcashparam(params)), ))(rest) - .map_err(|e| Zip321Error::ParseError(e.to_string()))? + .map_err(|e| { + Zip321Error::ParseError(format!("Error parsing query parameters: {}", e)) + })? }; // Construct sets of payment parameters, keyed by the payment index. diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index f8d353446e..524d8dfb64 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -10,6 +10,12 @@ and this library adheres to Rust's notion of ### Added - A new `orchard` feature flag has been added to make it possible to build client code without `orchard` dependendencies. +- `impl From for SqliteClientError` + +### Changed +- `zcash_client_sqlite::error::SqliteClientError` has changed variants: + - Added `AddressGeneration` + - Removed `DiversifierIndexOutOfRange` ## [0.9.0] - 2024-03-01 diff --git a/zcash_client_sqlite/src/error.rs b/zcash_client_sqlite/src/error.rs index c1993c39b8..7ec27b5d4d 100644 --- a/zcash_client_sqlite/src/error.rs +++ b/zcash_client_sqlite/src/error.rs @@ -8,6 +8,7 @@ use zcash_client_backend::{ encoding::{Bech32DecodeError, TransparentCodecError}, PoolType, }; +use zcash_keys::keys::AddressGenerationError; use zcash_primitives::{ consensus::BlockHeight, transaction::components::amount::BalanceError, zip32::AccountId, }; @@ -68,8 +69,8 @@ pub enum SqliteClientError { /// this error is (safe rewind height, requested height). RequestedRewindInvalid(BlockHeight, BlockHeight), - /// The space of allocatable diversifier indices has been exhausted for the given account. - DiversifierIndexOutOfRange, + /// An error occurred in generating a Zcash address. + AddressGeneration(AddressGenerationError), /// The account for which information was requested does not belong to the wallet. AccountUnknown(AccountId), @@ -119,6 +120,7 @@ impl error::Error for SqliteClientError { SqliteClientError::DbError(e) => Some(e), SqliteClientError::Io(e) => Some(e), SqliteClientError::BalanceError(e) => Some(e), + SqliteClientError::AddressGeneration(e) => Some(e), _ => None, } } @@ -146,7 +148,7 @@ impl fmt::Display for SqliteClientError { SqliteClientError::InvalidMemo(e) => write!(f, "{}", e), SqliteClientError::BlockConflict(h) => write!(f, "A block hash conflict occurred at height {}; rewind required.", u32::from(*h)), SqliteClientError::NonSequentialBlocks => write!(f, "`put_blocks` requires that the provided block range be sequential"), - SqliteClientError::DiversifierIndexOutOfRange => write!(f, "The space of available diversifier indices is exhausted"), + SqliteClientError::AddressGeneration(e) => write!(f, "{}", e), SqliteClientError::AccountUnknown(acct_id) => write!(f, "Account {} does not belong to this wallet.", u32::from(*acct_id)), SqliteClientError::KeyDerivationError(acct_id) => write!(f, "Key derivation failed for account {}", u32::from(*acct_id)), @@ -217,3 +219,9 @@ impl From for SqliteClientError { SqliteClientError::BalanceError(e) } } + +impl From for SqliteClientError { + fn from(e: AddressGenerationError) -> Self { + SqliteClientError::AddressGeneration(e) + } +} diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 75cada3f17..53872e6e74 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -66,7 +66,9 @@ use zcash_client_backend::{ ScannedBlock, SentTransaction, WalletCommitmentTrees, WalletRead, WalletSummary, WalletWrite, SAPLING_SHARD_HEIGHT, }, - keys::{UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey}, + keys::{ + AddressGenerationError, UnifiedAddressRequest, UnifiedFullViewingKey, UnifiedSpendingKey, + }, proto::compact_formats::CompactBlock, wallet::{Note, NoteId, ReceivedNote, Recipient, WalletTransparentOutput}, DecryptedOutput, ShieldedProtocol, TransferType, @@ -262,10 +264,15 @@ impl, P: consensus::Parameters> WalletRead for W // Keys are not comparable with `Eq`, but addresses are, so we derive what should // be equivalent addresses for each key and use those to check for key equality. UnifiedAddressRequest::all().map_or(Ok(false), |ua_request| { - Ok(usk - .to_unified_full_viewing_key() - .default_address(ua_request) - == ufvk.default_address(ua_request)) + match ( + usk.to_unified_full_viewing_key() + .default_address(ua_request), + ufvk.default_address(ua_request), + ) { + (Ok(a), Ok(b)) => Ok(a == b), + (Err(e), _) => Err(e.into()), + (_, Err(e)) => Err(e.into()), + } }) }) }) @@ -450,17 +457,15 @@ impl WalletWrite for WalletDb let search_from = match wallet::get_current_address(wdb.conn.0, &wdb.params, account)? { Some((_, mut last_diversifier_index)) => { - last_diversifier_index - .increment() - .map_err(|_| SqliteClientError::DiversifierIndexOutOfRange)?; + last_diversifier_index.increment().map_err(|_| { + AddressGenerationError::DiversifierSpaceExhausted + })?; last_diversifier_index } None => DiversifierIndex::default(), }; - let (addr, diversifier_index) = ufvk - .find_address(search_from, request) - .ok_or(SqliteClientError::DiversifierIndexOutOfRange)?; + let (addr, diversifier_index) = ufvk.find_address(search_from, request)?; wallet::insert_address( wdb.conn.0, @@ -1318,6 +1323,7 @@ mod tests { // The receiver for the default UA should be in the set. assert!(receivers.contains_key( ufvk.default_address(DEFAULT_UA_REQUEST) + .expect("A valid default address exists for the UFVK") .0 .transparent() .unwrap() diff --git a/zcash_client_sqlite/src/wallet.rs b/zcash_client_sqlite/src/wallet.rs index f51aa4a53f..29f04f7ea6 100644 --- a/zcash_client_sqlite/src/wallet.rs +++ b/zcash_client_sqlite/src/wallet.rs @@ -263,7 +263,9 @@ pub(crate) fn add_account( } // Always derive the default Unified Address for the account. - let (address, d_idx) = key.default_address(DEFAULT_UA_REQUEST); + let (address, d_idx) = key + .default_address(DEFAULT_UA_REQUEST) + .expect("A valid default address exists for the UFVK"); insert_address(conn, params, account, d_idx, &address)?; Ok(()) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 9493149593..95fd0b0752 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -1015,8 +1015,12 @@ mod tests { )?; let ufvk_str = ufvk.encode(&wdb.params); - let address_str = - Address::Unified(ufvk.default_address(DEFAULT_UA_REQUEST).0).encode(&wdb.params); + let address_str = Address::Unified( + ufvk.default_address(DEFAULT_UA_REQUEST) + .expect("A valid default address exists for the UFVK") + .0, + ) + .encode(&wdb.params); wdb.conn.execute( "INSERT INTO accounts (account, ufvk, address, transparent_address) VALUES (?, ?, ?, '')", @@ -1033,6 +1037,7 @@ mod tests { let taddr = Address::Transparent( *ufvk .default_address(DEFAULT_UA_REQUEST) + .expect("A valid default address exists for the UFVK") .0 .transparent() .unwrap(), diff --git a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs index 5dda3f1977..d3eed031e1 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/add_transaction_views.rs @@ -441,11 +441,13 @@ mod tests { let usk = UnifiedSpendingKey::from_seed(&network, &[0u8; 32][..], AccountId::ZERO).unwrap(); let ufvk = usk.to_unified_full_viewing_key(); - let (ua, _) = ufvk.default_address(UnifiedAddressRequest::unsafe_new( - false, - true, - UA_TRANSPARENT, - )); + let (ua, _) = ufvk + .default_address(UnifiedAddressRequest::unsafe_new( + false, + true, + UA_TRANSPARENT, + )) + .expect("A valid default address exists for the UFVK"); let taddr = ufvk .transparent() .and_then(|k| { diff --git a/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs b/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs index 353da97b27..06c5bf1995 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs @@ -88,11 +88,13 @@ impl RusqliteMigration for Migration

{ "Address in accounts table was not a Unified Address.".to_string(), )); }; - let (expected_address, idx) = ufvk.default_address(UnifiedAddressRequest::unsafe_new( - false, - true, - UA_TRANSPARENT, - )); + let (expected_address, idx) = ufvk + .default_address(UnifiedAddressRequest::unsafe_new( + false, + true, + UA_TRANSPARENT, + )) + .expect("A valid default address exists for the UFVK"); if decoded_address != expected_address { return Err(WalletMigrationError::CorruptedData(format!( "Decoded UA {} does not match the UFVK's default address {} at {:?}.", @@ -162,11 +164,13 @@ impl RusqliteMigration for Migration

{ ], )?; - let (address, d_idx) = ufvk.default_address(UnifiedAddressRequest::unsafe_new( - false, - true, - UA_TRANSPARENT, - )); + let (address, d_idx) = ufvk + .default_address(UnifiedAddressRequest::unsafe_new( + false, + true, + UA_TRANSPARENT, + )) + .expect("A valid default address exists for the UFVK"); insert_address(transaction, &self.params, account, d_idx, &address)?; } diff --git a/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs b/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs index 7e5f7b6e4c..953a110e31 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs @@ -111,7 +111,9 @@ impl RusqliteMigration for Migration

{ "Address field value decoded to a transparent address; should have been Sapling or unified.".to_string())); } Address::Unified(decoded_address) => { - let (expected_address, idx) = ufvk.default_address(ua_request); + let (expected_address, idx) = ufvk + .default_address(ua_request) + .expect("A valid default address exists for the UFVK"); if decoded_address != expected_address { return Err(WalletMigrationError::CorruptedData( format!("Decoded unified address {} does not match the ufvk's default address {} at {:?}.", @@ -123,7 +125,11 @@ impl RusqliteMigration for Migration

{ } let ufvk_str: String = ufvk.encode(&self.params); - let address_str: String = ufvk.default_address(ua_request).0.encode(&self.params); + let address_str: String = ufvk + .default_address(ua_request) + .expect("A valid default address exists for the UFVK") + .0 + .encode(&self.params); // This migration, and the wallet behaviour before it, stored the default // transparent address in the `accounts` table. This does not necessarily diff --git a/zcash_keys/CHANGELOG.md b/zcash_keys/CHANGELOG.md index 1db7830c5f..fd9a178f4d 100644 --- a/zcash_keys/CHANGELOG.md +++ b/zcash_keys/CHANGELOG.md @@ -8,6 +8,15 @@ and this library adheres to Rust's notion of ### Added - `zcash_keys::address::Address::has_receiver` +- `impl Display for zcash_keys::keys::AddressGenerationError` +- `impl std::error::Error for zcash_keys::keys::AddressGenerationError` + +### Changed +- `zcash_keys::keys::AddressGenerationError` has a new variant + `DiversifierSpaceExhausted` +- `zcash_keys::keys::UnifiedFullViewingKey::{find_address, default_address}` + now return `Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError>` + instead of `Option<(UnifiedAddress, DiversifierIndex)>` ## [0.1.1] - 2024-03-04 diff --git a/zcash_keys/src/keys.rs b/zcash_keys/src/keys.rs index 6995f8c8be..35fe68314d 100644 --- a/zcash_keys/src/keys.rs +++ b/zcash_keys/src/keys.rs @@ -1,4 +1,6 @@ //! Helper functions for managing light client key material. +use std::{error, fmt}; + use zcash_address::unified::{self, Container, Encoding, Typecode}; use zcash_protocol::consensus::{self, NetworkConstants}; use zip32::{AccountId, DiversifierIndex}; @@ -402,7 +404,9 @@ impl UnifiedSpendingKey { &self, request: UnifiedAddressRequest, ) -> (UnifiedAddress, DiversifierIndex) { - self.to_unified_full_viewing_key().default_address(request) + self.to_unified_full_viewing_key() + .default_address(request) + .unwrap() } #[cfg(all( @@ -428,6 +432,8 @@ pub enum AddressGenerationError { /// The diversifier index could not be mapped to a valid Sapling diversifier. #[cfg(feature = "sapling")] InvalidSaplingDiversifierIndex(DiversifierIndex), + /// The space of available diversifier indices has been exhausted. + DiversifierSpaceExhausted, /// A requested address typecode was not recognized, so we are unable to generate the address /// as requested. ReceiverTypeNotSupported(Typecode), @@ -439,6 +445,52 @@ pub enum AddressGenerationError { ShieldedReceiverRequired, } +impl fmt::Display for AddressGenerationError { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + match &self { + AddressGenerationError::InvalidTransparentChildIndex(i) => { + write!( + f, + "Child index {:?} does not generate a valid transparent receiver", + i + ) + } + AddressGenerationError::InvalidSaplingDiversifierIndex(i) => { + write!( + f, + "Child index {:?} does not generate a valid Sapling receiver", + i + ) + } + AddressGenerationError::DiversifierSpaceExhausted => { + write!( + f, + "Exhausted the space of diversifier indices without finding an address." + ) + } + AddressGenerationError::ReceiverTypeNotSupported(t) => { + write!( + f, + "Unified Address generation does not yet support receivers of type {:?}.", + t + ) + } + AddressGenerationError::KeyNotAvailable(t) => { + write!( + f, + "The Unified Viewing Key does not contain a key for typecode {:?}.", + t + ) + } + AddressGenerationError::ShieldedReceiverRequired => { + write!(f, "A Unified Address requires at least one shielded (Sapling or Orchard) receiver.") + } + } + } +} + +impl error::Error for AddressGenerationError {} + /// Specification for how a unified address should be generated from a unified viewing key. #[derive(Clone, Copy, Debug)] pub struct UnifiedAddressRequest { @@ -780,13 +832,16 @@ impl UnifiedFullViewingKey { &self, mut j: DiversifierIndex, request: UnifiedAddressRequest, - ) -> Option<(UnifiedAddress, DiversifierIndex)> { + ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> { // If we need to generate a transparent receiver, check that the user has not // specified an invalid transparent child index, from which we can never search to // find a valid index. #[cfg(feature = "transparent-inputs")] - if self.transparent.is_some() && to_transparent_child_index(j).is_none() { - return None; + if request.has_p2pkh + && self.transparent.is_some() + && to_transparent_child_index(j).is_none() + { + return Err(AddressGenerationError::InvalidTransparentChildIndex(j)); } // Find a working diversifier and construct the associated address. @@ -794,16 +849,16 @@ impl UnifiedFullViewingKey { let res = self.address(j, request); match res { Ok(ua) => { - break Some((ua, j)); + return Ok((ua, j)); } #[cfg(feature = "sapling")] Err(AddressGenerationError::InvalidSaplingDiversifierIndex(_)) => { if j.increment().is_err() { - break None; + return Err(AddressGenerationError::DiversifierSpaceExhausted); } } - Err(_) => { - break None; + Err(other) => { + return Err(other); } } } @@ -814,9 +869,8 @@ impl UnifiedFullViewingKey { pub fn default_address( &self, request: UnifiedAddressRequest, - ) -> (UnifiedAddress, DiversifierIndex) { + ) -> Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError> { self.find_address(DiversifierIndex::new(), request) - .expect("UFVK should have at least one valid diversifier") } } From 04fdac79319931bfeefe1f87bfb64bcb03437b1b Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Wed, 7 Feb 2024 10:01:56 -0700 Subject: [PATCH 11/18] zcash_primitives: Move the `memo` module to the `zcash_protocol` crate. --- components/zcash_protocol/src/lib.rs | 1 + {zcash_primitives => components/zcash_protocol}/src/memo.rs | 0 zcash_primitives/CHANGELOG.md | 1 + zcash_primitives/src/lib.rs | 2 +- 4 files changed, 3 insertions(+), 1 deletion(-) rename {zcash_primitives => components/zcash_protocol}/src/memo.rs (100%) diff --git a/components/zcash_protocol/src/lib.rs b/components/zcash_protocol/src/lib.rs index f752dc909d..2976a02643 100644 --- a/components/zcash_protocol/src/lib.rs +++ b/components/zcash_protocol/src/lib.rs @@ -21,6 +21,7 @@ pub mod consensus; pub mod constants; #[cfg(feature = "local-consensus")] pub mod local_consensus; +pub mod memo; pub mod value; /// A Zcash shielded transfer protocol. diff --git a/zcash_primitives/src/memo.rs b/components/zcash_protocol/src/memo.rs similarity index 100% rename from zcash_primitives/src/memo.rs rename to components/zcash_protocol/src/memo.rs diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index 4f1a8373e2..9a976d2067 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -17,6 +17,7 @@ and this library adheres to Rust's notion of `zcash_protocol::constants` module. - `zcash_primitives::transaction::components::amount` is now a reexport of the `zcash_protocol::value` module. +- `zcash_primitives::memo` is now a reexport of the `zcash_protocol::memo` module. ### Removed - `zcash_primitives::consensus::sapling_zip212_enforcement` instead use diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index e539dfeea0..37c37d653a 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -21,7 +21,7 @@ pub mod block; pub use zcash_protocol::consensus; pub use zcash_protocol::constants; pub mod legacy; -pub mod memo; +pub use zcash_protocol::memo; pub mod merkle_tree; use sapling; pub mod transaction; From b8aa5132c2c56df63678eea98d2d088dbb4a0059 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 5 Mar 2024 10:17:32 -0700 Subject: [PATCH 12/18] Apply suggestions from code review Co-authored-by: str4d Co-authored-by: Daira-Emma Hopwood --- components/zcash_address/CHANGELOG.md | 6 --- components/zcash_protocol/CHANGELOG.md | 8 +++- components/zcash_protocol/Cargo.toml | 17 ++++++-- components/zcash_protocol/LICENSE-MIT | 2 +- components/zcash_protocol/src/consensus.rs | 14 +------ .../zcash_protocol/src/constants/mainnet.rs | 2 +- .../zcash_protocol/src/constants/regtest.rs | 4 +- .../zcash_protocol/src/constants/testnet.rs | 4 +- components/zcash_protocol/src/value.rs | 39 +++---------------- zcash_client_sqlite/src/lib.rs | 13 ++----- zcash_client_sqlite/src/wallet/init.rs | 24 +++++++++--- .../wallet/init/migrations/addresses_table.rs | 22 ++++------- .../init/migrations/receiving_key_scopes.rs | 22 +++++++---- .../wallet/init/migrations/ufvk_support.rs | 18 +++------ zcash_keys/CHANGELOG.md | 9 ++++- zcash_keys/src/encoding.rs | 36 ++++++++--------- zcash_keys/src/keys.rs | 11 ++++-- zcash_primitives/CHANGELOG.md | 17 ++++---- zcash_primitives/Cargo.toml | 2 +- 19 files changed, 127 insertions(+), 143 deletions(-) diff --git a/components/zcash_address/CHANGELOG.md b/components/zcash_address/CHANGELOG.md index 4189e18852..780712c60c 100644 --- a/components/zcash_address/CHANGELOG.md +++ b/components/zcash_address/CHANGELOG.md @@ -7,12 +7,6 @@ and this library adheres to Rust's notion of ## [Unreleased] -### Removed -- `zcash_address::kind::p2pkh` - use constants from `zcash_protocol` instead. -- `zcash_address::kind::p2sh` - use constants from `zcash_protocol` instead. -- `zcash_address::kind::sapling` - use constants from `zcash_protocol` instead. -- `zcash_address::kind::sprout` - use constants from `zcash_protocol` instead. - ## [0.3.1] - 2024-01-12 ### Fixed - Stubs for `zcash_address::convert` traits that are created by `rust-analyzer` diff --git a/components/zcash_protocol/CHANGELOG.md b/components/zcash_protocol/CHANGELOG.md index f6b80a9c73..6c6d200898 100644 --- a/components/zcash_protocol/CHANGELOG.md +++ b/components/zcash_protocol/CHANGELOG.md @@ -16,9 +16,13 @@ The entries below are relative to the `zcash_primitives` crate as of the tag - `constants` - `zcash_protocol::value` replaces `zcash_primitives::transaction::components::amount` - `zcash_protocol::consensus`: - - `NetworkConstants` has been extracted from the `Parameters` trait. + - `NetworkConstants` has been extracted from the `Parameters` trait. Relative to the + state prior to the extraction, the bech32 prefixes now return `&'static str` instead + of `&str`. - `NetworkType` - `Parameters::b58_sprout_address_prefix` +- `zcash_protocol::consensus`: + - `impl Hash for LocalNetwork` - `zcash_protocol::constants::{mainnet, testnet}::B58_SPROUT_ADDRESS_PREFIX` - Added in `zcash_protocol::value`: - `Zatoshis` @@ -40,6 +44,8 @@ The entries below are relative to the `zcash_primitives` crate as of the tag - `TryFrom for Amount` - `From for sapling::value::NoteValue>` - `TryFrom for NonNegativeAmount` + - `impl AddAssign for NonNegativeAmount` + - `impl SubAssign for NonNegativeAmount` - `zcash_protocol::consensus::Parameters` has been split into two traits, with the `NetworkConstants` trait providing all network constant accessors. Also, the `address_network` method has been replaced with a new `network_type` diff --git a/components/zcash_protocol/Cargo.toml b/components/zcash_protocol/Cargo.toml index b5c8f651d4..45d2c53e63 100644 --- a/components/zcash_protocol/Cargo.toml +++ b/components/zcash_protocol/Cargo.toml @@ -37,13 +37,22 @@ proptest.workspace = true [features] ## Exposes APIs that are useful for testing, such as `proptest` strategies. -test-dependencies = ["dep:proptest", "incrementalmerkletree/test-dependencies"] +test-dependencies = [ + "dep:incrementalmerkletree", + "dep:proptest", + "incrementalmerkletree?/test-dependencies", +] + +## Exposes support for working with a local consensus (e.g. regtest). +local-consensus = [] + +#! ### Experimental features +#! +#! ⚠️ Enabling these features will likely make your code incompatible with current Zcash +#! consensus rules! ## Exposes the in-development NU6 features. unstable-nu6 = [] ## Exposes early in-development features that are not yet planned for any network upgrade. zfuture = [] - -## Exposes support for working with a local consensus (e.g. regtest -local-consensus = [] diff --git a/components/zcash_protocol/LICENSE-MIT b/components/zcash_protocol/LICENSE-MIT index 9500c140cc..c869731ad4 100644 --- a/components/zcash_protocol/LICENSE-MIT +++ b/components/zcash_protocol/LICENSE-MIT @@ -1,6 +1,6 @@ The MIT License (MIT) -Copyright (c) 2021 Electric Coin Company +Copyright (c) 2021-2024 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 diff --git a/components/zcash_protocol/src/consensus.rs b/components/zcash_protocol/src/consensus.rs index aabcebcde0..dc4a5d775d 100644 --- a/components/zcash_protocol/src/consensus.rs +++ b/components/zcash_protocol/src/consensus.rs @@ -8,9 +8,6 @@ use std::ops::{Add, Bound, RangeBounds, Sub}; use crate::constants::{mainnet, regtest, testnet}; -#[cfg(feature = "local-consensus")] -use crate::local_consensus::LocalNetwork; - /// A wrapper type representing blockchain heights. /// /// Safe conversion from various integer types, as well as addition and subtraction, are @@ -146,7 +143,7 @@ pub trait NetworkConstants: Clone { fn coin_type(&self) -> u32; /// Returns the human-readable prefix for Bech32-encoded Sapling extended spending keys - /// the network to which this NetworkConstants value applies. + /// for the network to which this NetworkConstants value applies. /// /// Defined in [ZIP 32]. /// @@ -164,7 +161,7 @@ pub trait NetworkConstants: Clone { fn hrp_sapling_extended_full_viewing_key(&self) -> &'static str; /// Returns the Bech32-encoded human-readable prefix for Sapling payment addresses - /// viewing keys for the network to which this NetworkConstants value applies. + /// for the network to which this NetworkConstants value applies. /// /// Defined in section 5.6.4 of the [Zcash Protocol Specification]. /// @@ -382,9 +379,6 @@ pub enum Network { MainNetwork, /// Zcash Testnet. TestNetwork, - /// Private integration / regression testing, used in `zcashd`. - #[cfg(feature = "local-consensus")] - Regtest(LocalNetwork), } memuse::impl_no_dynamic_usage!(Network); @@ -394,8 +388,6 @@ impl Parameters for Network { match self { Network::MainNetwork => NetworkType::Main, Network::TestNetwork => NetworkType::Test, - #[cfg(feature = "local-consensus")] - Network::Regtest(_) => NetworkType::Regtest, } } @@ -403,8 +395,6 @@ impl Parameters for Network { match self { Network::MainNetwork => MAIN_NETWORK.activation_height(nu), Network::TestNetwork => TEST_NETWORK.activation_height(nu), - #[cfg(feature = "local-consensus")] - Network::Regtest(network_params) => network_params.activation_height(nu), } } } diff --git a/components/zcash_protocol/src/constants/mainnet.rs b/components/zcash_protocol/src/constants/mainnet.rs index 467f22df5a..b30babc721 100644 --- a/components/zcash_protocol/src/constants/mainnet.rs +++ b/components/zcash_protocol/src/constants/mainnet.rs @@ -17,7 +17,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-main"; /// /// Defined in [ZIP 32]. /// -/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html# +/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviews"; diff --git a/components/zcash_protocol/src/constants/regtest.rs b/components/zcash_protocol/src/constants/regtest.rs index cd793f6c7c..2674a416de 100644 --- a/components/zcash_protocol/src/constants/regtest.rs +++ b/components/zcash_protocol/src/constants/regtest.rs @@ -21,7 +21,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-regtest /// /// It is defined in [the `zcashd` codebase]. /// -/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html# +/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html /// [the `zcashd` codebase]: pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewregtestsapling"; @@ -33,7 +33,7 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewregtestsapling"; /// [the `zcashd` codebase]: pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zregtestsapling"; -/// The prefix for a Base58Check-encoded regtest Sprout address +/// The prefix for a Base58Check-encoded regtest Sprout address. /// /// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. /// Same as the testnet prefix. diff --git a/components/zcash_protocol/src/constants/testnet.rs b/components/zcash_protocol/src/constants/testnet.rs index 7bf3309691..a0935ad2e7 100644 --- a/components/zcash_protocol/src/constants/testnet.rs +++ b/components/zcash_protocol/src/constants/testnet.rs @@ -17,7 +17,7 @@ pub const HRP_SAPLING_EXTENDED_SPENDING_KEY: &str = "secret-extended-key-test"; /// /// Defined in [ZIP 32]. /// -/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html# +/// [`ExtendedFullViewingKey`]: https://docs.rs/sapling-crypto/latest/sapling_crypto/zip32/struct.ExtendedFullViewingKey.html /// [ZIP 32]: https://github.com/zcash/zips/blob/master/zip-0032.rst pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewtestsapling"; @@ -29,7 +29,7 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewtestsapling"; /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "ztestsapling"; -/// The prefix for a Base58Check-encoded testnet Sprout address +/// The prefix for a Base58Check-encoded testnet Sprout addresses. /// /// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. /// diff --git a/components/zcash_protocol/src/value.rs b/components/zcash_protocol/src/value.rs index 65376fa8e0..45a3cd72f3 100644 --- a/components/zcash_protocol/src/value.rs +++ b/components/zcash_protocol/src/value.rs @@ -1,7 +1,7 @@ use std::convert::{Infallible, TryFrom}; use std::error; use std::iter::Sum; -use std::ops::{Add, AddAssign, Mul, Neg, Sub, SubAssign}; +use std::ops::{Add, Mul, Neg, Sub}; use memuse::DynamicUsage; @@ -12,12 +12,9 @@ pub const MAX_BALANCE: i64 = MAX_MONEY as i64; /// A type-safe representation of a Zcash value delta, in zatoshis. /// /// An ZatBalance can only be constructed from an integer that is within the valid monetary -/// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis). -/// However, this range is not preserved as an invariant internally; it is possible to -/// add two valid ZatBalances together to obtain an invalid ZatBalance. It is the user's -/// responsibility to handle the result of serializing potentially-invalid ZatBalances. In -/// particular, a [`Transaction`] containing serialized invalid ZatBalances will be rejected -/// by the network consensus rules. +/// range of `{-MAX_MONEY..MAX_MONEY}` (where `MAX_MONEY` = 21,000,000 × 10⁸ zatoshis), +/// and this is preserved as an invariant internally. (A [`Transaction`] containing serialized +/// invalid ZatBalances would also be rejected by the network consensus rules.) /// /// [`Transaction`]: https://docs.rs/zcash_primitives/latest/zcash_primitives/transaction/struct.Transaction.html #[derive(Clone, Copy, Debug, PartialEq, PartialOrd, Eq, Ord)] @@ -174,12 +171,6 @@ impl Add for Option { } } -impl AddAssign for ZatBalance { - fn add_assign(&mut self, rhs: ZatBalance) { - *self = (*self + rhs).expect("Addition must produce a valid amount value.") - } -} - impl Sub for ZatBalance { type Output = Option; @@ -196,12 +187,6 @@ impl Sub for Option { } } -impl SubAssign for ZatBalance { - fn sub_assign(&mut self, rhs: ZatBalance) { - *self = (*self - rhs).expect("Subtraction must produce a valid amount value.") - } -} - impl Sum for Option { fn sum>(iter: I) -> Self { iter.fold(Some(ZatBalance::zero()), |acc, a| acc? + a) @@ -272,7 +257,7 @@ impl Zatoshis { /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_nonnegative_i64(amount: i64) -> Result { - u64::try_from(amount).map(Zatoshis).map_err(|_| ()) + Self::from_u64(u64::try_from(amount).map_err(|_| ())?) } /// Reads an Zatoshis from an unsigned 64-bit little-endian integer. @@ -516,23 +501,9 @@ mod tests { assert_eq!(v + ZatBalance(1), None) } - #[test] - #[should_panic] - fn add_assign_panics_on_overflow() { - let mut a = ZatBalance(MAX_BALANCE); - a += ZatBalance(1); - } - #[test] fn sub_underflow() { let v = ZatBalance(-MAX_BALANCE); assert_eq!(v - ZatBalance(1), None) } - - #[test] - #[should_panic] - fn sub_assign_panics_on_underflow() { - let mut a = ZatBalance(-MAX_BALANCE); - a -= ZatBalance(1); - } } diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index 53872e6e74..b98deb817e 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -264,15 +264,10 @@ impl, P: consensus::Parameters> WalletRead for W // Keys are not comparable with `Eq`, but addresses are, so we derive what should // be equivalent addresses for each key and use those to check for key equality. UnifiedAddressRequest::all().map_or(Ok(false), |ua_request| { - match ( - usk.to_unified_full_viewing_key() - .default_address(ua_request), - ufvk.default_address(ua_request), - ) { - (Ok(a), Ok(b)) => Ok(a == b), - (Err(e), _) => Err(e.into()), - (_, Err(e)) => Err(e.into()), - } + Ok(usk + .to_unified_full_viewing_key() + .default_address(ua_request)? + == ufvk.default_address(ua_request)?) }) }) }) diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 95fd0b0752..7a6b2b0172 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -2,21 +2,18 @@ use std::fmt; -use rusqlite::{self}; use schemer::{Migrator, MigratorError}; use schemer_rusqlite::RusqliteAdapter; use secrecy::SecretVec; use shardtree::error::ShardTreeError; use uuid::Uuid; -use zcash_primitives::{ - consensus::{self}, - transaction::components::amount::BalanceError, -}; +use zcash_client_backend::keys::AddressGenerationError; +use zcash_primitives::{consensus, transaction::components::amount::BalanceError}; use crate::WalletDb; -use super::commitment_tree::{self}; +use super::commitment_tree; mod migrations; @@ -28,6 +25,9 @@ pub enum WalletMigrationError { /// Decoding of an existing value from its serialized form has failed. CorruptedData(String), + /// An error occurred in migrating a Zcash address or key. + AddressGeneration(AddressGenerationError), + /// Wrapper for rusqlite errors. DbError(rusqlite::Error), @@ -56,6 +56,12 @@ impl From> for WalletMigrationError { } } +impl From for WalletMigrationError { + fn from(e: AddressGenerationError) -> Self { + WalletMigrationError::AddressGeneration(e) + } +} + impl fmt::Display for WalletMigrationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self { @@ -71,6 +77,9 @@ impl fmt::Display for WalletMigrationError { WalletMigrationError::DbError(e) => write!(f, "{}", e), WalletMigrationError::BalanceError(e) => write!(f, "Balance error: {:?}", e), WalletMigrationError::CommitmentTree(e) => write!(f, "Commitment tree error: {:?}", e), + WalletMigrationError::AddressGeneration(e) => { + write!(f, "Address generation error: {:?}", e) + } } } } @@ -79,6 +88,9 @@ impl std::error::Error for WalletMigrationError { fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { match &self { WalletMigrationError::DbError(e) => Some(e), + WalletMigrationError::BalanceError(e) => Some(e), + WalletMigrationError::CommitmentTree(e) => Some(e), + WalletMigrationError::AddressGeneration(e) => Some(e), _ => None, } } diff --git a/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs b/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs index 06c5bf1995..8c3490736c 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/addresses_table.rs @@ -88,13 +88,9 @@ impl RusqliteMigration for Migration

{ "Address in accounts table was not a Unified Address.".to_string(), )); }; - let (expected_address, idx) = ufvk - .default_address(UnifiedAddressRequest::unsafe_new( - false, - true, - UA_TRANSPARENT, - )) - .expect("A valid default address exists for the UFVK"); + let (expected_address, idx) = ufvk.default_address( + UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT), + )?; if decoded_address != expected_address { return Err(WalletMigrationError::CorruptedData(format!( "Decoded UA {} does not match the UFVK's default address {} at {:?}.", @@ -164,13 +160,11 @@ impl RusqliteMigration for Migration

{ ], )?; - let (address, d_idx) = ufvk - .default_address(UnifiedAddressRequest::unsafe_new( - false, - true, - UA_TRANSPARENT, - )) - .expect("A valid default address exists for the UFVK"); + let (address, d_idx) = ufvk.default_address(UnifiedAddressRequest::unsafe_new( + false, + true, + UA_TRANSPARENT, + ))?; insert_address(transaction, &self.params, account, d_idx, &address)?; } diff --git a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs index 23cc0bc6ad..c45697e34b 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/receiving_key_scopes.rs @@ -172,17 +172,25 @@ impl RusqliteMigration for Migration

{ ); let ufvk_str: String = row.get(5)?; - let ufvk = UnifiedFullViewingKey::decode(&self.params, &ufvk_str) - .expect("Stored UFVKs must be valid"); - let dfvk = ufvk - .sapling() - .expect("UFVK must have a Sapling component to have received Sapling notes"); + let ufvk = UnifiedFullViewingKey::decode(&self.params, &ufvk_str).map_err(|e| { + WalletMigrationError::CorruptedData(format!("Stored UFVK was invalid: {:?}", e)) + })?; + + let dfvk = ufvk.sapling().ok_or_else(|| { + WalletMigrationError::CorruptedData( + "UFVK must have a Sapling component to have received Sapling notes.".to_owned(), + ) + })?; // We previously set the default to external scope, so we now verify whether the output // is decryptable using the intenally-scoped IVK and, if so, mark it as such. if let Some(tx_data) = tx_data_opt { - let tx = Transaction::read(&tx_data[..], BranchId::Canopy) - .expect("Transaction must be valid"); + let tx = Transaction::read(&tx_data[..], BranchId::Canopy).map_err(|e| { + WalletMigrationError::CorruptedData(format!( + "Unable to parse raw transaction: {:?}", + e + )) + })?; let output = tx .sapling_bundle() .and_then(|b| b.shielded_outputs().get(output_index)) diff --git a/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs b/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs index 953a110e31..99c27b5b33 100644 --- a/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs +++ b/zcash_client_sqlite/src/wallet/init/migrations/ufvk_support.rs @@ -68,8 +68,7 @@ impl RusqliteMigration for Migration

{ let mut stmt_fetch_accounts = transaction.prepare("SELECT account, address FROM accounts")?; - let ua_request = UnifiedAddressRequest::new(false, true, UA_TRANSPARENT) - .expect("A shielded receiver type is requested."); + let ua_request = UnifiedAddressRequest::unsafe_new(false, true, UA_TRANSPARENT); let mut rows = stmt_fetch_accounts.query([])?; while let Some(row) = rows.next()? { // We only need to check for the presence of the seed if we have keys that @@ -94,9 +93,8 @@ impl RusqliteMigration for Migration

{ })?; match decoded { Address::Sapling(decoded_address) => { - let dfvk = ufvk.sapling().expect( - "Derivation should have produced a UFVK containing a Sapling component.", - ); + let dfvk = ufvk.sapling().ok_or_else(|| + WalletMigrationError::CorruptedData("Derivation should have produced a UFVK containing a Sapling component.".to_owned()))?; let (idx, expected_address) = dfvk.default_address(); if decoded_address != expected_address { return Err(WalletMigrationError::CorruptedData( @@ -111,9 +109,7 @@ impl RusqliteMigration for Migration

{ "Address field value decoded to a transparent address; should have been Sapling or unified.".to_string())); } Address::Unified(decoded_address) => { - let (expected_address, idx) = ufvk - .default_address(ua_request) - .expect("A valid default address exists for the UFVK"); + let (expected_address, idx) = ufvk.default_address(ua_request)?; if decoded_address != expected_address { return Err(WalletMigrationError::CorruptedData( format!("Decoded unified address {} does not match the ufvk's default address {} at {:?}.", @@ -125,11 +121,7 @@ impl RusqliteMigration for Migration

{ } let ufvk_str: String = ufvk.encode(&self.params); - let address_str: String = ufvk - .default_address(ua_request) - .expect("A valid default address exists for the UFVK") - .0 - .encode(&self.params); + let address_str: String = ufvk.default_address(ua_request)?.0.encode(&self.params); // This migration, and the wallet behaviour before it, stored the default // transparent address in the `accounts` table. This does not necessarily diff --git a/zcash_keys/CHANGELOG.md b/zcash_keys/CHANGELOG.md index fd9a178f4d..5689f98bbc 100644 --- a/zcash_keys/CHANGELOG.md +++ b/zcash_keys/CHANGELOG.md @@ -13,10 +13,15 @@ and this library adheres to Rust's notion of ### Changed - `zcash_keys::keys::AddressGenerationError` has a new variant - `DiversifierSpaceExhausted` + `DiversifierSpaceExhausted`. - `zcash_keys::keys::UnifiedFullViewingKey::{find_address, default_address}` now return `Result<(UnifiedAddress, DiversifierIndex), AddressGenerationError>` - instead of `Option<(UnifiedAddress, DiversifierIndex)>` + (instead of `Option<(UnifiedAddress, DiversifierIndex)>` for `find_address`). + +### Fixed +- `UnifiedFullViewingKey::find_address` can now find an address for a diversifier + index outside the valid transparent range if you aren't requesting a + transparent receiver. ## [0.1.1] - 2024-03-04 diff --git a/zcash_keys/src/encoding.rs b/zcash_keys/src/encoding.rs index cb1fef8763..8de7125a05 100644 --- a/zcash_keys/src/encoding.rs +++ b/zcash_keys/src/encoding.rs @@ -131,16 +131,16 @@ impl AddressCodec

for TransparentAddress { fn encode(&self, params: &P) -> String { encode_transparent_address( - ¶ms.network_type().b58_pubkey_address_prefix(), - ¶ms.network_type().b58_script_address_prefix(), + ¶ms.b58_pubkey_address_prefix(), + ¶ms.b58_script_address_prefix(), self, ) } fn decode(params: &P, address: &str) -> Result { decode_transparent_address( - ¶ms.network_type().b58_pubkey_address_prefix(), - ¶ms.network_type().b58_script_address_prefix(), + ¶ms.b58_pubkey_address_prefix(), + ¶ms.b58_script_address_prefix(), address, ) .map_err(TransparentCodecError::Base58) @@ -155,11 +155,11 @@ impl AddressCodec

for sapling::PaymentAddress { type Error = Bech32DecodeError; fn encode(&self, params: &P) -> String { - encode_payment_address(params.network_type().hrp_sapling_payment_address(), self) + encode_payment_address(params.hrp_sapling_payment_address(), self) } fn decode(params: &P, address: &str) -> Result { - decode_payment_address(params.network_type().hrp_sapling_payment_address(), address) + decode_payment_address(params.hrp_sapling_payment_address(), address) } } @@ -299,7 +299,7 @@ pub fn encode_payment_address_p( params: &P, addr: &sapling::PaymentAddress, ) -> String { - encode_payment_address(params.network_type().hrp_sapling_payment_address(), addr) + encode_payment_address(params.hrp_sapling_payment_address(), addr) } /// Decodes a [`PaymentAddress`] from a Bech32-encoded string. @@ -326,7 +326,7 @@ pub fn encode_payment_address_p( /// /// assert_eq!( /// decode_payment_address( -/// TEST_NETWORK.network_type().hrp_sapling_payment_address(), +/// TEST_NETWORK.hrp_sapling_payment_address(), /// "ztestsapling1qqqqqqqqqqqqqqqqqqcguyvaw2vjk4sdyeg0lc970u659lvhqq7t0np6hlup5lusxle75ss7jnk", /// ), /// Ok(pa), @@ -364,8 +364,8 @@ pub fn decode_payment_address( /// /// assert_eq!( /// encode_transparent_address( -/// &TEST_NETWORK.network_type().b58_pubkey_address_prefix(), -/// &TEST_NETWORK.network_type().b58_script_address_prefix(), +/// &TEST_NETWORK.b58_pubkey_address_prefix(), +/// &TEST_NETWORK.b58_script_address_prefix(), /// &TransparentAddress::PublicKeyHash([0; 20]), /// ), /// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma", @@ -373,8 +373,8 @@ pub fn decode_payment_address( /// /// assert_eq!( /// encode_transparent_address( -/// &TEST_NETWORK.network_type().b58_pubkey_address_prefix(), -/// &TEST_NETWORK.network_type().b58_script_address_prefix(), +/// &TEST_NETWORK.b58_pubkey_address_prefix(), +/// &TEST_NETWORK.b58_script_address_prefix(), /// &TransparentAddress::ScriptHash([0; 20]), /// ), /// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2", @@ -411,8 +411,8 @@ pub fn encode_transparent_address_p( addr: &TransparentAddress, ) -> String { encode_transparent_address( - ¶ms.network_type().b58_pubkey_address_prefix(), - ¶ms.network_type().b58_script_address_prefix(), + ¶ms.b58_pubkey_address_prefix(), + ¶ms.b58_script_address_prefix(), addr, ) } @@ -432,8 +432,8 @@ pub fn encode_transparent_address_p( /// /// assert_eq!( /// decode_transparent_address( -/// &TEST_NETWORK.network_type().b58_pubkey_address_prefix(), -/// &TEST_NETWORK.network_type().b58_script_address_prefix(), +/// &TEST_NETWORK.b58_pubkey_address_prefix(), +/// &TEST_NETWORK.b58_script_address_prefix(), /// "tm9iMLAuYMzJ6jtFLcA7rzUmfreGuKvr7Ma", /// ), /// Ok(Some(TransparentAddress::PublicKeyHash([0; 20]))), @@ -441,8 +441,8 @@ pub fn encode_transparent_address_p( /// /// assert_eq!( /// decode_transparent_address( -/// &TEST_NETWORK.network_type().b58_pubkey_address_prefix(), -/// &TEST_NETWORK.network_type().b58_script_address_prefix(), +/// &TEST_NETWORK.b58_pubkey_address_prefix(), +/// &TEST_NETWORK.b58_script_address_prefix(), /// "t26YoyZ1iPgiMEWL4zGUm74eVWfhyDMXzY2", /// ), /// Ok(Some(TransparentAddress::ScriptHash([0; 20]))), diff --git a/zcash_keys/src/keys.rs b/zcash_keys/src/keys.rs index 35fe68314d..4529885966 100644 --- a/zcash_keys/src/keys.rs +++ b/zcash_keys/src/keys.rs @@ -448,6 +448,7 @@ pub enum AddressGenerationError { impl fmt::Display for AddressGenerationError { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match &self { + #[cfg(feature = "transparent-inputs")] AddressGenerationError::InvalidTransparentChildIndex(i) => { write!( f, @@ -826,7 +827,8 @@ impl UnifiedFullViewingKey { /// produce a valid diversifier, and return the Unified Address constructed using that /// diversifier along with the index at which the valid diversifier was found. /// - /// Returns `None` if no valid diversifier exists + /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features + /// required to satisfy the unified address request are not properly enabled. #[allow(unused_mut)] pub fn find_address( &self, @@ -864,8 +866,11 @@ impl UnifiedFullViewingKey { } } - /// Returns the Unified Address corresponding to the smallest valid diversifier index, - /// along with that index. + /// Find the Unified Address corresponding to the smallest valid diversifier index, along with + /// that index. + /// + /// Returns an `Err(AddressGenerationError)` if no valid diversifier exists or if the features + /// required to satisfy the unified address request are not properly enabled. pub fn default_address( &self, request: UnifiedAddressRequest, diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index 9a976d2067..de42eafa24 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -11,13 +11,14 @@ and this library adheres to Rust's notion of - `zcash_primitives::transaction::components::sapling::zip212_enforcement` ### Changed -- `zcash_primitives::consensus` is now a reexport of the - `zcash_protocol::consensus` module. -- `zcash_primitives::constants` is now a reexport of the - `zcash_protocol::constants` module. -- `zcash_primitives::transaction::components::amount` is now a reexport of the - `zcash_protocol::value` module. -- `zcash_primitives::memo` is now a reexport of the `zcash_protocol::memo` module. +- The following modules are now re-exported from the `zcash_protocol` crate. + Additional changes have also been made therein; refer to the `zcash_protocol` + changelog for details. + - `zcash_primitives::consensus` re-exports `zcash_protocol::consensus`. + - `zcash_primitives::constants` re-exports `zcash_protocol::constants`. + - `zcash_primitives::transaction::components::amount` re-exports + `zcash_protocol::value`. + - `zcash_primitives::memo` re-exports `zcash_protocol::memo`. ### Removed - `zcash_primitives::consensus::sapling_zip212_enforcement` instead use @@ -28,6 +29,8 @@ and this library adheres to Rust's notion of - `impl From for sapling::value::NoteValue` - `impl TryFrom for Amount` - `impl From for orchard::NoteValue` +- The `local_consensus` module and feature flag have been removed; use the module + from the `zcash_protocol` crate instead. ## [0.14.0] - 2024-03-01 ### Added diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index f2fc208217..c349215600 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -118,9 +118,9 @@ temporary-zcashd = [] ## Exposes APIs that are useful for testing, such as `proptest` strategies. test-dependencies = [ "dep:proptest", - "zcash_protocol/test-dependencies", "orchard/test-dependencies", "sapling/test-dependencies", + "zcash_protocol/test-dependencies", ] #! ### Experimental features From 51d4464472efe2bf49450f3a0659e7d3af257eab Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 5 Mar 2024 13:00:35 -0700 Subject: [PATCH 13/18] Remove `network_type` calls that are obviated by the blanket impl. --- .../zcash_protocol/src/local_consensus.rs | 17 ++++++----------- zcash_client_sqlite/src/lib.rs | 10 ++-------- zcash_client_sqlite/src/wallet/init.rs | 18 ++++++------------ zcash_keys/src/keys.rs | 4 ++-- zcash_primitives/src/legacy/keys.rs | 4 +--- 5 files changed, 17 insertions(+), 36 deletions(-) diff --git a/components/zcash_protocol/src/local_consensus.rs b/components/zcash_protocol/src/local_consensus.rs index cf4dffe606..994154240c 100644 --- a/components/zcash_protocol/src/local_consensus.rs +++ b/components/zcash_protocol/src/local_consensus.rs @@ -198,30 +198,25 @@ mod tests { z_future: Some(expected_z_future), }; + assert_eq!(regtest.coin_type(), constants::regtest::COIN_TYPE); assert_eq!( - regtest.network_type().coin_type(), - constants::regtest::COIN_TYPE - ); - assert_eq!( - regtest.network_type().hrp_sapling_extended_spending_key(), + regtest.hrp_sapling_extended_spending_key(), constants::regtest::HRP_SAPLING_EXTENDED_SPENDING_KEY ); assert_eq!( - regtest - .network_type() - .hrp_sapling_extended_full_viewing_key(), + regtest.hrp_sapling_extended_full_viewing_key(), constants::regtest::HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY ); assert_eq!( - regtest.network_type().hrp_sapling_payment_address(), + regtest.hrp_sapling_payment_address(), constants::regtest::HRP_SAPLING_PAYMENT_ADDRESS ); assert_eq!( - regtest.network_type().b58_pubkey_address_prefix(), + regtest.b58_pubkey_address_prefix(), constants::regtest::B58_PUBKEY_ADDRESS_PREFIX ); assert_eq!( - regtest.network_type().b58_script_address_prefix(), + regtest.b58_script_address_prefix(), constants::regtest::B58_SCRIPT_ADDRESS_PREFIX ); } diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index b98deb817e..c0e046b05e 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -1240,9 +1240,7 @@ mod tests { use { crate::testing::AddressType, zcash_client_backend::keys::sapling, - zcash_primitives::{ - consensus::Parameters, transaction::components::amount::NonNegativeAmount, - }, + zcash_primitives::transaction::components::amount::NonNegativeAmount, }; #[test] @@ -1341,11 +1339,7 @@ mod tests { // Generate some fake CompactBlocks. let seed = [0u8; 32]; let account = AccountId::ZERO; - let extsk = sapling::spending_key( - &seed, - st.wallet().params.network_type().coin_type(), - account, - ); + let extsk = sapling::spending_key(&seed, st.wallet().params.coin_type(), account); let dfvk = extsk.to_diversifiable_full_viewing_key(); let (h1, meta1, _) = st.generate_next_block( &dfvk, diff --git a/zcash_client_sqlite/src/wallet/init.rs b/zcash_client_sqlite/src/wallet/init.rs index 7a6b2b0172..35734806d9 100644 --- a/zcash_client_sqlite/src/wallet/init.rs +++ b/zcash_client_sqlite/src/wallet/init.rs @@ -712,13 +712,11 @@ mod tests { )?; let address = encode_payment_address( - wdb.params.network_type().hrp_sapling_payment_address(), + wdb.params.hrp_sapling_payment_address(), &extfvk.default_address().1, ); let extfvk = encode_extended_full_viewing_key( - wdb.params - .network_type() - .hrp_sapling_extended_full_viewing_key(), + wdb.params.hrp_sapling_extended_full_viewing_key(), extfvk, ); wdb.conn.execute( @@ -739,8 +737,7 @@ mod tests { let seed = [0xab; 32]; let account = AccountId::ZERO; - let secret_key = - sapling::spending_key(&seed, db_data.params.network_type().coin_type(), account); + let secret_key = sapling::spending_key(&seed, db_data.params.coin_type(), account); let extfvk = secret_key.to_extended_full_viewing_key(); init_0_3_0(&mut db_data, &extfvk, account).unwrap(); @@ -852,13 +849,11 @@ mod tests { )?; let address = encode_payment_address( - wdb.params.network_type().hrp_sapling_payment_address(), + wdb.params.hrp_sapling_payment_address(), &extfvk.default_address().1, ); let extfvk = encode_extended_full_viewing_key( - wdb.params - .network_type() - .hrp_sapling_extended_full_viewing_key(), + wdb.params.hrp_sapling_extended_full_viewing_key(), extfvk, ); wdb.conn.execute( @@ -913,8 +908,7 @@ mod tests { let seed = [0xab; 32]; let account = AccountId::ZERO; - let secret_key = - sapling::spending_key(&seed, db_data.params.network_type().coin_type(), account); + let secret_key = sapling::spending_key(&seed, db_data.params.coin_type(), account); let extfvk = secret_key.to_extended_full_viewing_key(); init_autoshielding(&mut db_data, &extfvk, account).unwrap(); diff --git a/zcash_keys/src/keys.rs b/zcash_keys/src/keys.rs index 4529885966..147b33cb18 100644 --- a/zcash_keys/src/keys.rs +++ b/zcash_keys/src/keys.rs @@ -190,11 +190,11 @@ impl UnifiedSpendingKey { transparent: legacy::AccountPrivKey::from_seed(_params, seed, _account) .map_err(DerivationError::Transparent)?, #[cfg(feature = "sapling")] - sapling: sapling::spending_key(seed, _params.network_type().coin_type(), _account), + sapling: sapling::spending_key(seed, _params.coin_type(), _account), #[cfg(feature = "orchard")] orchard: orchard::keys::SpendingKey::from_zip32_seed( seed, - _params.network_type().coin_type(), + _params.coin_type(), _account, ) .map_err(DerivationError::Orchard)?, diff --git a/zcash_primitives/src/legacy/keys.rs b/zcash_primitives/src/legacy/keys.rs index 19a12e59f7..fbed55028e 100644 --- a/zcash_primitives/src/legacy/keys.rs +++ b/zcash_primitives/src/legacy/keys.rs @@ -120,9 +120,7 @@ impl AccountPrivKey { ) -> Result { ExtendedPrivKey::with_seed(seed)? .derive_private_key(KeyIndex::hardened_from_normalize_index(44)?)? - .derive_private_key(KeyIndex::hardened_from_normalize_index( - params.network_type().coin_type(), - )?)? + .derive_private_key(KeyIndex::hardened_from_normalize_index(params.coin_type())?)? .derive_private_key(KeyIndex::hardened_from_normalize_index(account.into())?) .map(AccountPrivKey) } From 5675a76f0da43bb854cb389c17579867ad8b1c2c Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 5 Mar 2024 13:32:31 -0700 Subject: [PATCH 14/18] zcash_protocol: Use `BalanceError` instead of `()` for monetary range violations. --- components/zcash_protocol/src/value.rs | 50 +++++++++++++++----------- zcash_client_backend/src/zip321.rs | 11 +++--- zcash_client_sqlite/src/lib.rs | 3 +- zcash_primitives/CHANGELOG.md | 4 ++- 4 files changed, 40 insertions(+), 28 deletions(-) diff --git a/components/zcash_protocol/src/value.rs b/components/zcash_protocol/src/value.rs index 45a3cd72f3..cfd3a7fc30 100644 --- a/components/zcash_protocol/src/value.rs +++ b/components/zcash_protocol/src/value.rs @@ -47,40 +47,44 @@ impl ZatBalance { /// Creates an ZatBalance from an i64. /// /// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. - pub fn from_i64(amount: i64) -> Result { + pub fn from_i64(amount: i64) -> Result { if (-MAX_BALANCE..=MAX_BALANCE).contains(&amount) { Ok(ZatBalance(amount)) + } else if amount < -MAX_BALANCE { + Err(BalanceError::Underflow) } else { - Err(()) + Err(BalanceError::Overflow) } } /// Creates a non-negative ZatBalance from an i64. /// /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. - pub fn from_nonnegative_i64(amount: i64) -> Result { + pub fn from_nonnegative_i64(amount: i64) -> Result { if (0..=MAX_BALANCE).contains(&amount) { Ok(ZatBalance(amount)) + } else if amount < 0 { + Err(BalanceError::Underflow) } else { - Err(()) + Err(BalanceError::Overflow) } } /// Creates an ZatBalance from a u64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. - pub fn from_u64(amount: u64) -> Result { + pub fn from_u64(amount: u64) -> Result { if amount <= MAX_MONEY { Ok(ZatBalance(amount as i64)) } else { - Err(()) + Err(BalanceError::Overflow) } } /// Reads an ZatBalance from a signed 64-bit little-endian integer. /// /// Returns an error if the amount is outside the range `{-MAX_BALANCE..MAX_BALANCE}`. - pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result { + pub fn from_i64_le_bytes(bytes: [u8; 8]) -> Result { let amount = i64::from_le_bytes(bytes); ZatBalance::from_i64(amount) } @@ -88,7 +92,7 @@ impl ZatBalance { /// Reads a non-negative ZatBalance from a signed 64-bit little-endian integer. /// /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. - pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result { + pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result { let amount = i64::from_le_bytes(bytes); ZatBalance::from_nonnegative_i64(amount) } @@ -96,7 +100,7 @@ impl ZatBalance { /// Reads an ZatBalance from an unsigned 64-bit little-endian integer. /// /// Returns an error if the amount is outside the range `{0..MAX_BALANCE}`. - pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result { + pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result { let amount = u64::from_le_bytes(bytes); ZatBalance::from_u64(amount) } @@ -128,9 +132,9 @@ impl ZatBalance { } impl TryFrom for ZatBalance { - type Error = (); + type Error = BalanceError; - fn try_from(value: i64) -> Result { + fn try_from(value: i64) -> Result { ZatBalance::from_i64(value) } } @@ -148,10 +152,10 @@ impl From<&ZatBalance> for i64 { } impl TryFrom for u64 { - type Error = (); + type Error = BalanceError; fn try_from(value: ZatBalance) -> Result { - value.0.try_into().map_err(|_| ()) + value.0.try_into().map_err(|_| BalanceError::Overflow) } } @@ -237,11 +241,11 @@ impl Zatoshis { /// Creates a Zatoshis from a u64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. - pub fn from_u64(amount: u64) -> Result { + pub fn from_u64(amount: u64) -> Result { if (0..=MAX_MONEY).contains(&amount) { Ok(Zatoshis(amount)) } else { - Err(()) + Err(BalanceError::Overflow) } } @@ -256,14 +260,18 @@ impl Zatoshis { /// Creates a Zatoshis from an i64. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. - pub fn from_nonnegative_i64(amount: i64) -> Result { - Self::from_u64(u64::try_from(amount).map_err(|_| ())?) + pub fn from_nonnegative_i64(amount: i64) -> Result { + if amount >= 0 { + Self::from_u64(u64::try_from(amount).unwrap()) + } else { + Err(BalanceError::Underflow) + } } /// Reads an Zatoshis from an unsigned 64-bit little-endian integer. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. - pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result { + pub fn from_u64_le_bytes(bytes: [u8; 8]) -> Result { let amount = u64::from_le_bytes(bytes); Self::from_u64(amount) } @@ -272,7 +280,7 @@ impl Zatoshis { /// complement 64-bit little-endian value. /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. - pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result { + pub fn from_nonnegative_i64_le_bytes(bytes: [u8; 8]) -> Result { let amount = i64::from_le_bytes(bytes); Self::from_nonnegative_i64(amount) } @@ -313,7 +321,7 @@ impl From for u64 { } impl TryFrom for Zatoshis { - type Error = (); + type Error = BalanceError; fn try_from(value: u64) -> Result { Zatoshis::from_u64(value) @@ -321,7 +329,7 @@ impl TryFrom for Zatoshis { } impl TryFrom for Zatoshis { - type Error = (); + type Error = BalanceError; fn try_from(value: ZatBalance) -> Result { Zatoshis::from_nonnegative_i64(value.0) diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index 1faf3ce2a8..e795b74af2 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -19,7 +19,7 @@ use zcash_primitives::{ memo::{self, MemoBytes}, transaction::components::amount::NonNegativeAmount, }; -use zcash_protocol::consensus; +use zcash_protocol::{consensus, value::BalanceError}; use crate::address::Address; @@ -206,11 +206,13 @@ impl TransactionRequest { /// /// Returns `Err` in the case of overflow, or if the value is /// outside the range `0..=MAX_MONEY` zatoshis. - pub fn total(&self) -> Result { + pub fn total(&self) -> Result { self.payments .values() .map(|p| p.amount) - .fold(Ok(NonNegativeAmount::ZERO), |acc, a| (acc? + a).ok_or(())) + .fold(Ok(NonNegativeAmount::ZERO), |acc, a| { + (acc? + a).ok_or(BalanceError::Overflow) + }) } /// A utility for use in tests to help check round-trip serialization properties. @@ -469,6 +471,7 @@ mod parse { consensus, transaction::components::amount::NonNegativeAmount, transaction::components::amount::COIN, }; + use zcash_protocol::value::BalanceError; use crate::address::Address; @@ -666,7 +669,7 @@ mod parse { coins .checked_mul(COIN) .and_then(|coin_zats| coin_zats.checked_add(zats)) - .ok_or(()) + .ok_or(BalanceError::Overflow) .and_then(NonNegativeAmount::from_u64) .map_err(|_| format!("Not a valid zat amount: {}.{}", coins, zats)) }, diff --git a/zcash_client_sqlite/src/lib.rs b/zcash_client_sqlite/src/lib.rs index c0e046b05e..99f8ab5dda 100644 --- a/zcash_client_sqlite/src/lib.rs +++ b/zcash_client_sqlite/src/lib.rs @@ -1238,8 +1238,7 @@ mod tests { #[cfg(feature = "unstable")] use { - crate::testing::AddressType, - zcash_client_backend::keys::sapling, + crate::testing::AddressType, zcash_client_backend::keys::sapling, zcash_primitives::transaction::components::amount::NonNegativeAmount, }; diff --git a/zcash_primitives/CHANGELOG.md b/zcash_primitives/CHANGELOG.md index de42eafa24..35723435aa 100644 --- a/zcash_primitives/CHANGELOG.md +++ b/zcash_primitives/CHANGELOG.md @@ -17,7 +17,9 @@ and this library adheres to Rust's notion of - `zcash_primitives::consensus` re-exports `zcash_protocol::consensus`. - `zcash_primitives::constants` re-exports `zcash_protocol::constants`. - `zcash_primitives::transaction::components::amount` re-exports - `zcash_protocol::value`. + `zcash_protocol::value`. Many of the conversions to and from the + `Amount` and `NonNegativeAmount` value types now return + `Result<_, BalanceError>` instead of `Result<_, ()>`. - `zcash_primitives::memo` re-exports `zcash_protocol::memo`. ### Removed From 8955cfc559c54756c751f587361614cc70971866 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 5 Mar 2024 14:37:26 -0700 Subject: [PATCH 15/18] Add missing CHANGELOG entries. --- components/zcash_protocol/CHANGELOG.md | 11 +++++++---- zcash_client_sqlite/CHANGELOG.md | 2 ++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/components/zcash_protocol/CHANGELOG.md b/components/zcash_protocol/CHANGELOG.md index 6c6d200898..839e3b0845 100644 --- a/components/zcash_protocol/CHANGELOG.md +++ b/components/zcash_protocol/CHANGELOG.md @@ -33,6 +33,13 @@ The entries below are relative to the `zcash_primitives` crate as of the tag ### Changed - `zcash_protocol::value::COIN` has been changed from an `i64` to a `u64` - `zcash_protocol::value::MAX_MONEY` has been changed from an `i64` to a `u64` +- `zcash_protocol::consensus::Parameters` has been split into two traits, with + the newly added `NetworkConstants` trait providing all network constant + accessors. Also, the `address_network` method has been replaced with a new + `network_type` method that serves the same purpose. A blanket impl of + `NetworkConstants` is provided for all types that implement `Parameters`, + so call sites for methods that have moved to `NetworkConstants` should + remain unchanged (though they may require an additional `use` statement.) ### Removed - From `zcash_protocol::value`: @@ -46,7 +53,3 @@ The entries below are relative to the `zcash_primitives` crate as of the tag - `TryFrom for NonNegativeAmount` - `impl AddAssign for NonNegativeAmount` - `impl SubAssign for NonNegativeAmount` -- `zcash_protocol::consensus::Parameters` has been split into two traits, with - the `NetworkConstants` trait providing all network constant accessors. Also, - the `address_network` method has been replaced with a new `network_type` - method that serves the same purpose. diff --git a/zcash_client_sqlite/CHANGELOG.md b/zcash_client_sqlite/CHANGELOG.md index 524d8dfb64..fbf30976e6 100644 --- a/zcash_client_sqlite/CHANGELOG.md +++ b/zcash_client_sqlite/CHANGELOG.md @@ -16,6 +16,8 @@ and this library adheres to Rust's notion of - `zcash_client_sqlite::error::SqliteClientError` has changed variants: - Added `AddressGeneration` - Removed `DiversifierIndexOutOfRange` +- `zcash_client_sqlite::wallet::init::WalletMigrationError` has added variant + `AddressGeneration` ## [0.9.0] - 2024-03-01 From eaabc0f51420dcb74d6983720827e141acc57e09 Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 5 Mar 2024 19:44:29 -0700 Subject: [PATCH 16/18] Apply suggestions from code review Co-authored-by: Daira-Emma Hopwood --- .../zcash_protocol/src/constants/mainnet.rs | 2 +- .../zcash_protocol/src/constants/testnet.rs | 2 +- components/zcash_protocol/src/value.rs | 16 +++++--- zcash_client_backend/CHANGELOG.md | 4 +- zcash_client_backend/src/zip321.rs | 38 +++++++++---------- 5 files changed, 32 insertions(+), 30 deletions(-) diff --git a/components/zcash_protocol/src/constants/mainnet.rs b/components/zcash_protocol/src/constants/mainnet.rs index b30babc721..25193fab88 100644 --- a/components/zcash_protocol/src/constants/mainnet.rs +++ b/components/zcash_protocol/src/constants/mainnet.rs @@ -29,7 +29,7 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviews"; /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "zs"; -/// The prefix for a Base58Check-encoded mainnet Sprout address +/// The prefix for a Base58Check-encoded mainnet Sprout address. /// /// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. /// diff --git a/components/zcash_protocol/src/constants/testnet.rs b/components/zcash_protocol/src/constants/testnet.rs index a0935ad2e7..bbe74b6a55 100644 --- a/components/zcash_protocol/src/constants/testnet.rs +++ b/components/zcash_protocol/src/constants/testnet.rs @@ -29,7 +29,7 @@ pub const HRP_SAPLING_EXTENDED_FULL_VIEWING_KEY: &str = "zxviewtestsapling"; /// [Zcash Protocol Specification]: https://github.com/zcash/zips/blob/master/protocol/protocol.pdf pub const HRP_SAPLING_PAYMENT_ADDRESS: &str = "ztestsapling"; -/// The prefix for a Base58Check-encoded testnet Sprout addresses. +/// The prefix for a Base58Check-encoded testnet Sprout address. /// /// Defined in the [Zcash Protocol Specification section 5.6.3][sproutpaymentaddrencoding]. /// diff --git a/components/zcash_protocol/src/value.rs b/components/zcash_protocol/src/value.rs index cfd3a7fc30..395ac8edc2 100644 --- a/components/zcash_protocol/src/value.rs +++ b/components/zcash_protocol/src/value.rs @@ -155,7 +155,7 @@ impl TryFrom for u64 { type Error = BalanceError; fn try_from(value: ZatBalance) -> Result { - value.0.try_into().map_err(|_| BalanceError::Overflow) + value.0.try_into().map_err(|_| BalanceError::Underflow) } } @@ -261,11 +261,9 @@ impl Zatoshis { /// /// Returns an error if the amount is outside the range `{0..MAX_MONEY}`. pub fn from_nonnegative_i64(amount: i64) -> Result { - if amount >= 0 { - Self::from_u64(u64::try_from(amount).unwrap()) - } else { - Err(BalanceError::Underflow) - } + u64::try_from(amount) + .map_err(|_| BalanceError::Underflow) + .and_then(Self::from_u64) } /// Reads an Zatoshis from an unsigned 64-bit little-endian integer. @@ -439,6 +437,12 @@ pub mod testing { } } + prop_compose! { + pub fn arb_nonnegative_zat_balance()(amt in 0i64..MAX_BALANCE) -> ZatBalance { + ZatBalance::from_i64(amt).unwrap() + } + } + prop_compose! { pub fn arb_zatoshis()(amt in 0u64..MAX_MONEY) -> Zatoshis { Zatoshis::from_u64(amt).unwrap() diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index 17f58112f5..cb89963662 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -57,9 +57,11 @@ and this library adheres to Rust's notion of `NonNegativeAmount` rather than a signed `Amount` as its argument. - `zcash_client_backend::zip321::parse::parse_amount` now parses a `NonNegativeAmount` rather than a signed `Amount`. +- `zcash_client_backend::zip321::TransactionRequest::total` now + returns `Result<_, BalanceError>` instead of `Result<_, ()>`. ### Removed -- `zcash_client_backend::PoolType::is_receiver` use +- `zcash_client_backend::PoolType::is_receiver`: use `zcash_keys::Address::has_receiver` instead. ## [0.11.0] - 2024-03-01 diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index e795b74af2..6aa0bdf3e6 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -245,7 +245,7 @@ impl TransactionRequest { payment_index: Option, ) -> impl IntoIterator + '_ { std::iter::empty() - .chain(render::amount_param(payment.amount, payment_index)) + .chain(Some(render::amount_param(payment.amount, payment_index))) .chain( payment .memo @@ -414,28 +414,24 @@ mod render { format!("address{}={}", param_index(idx), addr.encode(params)) } - /// Converts an [`NonNegativeAmount`] value to a correctly formatted decimal ZEC - /// value for inclusion in a ZIP 321 URI. - pub fn amount_str(amount: NonNegativeAmount) -> Option { - if amount.is_positive() { - let coins = u64::from(amount) / COIN; - let zats = u64::from(amount) % COIN; - Some(if zats == 0 { - format!("{}", coins) - } else { - format!("{}.{:0>8}", coins, zats) - .trim_end_matches('0') - .to_string() - }) + /// Converts a [`NonNegativeAmount`] value to a correctly formatted decimal ZEC + /// string for inclusion in a ZIP 321 URI. + pub fn amount_str(amount: NonNegativeAmount) -> String { + let coins = u64::from(amount) / COIN; + let zats = u64::from(amount) % COIN; + if zats == 0 { + format!("{}", coins) } else { - None + format!("{}.{:0>8}", coins, zats) + .trim_end_matches('0') + .to_string() } } /// Constructs an "amount" key/value pair containing the encoded ZEC amount /// at the specified parameter index. - pub fn amount_param(amount: NonNegativeAmount, idx: Option) -> Option { - amount_str(amount).map(|s| format!("amount{}={}", param_index(idx), s)) + pub fn amount_param(amount: NonNegativeAmount, idx: Option) -> String { + format!("amount{}={}", param_index(idx), amount_str(amount)) } /// Constructs a "memo" key/value pair containing the base64URI-encoded memo @@ -650,7 +646,7 @@ mod parse { digit1, opt(preceded( char('.'), - map_opt(digit0, |s: &str| if s.len() > 8 { None } else { Some(s) }), + map_opt(digit1, |s: &str| if s.len() > 8 { None } else { Some(s) }), )), )), |(whole_s, decimal_s): (&str, Option<&str>)| { @@ -671,7 +667,7 @@ mod parse { .and_then(|coin_zats| coin_zats.checked_add(zats)) .ok_or(BalanceError::Overflow) .and_then(NonNegativeAmount::from_u64) - .map_err(|_| format!("Not a valid zat amount: {}.{}", coins, zats)) + .map_err(|_| format!("Not a valid amount: {}.{:0>8} ZEC", coins, zats)) }, )(input) } @@ -854,7 +850,7 @@ mod tests { for amt_u64 in amounts { let amt = NonNegativeAmount::from_u64(amt_u64).unwrap(); - let amt_str = amount_str(amt).unwrap(); + let amt_str = amount_str(amt); assert_eq!(amt, parse_amount(&amt_str).unwrap().1); } } @@ -1099,7 +1095,7 @@ mod tests { #[test] fn prop_zip321_roundtrip_amount(amt in arb_nonnegative_amount()) { - let amt_str = amount_str(amt).unwrap(); + let amt_str = amount_str(amt); assert_eq!(amt, parse_amount(&amt_str).unwrap().1); } From 6b4942f8ebb685f4ffd5f103e2ae825fa0e54c6e Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 5 Mar 2024 21:44:51 -0700 Subject: [PATCH 17/18] Add failing test for incorrect zip321 amount parsing. --- zcash_client_backend/src/zip321.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index 6aa0bdf3e6..871faad192 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -1078,6 +1078,11 @@ mod tests { "zcash:?amount.10000=1.23&address.10000=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU"; let i10r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_10); assert!(i10r.is_err()); + + // invalid: bad amount format + let invalid_11 = "zcash:?address=tmEZhbWHTpdKMw5it8YDspUXSMGQyFwovpU&amount=123."; + let i11r = TransactionRequest::from_uri(&TEST_NETWORK, invalid_11); + assert!(i11r.is_err()); } proptest! { From 376db4684b012e89fae8ef16535359f92223698f Mon Sep 17 00:00:00 2001 From: Kris Nuttycombe Date: Tue, 5 Mar 2024 21:45:12 -0700 Subject: [PATCH 18/18] Fix incorrect zip321 amount parsing. --- zcash_client_backend/CHANGELOG.md | 5 +++++ zcash_client_backend/src/zip321.rs | 8 ++++---- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/zcash_client_backend/CHANGELOG.md b/zcash_client_backend/CHANGELOG.md index cb89963662..87b13bab85 100644 --- a/zcash_client_backend/CHANGELOG.md +++ b/zcash_client_backend/CHANGELOG.md @@ -64,6 +64,11 @@ and this library adheres to Rust's notion of - `zcash_client_backend::PoolType::is_receiver`: use `zcash_keys::Address::has_receiver` instead. +### Fixed +- This release fixes an error in amount parsing in `zip321` that previously + allowed amounts having a decimal point but no decimal value to be parsed + as valid. + ## [0.11.0] - 2024-03-01 ### Added diff --git a/zcash_client_backend/src/zip321.rs b/zcash_client_backend/src/zip321.rs index 871faad192..bd020bb3e1 100644 --- a/zcash_client_backend/src/zip321.rs +++ b/zcash_client_backend/src/zip321.rs @@ -458,7 +458,7 @@ mod parse { use nom::{ bytes::complete::{tag, take_till}, character::complete::{alpha1, char, digit0, digit1, one_of}, - combinator::{map_opt, map_res, opt, recognize}, + combinator::{all_consuming, map_opt, map_res, opt, recognize}, sequence::{preceded, separated_pair, tuple}, AsChar, IResult, InputTakeAtPosition, }; @@ -642,13 +642,13 @@ mod parse { /// Parses a value in decimal ZEC. pub fn parse_amount(input: &str) -> IResult<&str, NonNegativeAmount> { map_res( - tuple(( + all_consuming(tuple(( digit1, opt(preceded( char('.'), map_opt(digit1, |s: &str| if s.len() > 8 { None } else { Some(s) }), )), - )), + ))), |(whole_s, decimal_s): (&str, Option<&str>)| { let coins: u64 = whole_s .to_string() @@ -667,7 +667,7 @@ mod parse { .and_then(|coin_zats| coin_zats.checked_add(zats)) .ok_or(BalanceError::Overflow) .and_then(NonNegativeAmount::from_u64) - .map_err(|_| format!("Not a valid amount: {}.{:0>8} ZEC", coins, zats)) + .map_err(|_| format!("Not a valid amount: {} ZEC", input)) }, )(input) }