From edf7bc144d22068d212f3dd80904c87f3a5cc42b Mon Sep 17 00:00:00 2001 From: Jack Grigg Date: Thu, 11 Apr 2019 15:01:36 -0700 Subject: [PATCH] Document note_encryption module --- zcash_primitives/src/note_encryption.rs | 92 ++++++++++++++++++++++--- 1 file changed, 84 insertions(+), 8 deletions(-) diff --git a/zcash_primitives/src/note_encryption.rs b/zcash_primitives/src/note_encryption.rs index b1c5a36..bd37b7b 100644 --- a/zcash_primitives/src/note_encryption.rs +++ b/zcash_primitives/src/note_encryption.rs @@ -1,3 +1,5 @@ +//! Implementation of in-band secret distribution for Zcash transactions. + use blake2_rfc::blake2b::{Blake2b, Blake2bResult}; use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; @@ -57,6 +59,7 @@ where Ok(()) } +/// An unencrypted memo received alongside a shielded note in a Zcash transaction. #[derive(Clone)] pub struct Memo([u8; 512]); @@ -87,9 +90,9 @@ impl PartialEq for Memo { } impl Memo { - /// Returns a Memo containing the given slice, appending with zero bytes if necessary, - /// or None if the slice is too long. If the slice is empty, Memo::default() is - /// returned. + /// Returns a `Memo` containing the given slice, appending with zero bytes if + /// necessary, or `None` if the slice is too long. If the slice is empty, + /// `Memo::default` is returned. pub fn from_bytes(memo: &[u8]) -> Option { if memo.is_empty() { Some(Memo::default()) @@ -103,19 +106,20 @@ impl Memo { } } - /// Returns a Memo containing the given string, or None if the string is too long. + /// Returns a `Memo` containing the given string, or `None` if the string is too long. pub fn from_str(memo: &str) -> Option { Memo::from_bytes(memo.as_bytes()) } + /// Returns the underlying bytes of the `Memo`. pub fn as_bytes(&self) -> &[u8] { &self.0[..] } /// Returns: - /// - None if the memo is not text - /// - Some(Ok(memo)) if the memo contains a valid UTF8 string - /// - Some(Err(e)) if the memo contains invalid UTF8 + /// - `None` if the memo is not text + /// - `Some(Ok(memo))` if the memo contains a valid UTF-8 string + /// - `Some(Err(e))` if the memo contains invalid UTF-8 pub fn to_utf8(&self) -> Option> { // Check if it is a text or binary memo if self.0[0] < 0xF5 { @@ -144,6 +148,9 @@ fn generate_esk() -> Fs { Fs::to_uniform(&buffer[..]) } +/// Sapling key agreement for note encryption. +/// +/// Implements section 5.4.4.3 of the Zcash Protocol Specification. pub fn sapling_ka_agree<'a, P>(esk: &Fs, pk_d: &'a P) -> [u8; 32] where edwards::Point: From<&'a P>, @@ -162,6 +169,9 @@ where result } +/// Sapling KDF for note encryption. +/// +/// Implements section 5.4.4.4 of the Zcash Protocol Specification. fn kdf_sapling(dhsecret: &[u8], epk: &edwards::Point) -> Blake2bResult { let mut input = [0u8; 64]; input[0..32].copy_from_slice(&dhsecret); @@ -172,6 +182,9 @@ fn kdf_sapling(dhsecret: &[u8], epk: &edwards::Point) -> Blak h.finalize() } +/// Sapling PRF^ock. +/// +/// Implemented per section 5.4.2 of the Zcash Protocol Specification. fn prf_ock( ovk: &OutgoingViewingKey, cv: &edwards::Point, @@ -189,6 +202,56 @@ fn prf_ock( h.finalize() } +/// An API for encrypting Sapling notes. +/// +/// This struct provides a safe API for encrypting Sapling 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.17.1 of the Zcash Protocol Specification. +/// +/// # Examples +/// +/// ``` +/// extern crate pairing; +/// extern crate rand; +/// extern crate sapling_crypto; +/// +/// use pairing::bls12_381::Bls12; +/// use rand::{OsRng, Rand}; +/// use sapling_crypto::{ +/// jubjub::fs::Fs, +/// primitives::{Diversifier, PaymentAddress, ValueCommitment}, +/// }; +/// use zcash_primitives::{ +/// keys::OutgoingViewingKey, +/// note_encryption::{Memo, SaplingNoteEncryption}, +/// JUBJUB, +/// }; +/// +/// let mut rng = OsRng::new().unwrap(); +/// +/// let diversifier = Diversifier([0; 11]); +/// let pk_d = diversifier.g_d::(&JUBJUB).unwrap(); +/// let to = PaymentAddress { +/// pk_d, +/// diversifier, +/// }; +/// let ovk = OutgoingViewingKey([0; 32]); +/// +/// let value = 1000; +/// let rcv = Fs::rand(&mut rng); +/// let cv = ValueCommitment:: { +/// value, +/// randomness: rcv.clone(), +/// }; +/// let note = to.create_note(value, rcv, &JUBJUB).unwrap(); +/// let cmu = note.cm(&JUBJUB); +/// +/// let enc = SaplingNoteEncryption::new(ovk, note, to, Memo::default()); +/// let encCiphertext = enc.encrypt_note_plaintext(); +/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.cm(&JUBJUB).into(), &cmu); +/// ``` pub struct SaplingNoteEncryption { epk: edwards::Point, esk: Fs, @@ -199,6 +262,7 @@ pub struct SaplingNoteEncryption { } impl SaplingNoteEncryption { + /// Creates a new encryption context for the given note. pub fn new( ovk: OutgoingViewingKey, note: Note, @@ -218,14 +282,17 @@ impl SaplingNoteEncryption { } } + /// Exposes the ephemeral secret key being used to encrypt this note. pub fn esk(&self) -> &Fs { &self.esk } + /// Exposes the ephemeral public key being used to encrypt this note. pub fn epk(&self) -> &edwards::Point { &self.epk } + /// Generates `encCiphertext` for this note. pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { let shared_secret = sapling_ka_agree(&self.esk, &self.to.pk_d); let key = kdf_sapling(&shared_secret, &self.epk); @@ -251,6 +318,7 @@ impl SaplingNoteEncryption { output } + /// Generates `outCiphertext` for this note. pub fn encrypt_outgoing_plaintext( &self, cv: &edwards::Point, @@ -311,6 +379,8 @@ fn parse_note_plaintext_minus_memo( Some((note, to)) } +/// 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. @@ -349,11 +419,15 @@ pub fn try_sapling_note_decryption( Some((note, to, Memo(memo))) } +/// 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. +/// Implements the procedure specified in [`ZIP 307`]. +/// +/// [`ZIP 307`]: https://github.com/zcash/zips/pull/226 pub fn try_sapling_compact_note_decryption( ivk: &Fs, epk: &edwards::Point, @@ -385,6 +459,8 @@ pub fn try_sapling_compact_note_decryption( parse_note_plaintext_minus_memo(ivk, cmu, &plaintext[CHACHA20_BLOCK_SIZE..]) } +/// Recovery of the full note plaintext by the sender. +/// /// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ovk`. /// If successful, the corresponding Sapling note and memo are returned, along with the /// `PaymentAddress` to which the note was sent.