2021-03-22 13:59:25 -07:00
|
|
|
//! Implementation of in-band secret distribution abstractions
|
|
|
|
//! for Zcash transactions. The implementations here provide
|
|
|
|
//! functionality that is shared between the Sapling and Orchard
|
|
|
|
//! protocols.
|
|
|
|
|
|
|
|
use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf};
|
|
|
|
use rand_core::RngCore;
|
2021-04-14 08:24:42 -07:00
|
|
|
use std::convert::TryFrom;
|
2021-04-05 10:51:07 -07:00
|
|
|
use subtle::{Choice, ConstantTimeEq};
|
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-03-22 13:59:25 -07:00
|
|
|
pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512;
|
|
|
|
pub const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d
|
|
|
|
32; // esk
|
2021-04-12 08:18:51 -07:00
|
|
|
pub const AEAD_TAG_SIZE: usize = 16;
|
|
|
|
pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE;
|
|
|
|
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
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub struct EphemeralKeyBytes(pub [u8; 32]);
|
|
|
|
|
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-03-22 13:59:25 -07:00
|
|
|
pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]);
|
|
|
|
pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]);
|
|
|
|
|
|
|
|
#[derive(Copy, Clone, PartialEq, Eq)]
|
2021-03-23 10:29:16 -07:00
|
|
|
pub enum NoteValidity {
|
2021-03-22 13:59:25 -07:00
|
|
|
Valid,
|
|
|
|
Invalid,
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait Domain {
|
2021-05-28 14:33:00 -07:00
|
|
|
type EphemeralSecretKey: ConstantTimeEq;
|
2021-03-22 13:59:25 -07:00
|
|
|
type EphemeralPublicKey;
|
|
|
|
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;
|
|
|
|
type ExtractedCommitmentBytes: Eq + TryFrom<Self::ExtractedCommitment>;
|
2021-03-22 13:59:25 -07:00
|
|
|
type Memo;
|
|
|
|
|
|
|
|
fn derive_esk(note: &Self::Note) -> Option<Self::EphemeralSecretKey>;
|
|
|
|
|
|
|
|
fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey;
|
|
|
|
|
|
|
|
fn ka_derive_public(
|
|
|
|
note: &Self::Note,
|
|
|
|
esk: &Self::EphemeralSecretKey,
|
|
|
|
) -> Self::EphemeralPublicKey;
|
|
|
|
|
|
|
|
fn ka_agree_enc(
|
|
|
|
esk: &Self::EphemeralSecretKey,
|
|
|
|
pk_d: &Self::DiversifiedTransmissionKey,
|
|
|
|
) -> Self::SharedSecret;
|
|
|
|
|
|
|
|
fn ka_agree_dec(
|
|
|
|
ivk: &Self::IncomingViewingKey,
|
|
|
|
epk: &Self::EphemeralPublicKey,
|
|
|
|
) -> Self::SharedSecret;
|
|
|
|
|
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
|
|
|
|
|
|
|
// for right now, we just need `recipient` to get `d`; in the future when we
|
|
|
|
// can get that from a Sapling note, the recipient parameter will be able
|
|
|
|
// to be removed.
|
2021-03-27 07:51:44 -07:00
|
|
|
fn note_plaintext_bytes(
|
2021-03-22 13:59:25 -07:00
|
|
|
note: &Self::Note,
|
|
|
|
recipient: &Self::Recipient,
|
|
|
|
memo: &Self::Memo,
|
|
|
|
) -> NotePlaintextBytes;
|
|
|
|
|
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-04-15 14:15:54 -07:00
|
|
|
cmstar: &Self::ExtractedCommitment,
|
2021-04-12 15:19:50 -07:00
|
|
|
ephemeral_key: &EphemeralKeyBytes,
|
2021-03-22 13:59:25 -07:00
|
|
|
) -> OutgoingCipherKey;
|
|
|
|
|
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-03-27 07:51:44 -07:00
|
|
|
fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
2021-03-23 10:29:16 -07:00
|
|
|
fn check_epk_bytes<F: Fn(&Self::EphemeralSecretKey) -> NoteValidity>(
|
2021-03-22 13:59:25 -07:00
|
|
|
note: &Self::Note,
|
|
|
|
check: F,
|
2021-03-23 10:29:16 -07:00
|
|
|
) -> NoteValidity;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
2021-04-15 14:15:54 -07:00
|
|
|
fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
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-03-23 10:29:16 -07:00
|
|
|
fn parse_note_plaintext_without_memo_ovk(
|
|
|
|
&self,
|
|
|
|
pk_d: &Self::DiversifiedTransmissionKey,
|
|
|
|
esk: &Self::EphemeralSecretKey,
|
|
|
|
epk: &Self::EphemeralPublicKey,
|
|
|
|
plaintext: &[u8],
|
|
|
|
) -> Option<(Self::Note, Self::Recipient)>;
|
|
|
|
|
2021-03-22 13:59:25 -07:00
|
|
|
// &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: &[u8]) -> Self::Memo;
|
2021-03-23 10:29:16 -07:00
|
|
|
|
|
|
|
fn extract_pk_d(
|
|
|
|
out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE],
|
|
|
|
) -> Option<Self::DiversifiedTransmissionKey>;
|
|
|
|
|
|
|
|
fn extract_esk(out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option<Self::EphemeralSecretKey>;
|
2021-03-22 13:59:25 -07:00
|
|
|
}
|
|
|
|
|
2021-04-05 10:51:07 -07:00
|
|
|
pub trait ShieldedOutput<D: Domain> {
|
|
|
|
fn epk(&self) -> &D::EphemeralPublicKey;
|
2021-04-15 14:15:54 -07:00
|
|
|
fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes;
|
2021-04-05 10:51:07 -07:00
|
|
|
fn enc_ciphertext(&self) -> &[u8];
|
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-04-15 16:10:05 -07:00
|
|
|
/// NB: the example code is only covering the post-Canopy case.
|
2021-03-22 13:59:25 -07:00
|
|
|
///
|
|
|
|
/// # Examples
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// extern crate ff;
|
|
|
|
/// extern crate rand_core;
|
|
|
|
/// extern crate zcash_primitives;
|
|
|
|
///
|
|
|
|
/// use ff::Field;
|
|
|
|
/// use rand_core::OsRng;
|
|
|
|
/// use zcash_primitives::{
|
2021-04-12 15:42:04 -07:00
|
|
|
/// consensus::{TEST_NETWORK, TestNetwork, NetworkUpgrade, Parameters},
|
2021-03-27 18:29:42 -07:00
|
|
|
/// memo::MemoBytes,
|
2021-03-22 13:59:25 -07:00
|
|
|
/// sapling::{
|
|
|
|
/// keys::{OutgoingViewingKey, prf_expand},
|
2021-04-12 15:42:04 -07:00
|
|
|
/// note_encryption::sapling_note_encryption,
|
|
|
|
/// util::generate_random_rseed,
|
2021-03-22 13:59:25 -07:00
|
|
|
/// Diversifier, PaymentAddress, Rseed, ValueCommitment
|
|
|
|
/// },
|
|
|
|
/// };
|
|
|
|
///
|
|
|
|
/// let mut rng = OsRng;
|
|
|
|
///
|
|
|
|
/// let diversifier = Diversifier([0; 11]);
|
|
|
|
/// let pk_d = diversifier.g_d().unwrap();
|
|
|
|
/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
|
|
|
|
/// let ovk = Some(OutgoingViewingKey([0; 32]));
|
|
|
|
///
|
|
|
|
/// let value = 1000;
|
|
|
|
/// let rcv = jubjub::Fr::random(&mut rng);
|
|
|
|
/// let cv = ValueCommitment {
|
|
|
|
/// value,
|
|
|
|
/// randomness: rcv.clone(),
|
|
|
|
/// };
|
2021-04-12 15:42:04 -07:00
|
|
|
/// let height = TEST_NETWORK.activation_height(NetworkUpgrade::Canopy).unwrap();
|
|
|
|
/// let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng);
|
|
|
|
/// let note = to.create_note(value, rseed).unwrap();
|
2021-03-22 13:59:25 -07:00
|
|
|
/// let cmu = note.cmu();
|
|
|
|
///
|
2021-03-27 18:29:42 -07:00
|
|
|
/// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, MemoBytes::empty(), &mut rng);
|
2021-03-22 13:59:25 -07:00
|
|
|
/// let encCiphertext = enc.encrypt_note_plaintext();
|
|
|
|
/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu, &mut rng);
|
|
|
|
/// ```
|
|
|
|
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>,
|
|
|
|
}
|
|
|
|
|
|
|
|
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 {
|
|
|
|
let esk = D::derive_esk(¬e).expect("ZIP 212 is active.");
|
|
|
|
Self::new_with_esk(esk, ovk, note, to, memo)
|
|
|
|
}
|
|
|
|
|
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-03-22 13:59:25 -07:00
|
|
|
pub fn new_with_esk(
|
|
|
|
esk: D::EphemeralSecretKey,
|
|
|
|
ovk: Option<D::OutgoingViewingKey>,
|
|
|
|
note: D::Note,
|
|
|
|
to: D::Recipient,
|
|
|
|
memo: D::Memo,
|
|
|
|
) -> Self {
|
|
|
|
NoteEncryption {
|
|
|
|
epk: D::ka_derive_public(¬e, &esk),
|
|
|
|
esk,
|
|
|
|
note,
|
|
|
|
to,
|
|
|
|
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));
|
2021-03-27 07:51:44 -07:00
|
|
|
let input = D::note_plaintext_bytes(&self.note, &self.to, &self.memo);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
|
|
let mut output = [0u8; ENC_CIPHERTEXT_SIZE];
|
|
|
|
assert_eq!(
|
|
|
|
ChachaPolyIetf::aead_cipher()
|
|
|
|
.seal_to(&mut output, &input.0, &[], key.as_ref(), &[0u8; 12])
|
|
|
|
.unwrap(),
|
|
|
|
ENC_CIPHERTEXT_SIZE
|
|
|
|
);
|
|
|
|
|
|
|
|
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 {
|
2021-04-15 14:15:54 -07:00
|
|
|
let ock = D::derive_ock(ovk, &cv, &cmstar, &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];
|
|
|
|
assert_eq!(
|
|
|
|
ChachaPolyIetf::aead_cipher()
|
|
|
|
.seal_to(&mut output, &input.0, &[], ock.as_ref(), &[0u8; 12])
|
|
|
|
.unwrap(),
|
|
|
|
OUT_CIPHERTEXT_SIZE
|
|
|
|
);
|
|
|
|
|
|
|
|
output
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Trial decryption of the full note plaintext by the recipient.
|
|
|
|
///
|
|
|
|
/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ivk`.
|
|
|
|
/// If successful, the corresponding Sapling note and memo are returned, along with the
|
|
|
|
/// `PaymentAddress` to which the note was sent.
|
|
|
|
///
|
2021-04-12 15:42:04 -07:00
|
|
|
/// Implements section 4.19.2 of the
|
|
|
|
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk)
|
2021-04-05 10:51:07 -07:00
|
|
|
pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
|
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-04-05 10:51:07 -07:00
|
|
|
assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
2021-04-05 10:51:07 -07:00
|
|
|
let shared_secret = D::ka_agree_dec(ivk, output.epk());
|
2021-04-12 15:19:50 -07:00
|
|
|
let key = D::kdf(shared_secret, &D::epk_bytes(output.epk()));
|
2021-03-22 13:59:25 -07:00
|
|
|
|
|
|
|
let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
|
|
|
|
assert_eq!(
|
|
|
|
ChachaPolyIetf::aead_cipher()
|
|
|
|
.open_to(
|
|
|
|
&mut plaintext,
|
2021-04-05 10:51:07 -07:00
|
|
|
output.enc_ciphertext(),
|
2021-03-22 13:59:25 -07:00
|
|
|
&[],
|
|
|
|
key.as_ref(),
|
|
|
|
&[0u8; 12]
|
|
|
|
)
|
|
|
|
.ok()?,
|
|
|
|
NOTE_PLAINTEXT_SIZE
|
|
|
|
);
|
|
|
|
|
2021-04-05 10:51:07 -07:00
|
|
|
let (note, to) = parse_note_plaintext_without_memo_ivk(
|
|
|
|
domain,
|
|
|
|
ivk,
|
|
|
|
output.epk(),
|
2021-04-15 14:15:54 -07:00
|
|
|
&output.cmstar_bytes(),
|
2021-04-05 10:51:07 -07:00
|
|
|
&plaintext,
|
|
|
|
)?;
|
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,
|
|
|
|
epk: &D::EphemeralPublicKey,
|
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)> {
|
2021-03-23 10:29:16 -07:00
|
|
|
let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, &plaintext)?;
|
2021-03-22 13:59:25 -07:00
|
|
|
|
2021-04-15 14:15:54 -07:00
|
|
|
if let NoteValidity::Valid = check_note_validity::<D>(¬e, epk, cmstar_bytes) {
|
2021-03-23 10:29:16 -07:00
|
|
|
Some((note, to))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
fn check_note_validity<D: Domain>(
|
|
|
|
note: &D::Note,
|
|
|
|
epk: &D::EphemeralPublicKey,
|
2021-04-15 14:15:54 -07:00
|
|
|
cmstar_bytes: &D::ExtractedCommitmentBytes,
|
2021-03-23 10:29:16 -07:00
|
|
|
) -> NoteValidity {
|
2021-04-15 14:15:54 -07:00
|
|
|
if D::ExtractedCommitmentBytes::try_from(D::cmstar(¬e))
|
|
|
|
.map_or(false, |cs| &cs == cmstar_bytes)
|
2021-04-14 08:24:42 -07:00
|
|
|
{
|
2021-03-27 07:51:44 -07:00
|
|
|
let epk_bytes = D::epk_bytes(epk);
|
2021-03-23 10:29:16 -07:00
|
|
|
D::check_epk_bytes(¬e, |derived_esk| {
|
2021-04-05 10:51:07 -07:00
|
|
|
if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk))
|
|
|
|
.ct_eq(&epk_bytes)
|
|
|
|
.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-03-23 10:29:16 -07:00
|
|
|
})
|
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.
|
|
|
|
///
|
|
|
|
/// Attempts to decrypt and validate the first 52 bytes of `enc_ciphertext` using the
|
|
|
|
/// given `ivk`. If successful, the corresponding Sapling note is returned, along with the
|
|
|
|
/// `PaymentAddress` to which the note was sent.
|
|
|
|
///
|
|
|
|
/// Implements the procedure specified in [`ZIP 307`].
|
|
|
|
///
|
|
|
|
/// [`ZIP 307`]: https://zips.z.cash/zip-0307
|
2021-04-05 10:51:07 -07:00
|
|
|
pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
|
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-04-05 10:51:07 -07:00
|
|
|
assert_eq!(output.enc_ciphertext().len(), COMPACT_NOTE_SIZE);
|
2021-03-22 13:59:25 -07:00
|
|
|
|
2021-04-05 10:51:07 -07:00
|
|
|
let shared_secret = D::ka_agree_dec(&ivk, output.epk());
|
2021-04-12 15:19:50 -07:00
|
|
|
let key = D::kdf(shared_secret, &D::epk_bytes(output.epk()));
|
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-03-22 13:59:25 -07:00
|
|
|
ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext);
|
|
|
|
|
2021-04-15 19:03:55 -07:00
|
|
|
parse_note_plaintext_without_memo_ivk(
|
|
|
|
domain,
|
|
|
|
ivk,
|
|
|
|
output.epk(),
|
|
|
|
&output.cmstar_bytes(),
|
|
|
|
&plaintext,
|
|
|
|
)
|
2021-03-23 10:29:16 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Recovery of the full note plaintext by the sender.
|
|
|
|
///
|
|
|
|
/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ock`.
|
|
|
|
/// If successful, the corresponding Sapling note and memo are returned, along with the
|
|
|
|
/// `PaymentAddress` to which the note was sent.
|
|
|
|
///
|
2021-04-12 15:42:04 -07:00
|
|
|
/// Implements part of section 4.19.3 of the
|
|
|
|
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptovk)
|
2021-03-23 10:29:16 -07:00
|
|
|
/// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`].
|
2021-04-05 10:51:07 -07:00
|
|
|
pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D>>(
|
2021-03-23 10:29:16 -07:00
|
|
|
domain: &D,
|
|
|
|
ock: &OutgoingCipherKey,
|
2021-04-05 10:51:07 -07:00
|
|
|
output: &Output,
|
2021-03-23 10:29:16 -07:00
|
|
|
out_ciphertext: &[u8],
|
|
|
|
) -> Option<(D::Note, D::Recipient, D::Memo)> {
|
2021-04-05 10:51:07 -07:00
|
|
|
assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE);
|
2021-03-23 10:29:16 -07:00
|
|
|
assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE);
|
|
|
|
|
|
|
|
let mut op = [0; OUT_CIPHERTEXT_SIZE];
|
|
|
|
assert_eq!(
|
|
|
|
ChachaPolyIetf::aead_cipher()
|
|
|
|
.open_to(&mut op, &out_ciphertext, &[], ock.as_ref(), &[0u8; 12])
|
|
|
|
.ok()?,
|
|
|
|
OUT_PLAINTEXT_SIZE
|
|
|
|
);
|
|
|
|
|
|
|
|
let pk_d = D::extract_pk_d(&op)?;
|
|
|
|
let esk = D::extract_esk(&op)?;
|
|
|
|
|
|
|
|
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-04-12 15:19:50 -07:00
|
|
|
let key = D::kdf(shared_secret, &D::epk_bytes(output.epk()));
|
2021-03-23 10:29:16 -07:00
|
|
|
|
|
|
|
let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
|
|
|
|
assert_eq!(
|
|
|
|
ChachaPolyIetf::aead_cipher()
|
|
|
|
.open_to(
|
|
|
|
&mut plaintext,
|
2021-04-05 10:51:07 -07:00
|
|
|
output.enc_ciphertext(),
|
2021-03-23 10:29:16 -07:00
|
|
|
&[],
|
|
|
|
key.as_ref(),
|
|
|
|
&[0u8; 12]
|
|
|
|
)
|
|
|
|
.ok()?,
|
|
|
|
NOTE_PLAINTEXT_SIZE
|
|
|
|
);
|
|
|
|
|
2021-04-05 10:51:07 -07:00
|
|
|
let (note, to) =
|
|
|
|
domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, output.epk(), &plaintext)?;
|
2021-03-23 10:29:16 -07:00
|
|
|
let memo = domain.extract_memo(&plaintext);
|
|
|
|
|
2021-05-28 14:33:00 -07:00
|
|
|
// ZIP 212: Check that the esk provided to this function is consistent with the esk we
|
|
|
|
// can derive from the note.
|
|
|
|
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 =
|
|
|
|
check_note_validity::<D>(¬e, output.epk(), &output.cmstar_bytes())
|
|
|
|
{
|
2021-03-23 10:29:16 -07:00
|
|
|
Some((note, to, memo))
|
|
|
|
} else {
|
|
|
|
None
|
|
|
|
}
|
2021-03-22 13:59:25 -07:00
|
|
|
}
|