Merge pull request #284 from str4d/259-ovk-none
zcash_primitives: Support ovk = ⊥ in note encryption
This commit is contained in:
commit
c7e9523dce
|
@ -257,7 +257,7 @@ mod tests {
|
|||
rseed,
|
||||
};
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
extfvk.fvk.ovk,
|
||||
Some(extfvk.fvk.ovk),
|
||||
note.clone(),
|
||||
to.clone(),
|
||||
Memo::default(),
|
||||
|
|
|
@ -133,7 +133,7 @@ mod tests {
|
|||
rseed,
|
||||
};
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
extfvk.fvk.ovk,
|
||||
Some(extfvk.fvk.ovk),
|
||||
note.clone(),
|
||||
to.clone(),
|
||||
Memo::default(),
|
||||
|
@ -193,7 +193,7 @@ mod tests {
|
|||
rseed,
|
||||
};
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
extfvk.fvk.ovk,
|
||||
Some(extfvk.fvk.ovk),
|
||||
note.clone(),
|
||||
to,
|
||||
Memo::default(),
|
||||
|
@ -221,7 +221,7 @@ mod tests {
|
|||
rseed,
|
||||
};
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
extfvk.fvk.ovk,
|
||||
Some(extfvk.fvk.ovk),
|
||||
note.clone(),
|
||||
change_addr,
|
||||
Memo::default(),
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
//! Functions for creating transactions.
|
||||
|
||||
use ff::PrimeField;
|
||||
use rand_core::{OsRng, RngCore};
|
||||
use rand_core::OsRng;
|
||||
use rusqlite::{types::ToSql, Connection, NO_PARAMS};
|
||||
use std::convert::TryInto;
|
||||
use std::path::Path;
|
||||
|
@ -148,16 +148,9 @@ pub fn create_to_address<P: AsRef<Path>>(
|
|||
|
||||
// Apply the outgoing viewing key policy.
|
||||
let ovk = match ovk_policy {
|
||||
OvkPolicy::Sender => extfvk.fvk.ovk,
|
||||
OvkPolicy::Custom(ovk) => ovk,
|
||||
OvkPolicy::Discard => {
|
||||
// Generate a random outgoing viewing key that the caller does not know.
|
||||
// The probability of this colliding with a legitimate outgoing viewing
|
||||
// key is negligible.
|
||||
let mut ovk = [0; 32];
|
||||
OsRng.fill_bytes(&mut ovk);
|
||||
OutgoingViewingKey(ovk)
|
||||
}
|
||||
OvkPolicy::Sender => Some(extfvk.fvk.ovk),
|
||||
OvkPolicy::Custom(ovk) => Some(ovk),
|
||||
OvkPolicy::Discard => None,
|
||||
};
|
||||
|
||||
// Target the next block, assuming we are up-to-date.
|
||||
|
|
|
@ -152,6 +152,21 @@ fn kdf_sapling(dhsecret: jubjub::SubgroupPoint, epk: &jubjub::SubgroupPoint) ->
|
|||
.finalize()
|
||||
}
|
||||
|
||||
/// A symmetric key that can be used to recover a single Sapling output.
|
||||
pub struct OutgoingCipherKey([u8; 32]);
|
||||
|
||||
impl From<[u8; 32]> for OutgoingCipherKey {
|
||||
fn from(ock: [u8; 32]) -> Self {
|
||||
OutgoingCipherKey(ock)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for OutgoingCipherKey {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Sapling PRF^ock.
|
||||
///
|
||||
/// Implemented per section 5.4.2 of the Zcash Protocol Specification.
|
||||
|
@ -160,16 +175,21 @@ pub fn prf_ock(
|
|||
cv: &jubjub::ExtendedPoint,
|
||||
cmu: &bls12_381::Scalar,
|
||||
epk: &jubjub::SubgroupPoint,
|
||||
) -> Blake2bHash {
|
||||
Blake2bParams::new()
|
||||
.hash_length(32)
|
||||
.personal(PRF_OCK_PERSONALIZATION)
|
||||
.to_state()
|
||||
.update(&ovk.0)
|
||||
.update(&cv.to_bytes())
|
||||
.update(&cmu.to_repr())
|
||||
.update(&epk.to_bytes())
|
||||
.finalize()
|
||||
) -> OutgoingCipherKey {
|
||||
OutgoingCipherKey(
|
||||
Blake2bParams::new()
|
||||
.hash_length(32)
|
||||
.personal(PRF_OCK_PERSONALIZATION)
|
||||
.to_state()
|
||||
.update(&ovk.0)
|
||||
.update(&cv.to_bytes())
|
||||
.update(&cmu.to_repr())
|
||||
.update(&epk.to_bytes())
|
||||
.finalize()
|
||||
.as_bytes()
|
||||
.try_into()
|
||||
.unwrap(),
|
||||
)
|
||||
}
|
||||
|
||||
/// An API for encrypting Sapling notes.
|
||||
|
@ -201,7 +221,7 @@ pub fn prf_ock(
|
|||
/// let diversifier = Diversifier([0; 11]);
|
||||
/// let pk_d = diversifier.g_d().unwrap();
|
||||
/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
|
||||
/// let ovk = OutgoingViewingKey([0; 32]);
|
||||
/// let ovk = Some(OutgoingViewingKey([0; 32]));
|
||||
///
|
||||
/// let value = 1000;
|
||||
/// let rcv = jubjub::Fr::random(&mut rng);
|
||||
|
@ -213,29 +233,34 @@ pub fn prf_ock(
|
|||
/// let note = to.create_note(value, Rseed::BeforeZip212(rcm)).unwrap();
|
||||
/// let cmu = note.cmu();
|
||||
///
|
||||
/// let enc = SaplingNoteEncryption::new(ovk, note, to, Memo::default(), &mut rng);
|
||||
/// let mut enc = SaplingNoteEncryption::new(ovk, note, to, Memo::default(), &mut rng);
|
||||
/// let encCiphertext = enc.encrypt_note_plaintext();
|
||||
/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu);
|
||||
/// ```
|
||||
pub struct SaplingNoteEncryption {
|
||||
pub struct SaplingNoteEncryption<R: RngCore + CryptoRng> {
|
||||
epk: jubjub::SubgroupPoint,
|
||||
esk: jubjub::Fr,
|
||||
note: Note,
|
||||
to: PaymentAddress,
|
||||
memo: Memo,
|
||||
ovk: OutgoingViewingKey,
|
||||
/// `None` represents the `ovk = ⊥` case.
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
rng: R,
|
||||
}
|
||||
|
||||
impl SaplingNoteEncryption {
|
||||
impl<R: RngCore + CryptoRng> SaplingNoteEncryption<R> {
|
||||
/// Creates a new encryption context for the given note.
|
||||
pub fn new<R: RngCore + CryptoRng>(
|
||||
ovk: OutgoingViewingKey,
|
||||
///
|
||||
/// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be
|
||||
/// recovered by the sender.
|
||||
pub fn new(
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
note: Note,
|
||||
to: PaymentAddress,
|
||||
memo: Memo,
|
||||
rng: &mut R,
|
||||
) -> SaplingNoteEncryption {
|
||||
let esk = note.generate_or_derive_esk(rng);
|
||||
mut rng: R,
|
||||
) -> Self {
|
||||
let esk = note.generate_or_derive_esk(&mut rng);
|
||||
let epk = note.g_d * esk;
|
||||
|
||||
SaplingNoteEncryption {
|
||||
|
@ -245,6 +270,7 @@ impl SaplingNoteEncryption {
|
|||
to,
|
||||
memo,
|
||||
ovk,
|
||||
rng,
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -297,20 +323,33 @@ impl SaplingNoteEncryption {
|
|||
|
||||
/// Generates `outCiphertext` for this note.
|
||||
pub fn encrypt_outgoing_plaintext(
|
||||
&self,
|
||||
&mut self,
|
||||
cv: &jubjub::ExtendedPoint,
|
||||
cmu: &bls12_381::Scalar,
|
||||
) -> [u8; OUT_CIPHERTEXT_SIZE] {
|
||||
let key = prf_ock(&self.ovk, &cv, &cmu, &self.epk);
|
||||
let (ock, input) = if let Some(ovk) = &self.ovk {
|
||||
let ock = prf_ock(ovk, &cv, &cmu, &self.epk);
|
||||
|
||||
let mut input = [0u8; OUT_PLAINTEXT_SIZE];
|
||||
input[0..32].copy_from_slice(&self.note.pk_d.to_bytes());
|
||||
input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(self.esk.to_repr().as_ref());
|
||||
let mut input = [0u8; OUT_PLAINTEXT_SIZE];
|
||||
input[0..32].copy_from_slice(&self.note.pk_d.to_bytes());
|
||||
input[32..OUT_PLAINTEXT_SIZE].copy_from_slice(self.esk.to_repr().as_ref());
|
||||
|
||||
(ock, input)
|
||||
} else {
|
||||
// ovk = ⊥
|
||||
let mut ock = OutgoingCipherKey([0; 32]);
|
||||
let mut input = [0u8; OUT_PLAINTEXT_SIZE];
|
||||
|
||||
self.rng.fill_bytes(&mut ock.0);
|
||||
self.rng.fill_bytes(&mut input);
|
||||
|
||||
(ock, input)
|
||||
};
|
||||
|
||||
let mut output = [0u8; OUT_CIPHERTEXT_SIZE];
|
||||
assert_eq!(
|
||||
ChachaPolyIetf::aead_cipher()
|
||||
.seal_to(&mut output, &input, &[], key.as_bytes(), &[0u8; 12])
|
||||
.seal_to(&mut output, &input, &[], ock.as_ref(), &[0u8; 12])
|
||||
.unwrap(),
|
||||
OUT_CIPHERTEXT_SIZE
|
||||
);
|
||||
|
@ -468,7 +507,7 @@ pub fn try_sapling_compact_note_decryption<P: consensus::Parameters>(
|
|||
/// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`].
|
||||
pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
|
||||
height: u32,
|
||||
ock: &[u8],
|
||||
ock: &OutgoingCipherKey,
|
||||
cmu: &bls12_381::Scalar,
|
||||
epk: &jubjub::SubgroupPoint,
|
||||
enc_ciphertext: &[u8],
|
||||
|
@ -480,7 +519,7 @@ pub fn try_sapling_output_recovery_with_ock<P: consensus::Parameters>(
|
|||
let mut op = [0; OUT_CIPHERTEXT_SIZE];
|
||||
assert_eq!(
|
||||
ChachaPolyIetf::aead_cipher()
|
||||
.open_to(&mut op, &out_ciphertext, &[], &ock, &[0u8; 12])
|
||||
.open_to(&mut op, &out_ciphertext, &[], ock.as_ref(), &[0u8; 12])
|
||||
.ok()?,
|
||||
OUT_PLAINTEXT_SIZE
|
||||
);
|
||||
|
@ -583,7 +622,7 @@ pub fn try_sapling_output_recovery<P: consensus::Parameters>(
|
|||
) -> Option<(Note, PaymentAddress, Memo)> {
|
||||
try_sapling_output_recovery_with_ock::<P>(
|
||||
height,
|
||||
prf_ock(&ovk, &cv, &cmu, &epk).as_bytes(),
|
||||
&prf_ock(&ovk, &cv, &cmu, &epk),
|
||||
cmu,
|
||||
epk,
|
||||
enc_ciphertext,
|
||||
|
@ -593,7 +632,6 @@ pub fn try_sapling_output_recovery<P: consensus::Parameters>(
|
|||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use blake2b_simd::Hash as Blake2bHash;
|
||||
use crypto_api_chachapoly::ChachaPolyIetf;
|
||||
use ff::{Field, PrimeField};
|
||||
use group::Group;
|
||||
|
@ -606,8 +644,9 @@ mod tests {
|
|||
use super::{
|
||||
kdf_sapling, prf_ock, sapling_ka_agree, try_sapling_compact_note_decryption,
|
||||
try_sapling_note_decryption, try_sapling_output_recovery,
|
||||
try_sapling_output_recovery_with_ock, Memo, SaplingNoteEncryption, COMPACT_NOTE_SIZE,
|
||||
ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE,
|
||||
try_sapling_output_recovery_with_ock, Memo, OutgoingCipherKey, SaplingNoteEncryption,
|
||||
COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE,
|
||||
OUT_PLAINTEXT_SIZE,
|
||||
};
|
||||
use crate::{
|
||||
consensus::{
|
||||
|
@ -741,7 +780,7 @@ mod tests {
|
|||
mut rng: &mut R,
|
||||
) -> (
|
||||
OutgoingViewingKey,
|
||||
Blake2bHash,
|
||||
OutgoingCipherKey,
|
||||
jubjub::Fr,
|
||||
jubjub::ExtendedPoint,
|
||||
bls12_381::Scalar,
|
||||
|
@ -782,7 +821,7 @@ mod tests {
|
|||
);
|
||||
let ock_output_recovery = try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
ock.as_bytes(),
|
||||
&ock,
|
||||
&cmu,
|
||||
&epk,
|
||||
&enc_ciphertext,
|
||||
|
@ -801,7 +840,7 @@ mod tests {
|
|||
mut rng: &mut R,
|
||||
) -> (
|
||||
OutgoingViewingKey,
|
||||
Blake2bHash,
|
||||
OutgoingCipherKey,
|
||||
jubjub::Fr,
|
||||
jubjub::ExtendedPoint,
|
||||
bls12_381::Scalar,
|
||||
|
@ -827,22 +866,13 @@ mod tests {
|
|||
let cmu = note.cmu();
|
||||
|
||||
let ovk = OutgoingViewingKey([0; 32]);
|
||||
let ne = SaplingNoteEncryption::new(ovk, note, pa, Memo([0; 512]), &mut rng);
|
||||
let epk = ne.epk();
|
||||
let mut ne = SaplingNoteEncryption::new(Some(ovk), note, pa, Memo([0; 512]), &mut rng);
|
||||
let epk = ne.epk().clone();
|
||||
let enc_ciphertext = ne.encrypt_note_plaintext();
|
||||
let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu);
|
||||
let ock = prf_ock(&ovk, &cv, &cmu, &epk);
|
||||
|
||||
(
|
||||
ovk,
|
||||
ock,
|
||||
ivk,
|
||||
cv,
|
||||
cmu,
|
||||
epk.clone(),
|
||||
enc_ciphertext,
|
||||
out_ciphertext,
|
||||
)
|
||||
(ovk, ock, ivk, cv, cmu, epk, enc_ciphertext, out_ciphertext)
|
||||
}
|
||||
|
||||
fn reencrypt_enc_ciphertext(
|
||||
|
@ -859,7 +889,7 @@ mod tests {
|
|||
let mut op = [0; OUT_CIPHERTEXT_SIZE];
|
||||
assert_eq!(
|
||||
ChachaPolyIetf::aead_cipher()
|
||||
.open_to(&mut op, out_ciphertext, &[], ock.as_bytes(), &[0u8; 12])
|
||||
.open_to(&mut op, out_ciphertext, &[], ock.as_ref(), &[0u8; 12])
|
||||
.unwrap(),
|
||||
OUT_PLAINTEXT_SIZE
|
||||
);
|
||||
|
@ -1351,7 +1381,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
&[0u8; 32],
|
||||
&OutgoingCipherKey([0u8; 32]),
|
||||
&cmu,
|
||||
&epk,
|
||||
&enc_ciphertext,
|
||||
|
@ -1416,7 +1446,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
&ock.as_bytes(),
|
||||
&ock,
|
||||
&bls12_381::Scalar::random(&mut rng),
|
||||
&epk,
|
||||
&enc_ctext,
|
||||
|
@ -1454,7 +1484,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
&ock.as_bytes(),
|
||||
&ock,
|
||||
&cmu,
|
||||
&jubjub::SubgroupPoint::random(&mut rng),
|
||||
&enc_ciphertext,
|
||||
|
@ -1493,7 +1523,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
&ock.as_bytes(),
|
||||
&ock,
|
||||
&cmu,
|
||||
&epk,
|
||||
&enc_ciphertext,
|
||||
|
@ -1532,7 +1562,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
&ock.as_bytes(),
|
||||
&ock,
|
||||
&cmu,
|
||||
&epk,
|
||||
&enc_ciphertext,
|
||||
|
@ -1582,7 +1612,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
&ock.as_bytes(),
|
||||
&ock,
|
||||
&cmu,
|
||||
&epk,
|
||||
&enc_ciphertext,
|
||||
|
@ -1629,7 +1659,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
&ock.as_bytes(),
|
||||
&ock,
|
||||
&cmu,
|
||||
&epk,
|
||||
&enc_ciphertext,
|
||||
|
@ -1676,7 +1706,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
&ock.as_bytes(),
|
||||
&ock,
|
||||
&cmu,
|
||||
&epk,
|
||||
&enc_ciphertext,
|
||||
|
@ -1715,7 +1745,7 @@ mod tests {
|
|||
assert_eq!(
|
||||
try_sapling_output_recovery_with_ock::<TestNetwork>(
|
||||
height,
|
||||
&ock.as_bytes(),
|
||||
&ock,
|
||||
&cmu,
|
||||
&epk,
|
||||
&enc_ciphertext,
|
||||
|
@ -1776,7 +1806,7 @@ mod tests {
|
|||
|
||||
let ovk = OutgoingViewingKey(tv.ovk);
|
||||
let ock = prf_ock(&ovk, &cv, &cmu, &epk);
|
||||
assert_eq!(ock.as_bytes(), tv.ock);
|
||||
assert_eq!(ock.as_ref(), tv.ock);
|
||||
|
||||
let to = PaymentAddress::from_parts(Diversifier(tv.default_d), pk_d).unwrap();
|
||||
let note = to.create_note(tv.v, Rseed::BeforeZip212(rcm)).unwrap();
|
||||
|
@ -1825,7 +1855,7 @@ mod tests {
|
|||
// Test encryption
|
||||
//
|
||||
|
||||
let mut ne = SaplingNoteEncryption::new(ovk, note, to, Memo(tv.memo), &mut OsRng);
|
||||
let mut ne = SaplingNoteEncryption::new(Some(ovk), note, to, Memo(tv.memo), OsRng);
|
||||
// Swap in the ephemeral keypair from the test vectors
|
||||
ne.esk = esk;
|
||||
ne.epk = epk;
|
||||
|
|
|
@ -76,7 +76,8 @@ struct SpendDescriptionInfo {
|
|||
}
|
||||
|
||||
pub struct SaplingOutput {
|
||||
ovk: OutgoingViewingKey,
|
||||
/// `None` represents the `ovk = ⊥` case.
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
to: PaymentAddress,
|
||||
note: Note,
|
||||
memo: Memo,
|
||||
|
@ -86,7 +87,7 @@ impl SaplingOutput {
|
|||
pub fn new<R: RngCore + CryptoRng, P: consensus::Parameters>(
|
||||
height: u32,
|
||||
rng: &mut R,
|
||||
ovk: OutgoingViewingKey,
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
to: PaymentAddress,
|
||||
value: Amount,
|
||||
memo: Option<Memo>,
|
||||
|
@ -122,7 +123,7 @@ impl SaplingOutput {
|
|||
ctx: &mut P::SaplingProvingContext,
|
||||
rng: &mut R,
|
||||
) -> OutputDescription {
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
let mut encryptor = SaplingNoteEncryption::new(
|
||||
self.ovk,
|
||||
self.note.clone(),
|
||||
self.to.clone(),
|
||||
|
@ -396,7 +397,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
|
|||
/// Adds a Sapling address to send funds to.
|
||||
pub fn add_sapling_output(
|
||||
&mut self,
|
||||
ovk: OutgoingViewingKey,
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
to: PaymentAddress,
|
||||
value: Amount,
|
||||
memo: Option<Memo>,
|
||||
|
@ -502,7 +503,7 @@ impl<P: consensus::Parameters, R: RngCore + CryptoRng> Builder<P, R> {
|
|||
return Err(Error::NoChangeAddress);
|
||||
};
|
||||
|
||||
self.add_sapling_output(change_address.0, change_address.1, change, None)?;
|
||||
self.add_sapling_output(Some(change_address.0), change_address.1, change, None)?;
|
||||
}
|
||||
|
||||
//
|
||||
|
@ -728,7 +729,7 @@ mod tests {
|
|||
|
||||
let mut builder = Builder::<TestNetwork, OsRng>::new(0);
|
||||
assert_eq!(
|
||||
builder.add_sapling_output(ovk, to, Amount::from_i64(-1).unwrap(), None),
|
||||
builder.add_sapling_output(Some(ovk), to, Amount::from_i64(-1).unwrap(), None),
|
||||
Err(Error::InvalidAmount)
|
||||
);
|
||||
}
|
||||
|
@ -840,7 +841,7 @@ mod tests {
|
|||
}
|
||||
|
||||
let extfvk = ExtendedFullViewingKey::from(&extsk);
|
||||
let ovk = extfvk.fvk.ovk;
|
||||
let ovk = Some(extfvk.fvk.ovk);
|
||||
let to = extfvk.default_address().unwrap().1;
|
||||
|
||||
// Fail if there is only a Sapling output
|
||||
|
|
Loading…
Reference in New Issue