Procházet zdrojové kódy

encrypted notes for outputs

narodnik před 5 roky
rodič
revize
a995de238e
7 změnil soubory, kde provedl 170 přidání a 2 odebrání
  1. 1 0
      Cargo.toml
  2. 19 1
      src/bin/tx.rs
  3. 5 1
      src/crypto/diffie_hellman.rs
  4. 25 0
      src/crypto/fr_serial.rs
  5. 2 0
      src/crypto/mod.rs
  6. 116 0
      src/crypto/note.rs
  7. 2 0
      src/error.rs

+ 1 - 0
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"

+ 19 - 1
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", &params);
@@ -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);
+}

+ 5 - 1
src/crypto/diffie_hellman.rs

@@ -13,6 +13,10 @@ pub fn sapling_ka_agree(esk: &jubjub::Fr, pk_d: &jubjub::ExtendedPoint) -> jubju
     // <ExtendedPoint as CofactorGroup>::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)

+ 25 - 0
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<S: io::Write>(&self, mut s: S) -> Result<usize> {
+        s.write_slice(&self.to_bytes()[..])?;
+        Ok(32)
+    }
+}
+
+impl Decodable for jubjub::Fr {
+    fn decode<D: io::Read>(mut d: D) -> Result<Self> {
+        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)
+        }
+    }
+}
+

+ 2 - 0
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;

+ 116 - 0
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<S: io::Write>(&self, mut s: S) -> Result<usize> {
+        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<D: io::Read>(mut d: D) -> Result<Self> {
+        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<EncryptedNote> {
+        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<Note> {
+        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);
+}
+

+ 2 - 0
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"),
         }
     }
 }