mirror of
https://github.com/darkrenaissance/darkfi.git
synced 2026-04-28 03:00:18 -04:00
encrypted notes for outputs
This commit is contained in:
@@ -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"
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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
25
src/crypto/fr_serial.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
116
src/crypto/note.rs
Normal 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);
|
||||
}
|
||||
|
||||
@@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user