Merge remote-tracking branch 'upstream/master' into autoshield-poc-daa

This commit is contained in:
Kris Nuttycombe 2021-11-24 15:48:02 -07:00
commit 0b9d7e4303
8 changed files with 5132 additions and 5026 deletions

View File

@ -20,7 +20,7 @@ codegen-units = 1
[patch.crates-io]
# In development.
orchard = { git = "https://github.com/zcash/orchard.git", rev = "2c8241f25b943aa05203eacf9905db117c69bd29" }
orchard = { git = "https://github.com/zcash/orchard.git", rev = "8c018eff7e795b16fc68aed22d0fd4eebe2710ec" }
incrementalmerkletree = { git = "https://github.com/zcash/incrementalmerkletree.git", rev = "b7bd6246122a6e9ace8edb51553fbf5228906cbb" }
zcash_encoding = { path = "components/zcash_encoding" }
zcash_note_encryption = { path = "components/zcash_note_encryption" }

View File

@ -14,7 +14,11 @@ license = "MIT OR Apache-2.0"
edition = "2018"
[dependencies]
blake2b_simd = "0.5"
blake2b_simd = { version = "0.5", default-features = false }
[dev-dependencies]
proptest = "1"
[features]
default = ["std"]
std = ["blake2b_simd/std"]

View File

@ -1,13 +1,51 @@
#![no_std]
use blake2b_simd::{Params as Blake2bParams, OUTBYTES};
use std::cmp::min;
use std::ops::RangeInclusive;
use core::cmp::min;
use core::fmt;
use core::ops::RangeInclusive;
use core::result::Result;
#[cfg(feature = "std")]
#[macro_use]
extern crate std;
#[cfg(feature = "std")]
use std::vec::Vec;
#[cfg(test)]
mod test_vectors;
#[cfg(test)]
#[cfg(all(test, feature = "std"))]
mod test_vectors_long;
const VALID_LENGTH: RangeInclusive<usize> = 48..=4194368;
/// Length of F4Jumbled message must lie in the range VALID_LENGTH.
///
/// VALID_LENGTH = 48..=4194368
pub const VALID_LENGTH: RangeInclusive<usize> = 48..=4194368;
/// Errors produced by F4Jumble.
#[derive(Debug)]
pub enum Error {
/// Value error indicating that length of F4Jumbled message does not
/// lie in the range [`VALID_LENGTH`].
InvalidLength,
}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::InvalidLength => write!(
f,
"Message length must be in interval ({}..={})",
*VALID_LENGTH.start(),
*VALID_LENGTH.end()
),
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for Error {}
macro_rules! H_PERS {
( $i:expr ) => {
@ -40,96 +78,107 @@ macro_rules! G_PERS {
};
}
struct Hashes {
l_l: usize,
l_r: usize,
struct State<'a> {
left: &'a mut [u8],
right: &'a mut [u8],
}
impl Hashes {
fn new(message_length: usize) -> Self {
let l_l = min(OUTBYTES, message_length / 2);
let l_r = message_length - l_l;
Hashes { l_l, l_r }
impl<'a> State<'a> {
fn new(message: &'a mut [u8]) -> Self {
let left_length = min(OUTBYTES, message.len() / 2);
let (left, right) = message.split_at_mut(left_length);
State { left, right }
}
fn h(&self, i: u8, u: &[u8]) -> Vec<u8> {
Blake2bParams::new()
.hash_length(self.l_l)
fn h_round(&mut self, i: u8) {
let hash = Blake2bParams::new()
.hash_length(self.left.len())
.personal(&H_PERS!(i))
.hash(&u)
.as_ref()
.to_vec()
.hash(&self.right);
xor(self.left, hash.as_bytes())
}
fn g(&self, i: u8, u: &[u8]) -> Vec<u8> {
(0..ceildiv(self.l_r, OUTBYTES))
.flat_map(|j| {
Blake2bParams::new()
.hash_length(OUTBYTES)
.personal(&G_PERS!(i, j as u16))
.hash(u)
.as_ref()
.to_vec()
.into_iter()
})
.take(self.l_r)
.collect()
fn g_round(&mut self, i: u8) {
for j in 0..ceildiv(self.right.len(), OUTBYTES) {
let hash = Blake2bParams::new()
.hash_length(OUTBYTES)
.personal(&G_PERS!(i, j as u16))
.hash(&self.left);
xor(&mut self.right[j * OUTBYTES..], hash.as_bytes());
}
}
fn apply_f4jumble(&mut self) {
self.g_round(0);
self.h_round(0);
self.g_round(1);
self.h_round(1);
}
fn apply_f4jumble_inv(&mut self) {
self.h_round(1);
self.g_round(1);
self.h_round(0);
self.g_round(0);
}
}
fn xor(a: &[u8], b: &[u8]) -> Vec<u8> {
a.iter().zip(b.iter()).map(|(a0, b0)| a0 ^ b0).collect()
/// XORs bytes of the `source` to bytes of the `target`.
///
/// This method operates over the first `min(source.len(), target.len())` bytes.
fn xor(target: &mut [u8], source: &[u8]) {
for (source, target) in source.iter().zip(target.iter_mut()) {
*target ^= source;
}
}
fn ceildiv(num: usize, den: usize) -> usize {
(num + den - 1) / den
}
#[allow(clippy::many_single_char_names)]
pub fn f4jumble(a: &[u8]) -> Option<Vec<u8>> {
if VALID_LENGTH.contains(&a.len()) {
let hashes = Hashes::new(a.len());
let (a, b) = a.split_at(hashes.l_l);
pub fn f4jumble_mut(message: &mut [u8]) -> Result<(), Error> {
if VALID_LENGTH.contains(&message.len()) {
State::new(message).apply_f4jumble();
Ok(())
} else {
Err(Error::InvalidLength)
}
}
let x = xor(b, &hashes.g(0, a));
let y = xor(a, &hashes.h(0, &x));
let d = xor(&x, &hashes.g(1, &y));
let mut c = xor(&y, &hashes.h(1, &d));
pub fn f4jumble_inv_mut(message: &mut [u8]) -> Result<(), Error> {
if VALID_LENGTH.contains(&message.len()) {
State::new(message).apply_f4jumble_inv();
Ok(())
} else {
Err(Error::InvalidLength)
}
}
c.extend(d);
Some(c)
#[cfg(feature = "std")]
pub fn f4jumble(message: &[u8]) -> Option<Vec<u8>> {
let mut result = message.to_vec();
let res = f4jumble_mut(&mut result);
if res.is_ok() {
Some(result)
} else {
None
}
}
#[allow(clippy::many_single_char_names)]
pub fn f4jumble_inv(c: &[u8]) -> Option<Vec<u8>> {
if VALID_LENGTH.contains(&c.len()) {
let hashes = Hashes::new(c.len());
let (c, d) = c.split_at(hashes.l_l);
let y = xor(c, &hashes.h(1, d));
let x = xor(d, &hashes.g(1, &y));
let mut a = xor(&y, &hashes.h(0, &x));
let b = xor(&x, &hashes.g(0, &a));
a.extend(b);
Some(a)
#[cfg(feature = "std")]
pub fn f4jumble_inv(message: &[u8]) -> Option<Vec<u8>> {
let mut result = message.to_vec();
let res = f4jumble_inv_mut(&mut result);
if res.is_ok() {
Some(result)
} else {
None
}
}
#[cfg(test)]
mod tests {
use blake2b_simd::blake2b;
use proptest::collection::vec;
use proptest::prelude::*;
use super::{
f4jumble, f4jumble_inv, test_vectors::test_vectors, test_vectors_long, VALID_LENGTH,
};
mod common_tests {
use super::{f4jumble_inv_mut, f4jumble_mut, test_vectors};
#[test]
fn h_pers() {
@ -142,6 +191,34 @@ mod tests {
assert_eq!(&G_PERS!(7, 65535), b"UA_F4Jumble_G\x07\xff\xff");
}
#[test]
fn f4jumble_check_vectors_mut() {
#[cfg(not(feature = "std"))]
let mut cache = [0u8; test_vectors::MAX_VECTOR_LENGTH];
#[cfg(feature = "std")]
let mut cache = vec![0u8; test_vectors::MAX_VECTOR_LENGTH];
for v in test_vectors::TEST_VECTORS {
let mut data = &mut cache[..v.normal.len()];
data.clone_from_slice(&v.normal);
f4jumble_mut(&mut data).unwrap();
assert_eq!(data, v.jumbled);
f4jumble_inv_mut(&mut data).unwrap();
assert_eq!(data, v.normal);
}
}
}
#[cfg(feature = "std")]
#[cfg(test)]
mod std_tests {
use blake2b_simd::blake2b;
use proptest::collection::vec;
use proptest::prelude::*;
use std::format;
use std::vec::Vec;
use super::{f4jumble, f4jumble_inv, test_vectors, test_vectors_long, VALID_LENGTH};
proptest! {
#![proptest_config(ProptestConfig::with_cases(5))]
@ -168,7 +245,7 @@ mod tests {
#[test]
fn f4jumble_check_vectors() {
for v in test_vectors() {
for v in test_vectors::TEST_VECTORS {
let jumbled = f4jumble(&v.normal).unwrap();
assert_eq!(jumbled, v.jumbled);
let unjumbled = f4jumble_inv(&v.jumbled).unwrap();

File diff suppressed because it is too large Load Diff

View File

@ -12,18 +12,23 @@ license = "MIT OR Apache-2.0"
edition = "2018"
[dependencies]
blake2b_simd = "0.5"
byteorder = "1"
chacha20 = "0.8"
chacha20poly1305 = "0.9"
ff = "0.11"
group = "0.11"
rand_core = "0.6"
subtle = "2.2.3"
blake2b_simd = { version = "0.5", default-features = false }
byteorder = { version = "1", default-features = false }
chacha20 = { version = "0.8", default-features = false }
chacha20poly1305 = { version = "0.9", default-features = false }
ff = { version = "0.11", default-features = false }
group = { version = "0.11", default-features = false }
rand_core = { version = "0.6", default-features = false }
subtle = { version = "2.2.3", default-features = false }
[dev-dependencies]
zcash_primitives = { version = "0.5", path = "../../zcash_primitives" }
jubjub = "0.8"
[features]
default = ["std"]
alloc = []
std = ["alloc", "blake2b_simd/std"]
[lib]
bench = false

View File

@ -1,9 +1,10 @@
//! APIs for batch trial decryption.
use std::iter;
use alloc::vec::Vec; // module is alloc only
use core::iter;
use crate::{
try_compact_note_decryption_inner, try_note_decryption_inner, Domain, EphemeralKeyBytes,
try_compact_note_decryption_inner, try_note_decryption_inner, BatchDomain, EphemeralKeyBytes,
ShieldedOutput,
};
@ -11,7 +12,7 @@ use crate::{
///
/// This is the batched version of [`crate::try_note_decryption`].
#[allow(clippy::type_complexity)]
pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
pub fn try_note_decryption<D: BatchDomain, Output: ShieldedOutput<D>>(
ivks: &[D::IncomingViewingKey],
outputs: &[(D, Output)],
) -> Vec<Option<(D::Note, D::Recipient, D::Memo)>> {
@ -21,14 +22,14 @@ pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
/// Trial decryption of a batch of notes for light clients with a set of recipients.
///
/// This is the batched version of [`crate::try_compact_note_decryption`].
pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
pub fn try_compact_note_decryption<D: BatchDomain, Output: ShieldedOutput<D>>(
ivks: &[D::IncomingViewingKey],
outputs: &[(D, Output)],
) -> Vec<Option<(D::Note, D::Recipient)>> {
batch_note_decryption(ivks, outputs, try_compact_note_decryption_inner)
}
fn batch_note_decryption<D: Domain, Output: ShieldedOutput<D>, F, FR>(
fn batch_note_decryption<D: BatchDomain, Output: ShieldedOutput<D>, F, FR>(
ivks: &[D::IncomingViewingKey],
outputs: &[(D, Output)],
decrypt_inner: F,

View File

@ -3,12 +3,18 @@
//! functionality that is shared between the Sapling and Orchard
//! protocols.
#![no_std]
// Catch documentation errors caused by code changes.
#![deny(broken_intra_doc_links)]
#![deny(unsafe_code)]
// TODO: #![deny(missing_docs)]
use std::convert::TryInto;
#[cfg(feature = "alloc")]
extern crate alloc;
#[cfg(feature = "alloc")]
use alloc::vec::Vec;
use core::convert::TryInto;
use chacha20::{
cipher::{NewCipher, StreamCipher, StreamCipherSeek},
@ -22,6 +28,7 @@ use chacha20poly1305::{
use rand_core::RngCore;
use subtle::{Choice, ConstantTimeEq};
#[cfg(feature = "alloc")]
pub mod batch;
pub const COMPACT_NOTE_SIZE: usize = 1 + // version
@ -116,19 +123,6 @@ pub trait Domain {
fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey;
/// Computes `Self::kdf` on a batch of items.
///
/// For each item in the batch, if the shared secret is `None`, this returns `None` at
/// that position.
fn batch_kdf<'a>(
items: impl Iterator<Item = (Option<Self::SharedSecret>, &'a EphemeralKeyBytes)>,
) -> Vec<Option<Self::SymmetricKey>> {
// Default implementation: do the non-batched thing.
items
.map(|(secret, ephemeral_key)| secret.map(|secret| Self::kdf(secret, ephemeral_key)))
.collect()
}
// for right now, we just need `recipient` to get `d`; in the future when we
// can get that from a Sapling note, the recipient parameter will be able
// to be removed.
@ -154,22 +148,6 @@ pub trait Domain {
fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option<Self::EphemeralPublicKey>;
/// Computes `Self::epk` on a batch of ephemeral keys.
///
/// This is useful for protocols where the underlying curve requires an inversion to
/// parse an encoded point.
///
/// For usability, this returns tuples of the ephemeral keys and the result of parsing
/// them.
fn batch_epk(
ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>,
) -> Vec<(Option<Self::EphemeralPublicKey>, EphemeralKeyBytes)> {
// Default implementation: do the non-batched thing.
ephemeral_keys
.map(|ephemeral_key| (Self::epk(&ephemeral_key), ephemeral_key))
.collect()
}
fn check_epk_bytes<F: Fn(&Self::EphemeralSecretKey) -> NoteValidity>(
note: &Self::Note,
check: F,
@ -203,6 +181,38 @@ pub trait Domain {
fn extract_esk(out_plaintext: &[u8; OUT_PLAINTEXT_SIZE]) -> Option<Self::EphemeralSecretKey>;
}
#[cfg(feature = "alloc")]
pub trait BatchDomain: Domain {
/// Computes `Self::kdf` on a batch of items.
///
/// For each item in the batch, if the shared secret is `None`, this returns `None` at
/// that position.
fn batch_kdf<'a>(
items: impl Iterator<Item = (Option<Self::SharedSecret>, &'a EphemeralKeyBytes)>,
) -> Vec<Option<Self::SymmetricKey>> {
// Default implementation: do the non-batched thing.
items
.map(|(secret, ephemeral_key)| secret.map(|secret| Self::kdf(secret, ephemeral_key)))
.collect()
}
/// Computes `Self::epk` on a batch of ephemeral keys.
///
/// This is useful for protocols where the underlying curve requires an inversion to
/// parse an encoded point.
///
/// For usability, this returns tuples of the ephemeral keys and the result of parsing
/// them.
fn batch_epk(
ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>,
) -> Vec<(Option<Self::EphemeralPublicKey>, EphemeralKeyBytes)> {
// Default implementation: do the non-batched thing.
ephemeral_keys
.map(|ephemeral_key| (Self::epk(&ephemeral_key), ephemeral_key))
.collect()
}
}
pub trait ShieldedOutput<D: Domain> {
fn ephemeral_key(&self) -> EphemeralKeyBytes;
fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes;

View File

@ -9,9 +9,9 @@ use std::convert::TryInto;
use zcash_note_encryption::{
try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ock,
try_output_recovery_with_ovk, Domain, EphemeralKeyBytes, NoteEncryption, NotePlaintextBytes,
NoteValidity, OutPlaintextBytes, OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE,
NOTE_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE,
try_output_recovery_with_ovk, BatchDomain, Domain, EphemeralKeyBytes, NoteEncryption,
NotePlaintextBytes, NoteValidity, OutPlaintextBytes, OutgoingCipherKey, ShieldedOutput,
COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, OUT_PLAINTEXT_SIZE,
};
use crate::{
@ -185,37 +185,6 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
kdf_sapling(dhsecret, epk)
}
fn batch_kdf<'a>(
items: impl Iterator<Item = (Option<Self::SharedSecret>, &'a EphemeralKeyBytes)>,
) -> Vec<Option<Self::SymmetricKey>> {
let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip();
let secrets: Vec<_> = shared_secrets
.iter()
.filter_map(|s| s.map(ExtendedPoint::from))
.collect();
let mut secrets_affine = vec![AffinePoint::identity(); shared_secrets.len()];
group::Curve::batch_normalize(&secrets, &mut secrets_affine);
let mut secrets_affine = secrets_affine.into_iter();
shared_secrets
.into_iter()
.map(|s| s.and_then(|_| secrets_affine.next()))
.zip(ephemeral_keys.into_iter())
.map(|(secret, ephemeral_key)| {
secret.map(|dhsecret| {
Blake2bParams::new()
.hash_length(32)
.personal(KDF_SAPLING_PERSONALIZATION)
.to_state()
.update(&dhsecret.to_bytes())
.update(ephemeral_key.as_ref())
.finalize()
})
})
.collect()
}
fn note_plaintext_bytes(
note: &Self::Note,
to: &Self::Recipient,
@ -278,19 +247,6 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
jubjub::ExtendedPoint::from_bytes(&ephemeral_key.0).into()
}
fn batch_epk(
ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>,
) -> Vec<(Option<Self::EphemeralPublicKey>, EphemeralKeyBytes)> {
let ephemeral_keys: Vec<_> = ephemeral_keys.collect();
let epks = jubjub::AffinePoint::batch_from_bytes(ephemeral_keys.iter().map(|b| b.0));
epks.into_iter()
.zip(ephemeral_keys.into_iter())
.map(|(epk, ephemeral_key)| {
(epk.map(jubjub::ExtendedPoint::from).into(), ephemeral_key)
})
.collect()
}
fn check_epk_bytes<F: FnOnce(&Self::EphemeralSecretKey) -> NoteValidity>(
note: &Note,
check: F,
@ -359,6 +315,52 @@ impl<P: consensus::Parameters> Domain for SaplingDomain<P> {
}
}
impl<P: consensus::Parameters> BatchDomain for SaplingDomain<P> {
fn batch_kdf<'a>(
items: impl Iterator<Item = (Option<Self::SharedSecret>, &'a EphemeralKeyBytes)>,
) -> Vec<Option<Self::SymmetricKey>> {
let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip();
let secrets: Vec<_> = shared_secrets
.iter()
.filter_map(|s| s.map(ExtendedPoint::from))
.collect();
let mut secrets_affine = vec![AffinePoint::identity(); shared_secrets.len()];
group::Curve::batch_normalize(&secrets, &mut secrets_affine);
let mut secrets_affine = secrets_affine.into_iter();
shared_secrets
.into_iter()
.map(|s| s.and_then(|_| secrets_affine.next()))
.zip(ephemeral_keys.into_iter())
.map(|(secret, ephemeral_key)| {
secret.map(|dhsecret| {
Blake2bParams::new()
.hash_length(32)
.personal(KDF_SAPLING_PERSONALIZATION)
.to_state()
.update(&dhsecret.to_bytes())
.update(ephemeral_key.as_ref())
.finalize()
})
})
.collect()
}
fn batch_epk(
ephemeral_keys: impl Iterator<Item = EphemeralKeyBytes>,
) -> Vec<(Option<Self::EphemeralPublicKey>, EphemeralKeyBytes)> {
let ephemeral_keys: Vec<_> = ephemeral_keys.collect();
let epks = jubjub::AffinePoint::batch_from_bytes(ephemeral_keys.iter().map(|b| b.0));
epks.into_iter()
.zip(ephemeral_keys.into_iter())
.map(|(epk, ephemeral_key)| {
(epk.map(jubjub::ExtendedPoint::from).into(), ephemeral_key)
})
.collect()
}
}
/// Creates a new encryption context for the given note.
///
/// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be