Enforce consistent plaintext and ciphertext lengths

This commit is contained in:
Jack Grigg 2019-03-23 18:07:38 +13:00
parent 899d852c39
commit 9086dd9afb
No known key found for this signature in database
GPG Key ID: 9E8255172BBF9898
1 changed files with 63 additions and 31 deletions

View File

@ -20,6 +20,22 @@ use crate::{keys::OutgoingViewingKey, JUBJUB};
pub const KDF_SAPLING_PERSONALIZATION: &'static [u8; 16] = b"Zcash_SaplingKDF"; pub const KDF_SAPLING_PERSONALIZATION: &'static [u8; 16] = b"Zcash_SaplingKDF";
pub const PRF_OCK_PERSONALIZATION: &'static [u8; 16] = b"Zcash_Derive_ock"; pub const PRF_OCK_PERSONALIZATION: &'static [u8; 16] = b"Zcash_Derive_ock";
const COMPACT_NOTE_SIZE: usize = (
1 + // version
11 + // diversifier
8 + // value
32
// rcv
);
const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512;
const OUT_PLAINTEXT_SIZE: usize = (
32 + // pk_d
32
// esk
);
const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + 16;
const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + 16;
/// Format a byte array as a colon-delimited hex string. /// Format a byte array as a colon-delimited hex string.
/// ///
/// Source: https://github.com/tendermint/signatory /// Source: https://github.com/tendermint/signatory
@ -210,11 +226,11 @@ impl SaplingNoteEncryption {
&self.epk &self.epk
} }
pub fn encrypt_note_plaintext(&self) -> [u8; 580] { pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] {
let shared_secret = sapling_ka_agree(&self.esk, &self.to.pk_d); let shared_secret = sapling_ka_agree(&self.esk, &self.to.pk_d);
let key = kdf_sapling(&shared_secret, &self.epk); let key = kdf_sapling(&shared_secret, &self.epk);
let mut input = Vec::with_capacity(564); let mut input = Vec::with_capacity(NOTE_PLAINTEXT_SIZE);
input.push(1); input.push(1);
input.extend_from_slice(&self.to.diversifier.0); input.extend_from_slice(&self.to.diversifier.0);
(&mut input) (&mut input)
@ -222,8 +238,9 @@ impl SaplingNoteEncryption {
.unwrap(); .unwrap();
self.note.r.into_repr().write_le(&mut input).unwrap(); self.note.r.into_repr().write_le(&mut input).unwrap();
input.extend_from_slice(&self.memo.0); input.extend_from_slice(&self.memo.0);
assert_eq!(input.len(), NOTE_PLAINTEXT_SIZE);
let mut ciphertext = Vec::with_capacity(564); let mut ciphertext = Vec::with_capacity(NOTE_PLAINTEXT_SIZE);
let tag = chacha20_poly1305_aead::encrypt( let tag = chacha20_poly1305_aead::encrypt(
&key.as_bytes(), &key.as_bytes(),
&[0u8; 12], &[0u8; 12],
@ -233,9 +250,9 @@ impl SaplingNoteEncryption {
) )
.unwrap(); .unwrap();
let mut output = [0u8; 580]; let mut output = [0u8; ENC_CIPHERTEXT_SIZE];
output[0..564].copy_from_slice(&ciphertext); output[0..NOTE_PLAINTEXT_SIZE].copy_from_slice(&ciphertext);
output[564..580].copy_from_slice(&tag); output[NOTE_PLAINTEXT_SIZE..ENC_CIPHERTEXT_SIZE].copy_from_slice(&tag);
output output
} }
@ -243,21 +260,24 @@ impl SaplingNoteEncryption {
&self, &self,
cv: &edwards::Point<Bls12, Unknown>, cv: &edwards::Point<Bls12, Unknown>,
cmu: &Fr, cmu: &Fr,
) -> [u8; 80] { ) -> [u8; OUT_CIPHERTEXT_SIZE] {
let key = prf_ock(&self.ovk, &cv, &cmu, &self.epk); let key = prf_ock(&self.ovk, &cv, &cmu, &self.epk);
let mut input = [0u8; 64]; let mut input = [0u8; OUT_PLAINTEXT_SIZE];
self.note.pk_d.write(&mut input[0..32]).unwrap(); self.note.pk_d.write(&mut input[0..32]).unwrap();
self.esk.into_repr().write_le(&mut input[32..64]).unwrap(); self.esk
.into_repr()
.write_le(&mut input[32..OUT_PLAINTEXT_SIZE])
.unwrap();
let mut buffer = Vec::with_capacity(64); let mut buffer = Vec::with_capacity(OUT_PLAINTEXT_SIZE);
let tag = let tag =
chacha20_poly1305_aead::encrypt(key.as_bytes(), &[0u8; 12], &[], &input, &mut buffer) chacha20_poly1305_aead::encrypt(key.as_bytes(), &[0u8; 12], &[], &input, &mut buffer)
.unwrap(); .unwrap();
let mut output = [0u8; 80]; let mut output = [0u8; OUT_CIPHERTEXT_SIZE];
output[0..64].copy_from_slice(&buffer); output[0..OUT_PLAINTEXT_SIZE].copy_from_slice(&buffer);
output[64..80].copy_from_slice(&tag[..]); output[OUT_PLAINTEXT_SIZE..OUT_CIPHERTEXT_SIZE].copy_from_slice(&tag[..]);
output output
} }
@ -274,7 +294,7 @@ fn parse_note_plaintext_minus_memo(
let v = (&plaintext[12..20]).read_u64::<LittleEndian>().ok()?; let v = (&plaintext[12..20]).read_u64::<LittleEndian>().ok()?;
let mut rcm = FsRepr::default(); let mut rcm = FsRepr::default();
rcm.read_le(&plaintext[20..52]).ok()?; rcm.read_le(&plaintext[20..COMPACT_NOTE_SIZE]).ok()?;
let rcm = Fs::from_repr(rcm).ok()?; let rcm = Fs::from_repr(rcm).ok()?;
let diversifier = Diversifier(d); let diversifier = Diversifier(d);
@ -304,16 +324,18 @@ pub fn try_sapling_note_decryption(
cmu: &Fr, cmu: &Fr,
enc_ciphertext: &[u8], enc_ciphertext: &[u8],
) -> Option<(Note<Bls12>, PaymentAddress<Bls12>, Memo)> { ) -> Option<(Note<Bls12>, PaymentAddress<Bls12>, Memo)> {
assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE);
let shared_secret = sapling_ka_agree(ivk, epk); let shared_secret = sapling_ka_agree(ivk, epk);
let key = kdf_sapling(&shared_secret, &epk); let key = kdf_sapling(&shared_secret, &epk);
let mut plaintext = Vec::with_capacity(564); let mut plaintext = Vec::with_capacity(NOTE_PLAINTEXT_SIZE);
chacha20_poly1305_aead::decrypt( chacha20_poly1305_aead::decrypt(
key.as_bytes(), key.as_bytes(),
&[0u8; 12], &[0u8; 12],
&[], &[],
&enc_ciphertext[..564], &enc_ciphertext[..NOTE_PLAINTEXT_SIZE],
&enc_ciphertext[564..], &enc_ciphertext[NOTE_PLAINTEXT_SIZE..],
&mut plaintext, &mut plaintext,
) )
.ok()?; .ok()?;
@ -321,7 +343,7 @@ pub fn try_sapling_note_decryption(
let (note, to) = parse_note_plaintext_minus_memo(ivk, cmu, &plaintext)?; let (note, to) = parse_note_plaintext_minus_memo(ivk, cmu, &plaintext)?;
let mut memo = [0u8; 512]; let mut memo = [0u8; 512];
memo.copy_from_slice(&plaintext[52..564]); memo.copy_from_slice(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]);
Some((note, to, Memo(memo))) Some((note, to, Memo(memo)))
} }
@ -337,6 +359,8 @@ pub fn try_sapling_compact_note_decryption(
cmu: &Fr, cmu: &Fr,
enc_ciphertext: &[u8], enc_ciphertext: &[u8],
) -> Option<(Note<Bls12>, PaymentAddress<Bls12>)> { ) -> Option<(Note<Bls12>, PaymentAddress<Bls12>)> {
assert_eq!(enc_ciphertext.len(), COMPACT_NOTE_SIZE);
let shared_secret = sapling_ka_agree(ivk, epk); let shared_secret = sapling_ka_agree(ivk, epk);
let key = kdf_sapling(&shared_secret, &epk); let key = kdf_sapling(&shared_secret, &epk);
@ -344,10 +368,10 @@ pub fn try_sapling_compact_note_decryption(
// Skip over Poly1305 keying output // Skip over Poly1305 keying output
chacha20.next(); chacha20.next();
let mut plaintext = Vec::with_capacity(52); let mut plaintext = Vec::with_capacity(COMPACT_NOTE_SIZE);
plaintext.extend_from_slice(&enc_ciphertext[0..52]); plaintext.extend_from_slice(&enc_ciphertext[0..COMPACT_NOTE_SIZE]);
let keystream = chacha20.next(); let keystream = chacha20.next();
for i in 0..52 { for i in 0..COMPACT_NOTE_SIZE {
plaintext[i] ^= keystream.as_bytes()[i]; plaintext[i] ^= keystream.as_bytes()[i];
} }
@ -367,15 +391,18 @@ pub fn try_sapling_output_recovery(
enc_ciphertext: &[u8], enc_ciphertext: &[u8],
out_ciphertext: &[u8], out_ciphertext: &[u8],
) -> Option<(Note<Bls12>, PaymentAddress<Bls12>, Memo)> { ) -> Option<(Note<Bls12>, PaymentAddress<Bls12>, Memo)> {
assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE);
assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE);
let ock = prf_ock(&ovk, &cv, &cmu, &epk); let ock = prf_ock(&ovk, &cv, &cmu, &epk);
let mut op = Vec::with_capacity(64); let mut op = Vec::with_capacity(OUT_PLAINTEXT_SIZE);
chacha20_poly1305_aead::decrypt( chacha20_poly1305_aead::decrypt(
ock.as_bytes(), ock.as_bytes(),
&[0u8; 12], &[0u8; 12],
&[], &[],
&out_ciphertext[..64], &out_ciphertext[..OUT_PLAINTEXT_SIZE],
&out_ciphertext[64..], &out_ciphertext[OUT_PLAINTEXT_SIZE..],
&mut op, &mut op,
) )
.ok()?; .ok()?;
@ -385,19 +412,19 @@ pub fn try_sapling_output_recovery(
.as_prime_order(&JUBJUB)?; .as_prime_order(&JUBJUB)?;
let mut esk = FsRepr::default(); let mut esk = FsRepr::default();
esk.read_le(&op[32..64]).ok()?; esk.read_le(&op[32..OUT_PLAINTEXT_SIZE]).ok()?;
let esk = Fs::from_repr(esk).ok()?; let esk = Fs::from_repr(esk).ok()?;
let shared_secret = sapling_ka_agree(&esk, &pk_d); let shared_secret = sapling_ka_agree(&esk, &pk_d);
let key = kdf_sapling(&shared_secret, &epk); let key = kdf_sapling(&shared_secret, &epk);
let mut plaintext = Vec::with_capacity(564); let mut plaintext = Vec::with_capacity(NOTE_PLAINTEXT_SIZE);
chacha20_poly1305_aead::decrypt( chacha20_poly1305_aead::decrypt(
key.as_bytes(), key.as_bytes(),
&[0u8; 12], &[0u8; 12],
&[], &[],
&enc_ciphertext[..564], &enc_ciphertext[..NOTE_PLAINTEXT_SIZE],
&enc_ciphertext[564..], &enc_ciphertext[NOTE_PLAINTEXT_SIZE..],
&mut plaintext, &mut plaintext,
) )
.ok()?; .ok()?;
@ -408,11 +435,11 @@ pub fn try_sapling_output_recovery(
let v = (&plaintext[12..20]).read_u64::<LittleEndian>().ok()?; let v = (&plaintext[12..20]).read_u64::<LittleEndian>().ok()?;
let mut rcm = FsRepr::default(); let mut rcm = FsRepr::default();
rcm.read_le(&plaintext[20..52]).ok()?; rcm.read_le(&plaintext[20..COMPACT_NOTE_SIZE]).ok()?;
let rcm = Fs::from_repr(rcm).ok()?; let rcm = Fs::from_repr(rcm).ok()?;
let mut memo = [0u8; 512]; let mut memo = [0u8; 512];
memo.copy_from_slice(&plaintext[52..564]); memo.copy_from_slice(&plaintext[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]);
let diversifier = Diversifier(d); let diversifier = Diversifier(d);
if diversifier if diversifier
@ -643,7 +670,12 @@ mod tests {
None => panic!("Note decryption failed"), None => panic!("Note decryption failed"),
} }
match try_sapling_compact_note_decryption(&ivk, &epk, &cmu, &tv.c_enc[..52]) { match try_sapling_compact_note_decryption(
&ivk,
&epk,
&cmu,
&tv.c_enc[..COMPACT_NOTE_SIZE],
) {
Some((decrypted_note, decrypted_to)) => { Some((decrypted_note, decrypted_to)) => {
assert_eq!(decrypted_note, note); assert_eq!(decrypted_note, note);
assert_eq!(decrypted_to, to); assert_eq!(decrypted_to, to);