Compare commits

...

17 Commits
0.2.0 ... main

Author SHA1 Message Date
str4d 7db1c6fe5e
Merge pull request #1 from zcash/ci
Set up CI
2023-11-18 04:29:59 +00:00
Jack Grigg 461d99d42d Set up CI 2023-11-18 16:59:13 +13:00
Jack Grigg 9c76d5789f Render byte slices as hex more often in `Debug` impls
This is more generally useful for debugging purposes than the default
`Debug` impl for `&[u8]`.

We also provide an alternate `Debug` impl for `legacy::Script` that
parses and renders known opcodes. Note that we only parse a subset of
the full opcode set.


Extracted from: c8e2d81f58
2023-08-30 20:41:27 +00:00
Kris Nuttycombe b459f710c2 Merge pull request #856 from zcash/release/primitives_0.12-etc
Release zcash_address 0.3.0, zcash_primitives 0.12.0 and zcash_proofs 0.12.0

Extracted from: d2f105efe9
2023-06-06 16:59:51 -06:00
Kris Nuttycombe 642ac4b7a7 Release zcash_note_encryption version 0.4.0
Extracted from: 80adb54e26
2023-06-06 10:12:33 -06:00
Kris Nuttycombe f000a52148 Add comments detailing the checks required prior to calling `check_note_validity`
Extracted from: fe3d0269d1
2023-05-26 10:12:21 -06:00
Kris Nuttycombe 5076943e69 Update `zcash_primitives` to reflect argument changes to `parse_note_plaintext_without_memo_ovk`
Extracted from: 696a9be0a0
2023-05-26 09:43:26 -06:00
Kris Nuttycombe a7fa69c504 Remove `esk` and `ephemeral_key` arguments from `parse_note_plaintext_without_memo_ovk`
Fixes #850


Extracted from: be89e81534
2023-05-26 09:24:22 -06:00
Jack Grigg 81d8f61b0d zcash_note_encryption: Remove `esk` check requirement from `Domain::parse_note_plaintext_without_memo_ovk`
This method is only called from `try_output_recovery_with_ock`, and we
can instead rely on the check performed in `check_note_validity`,
reducing the number of checks that `Domain` implementations need to
perform.

The `esk` and `ephemeral_key` parameters become unused, and will be
removed in a subsequent commit (as this change needs to be synchronized
with the `orchard` crate).


Extracted from: a115a8f00f
2023-05-19 16:30:47 +00:00
Kris Nuttycombe 9198dc7748 Merge pull request #798 from nuttycom/zcash_note_encryption-0.3.0
Update zcash_note_encryption changelog for 0.3.0 release.

Extracted from: 21ae514e99
2023-03-22 10:28:06 -06:00
Kris Nuttycombe 8e2955da7c Update zcash_note_encryption changelog for 0.3.0 release.
Extracted from: 1a27a7f9ce
2023-03-22 09:54:18 -06:00
Kris Nuttycombe e66605f78f Merge pull request #790 from zcash/sapling/zcash_note_encryption-0.3.0
Update zcash_primitives to use zcash_note_encryption 0.3.0

Extracted from: ca349b62fd
2023-03-21 15:39:05 -06:00
Kris Nuttycombe d8885bec55 Update Sapling note encryption for `zcash_note_encryption 0.3.0`
Fixes zcash/librustzcash#455


Extracted from: d6fafa291b
2023-03-21 11:09:38 -06:00
Kris Nuttycombe f13c6cb13c Merge pull request #797 from zcash/note_encryption_remove_recipient
zcash_note_encryption: remove `recipient` parameter from `Domain::note_plaintext_bytes`

Extracted from: 169782c672
2023-03-21 10:44:28 -06:00
Kris Nuttycombe 8d0d9c9510 Bump zcash_note_encryption to version 0.3.0 for release.
This removes the path-based dependencies on the `zcash_note_encryption`
crate in favor of using versioned dependencies locally. This better
reflects the future state in which `zcash_note_encryption` is factored
out of the workspace and maintained in a separate repository.


Extracted from: c696069f93
2023-03-20 16:16:34 -06:00
Kris Nuttycombe b060f1c1df Remove the `recipient` parameter from zcash_note_encyption::Domain::note_plaintext_bytes
The `Domain::Note` type is now expected to contain information about the
recipient of the note, eliminating the need to pass this information in
via the encryption context.


Extracted from: c88f3e1b9d
2023-03-20 16:16:00 -06:00
str4d cd883e2227 Merge pull request #675 from zcash/zcash_note_encryption-0.2.0
zcash_note_encryption 0.2.0

Extracted from: 1a20fc1578
2022-10-13 23:23:12 +01:00
8 changed files with 319 additions and 35 deletions

10
.github/dependabot.yml vendored Normal file
View File

@ -0,0 +1,10 @@
version: 2
updates:
- package-ecosystem: github-actions
directory: "/"
schedule:
interval: daily
timezone: Etc/UTC
open-pull-requests-limit: 10
labels:
- "A-CI"

88
.github/workflows/ci.yml vendored Normal file
View File

@ -0,0 +1,88 @@
name: CI checks
on: [push, pull_request]
jobs:
test:
name: Test on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v4
- name: Run tests
run: cargo test --all-features --verbose
- name: Verify working directory is clean
run: git diff --exit-code
build-latest:
name: Latest build on ${{ matrix.os }}
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest, windows-latest, macOS-latest]
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
id: toolchain
- run: rustup override set ${{steps.toolchain.outputs.name}}
- name: Remove lockfile to build with latest dependencies
run: rm Cargo.lock
- name: Build crate
run: cargo build --all-targets --all-features --verbose
- name: Verify working directory is clean (excluding lockfile)
run: git diff --exit-code ':!Cargo.lock'
build-nodefault:
name: Build target ${{ matrix.target }}
runs-on: ubuntu-latest
strategy:
matrix:
target:
- wasm32-wasi
steps:
- uses: actions/checkout@v4
- name: Add target
run: rustup target add ${{ matrix.target }}
- name: Build crate
run: cargo build --no-default-features --verbose --target ${{ matrix.target }}
bitrot:
name: Bitrot check
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
# Build benchmarks to prevent bitrot
- name: Build benchmarks
run: cargo build --benches
clippy:
name: Clippy (MSRV)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Clippy
uses: auguwu/clippy-action@1.3.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
working-directory: ${{ inputs.target }}
deny: warnings
doc-links:
name: Intra-doc links
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- run: cargo fetch
# Requires #![deny(rustdoc::broken_intra_doc_links)] in crate.
- name: Check intra-doc links
run: cargo doc --all-features --document-private-items
fmt:
name: Rustfmt
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check formatting
run: cargo fmt -- --check

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
target

View File

@ -7,6 +7,25 @@ and this library adheres to Rust's notion of
## [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`:

166
Cargo.lock generated Normal file
View File

@ -0,0 +1,166 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 3
[[package]]
name = "aead"
version = "0.5.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d122413f284cf2d62fb1b7db97e02edb8cda96d769b16e443a4f6195e35662b0"
dependencies = [
"crypto-common",
"generic-array",
]
[[package]]
name = "cfg-if"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd"
[[package]]
name = "chacha20"
version = "0.9.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c3613f74bd2eac03dad61bd53dbe620703d4371614fe0bc3b9f04dd36fe4e818"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "chacha20poly1305"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "10cd79432192d1c0f4e1a0fef9527696cc039165d729fb41b3f4f4f354c2dc35"
dependencies = [
"aead",
"chacha20",
"cipher",
"poly1305",
"zeroize",
]
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
"zeroize",
]
[[package]]
name = "cpufeatures"
version = "0.2.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ce420fe07aecd3e67c5f910618fe65e94158f6dcc0adf44e00d69ce2bdfe0fd0"
dependencies = [
"libc",
]
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "inout"
version = "0.1.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0c10553d664a4d0bcff9f4215d0aac67a639cc68ef660840afe309b807bc9f5"
dependencies = [
"generic-array",
]
[[package]]
name = "libc"
version = "0.2.150"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89d92a4743f9a61002fae18374ed11e7973f530cb3a3255fb354818118b2203c"
[[package]]
name = "opaque-debug"
version = "0.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "624a8340c38c1b80fd549087862da4ba43e08858af025b236e509b6649fc13d5"
[[package]]
name = "poly1305"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8159bd90725d2df49889a078b54f4f79e87f1f8a8444194cdca81d38f5393abf"
dependencies = [
"cpufeatures",
"opaque-debug",
"universal-hash",
]
[[package]]
name = "rand_core"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c"
[[package]]
name = "subtle"
version = "2.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "81cdd64d312baedb58e21336b31bc043b77e01cc99033ce76ef539f78e965ebc"
[[package]]
name = "typenum"
version = "1.17.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "42ff0bf0c66b8238c6f3b578df37d0b7848e55df8577b3f74f92a69acceeb825"
[[package]]
name = "universal-hash"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fc1de2c688dc15305988b563c3854064043356019f97a4b46276fe734c4f07ea"
dependencies = [
"crypto-common",
"subtle",
]
[[package]]
name = "version_check"
version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "zcash_note_encryption"
version = "0.4.0"
dependencies = [
"chacha20",
"chacha20poly1305",
"cipher",
"rand_core",
"subtle",
]
[[package]]
name = "zeroize"
version = "1.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2a0956f1ba7c7909bfb66c2e9e4124ab6f6482560f6628b5aaeba39207c9aad9"

View File

@ -1,7 +1,7 @@
[package]
name = "zcash_note_encryption"
description = "Note encryption for Zcash transactions"
version = "0.2.0"
version = "0.4.0"
authors = [
"Jack Grigg <jack@electriccoin.co>",
"Kris Nuttycombe <kris@electriccoin.co>"

3
rust-toolchain.toml Normal file
View File

@ -0,0 +1,3 @@
[toolchain]
channel = "1.56.1"
components = ["clippy", "rustfmt"]

View File

@ -19,6 +19,8 @@
#![deny(unsafe_code)]
// TODO: #![deny(missing_docs)]
use core::fmt::{self, Write};
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
@ -72,9 +74,28 @@ impl AsRef<[u8]> for OutgoingCipherKey {
/// Newtype representing the byte encoding of an [`EphemeralPublicKey`].
///
/// [`EphemeralPublicKey`]: Domain::EphemeralPublicKey
#[derive(Clone, Debug)]
#[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
@ -171,20 +192,7 @@ pub trait Domain {
fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey;
/// Encodes the given `Note` and `Memo` as a note plaintext.
///
/// # Future breaking changes
///
/// The `recipient` argument is present as a secondary way to obtain the diversifier;
/// this is due to a historical quirk of how the Sapling `Note` struct was implemented
/// in the `zcash_primitives` crate. `recipient` will be removed from this method in a
/// future crate release, once [`zcash_primitives` has been refactored].
///
/// [`zcash_primitives` has been refactored]: https://github.com/zcash/librustzcash/issues/454
fn note_plaintext_bytes(
note: &Self::Note,
recipient: &Self::Recipient,
memo: &Self::Memo,
) -> NotePlaintextBytes;
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`.
@ -242,8 +250,6 @@ pub trait Domain {
/// which may be passed via `self`).
/// - The note plaintext contains valid encodings of its various fields.
/// - Any domain-specific requirements are satisfied.
/// - `ephemeral_key` can be derived from `esk` and the diversifier within the note
/// plaintext.
///
/// `&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.
@ -252,8 +258,6 @@ pub trait Domain {
fn parse_note_plaintext_without_memo_ovk(
&self,
pk_d: &Self::DiversifiedTransmissionKey,
esk: &Self::EphemeralSecretKey,
ephemeral_key: &EphemeralKeyBytes,
plaintext: &NotePlaintextBytes,
) -> Option<(Self::Note, Self::Recipient)>;
@ -349,7 +353,6 @@ pub struct NoteEncryption<D: Domain> {
epk: D::EphemeralPublicKey,
esk: D::EphemeralSecretKey,
note: D::Note,
to: D::Recipient,
memo: D::Memo,
/// `None` represents the `ovk = ⊥` case.
ovk: Option<D::OutgoingViewingKey>,
@ -358,18 +361,12 @@ pub struct NoteEncryption<D: Domain> {
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,
to: D::Recipient,
memo: D::Memo,
) -> Self {
pub fn new(ovk: Option<D::OutgoingViewingKey>, note: D::Note, memo: D::Memo) -> Self {
let esk = D::derive_esk(&note).expect("ZIP 212 is active.");
NoteEncryption {
epk: D::ka_derive_public(&note, &esk),
esk,
note,
to,
memo,
ovk,
}
@ -384,14 +381,12 @@ impl<D: Domain> NoteEncryption<D> {
esk: D::EphemeralSecretKey,
ovk: Option<D::OutgoingViewingKey>,
note: D::Note,
to: D::Recipient,
memo: D::Memo,
) -> Self {
NoteEncryption {
epk: D::ka_derive_public(&note, &esk),
esk,
note,
to,
memo,
ovk,
}
@ -412,7 +407,7 @@ impl<D: Domain> NoteEncryption<D> {
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.to, &self.memo);
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);
@ -539,6 +534,8 @@ fn check_note_validity<D: Domain>(
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)
@ -677,12 +674,12 @@ pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D, ENC_CIP
)
.ok()?;
let (note, to) =
domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, &ephemeral_key, &plaintext)?;
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.
// 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(&note) {
if (!derived_esk.ct_eq(&esk)).into() {
return None;