diff --git a/Cargo.lock b/Cargo.lock index da35e288f..159792086 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -114,6 +114,14 @@ name = "byteorder" version = "1.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" +[[package]] +name = "chacha20-poly1305-aead" +version = "0.1.2" +source = "git+https://github.com/gtank/chacha20-poly1305-aead?rev=aefc71f95e8bc43f2070e3c5b08130d9c86bbf4f#aefc71f95e8bc43f2070e3c5b08130d9c86bbf4f" +dependencies = [ + "constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)", +] + [[package]] name = "constant_time_eq" version = "0.1.3" @@ -500,6 +508,7 @@ version = "0.0.0" dependencies = [ "blake2-rfc 0.2.18 (git+https://github.com/gtank/blake2-rfc?rev=7a5b5fc99ae483a0043db7547fb79a6fa44b88a9)", "byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)", + "chacha20-poly1305-aead 0.1.2 (git+https://github.com/gtank/chacha20-poly1305-aead?rev=aefc71f95e8bc43f2070e3c5b08130d9c86bbf4f)", "ff 0.4.0", "hex 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)", "lazy_static 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)", @@ -554,6 +563,7 @@ dependencies = [ "checksum byte-tools 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "560c32574a12a89ecd91f5e742165893f86e3ab98d21f8ea548658eb9eef5f40" "checksum byte-tools 0.3.0 (registry+https://github.com/rust-lang/crates.io-index)" = "980479e6fde23246dfb54d47580d66b4e99202e7579c5eaa9fe10ecb5ebd2182" "checksum byteorder 1.2.2 (registry+https://github.com/rust-lang/crates.io-index)" = "73b5bdfe7ee3ad0b99c9801d58807a9dbc9e09196365b0203853b99889ab3c87" +"checksum chacha20-poly1305-aead 0.1.2 (git+https://github.com/gtank/chacha20-poly1305-aead?rev=aefc71f95e8bc43f2070e3c5b08130d9c86bbf4f)" = "" "checksum constant_time_eq 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "8ff012e225ce166d4422e0e78419d901719760f62ae2b7969ca6b564d1b54a9e" "checksum crossbeam 0.3.2 (registry+https://github.com/rust-lang/crates.io-index)" = "24ce9782d4d5c53674646a6a4c1863a21a8fc0cb649b3c94dfc16e45071dea19" "checksum digest 0.7.2 (registry+https://github.com/rust-lang/crates.io-index)" = "00a49051fef47a72c9623101b19bd71924a45cca838826caae3eaa4d00772603" diff --git a/sapling-crypto/src/primitives/mod.rs b/sapling-crypto/src/primitives/mod.rs index 849aaf297..77b81c108 100644 --- a/sapling-crypto/src/primitives/mod.rs +++ b/sapling-crypto/src/primitives/mod.rs @@ -162,6 +162,7 @@ impl PaymentAddress { } } +#[derive(Clone)] pub struct Note { /// The value of the note pub value: u64, diff --git a/zcash_primitives/Cargo.toml b/zcash_primitives/Cargo.toml index aac2e3b7c..aabac794e 100644 --- a/zcash_primitives/Cargo.toml +++ b/zcash_primitives/Cargo.toml @@ -18,3 +18,7 @@ sha2 = "0.8" [dependencies.blake2-rfc] git = "https://github.com/gtank/blake2-rfc" rev = "7a5b5fc99ae483a0043db7547fb79a6fa44b88a9" + +[dependencies.chacha20-poly1305-aead] +git = "https://github.com/gtank/chacha20-poly1305-aead" +rev = "aefc71f95e8bc43f2070e3c5b08130d9c86bbf4f" diff --git a/zcash_primitives/src/lib.rs b/zcash_primitives/src/lib.rs index fe61d395b..dcbfe1a3e 100644 --- a/zcash_primitives/src/lib.rs +++ b/zcash_primitives/src/lib.rs @@ -3,6 +3,7 @@ extern crate lazy_static; extern crate blake2_rfc; extern crate byteorder; +extern crate chacha20_poly1305_aead; extern crate ff; extern crate hex; extern crate pairing; @@ -14,6 +15,7 @@ use sapling_crypto::jubjub::JubjubBls12; pub mod block; pub mod keys; +pub mod note_encryption; pub mod sapling; mod serialize; pub mod transaction; diff --git a/zcash_primitives/src/note_encryption.rs b/zcash_primitives/src/note_encryption.rs new file mode 100644 index 000000000..8849ff02f --- /dev/null +++ b/zcash_primitives/src/note_encryption.rs @@ -0,0 +1,154 @@ +use blake2_rfc::blake2b::{Blake2b, Blake2bResult}; +use byteorder::{LittleEndian, WriteBytesExt}; +use chacha20_poly1305_aead; +use ff::{PrimeField, PrimeFieldRepr}; +use pairing::bls12_381::{Bls12, Fr}; +use rand::{OsRng, Rng}; +use sapling_crypto::{ + jubjub::{edwards, fs::Fs, PrimeOrder, ToUniform, Unknown}, + primitives::{Note, PaymentAddress}, +}; + +use crate::{keys::OutgoingViewingKey, JUBJUB}; + +pub const KDF_SAPLING_PERSONALIZATION: &'static [u8; 16] = b"Zcash_SaplingKDF"; +pub const PRF_OCK_PERSONALIZATION: &'static [u8; 16] = b"Zcash_Derive_ock"; + +pub struct Memo([u8; 512]); + +impl Default for Memo { + fn default() -> Self { + // Empty memo field indication per ZIP 302 + let mut memo = [0u8; 512]; + memo[0] = 0xF6; + Memo(memo) + } +} + +fn generate_esk() -> Fs { + // create random 64 byte buffer + let mut rng = OsRng::new().expect("should be able to construct RNG"); + let mut buffer = [0u8; 64]; + for i in 0..buffer.len() { + buffer[i] = rng.gen(); + } + + // reduce to uniform value + Fs::to_uniform(&buffer[..]) +} + +fn sapling_ka_agree(esk: &Fs, pk_d: &edwards::Point) -> Vec { + let ka = pk_d + .mul(esk.into_repr(), &JUBJUB) + .double(&JUBJUB) + .double(&JUBJUB) + .double(&JUBJUB); + let mut result = Vec::with_capacity(32); + ka.write(&mut result).expect("length is not 32 bytes"); + result +} + +fn kdf_sapling(dhsecret: &[u8], epk: &edwards::Point) -> Blake2bResult { + let mut input = [0u8; 64]; + input[0..32].copy_from_slice(&dhsecret); + epk.write(&mut input[32..64]).unwrap(); + + let mut h = Blake2b::with_params(32, &[], &[], KDF_SAPLING_PERSONALIZATION); + h.update(&input); + h.finalize() +} + +pub struct SaplingNoteEncryption { + epk: edwards::Point, + esk: Fs, + note: Note, + to: PaymentAddress, + memo: Memo, + ovk: OutgoingViewingKey, +} + +impl SaplingNoteEncryption { + pub fn new( + ovk: OutgoingViewingKey, + note: Note, + to: PaymentAddress, + memo: Memo, + ) -> SaplingNoteEncryption { + let esk = generate_esk(); + let epk = note.g_d.mul(esk, &JUBJUB); + + SaplingNoteEncryption { + epk, + esk, + note, + to, + memo, + ovk, + } + } + + pub fn esk(&self) -> &Fs { + &self.esk + } + + pub fn epk(&self) -> &edwards::Point { + &self.epk + } + + pub fn encrypt_note_plaintext(&self) -> [u8; 580] { + let shared_secret = sapling_ka_agree(&self.esk, &self.to.pk_d); + let key = kdf_sapling(&shared_secret, &self.epk); + + let nonce = [0u8; 12]; + + let mut input = Vec::with_capacity(564); + input.push(1); + input.extend_from_slice(&self.to.diversifier.0); + (&mut input) + .write_u64::(self.note.value) + .unwrap(); + self.note.r.into_repr().write_le(&mut input).unwrap(); + input.extend_from_slice(&self.memo.0); + + let mut ciphertext = Vec::with_capacity(564); + let tag = + chacha20_poly1305_aead::encrypt(&key.as_bytes(), &nonce, &[], &input, &mut ciphertext) + .unwrap(); + + let mut output = [0u8; 580]; + output[0..564].copy_from_slice(&ciphertext); + output[564..580].copy_from_slice(&tag); + output + } + + pub fn encrypt_outgoing_plaintext( + &self, + cv: &edwards::Point, + cmu: &Fr, + ) -> [u8; 80] { + let mut ock_input = [0u8; 128]; + ock_input[0..32].copy_from_slice(&self.ovk.0); + cv.write(&mut ock_input[32..64]).unwrap(); + cmu.into_repr().write_le(&mut ock_input[64..96]).unwrap(); + self.epk.write(&mut ock_input[96..128]).unwrap(); + + let mut h = Blake2b::with_params(32, &[], &[], PRF_OCK_PERSONALIZATION); + h.update(&ock_input); + let key = h.finalize(); + + let mut input = [0u8; 64]; + self.note.pk_d.write(&mut input[0..32]).unwrap(); + self.esk.into_repr().write_le(&mut input[32..64]).unwrap(); + + let mut buffer = Vec::with_capacity(64); + let nonce = [0u8; 12]; + let tag = chacha20_poly1305_aead::encrypt(key.as_bytes(), &nonce, &[], &input, &mut buffer) + .unwrap(); + + let mut output = [0u8; 80]; + output[0..64].copy_from_slice(&buffer); + output[64..80].copy_from_slice(&tag[..]); + + output + } +}