2021-12-17 06:57:17 -08:00
|
|
|
|
//! 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
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2021-11-24 05:58:52 -08:00
|
|
|
|
#![no_std]
|
2021-12-16 17:07:07 -08:00
|
|
|
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
2021-09-01 09:21:05 -07:00
|
|
|
|
// Catch documentation errors caused by code changes.
|
2022-07-29 14:56:44 -07:00
|
|
|
|
#![deny(rustdoc::broken_intra_doc_links)]
|
2021-09-01 09:21:05 -07:00
|
|
|
|
#![deny(unsafe_code)]
|
|
|
|
|
// TODO: #![deny(missing_docs)]
|
|
|
|
|
|
2023-05-15 13:45:38 -07:00
|
|
|
|
use core::fmt::{self, Write};
|
|
|
|
|
|
2021-11-24 05:58:52 -08:00
|
|
|
|
#[cfg(feature = "alloc")]
|
|
|
|
|
extern crate alloc;
|
|
|
|
|
#[cfg(feature = "alloc")]
|
|
|
|
|
use alloc::vec::Vec;
|
2021-08-30 07:03:39 -07:00
|
|
|
|
|
|
|
|
|
use chacha20::{
|
2022-09-16 11:18:50 -07:00
|
|
|
|
cipher::{StreamCipher, StreamCipherSeek},
|
2021-08-30 07:03:39 -07:00
|
|
|
|
ChaCha20,
|
|
|
|
|
};
|
2022-09-16 11:18:50 -07:00
|
|
|
|
use chacha20poly1305::{aead::AeadInPlace, ChaCha20Poly1305, KeyInit};
|
|
|
|
|
use cipher::KeyIvInit;
|
2021-09-01 09:21:05 -07:00
|
|
|
|
|
2021-03-22 13:59:25 -07:00
|
|
|
|
use rand_core::RngCore;
|
2021-04-05 10:51:07 -07:00
|
|
|
|
use subtle::{Choice, ConstantTimeEq};
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2021-11-24 05:58:52 -08:00
|
|
|
|
#[cfg(feature = "alloc")]
|
2021-12-16 17:07:07 -08:00
|
|
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
|
2021-08-06 08:20:42 -07:00
|
|
|
|
pub mod batch;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// The size of a compact note.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
pub const COMPACT_NOTE_SIZE: usize = 1 + // version
|
|
|
|
|
11 + // diversifier
|
|
|
|
|
8 + // value
|
2021-04-08 09:08:58 -07:00
|
|
|
|
32; // rseed (or rcm prior to ZIP 212)
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// The size of [`NotePlaintextBytes`].
|
2021-03-22 13:59:25 -07:00
|
|
|
|
pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512;
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// The size of [`OutPlaintextBytes`].
|
2021-03-22 13:59:25 -07:00
|
|
|
|
pub const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d
|
|
|
|
|
32; // esk
|
2021-12-16 21:36:21 -08:00
|
|
|
|
const AEAD_TAG_SIZE: usize = 16;
|
|
|
|
|
/// The size of an encrypted note plaintext.
|
2021-04-12 08:18:51 -07:00
|
|
|
|
pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE;
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// The size of an encrypted outgoing plaintext.
|
2021-04-12 08:18:51 -07:00
|
|
|
|
pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + AEAD_TAG_SIZE;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
|
|
|
|
/// 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)
|
2021-03-19 13:56:20 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
|
|
|
|
impl AsRef<[u8]> for OutgoingCipherKey {
|
|
|
|
|
fn as_ref(&self) -> &[u8] {
|
|
|
|
|
&self.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Newtype representing the byte encoding of an [`EphemeralPublicKey`].
|
|
|
|
|
///
|
|
|
|
|
/// [`EphemeralPublicKey`]: Domain::EphemeralPublicKey
|
2023-05-15 13:45:38 -07:00
|
|
|
|
#[derive(Clone)]
|
2021-03-22 13:59:25 -07:00
|
|
|
|
pub struct EphemeralKeyBytes(pub [u8; 32]);
|
|
|
|
|
|
2023-05-15 13:45:38 -07:00
|
|
|
|
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()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-12 15:19:50 -07:00
|
|
|
|
impl AsRef<[u8]> for EphemeralKeyBytes {
|
|
|
|
|
fn as_ref(&self) -> &[u8] {
|
|
|
|
|
&self.0
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-22 13:59:25 -07:00
|
|
|
|
impl From<[u8; 32]> for EphemeralKeyBytes {
|
|
|
|
|
fn from(value: [u8; 32]) -> EphemeralKeyBytes {
|
|
|
|
|
EphemeralKeyBytes(value)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-12 08:13:04 -07:00
|
|
|
|
impl ConstantTimeEq for EphemeralKeyBytes {
|
|
|
|
|
fn ct_eq(&self, other: &Self) -> Choice {
|
|
|
|
|
self.0.ct_eq(&other.0)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Newtype representing the byte encoding of a note plaintext.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]);
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Newtype representing the byte encoding of a outgoing plaintext.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]);
|
|
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
2021-12-16 20:18:51 -08:00
|
|
|
|
enum NoteValidity {
|
2021-03-22 13:59:25 -07:00
|
|
|
|
Valid,
|
|
|
|
|
Invalid,
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// 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.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
pub trait Domain {
|
2021-05-28 14:33:00 -07:00
|
|
|
|
type EphemeralSecretKey: ConstantTimeEq;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
type EphemeralPublicKey;
|
2022-09-12 14:52:54 -07:00
|
|
|
|
type PreparedEphemeralPublicKey;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
type SharedSecret;
|
|
|
|
|
type SymmetricKey: AsRef<[u8]>;
|
|
|
|
|
type Note;
|
|
|
|
|
type Recipient;
|
|
|
|
|
type DiversifiedTransmissionKey;
|
|
|
|
|
type IncomingViewingKey;
|
|
|
|
|
type OutgoingViewingKey;
|
|
|
|
|
type ValueCommitment;
|
2021-04-15 14:15:54 -07:00
|
|
|
|
type ExtractedCommitment;
|
2021-05-28 14:57:48 -07:00
|
|
|
|
type ExtractedCommitmentBytes: Eq + for<'a> From<&'a Self::ExtractedCommitment>;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
type Memo;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// 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
|
2021-03-22 13:59:25 -07:00
|
|
|
|
fn derive_esk(note: &Self::Note) -> Option<Self::EphemeralSecretKey>;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Extracts the `DiversifiedTransmissionKey` from the note.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey;
|
|
|
|
|
|
2022-09-12 14:52:54 -07:00
|
|
|
|
/// Prepare an ephemeral public key for more efficient scalar multiplication.
|
|
|
|
|
fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Derives `EphemeralPublicKey` from `esk` and the note's diversifier.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
fn ka_derive_public(
|
|
|
|
|
note: &Self::Note,
|
|
|
|
|
esk: &Self::EphemeralSecretKey,
|
|
|
|
|
) -> Self::EphemeralPublicKey;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Derives the `SharedSecret` from the sender's information during note encryption.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
fn ka_agree_enc(
|
|
|
|
|
esk: &Self::EphemeralSecretKey,
|
|
|
|
|
pk_d: &Self::DiversifiedTransmissionKey,
|
|
|
|
|
) -> Self::SharedSecret;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Derives the `SharedSecret` from the recipient's information during note trial
|
|
|
|
|
/// decryption.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
fn ka_agree_dec(
|
|
|
|
|
ivk: &Self::IncomingViewingKey,
|
2022-09-12 14:52:54 -07:00
|
|
|
|
epk: &Self::PreparedEphemeralPublicKey,
|
2021-03-22 13:59:25 -07:00
|
|
|
|
) -> Self::SharedSecret;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// 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
|
2021-04-12 15:19:50 -07:00
|
|
|
|
fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Encodes the given `Note` and `Memo` as a note plaintext.
|
2023-03-09 14:22:13 -08:00
|
|
|
|
fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> NotePlaintextBytes;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Derives the [`OutgoingCipherKey`] for an encrypted note, given the note-specific
|
|
|
|
|
/// public data and an `OutgoingViewingKey`.
|
2021-04-12 15:19:50 -07:00
|
|
|
|
fn derive_ock(
|
2021-03-22 13:59:25 -07:00
|
|
|
|
ovk: &Self::OutgoingViewingKey,
|
|
|
|
|
cv: &Self::ValueCommitment,
|
2021-05-28 15:14:48 -07:00
|
|
|
|
cmstar_bytes: &Self::ExtractedCommitmentBytes,
|
2021-04-12 15:19:50 -07:00
|
|
|
|
ephemeral_key: &EphemeralKeyBytes,
|
2021-03-22 13:59:25 -07:00
|
|
|
|
) -> OutgoingCipherKey;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Encodes the outgoing plaintext for the given note.
|
2021-03-27 07:51:44 -07:00
|
|
|
|
fn outgoing_plaintext_bytes(
|
2021-03-22 13:59:25 -07:00
|
|
|
|
note: &Self::Note,
|
|
|
|
|
esk: &Self::EphemeralSecretKey,
|
|
|
|
|
) -> OutPlaintextBytes;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Returns the byte encoding of the given `EphemeralPublicKey`.
|
2021-03-27 07:51:44 -07:00
|
|
|
|
fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Attempts to parse `ephemeral_key` as an `EphemeralPublicKey`.
|
|
|
|
|
///
|
|
|
|
|
/// Returns `None` if `ephemeral_key` is not a valid byte encoding of an
|
|
|
|
|
/// `EphemeralPublicKey`.
|
2021-06-10 10:35:19 -07:00
|
|
|
|
fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option<Self::EphemeralPublicKey>;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Derives the `ExtractedCommitment` for this note.
|
2021-04-15 14:15:54 -07:00
|
|
|
|
fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// 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`].
|
2021-03-23 10:29:16 -07:00
|
|
|
|
fn parse_note_plaintext_without_memo_ivk(
|
2021-03-22 13:59:25 -07:00
|
|
|
|
&self,
|
|
|
|
|
ivk: &Self::IncomingViewingKey,
|
|
|
|
|
plaintext: &[u8],
|
|
|
|
|
) -> Option<(Self::Note, Self::Recipient)>;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// 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
|
2021-03-23 10:29:16 -07:00
|
|
|
|
fn parse_note_plaintext_without_memo_ovk(
|
|
|
|
|
&self,
|
|
|
|
|
pk_d: &Self::DiversifiedTransmissionKey,
|
2021-12-16 19:29:21 -08:00
|
|
|
|
plaintext: &NotePlaintextBytes,
|
2021-03-23 10:29:16 -07:00
|
|
|
|
) -> Option<(Self::Note, Self::Recipient)>;
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// 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.
|
2021-12-16 19:29:21 -08:00
|
|
|
|
fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo;
|
2021-03-23 10:29:16 -07:00
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Parses the `DiversifiedTransmissionKey` field of the outgoing plaintext.
|
|
|
|
|
///
|
|
|
|
|
/// Returns `None` if `out_plaintext` does not contain a valid byte encoding of a
|
|
|
|
|
/// `DiversifiedTransmissionKey`.
|
2021-12-16 19:29:21 -08:00
|
|
|
|
fn extract_pk_d(out_plaintext: &OutPlaintextBytes) -> Option<Self::DiversifiedTransmissionKey>;
|
2021-03-23 10:29:16 -07:00
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Parses the `EphemeralSecretKey` field of the outgoing plaintext.
|
|
|
|
|
///
|
|
|
|
|
/// Returns `None` if `out_plaintext` does not contain a valid byte encoding of an
|
|
|
|
|
/// `EphemeralSecretKey`.
|
2021-12-16 19:29:21 -08:00
|
|
|
|
fn extract_esk(out_plaintext: &OutPlaintextBytes) -> Option<Self::EphemeralSecretKey>;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// 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.
|
2021-11-24 05:58:52 -08:00
|
|
|
|
#[cfg(feature = "alloc")]
|
2021-12-16 17:07:07 -08:00
|
|
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "alloc")))]
|
2021-11-17 04:14:26 -08:00
|
|
|
|
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>,
|
2022-09-12 14:52:54 -07:00
|
|
|
|
) -> Vec<(Option<Self::PreparedEphemeralPublicKey>, EphemeralKeyBytes)> {
|
2021-11-17 04:14:26 -08:00
|
|
|
|
// Default implementation: do the non-batched thing.
|
|
|
|
|
ephemeral_keys
|
2022-09-12 14:52:54 -07:00
|
|
|
|
.map(|ephemeral_key| {
|
|
|
|
|
(
|
|
|
|
|
Self::epk(&ephemeral_key).map(Self::prepare_epk),
|
|
|
|
|
ephemeral_key,
|
|
|
|
|
)
|
|
|
|
|
})
|
2021-11-17 04:14:26 -08:00
|
|
|
|
.collect()
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// 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`].
|
2021-12-16 21:13:52 -08:00
|
|
|
|
pub trait ShieldedOutput<D: Domain, const CIPHERTEXT_SIZE: usize> {
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Exposes the `ephemeral_key` field of the output.
|
2021-06-10 10:35:19 -07:00
|
|
|
|
fn ephemeral_key(&self) -> EphemeralKeyBytes;
|
2021-12-16 21:36:21 -08:00
|
|
|
|
|
|
|
|
|
/// Exposes the `cmu_bytes` or `cmx_bytes` field of the output.
|
2021-04-15 14:15:54 -07:00
|
|
|
|
fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes;
|
2021-12-16 21:36:21 -08:00
|
|
|
|
|
|
|
|
|
/// Exposes the note ciphertext of the output.
|
2021-12-16 21:13:52 -08:00
|
|
|
|
fn enc_ciphertext(&self) -> &[u8; CIPHERTEXT_SIZE];
|
2021-03-22 13:59:25 -07:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// 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.
|
|
|
|
|
///
|
2021-04-12 15:42:04 -07:00
|
|
|
|
/// Implements section 4.19 of the
|
|
|
|
|
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#saplingandorchardinband)
|
2021-03-22 13:59:25 -07:00
|
|
|
|
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.
|
2023-03-09 14:22:13 -08:00
|
|
|
|
pub fn new(ovk: Option<D::OutgoingViewingKey>, note: D::Note, memo: D::Memo) -> Self {
|
2021-03-22 13:59:25 -07:00
|
|
|
|
let esk = D::derive_esk(¬e).expect("ZIP 212 is active.");
|
2021-12-16 17:32:50 -08:00
|
|
|
|
NoteEncryption {
|
|
|
|
|
epk: D::ka_derive_public(¬e, &esk),
|
|
|
|
|
esk,
|
|
|
|
|
note,
|
|
|
|
|
memo,
|
|
|
|
|
ovk,
|
|
|
|
|
}
|
2021-03-22 13:59:25 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-04-12 15:19:50 -07:00
|
|
|
|
/// 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.
|
2021-12-16 17:32:50 -08:00
|
|
|
|
#[cfg(feature = "pre-zip-212")]
|
|
|
|
|
#[cfg_attr(docsrs, doc(cfg(feature = "pre-zip-212")))]
|
2021-03-22 13:59:25 -07:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-12 08:18:51 -07:00
|
|
|
|
/// Exposes the encoding of the ephemeral public key being used to encrypt this note.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
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);
|
2021-04-12 15:19:50 -07:00
|
|
|
|
let key = D::kdf(shared_secret, &D::epk_bytes(&self.epk));
|
2023-03-09 14:22:13 -08:00
|
|
|
|
let input = D::note_plaintext_bytes(&self.note, &self.memo);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
|
|
|
|
let mut output = [0u8; ENC_CIPHERTEXT_SIZE];
|
2021-08-30 07:03:39 -07:00
|
|
|
|
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);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
|
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Generates `outCiphertext` for this note.
|
|
|
|
|
pub fn encrypt_outgoing_plaintext<R: RngCore>(
|
2021-04-08 09:08:00 -07:00
|
|
|
|
&self,
|
2021-03-22 13:59:25 -07:00
|
|
|
|
cv: &D::ValueCommitment,
|
2021-04-15 14:15:54 -07:00
|
|
|
|
cmstar: &D::ExtractedCommitment,
|
2021-03-22 13:59:25 -07:00
|
|
|
|
rng: &mut R,
|
|
|
|
|
) -> [u8; OUT_CIPHERTEXT_SIZE] {
|
|
|
|
|
let (ock, input) = if let Some(ovk) = &self.ovk {
|
2022-02-01 13:02:07 -08:00
|
|
|
|
let ock = D::derive_ock(ovk, cv, &cmstar.into(), &D::epk_bytes(&self.epk));
|
2021-03-27 07:51:44 -07:00
|
|
|
|
let input = D::outgoing_plaintext_bytes(&self.note, &self.esk);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
|
|
|
|
(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];
|
2021-08-30 07:03:39 -07:00
|
|
|
|
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);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
|
|
|
|
output
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Trial decryption of the full note plaintext by the recipient.
|
|
|
|
|
///
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Attempts to decrypt and validate the given shielded output using the given `ivk`.
|
2021-05-28 16:15:25 -07:00
|
|
|
|
/// If successful, the corresponding note and memo are returned, along with the address to
|
|
|
|
|
/// which the note was sent.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
///
|
2021-04-12 15:42:04 -07:00
|
|
|
|
/// Implements section 4.19.2 of the
|
2021-05-28 16:15:25 -07:00
|
|
|
|
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk).
|
2021-12-16 21:13:52 -08:00
|
|
|
|
pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D, ENC_CIPHERTEXT_SIZE>>(
|
2021-03-22 13:59:25 -07:00
|
|
|
|
domain: &D,
|
|
|
|
|
ivk: &D::IncomingViewingKey,
|
2021-04-05 10:51:07 -07:00
|
|
|
|
output: &Output,
|
2021-03-22 13:59:25 -07:00
|
|
|
|
) -> Option<(D::Note, D::Recipient, D::Memo)> {
|
2021-06-10 10:35:19 -07:00
|
|
|
|
let ephemeral_key = output.ephemeral_key();
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2022-09-12 14:52:54 -07:00
|
|
|
|
let epk = D::prepare_epk(D::epk(&ephemeral_key)?);
|
2021-06-10 10:35:19 -07:00
|
|
|
|
let shared_secret = D::ka_agree_dec(ivk, &epk);
|
|
|
|
|
let key = D::kdf(shared_secret, &ephemeral_key);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2022-07-25 18:06:53 -07:00
|
|
|
|
try_note_decryption_inner(domain, ivk, &ephemeral_key, output, &key)
|
2021-08-06 08:20:42 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-16 21:13:52 -08:00
|
|
|
|
fn try_note_decryption_inner<D: Domain, Output: ShieldedOutput<D, ENC_CIPHERTEXT_SIZE>>(
|
2021-08-06 08:20:42 -07:00
|
|
|
|
domain: &D,
|
|
|
|
|
ivk: &D::IncomingViewingKey,
|
|
|
|
|
ephemeral_key: &EphemeralKeyBytes,
|
|
|
|
|
output: &Output,
|
2022-07-25 18:06:53 -07:00
|
|
|
|
key: &D::SymmetricKey,
|
2021-08-06 08:20:42 -07:00
|
|
|
|
) -> Option<(D::Note, D::Recipient, D::Memo)> {
|
2021-08-30 07:03:39 -07:00
|
|
|
|
let enc_ciphertext = output.enc_ciphertext();
|
|
|
|
|
|
2021-12-16 19:29:21 -08:00
|
|
|
|
let mut plaintext =
|
|
|
|
|
NotePlaintextBytes(enc_ciphertext[..NOTE_PLAINTEXT_SIZE].try_into().unwrap());
|
2021-08-30 07:03:39 -07:00
|
|
|
|
|
|
|
|
|
ChaCha20Poly1305::new(key.as_ref().into())
|
|
|
|
|
.decrypt_in_place_detached(
|
|
|
|
|
[0u8; 12][..].into(),
|
|
|
|
|
&[],
|
2021-12-16 19:29:21 -08:00
|
|
|
|
&mut plaintext.0,
|
2021-08-30 07:03:39 -07:00
|
|
|
|
enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(),
|
|
|
|
|
)
|
|
|
|
|
.ok()?;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2021-04-05 10:51:07 -07:00
|
|
|
|
let (note, to) = parse_note_plaintext_without_memo_ivk(
|
|
|
|
|
domain,
|
|
|
|
|
ivk,
|
2021-08-06 08:20:42 -07:00
|
|
|
|
ephemeral_key,
|
2021-04-15 14:15:54 -07:00
|
|
|
|
&output.cmstar_bytes(),
|
2021-12-16 19:29:21 -08:00
|
|
|
|
&plaintext.0,
|
2021-04-05 10:51:07 -07:00
|
|
|
|
)?;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
let memo = domain.extract_memo(&plaintext);
|
|
|
|
|
|
|
|
|
|
Some((note, to, memo))
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 10:29:16 -07:00
|
|
|
|
fn parse_note_plaintext_without_memo_ivk<D: Domain>(
|
2021-03-22 13:59:25 -07:00
|
|
|
|
domain: &D,
|
|
|
|
|
ivk: &D::IncomingViewingKey,
|
2021-06-10 10:35:19 -07:00
|
|
|
|
ephemeral_key: &EphemeralKeyBytes,
|
2021-04-15 14:15:54 -07:00
|
|
|
|
cmstar_bytes: &D::ExtractedCommitmentBytes,
|
2021-03-22 13:59:25 -07:00
|
|
|
|
plaintext: &[u8],
|
|
|
|
|
) -> Option<(D::Note, D::Recipient)> {
|
2022-02-01 13:02:07 -08:00
|
|
|
|
let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, plaintext)?;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2021-06-10 10:35:19 -07:00
|
|
|
|
if let NoteValidity::Valid = check_note_validity::<D>(¬e, ephemeral_key, cmstar_bytes) {
|
2021-03-23 10:29:16 -07:00
|
|
|
|
Some((note, to))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
fn check_note_validity<D: Domain>(
|
|
|
|
|
note: &D::Note,
|
2021-06-10 10:35:19 -07:00
|
|
|
|
ephemeral_key: &EphemeralKeyBytes,
|
2021-04-15 14:15:54 -07:00
|
|
|
|
cmstar_bytes: &D::ExtractedCommitmentBytes,
|
2021-03-23 10:29:16 -07:00
|
|
|
|
) -> NoteValidity {
|
2022-02-01 13:02:07 -08:00
|
|
|
|
if &D::ExtractedCommitmentBytes::from(&D::cmstar(note)) == cmstar_bytes {
|
2023-05-26 09:12:21 -07:00
|
|
|
|
// 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.
|
2021-12-16 20:18:51 -08:00
|
|
|
|
if let Some(derived_esk) = D::derive_esk(note) {
|
2022-02-01 13:02:07 -08:00
|
|
|
|
if D::epk_bytes(&D::ka_derive_public(note, &derived_esk))
|
|
|
|
|
.ct_eq(ephemeral_key)
|
2021-04-05 10:51:07 -07:00
|
|
|
|
.into()
|
|
|
|
|
{
|
2021-03-23 10:29:16 -07:00
|
|
|
|
NoteValidity::Valid
|
2021-03-22 13:59:25 -07:00
|
|
|
|
} else {
|
2021-03-23 10:29:16 -07:00
|
|
|
|
NoteValidity::Invalid
|
2021-03-22 13:59:25 -07:00
|
|
|
|
}
|
2021-12-16 20:18:51 -08:00
|
|
|
|
} else {
|
|
|
|
|
// Before ZIP 212
|
|
|
|
|
NoteValidity::Valid
|
|
|
|
|
}
|
2021-04-14 08:24:42 -07:00
|
|
|
|
} else {
|
|
|
|
|
// Published commitment doesn't match calculated commitment
|
|
|
|
|
NoteValidity::Invalid
|
2021-03-22 13:59:25 -07:00
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/// Trial decryption of the compact note plaintext by the recipient for light clients.
|
|
|
|
|
///
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Attempts to decrypt and validate the given compact shielded output using the
|
2021-05-28 16:15:25 -07:00
|
|
|
|
/// given `ivk`. If successful, the corresponding note is returned, along with the address
|
|
|
|
|
/// to which the note was sent.
|
2021-03-22 13:59:25 -07:00
|
|
|
|
///
|
|
|
|
|
/// Implements the procedure specified in [`ZIP 307`].
|
|
|
|
|
///
|
|
|
|
|
/// [`ZIP 307`]: https://zips.z.cash/zip-0307
|
2021-12-16 21:13:52 -08:00
|
|
|
|
pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D, COMPACT_NOTE_SIZE>>(
|
2021-03-22 13:59:25 -07:00
|
|
|
|
domain: &D,
|
|
|
|
|
ivk: &D::IncomingViewingKey,
|
2021-04-05 10:51:07 -07:00
|
|
|
|
output: &Output,
|
2021-03-22 13:59:25 -07:00
|
|
|
|
) -> Option<(D::Note, D::Recipient)> {
|
2021-06-10 10:35:19 -07:00
|
|
|
|
let ephemeral_key = output.ephemeral_key();
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2022-09-12 14:52:54 -07:00
|
|
|
|
let epk = D::prepare_epk(D::epk(&ephemeral_key)?);
|
2022-02-01 13:02:07 -08:00
|
|
|
|
let shared_secret = D::ka_agree_dec(ivk, &epk);
|
2021-06-10 10:35:19 -07:00
|
|
|
|
let key = D::kdf(shared_secret, &ephemeral_key);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2022-07-25 18:06:53 -07:00
|
|
|
|
try_compact_note_decryption_inner(domain, ivk, &ephemeral_key, output, &key)
|
2021-08-06 08:20:42 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-12-16 21:13:52 -08:00
|
|
|
|
fn try_compact_note_decryption_inner<D: Domain, Output: ShieldedOutput<D, COMPACT_NOTE_SIZE>>(
|
2021-08-06 08:20:42 -07:00
|
|
|
|
domain: &D,
|
|
|
|
|
ivk: &D::IncomingViewingKey,
|
|
|
|
|
ephemeral_key: &EphemeralKeyBytes,
|
|
|
|
|
output: &Output,
|
2022-07-25 18:06:53 -07:00
|
|
|
|
key: &D::SymmetricKey,
|
2021-08-06 08:20:42 -07:00
|
|
|
|
) -> Option<(D::Note, D::Recipient)> {
|
2021-03-22 13:59:25 -07:00
|
|
|
|
// Start from block 1 to skip over Poly1305 keying output
|
|
|
|
|
let mut plaintext = [0; COMPACT_NOTE_SIZE];
|
2021-04-05 10:51:07 -07:00
|
|
|
|
plaintext.copy_from_slice(output.enc_ciphertext());
|
2021-08-30 07:03:39 -07:00
|
|
|
|
let mut keystream = ChaCha20::new(key.as_ref().into(), [0u8; 12][..].into());
|
|
|
|
|
keystream.seek(64);
|
|
|
|
|
keystream.apply_keystream(&mut plaintext);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
2021-04-15 19:03:55 -07:00
|
|
|
|
parse_note_plaintext_without_memo_ivk(
|
|
|
|
|
domain,
|
|
|
|
|
ivk,
|
2021-08-06 08:20:42 -07:00
|
|
|
|
ephemeral_key,
|
2021-04-15 19:03:55 -07:00
|
|
|
|
&output.cmstar_bytes(),
|
|
|
|
|
&plaintext,
|
|
|
|
|
)
|
2021-03-23 10:29:16 -07:00
|
|
|
|
}
|
|
|
|
|
|
2021-05-28 15:45:23 -07:00
|
|
|
|
/// Recovery of the full note plaintext by the sender.
|
|
|
|
|
///
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Attempts to decrypt and validate the given shielded output using the given `ovk`.
|
2021-05-28 15:45:23 -07:00
|
|
|
|
/// 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
|
2021-12-16 21:13:52 -08:00
|
|
|
|
pub fn try_output_recovery_with_ovk<D: Domain, Output: ShieldedOutput<D, ENC_CIPHERTEXT_SIZE>>(
|
2021-05-28 15:45:23 -07:00
|
|
|
|
domain: &D,
|
|
|
|
|
ovk: &D::OutgoingViewingKey,
|
|
|
|
|
output: &Output,
|
|
|
|
|
cv: &D::ValueCommitment,
|
2021-12-16 21:22:24 -08:00
|
|
|
|
out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
|
2021-05-28 15:45:23 -07:00
|
|
|
|
) -> Option<(D::Note, D::Recipient, D::Memo)> {
|
2022-02-01 13:02:07 -08:00
|
|
|
|
let ock = D::derive_ock(ovk, cv, &output.cmstar_bytes(), &output.ephemeral_key());
|
2021-05-28 15:45:23 -07:00
|
|
|
|
try_output_recovery_with_ock(domain, &ock, output, out_ciphertext)
|
|
|
|
|
}
|
|
|
|
|
|
2021-03-23 10:29:16 -07:00
|
|
|
|
/// Recovery of the full note plaintext by the sender.
|
|
|
|
|
///
|
2021-12-16 21:36:21 -08:00
|
|
|
|
/// Attempts to decrypt and validate the given shielded output using the given `ock`.
|
2021-05-28 16:15:25 -07:00
|
|
|
|
/// If successful, the corresponding note and memo are returned, along with the address to
|
|
|
|
|
/// which the note was sent.
|
2021-03-23 10:29:16 -07:00
|
|
|
|
///
|
2021-04-12 15:42:04 -07:00
|
|
|
|
/// Implements part of section 4.19.3 of the
|
2021-05-28 16:15:25 -07:00
|
|
|
|
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptovk).
|
|
|
|
|
/// For decryption using a Full Viewing Key see [`try_output_recovery_with_ovk`].
|
2021-12-16 21:13:52 -08:00
|
|
|
|
pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D, ENC_CIPHERTEXT_SIZE>>(
|
2021-03-23 10:29:16 -07:00
|
|
|
|
domain: &D,
|
|
|
|
|
ock: &OutgoingCipherKey,
|
2021-04-05 10:51:07 -07:00
|
|
|
|
output: &Output,
|
2021-12-16 21:22:24 -08:00
|
|
|
|
out_ciphertext: &[u8; OUT_CIPHERTEXT_SIZE],
|
2021-03-23 10:29:16 -07:00
|
|
|
|
) -> Option<(D::Note, D::Recipient, D::Memo)> {
|
2021-08-30 07:03:39 -07:00
|
|
|
|
let enc_ciphertext = output.enc_ciphertext();
|
2021-03-23 10:29:16 -07:00
|
|
|
|
|
2021-12-16 19:29:21 -08:00
|
|
|
|
let mut op = OutPlaintextBytes([0; OUT_PLAINTEXT_SIZE]);
|
|
|
|
|
op.0.copy_from_slice(&out_ciphertext[..OUT_PLAINTEXT_SIZE]);
|
2021-08-30 07:03:39 -07:00
|
|
|
|
|
|
|
|
|
ChaCha20Poly1305::new(ock.as_ref().into())
|
|
|
|
|
.decrypt_in_place_detached(
|
|
|
|
|
[0u8; 12][..].into(),
|
|
|
|
|
&[],
|
2021-12-16 19:29:21 -08:00
|
|
|
|
&mut op.0,
|
2021-08-30 07:03:39 -07:00
|
|
|
|
out_ciphertext[OUT_PLAINTEXT_SIZE..].into(),
|
|
|
|
|
)
|
|
|
|
|
.ok()?;
|
2021-03-23 10:29:16 -07:00
|
|
|
|
|
|
|
|
|
let pk_d = D::extract_pk_d(&op)?;
|
|
|
|
|
let esk = D::extract_esk(&op)?;
|
|
|
|
|
|
2021-06-10 10:35:19 -07:00
|
|
|
|
let ephemeral_key = output.ephemeral_key();
|
2021-03-23 10:29:16 -07:00
|
|
|
|
let shared_secret = D::ka_agree_enc(&esk, &pk_d);
|
2021-04-12 17:43:21 -07:00
|
|
|
|
// The small-order point check at the point of output parsing rejects
|
|
|
|
|
// non-canonical encodings, so reencoding here for the KDF should
|
|
|
|
|
// be okay.
|
2021-06-10 10:35:19 -07:00
|
|
|
|
let key = D::kdf(shared_secret, &ephemeral_key);
|
2021-03-23 10:29:16 -07:00
|
|
|
|
|
2021-12-16 19:29:21 -08:00
|
|
|
|
let mut plaintext = NotePlaintextBytes([0; NOTE_PLAINTEXT_SIZE]);
|
|
|
|
|
plaintext
|
|
|
|
|
.0
|
|
|
|
|
.copy_from_slice(&enc_ciphertext[..NOTE_PLAINTEXT_SIZE]);
|
2021-08-30 07:03:39 -07:00
|
|
|
|
|
|
|
|
|
ChaCha20Poly1305::new(key.as_ref().into())
|
|
|
|
|
.decrypt_in_place_detached(
|
|
|
|
|
[0u8; 12][..].into(),
|
|
|
|
|
&[],
|
2021-12-16 19:29:21 -08:00
|
|
|
|
&mut plaintext.0,
|
2021-08-30 07:03:39 -07:00
|
|
|
|
enc_ciphertext[NOTE_PLAINTEXT_SIZE..].into(),
|
|
|
|
|
)
|
|
|
|
|
.ok()?;
|
2021-03-23 10:29:16 -07:00
|
|
|
|
|
2023-05-26 08:32:58 -07:00
|
|
|
|
let (note, to) = domain.parse_note_plaintext_without_memo_ovk(&pk_d, &plaintext)?;
|
2021-03-23 10:29:16 -07:00
|
|
|
|
let memo = domain.extract_memo(&plaintext);
|
|
|
|
|
|
2023-05-26 09:12:21 -07:00
|
|
|
|
// 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.)
|
2021-05-28 14:33:00 -07:00
|
|
|
|
if let Some(derived_esk) = D::derive_esk(¬e) {
|
|
|
|
|
if (!derived_esk.ct_eq(&esk)).into() {
|
|
|
|
|
return None;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2021-04-15 19:03:55 -07:00
|
|
|
|
if let NoteValidity::Valid =
|
2021-06-10 10:35:19 -07:00
|
|
|
|
check_note_validity::<D>(¬e, &ephemeral_key, &output.cmstar_bytes())
|
2021-04-15 19:03:55 -07:00
|
|
|
|
{
|
2021-03-23 10:29:16 -07:00
|
|
|
|
Some((note, to, memo))
|
|
|
|
|
} else {
|
|
|
|
|
None
|
|
|
|
|
}
|
2021-03-22 13:59:25 -07:00
|
|
|
|
}
|