|
|
@ -1,3 +1,5 @@ |
|
|
|
|
|
|
|
//! Implementation of in-band secret distribution for Zcash transactions.
|
|
|
|
|
|
|
|
|
|
|
|
use blake2_rfc::blake2b::{Blake2b, Blake2bResult}; |
|
|
|
use blake2_rfc::blake2b::{Blake2b, Blake2bResult}; |
|
|
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; |
|
|
|
use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt}; |
|
|
|
use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; |
|
|
|
use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; |
|
|
@ -57,6 +59,7 @@ where |
|
|
|
Ok(()) |
|
|
|
Ok(()) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// An unencrypted memo received alongside a shielded note in a Zcash transaction.
|
|
|
|
#[derive(Clone)] |
|
|
|
#[derive(Clone)] |
|
|
|
pub struct Memo([u8; 512]); |
|
|
|
pub struct Memo([u8; 512]); |
|
|
|
|
|
|
|
|
|
|
@ -87,9 +90,9 @@ impl PartialEq for Memo { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl Memo { |
|
|
|
impl Memo { |
|
|
|
/// Returns a Memo containing the given slice, appending with zero bytes if necessary,
|
|
|
|
/// Returns a `Memo` containing the given slice, appending with zero bytes if
|
|
|
|
/// or None if the slice is too long. If the slice is empty, Memo::default() is
|
|
|
|
/// necessary, or `None` if the slice is too long. If the slice is empty,
|
|
|
|
/// returned.
|
|
|
|
/// `Memo::default` is returned.
|
|
|
|
pub fn from_bytes(memo: &[u8]) -> Option<Memo> { |
|
|
|
pub fn from_bytes(memo: &[u8]) -> Option<Memo> { |
|
|
|
if memo.is_empty() { |
|
|
|
if memo.is_empty() { |
|
|
|
Some(Memo::default()) |
|
|
|
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> { |
|
|
|
pub fn from_str(memo: &str) -> Option<Memo> { |
|
|
|
Memo::from_bytes(memo.as_bytes()) |
|
|
|
Memo::from_bytes(memo.as_bytes()) |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Returns the underlying bytes of the `Memo`.
|
|
|
|
pub fn as_bytes(&self) -> &[u8] { |
|
|
|
pub fn as_bytes(&self) -> &[u8] { |
|
|
|
&self.0[..] |
|
|
|
&self.0[..] |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
/// Returns:
|
|
|
|
/// Returns:
|
|
|
|
/// - None if the memo is not text
|
|
|
|
/// - `None` if the memo is not text
|
|
|
|
/// - Some(Ok(memo)) if the memo contains a valid UTF8 string
|
|
|
|
/// - `Some(Ok(memo))` if the memo contains a valid UTF-8 string
|
|
|
|
/// - Some(Err(e)) if the memo contains invalid UTF8
|
|
|
|
/// - `Some(Err(e))` if the memo contains invalid UTF-8
|
|
|
|
pub fn to_utf8(&self) -> Option<Result<String, str::Utf8Error>> { |
|
|
|
pub fn to_utf8(&self) -> Option<Result<String, str::Utf8Error>> { |
|
|
|
// Check if it is a text or binary memo
|
|
|
|
// Check if it is a text or binary memo
|
|
|
|
if self.0[0] < 0xF5 { |
|
|
|
if self.0[0] < 0xF5 { |
|
|
@ -144,6 +148,9 @@ fn generate_esk() -> Fs { |
|
|
|
Fs::to_uniform(&buffer[..]) |
|
|
|
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] |
|
|
|
pub fn sapling_ka_agree<'a, P>(esk: &Fs, pk_d: &'a P) -> [u8; 32] |
|
|
|
where |
|
|
|
where |
|
|
|
edwards::Point<Bls12, Unknown>: From<&'a P>, |
|
|
|
edwards::Point<Bls12, Unknown>: From<&'a P>, |
|
|
@ -162,6 +169,9 @@ where |
|
|
|
result |
|
|
|
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<Bls12, PrimeOrder>) -> Blake2bResult { |
|
|
|
fn kdf_sapling(dhsecret: &[u8], epk: &edwards::Point<Bls12, PrimeOrder>) -> Blake2bResult { |
|
|
|
let mut input = [0u8; 64]; |
|
|
|
let mut input = [0u8; 64]; |
|
|
|
input[0..32].copy_from_slice(&dhsecret); |
|
|
|
input[0..32].copy_from_slice(&dhsecret); |
|
|
@ -172,6 +182,9 @@ fn kdf_sapling(dhsecret: &[u8], epk: &edwards::Point<Bls12, PrimeOrder>) -> Blak |
|
|
|
h.finalize() |
|
|
|
h.finalize() |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Sapling PRF^ock.
|
|
|
|
|
|
|
|
///
|
|
|
|
|
|
|
|
/// Implemented per section 5.4.2 of the Zcash Protocol Specification.
|
|
|
|
fn prf_ock( |
|
|
|
fn prf_ock( |
|
|
|
ovk: &OutgoingViewingKey, |
|
|
|
ovk: &OutgoingViewingKey, |
|
|
|
cv: &edwards::Point<Bls12, Unknown>, |
|
|
|
cv: &edwards::Point<Bls12, Unknown>, |
|
|
@ -189,6 +202,56 @@ fn prf_ock( |
|
|
|
h.finalize() |
|
|
|
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::<Bls12>(&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::<Bls12> {
|
|
|
|
|
|
|
|
/// 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 { |
|
|
|
pub struct SaplingNoteEncryption { |
|
|
|
epk: edwards::Point<Bls12, PrimeOrder>, |
|
|
|
epk: edwards::Point<Bls12, PrimeOrder>, |
|
|
|
esk: Fs, |
|
|
|
esk: Fs, |
|
|
@ -199,6 +262,7 @@ pub struct SaplingNoteEncryption { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
impl SaplingNoteEncryption { |
|
|
|
impl SaplingNoteEncryption { |
|
|
|
|
|
|
|
/// Creates a new encryption context for the given note.
|
|
|
|
pub fn new( |
|
|
|
pub fn new( |
|
|
|
ovk: OutgoingViewingKey, |
|
|
|
ovk: OutgoingViewingKey, |
|
|
|
note: Note<Bls12>, |
|
|
|
note: Note<Bls12>, |
|
|
@ -218,14 +282,17 @@ impl SaplingNoteEncryption { |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Exposes the ephemeral secret key being used to encrypt this note.
|
|
|
|
pub fn esk(&self) -> &Fs { |
|
|
|
pub fn esk(&self) -> &Fs { |
|
|
|
&self.esk |
|
|
|
&self.esk |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Exposes the ephemeral public key being used to encrypt this note.
|
|
|
|
pub fn epk(&self) -> &edwards::Point<Bls12, PrimeOrder> { |
|
|
|
pub fn epk(&self) -> &edwards::Point<Bls12, PrimeOrder> { |
|
|
|
&self.epk |
|
|
|
&self.epk |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Generates `encCiphertext` for this note.
|
|
|
|
pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { |
|
|
|
pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] { |
|
|
|
let shared_secret = sapling_ka_agree(&self.esk, &self.to.pk_d); |
|
|
|
let shared_secret = sapling_ka_agree(&self.esk, &self.to.pk_d); |
|
|
|
let key = kdf_sapling(&shared_secret, &self.epk); |
|
|
|
let key = kdf_sapling(&shared_secret, &self.epk); |
|
|
@ -251,6 +318,7 @@ impl SaplingNoteEncryption { |
|
|
|
output |
|
|
|
output |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/// Generates `outCiphertext` for this note.
|
|
|
|
pub fn encrypt_outgoing_plaintext( |
|
|
|
pub fn encrypt_outgoing_plaintext( |
|
|
|
&self, |
|
|
|
&self, |
|
|
|
cv: &edwards::Point<Bls12, Unknown>, |
|
|
|
cv: &edwards::Point<Bls12, Unknown>, |
|
|
@ -311,6 +379,8 @@ fn parse_note_plaintext_minus_memo( |
|
|
|
Some((note, to)) |
|
|
|
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`.
|
|
|
|
/// 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
|
|
|
|
/// If successful, the corresponding Sapling note and memo are returned, along with the
|
|
|
|
/// `PaymentAddress` to which the note was sent.
|
|
|
|
/// `PaymentAddress` to which the note was sent.
|
|
|
@ -349,11 +419,15 @@ pub fn try_sapling_note_decryption( |
|
|
|
Some((note, to, Memo(memo))) |
|
|
|
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
|
|
|
|
/// 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
|
|
|
|
/// given `ivk`. If successful, the corresponding Sapling note is returned, along with the
|
|
|
|
/// `PaymentAddress` to which the note was sent.
|
|
|
|
/// `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( |
|
|
|
pub fn try_sapling_compact_note_decryption( |
|
|
|
ivk: &Fs, |
|
|
|
ivk: &Fs, |
|
|
|
epk: &edwards::Point<Bls12, PrimeOrder>, |
|
|
|
epk: &edwards::Point<Bls12, PrimeOrder>, |
|
|
@ -385,6 +459,8 @@ pub fn try_sapling_compact_note_decryption( |
|
|
|
parse_note_plaintext_minus_memo(ivk, cmu, &plaintext[CHACHA20_BLOCK_SIZE..]) |
|
|
|
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`.
|
|
|
|
/// 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
|
|
|
|
/// If successful, the corresponding Sapling note and memo are returned, along with the
|
|
|
|
/// `PaymentAddress` to which the note was sent.
|
|
|
|
/// `PaymentAddress` to which the note was sent.
|
|
|
|