encrypted notes for outputs

This commit is contained in:
narodnik
2021-05-04 12:38:13 +02:00
parent a8b498dfee
commit a995de238e
7 changed files with 170 additions and 2 deletions

View File

@@ -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"

View File

@@ -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);
}

View File

@@ -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
src/crypto/fr_serial.rs Normal file
View File

@@ -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)
}
}
}

View File

@@ -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
src/crypto/note.rs Normal file
View File

@@ -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);
}

View File

@@ -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"),
}
}
}