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