Merge remote-tracking branch 'upstream/master' into autoshield-poc-daa
This commit is contained in:
commit
0b9d7e4303
|
@ -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" }
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue