From a995de238ed49987e5674f086dce2d3219955813 Mon Sep 17 00:00:00 2001 From: narodnik Date: Tue, 4 May 2021 12:38:13 +0200 Subject: [PATCH] encrypted notes for outputs --- Cargo.toml | 1 + src/bin/tx.rs | 20 +++++- src/crypto/diffie_hellman.rs | 6 +- src/crypto/fr_serial.rs | 25 ++++++++ src/crypto/mod.rs | 2 + src/crypto/note.rs | 116 +++++++++++++++++++++++++++++++++++ src/error.rs | 2 + 7 files changed, 170 insertions(+), 2 deletions(-) create mode 100644 src/crypto/fr_serial.rs create mode 100644 src/crypto/note.rs diff --git a/Cargo.toml b/Cargo.toml index cef3f8f40..d558b0107 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -28,6 +28,7 @@ sha2 = "0.9.1" rand_xorshift = "0.2" blake2s_simd = "0.5" blake2b_simd = "0.5.11" +crypto_api_chachapoly = "0.4" bitvec = "0.18" bimap = "0.5.2" async-trait = "0.1.42" diff --git a/src/bin/tx.rs b/src/bin/tx.rs index ea188ac57..6d646193b 100644 --- a/src/bin/tx.rs +++ b/src/bin/tx.rs @@ -7,6 +7,7 @@ use rand::rngs::OsRng; use sapvi::crypto::{ create_mint_proof, load_params, save_params, setup_mint_prover, verify_mint_proof, MintRevealedValues, + note::Note }; struct TransactionBuilder { @@ -126,7 +127,7 @@ struct TransactionOutput { revealed: MintRevealedValues, } -fn main() { +fn txbuilding() { { let params = setup_mint_prover(); save_params("mint.params", ¶ms); @@ -143,3 +144,20 @@ fn main() { let tx = builder.build(&mint_params); assert!(tx.verify(&mint_pvk)); } + +fn main() { + // txbuilding() + let note = Note { + serial: jubjub::Fr::random(&mut OsRng), + value: 110, + coin_blind: jubjub::Fr::random(&mut OsRng), + valcom_blind: jubjub::Fr::random(&mut OsRng), + }; + + let secret = jubjub::Fr::random(&mut OsRng); + let public = zcash_primitives::constants::SPENDING_KEY_GENERATOR * secret; + + let encrypted_note = note.encrypt(&public).unwrap(); + let note2 = encrypted_note.decrypt(&secret).unwrap(); + assert_eq!(note.value, note2.value); +} diff --git a/src/crypto/diffie_hellman.rs b/src/crypto/diffie_hellman.rs index b5d613b5e..24cdbd458 100644 --- a/src/crypto/diffie_hellman.rs +++ b/src/crypto/diffie_hellman.rs @@ -13,6 +13,10 @@ pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubju // ::clear_cofactor is implemented using // ExtendedPoint::mul_by_cofactor in the jubjub crate. + // ExtendedPoint::multiply currently just implements double-and-add, + // so using wNAF is a concrete speed improvement (as it operates over a window of bits + // instead of individual bits). + // We want that to be fast because it's in the hot path for trial decryption of notes on chain. let mut wnaf = group::Wnaf::new(); wnaf.scalar(esk).base(*pk_d).clear_cofactor() } @@ -20,7 +24,7 @@ pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubju /// Sapling KDF for note encryption. /// /// Implements section 5.4.4.4 of the Zcash Protocol Specification. -fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, epk: &jubjub::ExtendedPoint) -> Blake2bHash { +pub fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, epk: &jubjub::ExtendedPoint) -> Blake2bHash { Blake2bParams::new() .hash_length(32) .personal(KDF_SAPLING_PERSONALIZATION) diff --git a/src/crypto/fr_serial.rs b/src/crypto/fr_serial.rs new file mode 100644 index 000000000..88d8c19d8 --- /dev/null +++ b/src/crypto/fr_serial.rs @@ -0,0 +1,25 @@ +use std::io; +use crate::serial::{Encodable, Decodable, ReadExt, WriteExt}; + +use crate::error::{Error, Result}; + +impl Encodable for jubjub::Fr { + fn encode(&self, mut s: S) -> Result { + s.write_slice(&self.to_bytes()[..])?; + Ok(32) + } +} + +impl Decodable for jubjub::Fr { + fn decode(mut d: D) -> Result { + let mut bytes = [0u8; 32]; + d.read_slice(&mut bytes)?; + let result = Self::from_bytes(&bytes); + if result.is_some().into() { + Ok(result.unwrap()) + } else { + Err(Error::BadOperationType) + } + } +} + diff --git a/src/crypto/mod.rs b/src/crypto/mod.rs index 781595336..fd9ddec82 100644 --- a/src/crypto/mod.rs +++ b/src/crypto/mod.rs @@ -1,5 +1,7 @@ pub mod diffie_hellman; +pub mod fr_serial; pub mod mint_proof; +pub mod note; pub mod schnorr; pub mod spend_proof; pub mod util; diff --git a/src/crypto/note.rs b/src/crypto/note.rs new file mode 100644 index 000000000..6ae7d9b61 --- /dev/null +++ b/src/crypto/note.rs @@ -0,0 +1,116 @@ +use crypto_api_chachapoly::ChachaPolyIetf; +use ff::Field; +use std::io; +use rand::rngs::OsRng; + +use crate::serial::{Encodable, Decodable, ReadExt, WriteExt}; +use crate::error::{Error, Result}; +use super::diffie_hellman::{sapling_ka_agree, kdf_sapling}; + +pub const NOTE_PLAINTEXT_SIZE: usize = + 32 + // serial + 8 + // value + 32 + // coin_blind + 32; // valcom_blind +pub const AEAD_TAG_SIZE: usize = 16; +pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE; + +pub struct Note { + pub serial: jubjub::Fr, + pub value: u64, + pub coin_blind: jubjub::Fr, + pub valcom_blind: jubjub::Fr, +} + +impl Encodable for Note { + fn encode(&self, mut s: S) -> Result { + let mut len = 0; + len += self.serial.encode(&mut s)?; + len += self.value.encode(&mut s)?; + len += self.coin_blind.encode(&mut s)?; + len += self.valcom_blind.encode(&mut s)?; + Ok(len) + } +} + +impl Decodable for Note { + fn decode(mut d: D) -> Result { + Ok(Self { + serial: Decodable::decode(&mut d)?, + value: Decodable::decode(&mut d)?, + coin_blind: Decodable::decode(&mut d)?, + valcom_blind: Decodable::decode(d)? + }) + } +} + +impl Note { + pub fn encrypt(&self, public: &jubjub::SubgroupPoint) -> Result { + let ephem_secret = jubjub::Fr::random(&mut OsRng); + let ephem_public = zcash_primitives::constants::SPENDING_KEY_GENERATOR * ephem_secret; + let shared_secret = sapling_ka_agree(&ephem_secret, public.into()); + let key = kdf_sapling(shared_secret, &ephem_public.into()); + + let mut input = Vec::new(); + self.encode(&mut input)?; + + let mut ciphertext = [0u8; ENC_CIPHERTEXT_SIZE]; + assert_eq!( + ChachaPolyIetf::aead_cipher() + .seal_to(&mut ciphertext, &input, &[], key.as_ref(), &[0u8; 12]) + .unwrap(), + ENC_CIPHERTEXT_SIZE + ); + + Ok(EncryptedNote { + ciphertext, + ephem_public + }) + } +} + +pub struct EncryptedNote { + ciphertext: [u8; ENC_CIPHERTEXT_SIZE], + ephem_public: jubjub::SubgroupPoint +} + +impl EncryptedNote { + pub fn decrypt(&self, secret: &jubjub::Fr) -> Result { + let shared_secret = sapling_ka_agree(&secret, &self.ephem_public.into()); + let key = kdf_sapling(shared_secret, &self.ephem_public.into()); + + let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; + assert_eq!( + ChachaPolyIetf::aead_cipher() + .open_to( + &mut plaintext, + &self.ciphertext, + &[], + key.as_ref(), + &[0u8; 12] + ) + .map_err(|_| Error::NoteDecryptionFailed)?, + NOTE_PLAINTEXT_SIZE + ); + + Note::decode(&plaintext[..]) + } +} + +#[test] +fn test_note_encdec() { + let note = Note { + serial: jubjub::Fr::random(&mut OsRng), + value: 110, + coin_blind: jubjub::Fr::random(&mut OsRng), + valcom_blind: jubjub::Fr::random(&mut OsRng), + }; + + let secret = jubjub::Fr::random(&mut OsRng); + let public = zcash_primitives::constants::SPENDING_KEY_GENERATOR * secret; + + let encrypted_note = note.encrypt(&public).unwrap(); + let note2 = encrypted_note.decrypt(&secret).unwrap(); + assert_eq!(note.value, note2.value); +} + diff --git a/src/error.rs b/src/error.rs index 7c237aa47..31d9fb0ab 100644 --- a/src/error.rs +++ b/src/error.rs @@ -40,6 +40,7 @@ pub enum Error { ChannelTimeout, ServiceStopped, Utf8Error, + NoteDecryptionFailed, } impl std::error::Error for Error {} @@ -82,6 +83,7 @@ impl fmt::Display for Error { Error::ChannelTimeout => f.write_str("Channel timed out"), Error::ServiceStopped => f.write_str("Service stopped"), Error::Utf8Error => f.write_str("Malformed UTF8"), + Error::NoteDecryptionFailed => f.write_str("Unable to decrypt mint note"), } } }