Merge pull request #358 from nuttycom/refactor/component_modules_2
Generalize Sapling note encryption to allow reuse with Orchard notes.
This commit is contained in:
commit
3b02c8b26e
|
@ -4,6 +4,7 @@ description = "TBD"
|
|||
version = "0.0.0"
|
||||
authors = [
|
||||
"Jack Grigg <jack@electriccoin.co>",
|
||||
"Kris Nuttycombe <kris@electriccoin.co>"
|
||||
]
|
||||
homepage = "https://github.com/zcash/librustzcash"
|
||||
repository = "https://github.com/zcash/librustzcash"
|
||||
|
@ -11,3 +12,14 @@ license = "MIT OR Apache-2.0"
|
|||
edition = "2018"
|
||||
|
||||
[dependencies]
|
||||
blake2b_simd = "0.5"
|
||||
byteorder = "1"
|
||||
crypto_api_chachapoly = "0.4"
|
||||
ff = "0.8"
|
||||
group = "0.8"
|
||||
rand_core = "0.5.1"
|
||||
subtle = "2.2.3"
|
||||
|
||||
[dev-dependencies]
|
||||
zcash_primitives = { version = "0.5", path = "../../zcash_primitives" }
|
||||
jubjub = "0.5.1"
|
||||
|
|
|
@ -1,8 +1,500 @@
|
|||
#[cfg(test)]
|
||||
mod tests {
|
||||
#[allow(clippy::eq_op)]
|
||||
#[test]
|
||||
fn it_works() {
|
||||
assert_eq!(2 + 2, 4);
|
||||
//! Implementation of in-band secret distribution abstractions
|
||||
//! for Zcash transactions. The implementations here provide
|
||||
//! functionality that is shared between the Sapling and Orchard
|
||||
//! protocols.
|
||||
|
||||
use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf};
|
||||
use rand_core::RngCore;
|
||||
use std::convert::TryFrom;
|
||||
use subtle::{Choice, ConstantTimeEq};
|
||||
|
||||
pub const COMPACT_NOTE_SIZE: usize = 1 + // version
|
||||
11 + // diversifier
|
||||
8 + // value
|
||||
32; // rseed (or rcm prior to ZIP 212)
|
||||
pub const NOTE_PLAINTEXT_SIZE: usize = COMPACT_NOTE_SIZE + 512;
|
||||
pub const OUT_PLAINTEXT_SIZE: usize = 32 + // pk_d
|
||||
32; // esk
|
||||
pub const AEAD_TAG_SIZE: usize = 16;
|
||||
pub const ENC_CIPHERTEXT_SIZE: usize = NOTE_PLAINTEXT_SIZE + AEAD_TAG_SIZE;
|
||||
pub const OUT_CIPHERTEXT_SIZE: usize = OUT_PLAINTEXT_SIZE + AEAD_TAG_SIZE;
|
||||
|
||||
/// A symmetric key that can be used to recover a single Sapling or Orchard output.
|
||||
pub struct OutgoingCipherKey(pub [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
|
||||
}
|
||||
}
|
||||
|
||||
pub struct EphemeralKeyBytes(pub [u8; 32]);
|
||||
|
||||
impl AsRef<[u8]> for EphemeralKeyBytes {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl From<[u8; 32]> for EphemeralKeyBytes {
|
||||
fn from(value: [u8; 32]) -> EphemeralKeyBytes {
|
||||
EphemeralKeyBytes(value)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConstantTimeEq for EphemeralKeyBytes {
|
||||
fn ct_eq(&self, other: &Self) -> Choice {
|
||||
self.0.ct_eq(&other.0)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NotePlaintextBytes(pub [u8; NOTE_PLAINTEXT_SIZE]);
|
||||
pub struct OutPlaintextBytes(pub [u8; OUT_PLAINTEXT_SIZE]);
|
||||
|
||||
#[derive(Copy, Clone, PartialEq, Eq)]
|
||||
pub enum NoteValidity {
|
||||
Valid,
|
||||
Invalid,
|
||||
}
|
||||
|
||||
pub trait Domain {
|
||||
type EphemeralSecretKey;
|
||||
type EphemeralPublicKey;
|
||||
type SharedSecret;
|
||||
type SymmetricKey: AsRef<[u8]>;
|
||||
type Note;
|
||||
type Recipient;
|
||||
type DiversifiedTransmissionKey;
|
||||
type IncomingViewingKey;
|
||||
type OutgoingViewingKey;
|
||||
type ValueCommitment;
|
||||
type ExtractedCommitment;
|
||||
type ExtractedCommitmentBytes: Eq + TryFrom<Self::ExtractedCommitment>;
|
||||
type Memo;
|
||||
|
||||
fn derive_esk(note: &Self::Note) -> Option<Self::EphemeralSecretKey>;
|
||||
|
||||
fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey;
|
||||
|
||||
fn ka_derive_public(
|
||||
note: &Self::Note,
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
) -> Self::EphemeralPublicKey;
|
||||
|
||||
fn ka_agree_enc(
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
pk_d: &Self::DiversifiedTransmissionKey,
|
||||
) -> Self::SharedSecret;
|
||||
|
||||
fn ka_agree_dec(
|
||||
ivk: &Self::IncomingViewingKey,
|
||||
epk: &Self::EphemeralPublicKey,
|
||||
) -> Self::SharedSecret;
|
||||
|
||||
fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey;
|
||||
|
||||
// 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.
|
||||
fn note_plaintext_bytes(
|
||||
note: &Self::Note,
|
||||
recipient: &Self::Recipient,
|
||||
memo: &Self::Memo,
|
||||
) -> NotePlaintextBytes;
|
||||
|
||||
fn derive_ock(
|
||||
ovk: &Self::OutgoingViewingKey,
|
||||
cv: &Self::ValueCommitment,
|
||||
cmstar: &Self::ExtractedCommitment,
|
||||
ephemeral_key: &EphemeralKeyBytes,
|
||||
) -> OutgoingCipherKey;
|
||||
|
||||
fn outgoing_plaintext_bytes(
|
||||
note: &Self::Note,
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
) -> OutPlaintextBytes;
|
||||
|
||||
fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes;
|
||||
|
||||
fn check_epk_bytes<F: Fn(&Self::EphemeralSecretKey) -> NoteValidity>(
|
||||
note: &Self::Note,
|
||||
check: F,
|
||||
) -> NoteValidity;
|
||||
|
||||
fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment;
|
||||
|
||||
fn parse_note_plaintext_without_memo_ivk(
|
||||
&self,
|
||||
ivk: &Self::IncomingViewingKey,
|
||||
plaintext: &[u8],
|
||||
) -> Option<(Self::Note, Self::Recipient)>;
|
||||
|
||||
fn parse_note_plaintext_without_memo_ovk(
|
||||
&self,
|
||||
pk_d: &Self::DiversifiedTransmissionKey,
|
||||
esk: &Self::EphemeralSecretKey,
|
||||
epk: &Self::EphemeralPublicKey,
|
||||
plaintext: &[u8],
|
||||
) -> Option<(Self::Note, Self::Recipient)>;
|
||||
|
||||
// &self is passed here in anticipation of future changes
|
||||
// to memo handling where the memos may no longer be
|
||||
// part of the note plaintext.
|
||||
fn extract_memo(&self, plaintext: &[u8]) -> Self::Memo;
|
||||
|
||||
fn extract_pk_d(
|
||||
out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE],
|
||||
) -> Option<Self::DiversifiedTransmissionKey>;
|
||||
|
||||
fn extract_esk(out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option<Self::EphemeralSecretKey>;
|
||||
}
|
||||
|
||||
pub trait ShieldedOutput<D: Domain> {
|
||||
fn epk(&self) -> &D::EphemeralPublicKey;
|
||||
fn cmstar_bytes(&self) -> D::ExtractedCommitmentBytes;
|
||||
fn enc_ciphertext(&self) -> &[u8];
|
||||
}
|
||||
|
||||
/// A struct containing context required for encrypting Sapling and Orchard notes.
|
||||
///
|
||||
/// This struct provides a safe API for encrypting Sapling and Orchard notes. In particular, it
|
||||
/// enforces that fresh ephemeral keys are used for every note, and that the ciphertexts are
|
||||
/// consistent with each other.
|
||||
///
|
||||
/// Implements section 4.19 of the
|
||||
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#saplingandorchardinband)
|
||||
/// NB: the example code is only covering the post-Canopy case.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```
|
||||
/// extern crate ff;
|
||||
/// extern crate rand_core;
|
||||
/// extern crate zcash_primitives;
|
||||
///
|
||||
/// use ff::Field;
|
||||
/// use rand_core::OsRng;
|
||||
/// use zcash_primitives::{
|
||||
/// consensus::{TEST_NETWORK, TestNetwork, NetworkUpgrade, Parameters},
|
||||
/// memo::MemoBytes,
|
||||
/// sapling::{
|
||||
/// keys::{OutgoingViewingKey, prf_expand},
|
||||
/// note_encryption::sapling_note_encryption,
|
||||
/// util::generate_random_rseed,
|
||||
/// Diversifier, PaymentAddress, Rseed, ValueCommitment
|
||||
/// },
|
||||
/// };
|
||||
///
|
||||
/// let mut rng = OsRng;
|
||||
///
|
||||
/// let diversifier = Diversifier([0; 11]);
|
||||
/// let pk_d = diversifier.g_d().unwrap();
|
||||
/// let to = PaymentAddress::from_parts(diversifier, pk_d).unwrap();
|
||||
/// let ovk = Some(OutgoingViewingKey([0; 32]));
|
||||
///
|
||||
/// let value = 1000;
|
||||
/// let rcv = jubjub::Fr::random(&mut rng);
|
||||
/// let cv = ValueCommitment {
|
||||
/// value,
|
||||
/// randomness: rcv.clone(),
|
||||
/// };
|
||||
/// let height = TEST_NETWORK.activation_height(NetworkUpgrade::Canopy).unwrap();
|
||||
/// let rseed = generate_random_rseed(&TEST_NETWORK, height, &mut rng);
|
||||
/// let note = to.create_note(value, rseed).unwrap();
|
||||
/// let cmu = note.cmu();
|
||||
///
|
||||
/// let mut enc = sapling_note_encryption::<_, TestNetwork>(ovk, note, to, MemoBytes::empty(), &mut rng);
|
||||
/// let encCiphertext = enc.encrypt_note_plaintext();
|
||||
/// let outCiphertext = enc.encrypt_outgoing_plaintext(&cv.commitment().into(), &cmu, &mut rng);
|
||||
/// ```
|
||||
pub struct NoteEncryption<D: Domain> {
|
||||
epk: D::EphemeralPublicKey,
|
||||
esk: D::EphemeralSecretKey,
|
||||
note: D::Note,
|
||||
to: D::Recipient,
|
||||
memo: D::Memo,
|
||||
/// `None` represents the `ovk = ⊥` case.
|
||||
ovk: Option<D::OutgoingViewingKey>,
|
||||
}
|
||||
|
||||
impl<D: Domain> NoteEncryption<D> {
|
||||
/// Construct a new note encryption context for the specified note,
|
||||
/// recipient, and memo.
|
||||
pub fn new(
|
||||
ovk: Option<D::OutgoingViewingKey>,
|
||||
note: D::Note,
|
||||
to: D::Recipient,
|
||||
memo: D::Memo,
|
||||
) -> Self {
|
||||
let esk = D::derive_esk(¬e).expect("ZIP 212 is active.");
|
||||
Self::new_with_esk(esk, ovk, note, to, memo)
|
||||
}
|
||||
|
||||
/// For use only with Sapling. This method is preserved in order that test code
|
||||
/// be able to generate pre-ZIP-212 ciphertexts so that tests can continue to
|
||||
/// cover pre-ZIP-212 transaction decryption.
|
||||
pub fn new_with_esk(
|
||||
esk: D::EphemeralSecretKey,
|
||||
ovk: Option<D::OutgoingViewingKey>,
|
||||
note: D::Note,
|
||||
to: D::Recipient,
|
||||
memo: D::Memo,
|
||||
) -> Self {
|
||||
NoteEncryption {
|
||||
epk: D::ka_derive_public(¬e, &esk),
|
||||
esk,
|
||||
note,
|
||||
to,
|
||||
memo,
|
||||
ovk,
|
||||
}
|
||||
}
|
||||
|
||||
/// Exposes the ephemeral secret key being used to encrypt this note.
|
||||
pub fn esk(&self) -> &D::EphemeralSecretKey {
|
||||
&self.esk
|
||||
}
|
||||
|
||||
/// Exposes the encoding of the ephemeral public key being used to encrypt this note.
|
||||
pub fn epk(&self) -> &D::EphemeralPublicKey {
|
||||
&self.epk
|
||||
}
|
||||
|
||||
/// Generates `encCiphertext` for this note.
|
||||
pub fn encrypt_note_plaintext(&self) -> [u8; ENC_CIPHERTEXT_SIZE] {
|
||||
let pk_d = D::get_pk_d(&self.note);
|
||||
let shared_secret = D::ka_agree_enc(&self.esk, &pk_d);
|
||||
let key = D::kdf(shared_secret, &D::epk_bytes(&self.epk));
|
||||
let input = D::note_plaintext_bytes(&self.note, &self.to, &self.memo);
|
||||
|
||||
let mut output = [0u8; ENC_CIPHERTEXT_SIZE];
|
||||
assert_eq!(
|
||||
ChachaPolyIetf::aead_cipher()
|
||||
.seal_to(&mut output, &input.0, &[], key.as_ref(), &[0u8; 12])
|
||||
.unwrap(),
|
||||
ENC_CIPHERTEXT_SIZE
|
||||
);
|
||||
|
||||
output
|
||||
}
|
||||
|
||||
/// Generates `outCiphertext` for this note.
|
||||
pub fn encrypt_outgoing_plaintext<R: RngCore>(
|
||||
&self,
|
||||
cv: &D::ValueCommitment,
|
||||
cmstar: &D::ExtractedCommitment,
|
||||
rng: &mut R,
|
||||
) -> [u8; OUT_CIPHERTEXT_SIZE] {
|
||||
let (ock, input) = if let Some(ovk) = &self.ovk {
|
||||
let ock = D::derive_ock(ovk, &cv, &cmstar, &D::epk_bytes(&self.epk));
|
||||
let input = D::outgoing_plaintext_bytes(&self.note, &self.esk);
|
||||
|
||||
(ock, input)
|
||||
} else {
|
||||
// ovk = ⊥
|
||||
let mut ock = OutgoingCipherKey([0; 32]);
|
||||
let mut input = [0u8; OUT_PLAINTEXT_SIZE];
|
||||
|
||||
rng.fill_bytes(&mut ock.0);
|
||||
rng.fill_bytes(&mut input);
|
||||
|
||||
(ock, OutPlaintextBytes(input))
|
||||
};
|
||||
|
||||
let mut output = [0u8; OUT_CIPHERTEXT_SIZE];
|
||||
assert_eq!(
|
||||
ChachaPolyIetf::aead_cipher()
|
||||
.seal_to(&mut output, &input.0, &[], ock.as_ref(), &[0u8; 12])
|
||||
.unwrap(),
|
||||
OUT_CIPHERTEXT_SIZE
|
||||
);
|
||||
|
||||
output
|
||||
}
|
||||
}
|
||||
|
||||
/// Trial decryption of the full note plaintext by the recipient.
|
||||
///
|
||||
/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ivk`.
|
||||
/// If successful, the corresponding Sapling note and memo are returned, along with the
|
||||
/// `PaymentAddress` to which the note was sent.
|
||||
///
|
||||
/// Implements section 4.19.2 of the
|
||||
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptivk)
|
||||
pub fn try_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
|
||||
domain: &D,
|
||||
ivk: &D::IncomingViewingKey,
|
||||
output: &Output,
|
||||
) -> Option<(D::Note, D::Recipient, D::Memo)> {
|
||||
assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE);
|
||||
|
||||
let shared_secret = D::ka_agree_dec(ivk, output.epk());
|
||||
let key = D::kdf(shared_secret, &D::epk_bytes(output.epk()));
|
||||
|
||||
let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
|
||||
assert_eq!(
|
||||
ChachaPolyIetf::aead_cipher()
|
||||
.open_to(
|
||||
&mut plaintext,
|
||||
output.enc_ciphertext(),
|
||||
&[],
|
||||
key.as_ref(),
|
||||
&[0u8; 12]
|
||||
)
|
||||
.ok()?,
|
||||
NOTE_PLAINTEXT_SIZE
|
||||
);
|
||||
|
||||
let (note, to) = parse_note_plaintext_without_memo_ivk(
|
||||
domain,
|
||||
ivk,
|
||||
output.epk(),
|
||||
&output.cmstar_bytes(),
|
||||
&plaintext,
|
||||
)?;
|
||||
let memo = domain.extract_memo(&plaintext);
|
||||
|
||||
Some((note, to, memo))
|
||||
}
|
||||
|
||||
fn parse_note_plaintext_without_memo_ivk<D: Domain>(
|
||||
domain: &D,
|
||||
ivk: &D::IncomingViewingKey,
|
||||
epk: &D::EphemeralPublicKey,
|
||||
cmstar_bytes: &D::ExtractedCommitmentBytes,
|
||||
plaintext: &[u8],
|
||||
) -> Option<(D::Note, D::Recipient)> {
|
||||
let (note, to) = domain.parse_note_plaintext_without_memo_ivk(ivk, &plaintext)?;
|
||||
|
||||
if let NoteValidity::Valid = check_note_validity::<D>(¬e, epk, cmstar_bytes) {
|
||||
Some((note, to))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn check_note_validity<D: Domain>(
|
||||
note: &D::Note,
|
||||
epk: &D::EphemeralPublicKey,
|
||||
cmstar_bytes: &D::ExtractedCommitmentBytes,
|
||||
) -> NoteValidity {
|
||||
if D::ExtractedCommitmentBytes::try_from(D::cmstar(¬e))
|
||||
.map_or(false, |cs| &cs == cmstar_bytes)
|
||||
{
|
||||
let epk_bytes = D::epk_bytes(epk);
|
||||
D::check_epk_bytes(¬e, |derived_esk| {
|
||||
if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk))
|
||||
.ct_eq(&epk_bytes)
|
||||
.into()
|
||||
{
|
||||
NoteValidity::Valid
|
||||
} else {
|
||||
NoteValidity::Invalid
|
||||
}
|
||||
})
|
||||
} else {
|
||||
// Published commitment doesn't match calculated commitment
|
||||
NoteValidity::Invalid
|
||||
}
|
||||
}
|
||||
|
||||
/// Trial decryption of the compact note plaintext by the recipient for light clients.
|
||||
///
|
||||
/// Attempts to decrypt and validate the first 52 bytes of `enc_ciphertext` using the
|
||||
/// given `ivk`. If successful, the corresponding Sapling note is returned, along with the
|
||||
/// `PaymentAddress` to which the note was sent.
|
||||
///
|
||||
/// Implements the procedure specified in [`ZIP 307`].
|
||||
///
|
||||
/// [`ZIP 307`]: https://zips.z.cash/zip-0307
|
||||
pub fn try_compact_note_decryption<D: Domain, Output: ShieldedOutput<D>>(
|
||||
domain: &D,
|
||||
ivk: &D::IncomingViewingKey,
|
||||
output: &Output,
|
||||
) -> Option<(D::Note, D::Recipient)> {
|
||||
assert_eq!(output.enc_ciphertext().len(), COMPACT_NOTE_SIZE);
|
||||
|
||||
let shared_secret = D::ka_agree_dec(&ivk, output.epk());
|
||||
let key = D::kdf(shared_secret, &D::epk_bytes(output.epk()));
|
||||
|
||||
// Start from block 1 to skip over Poly1305 keying output
|
||||
let mut plaintext = [0; COMPACT_NOTE_SIZE];
|
||||
plaintext.copy_from_slice(output.enc_ciphertext());
|
||||
ChaCha20Ietf::xor(key.as_ref(), &[0u8; 12], 1, &mut plaintext);
|
||||
|
||||
parse_note_plaintext_without_memo_ivk(
|
||||
domain,
|
||||
ivk,
|
||||
output.epk(),
|
||||
&output.cmstar_bytes(),
|
||||
&plaintext,
|
||||
)
|
||||
}
|
||||
|
||||
/// Recovery of the full note plaintext by the sender.
|
||||
///
|
||||
/// Attempts to decrypt and validate the given `enc_ciphertext` using the given `ock`.
|
||||
/// If successful, the corresponding Sapling note and memo are returned, along with the
|
||||
/// `PaymentAddress` to which the note was sent.
|
||||
///
|
||||
/// Implements part of section 4.19.3 of the
|
||||
/// [Zcash Protocol Specification](https://zips.z.cash/protocol/nu5.pdf#decryptovk)
|
||||
/// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`].
|
||||
pub fn try_output_recovery_with_ock<D: Domain, Output: ShieldedOutput<D>>(
|
||||
domain: &D,
|
||||
ock: &OutgoingCipherKey,
|
||||
output: &Output,
|
||||
out_ciphertext: &[u8],
|
||||
) -> Option<(D::Note, D::Recipient, D::Memo)> {
|
||||
assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE);
|
||||
assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE);
|
||||
|
||||
let mut op = [0; OUT_CIPHERTEXT_SIZE];
|
||||
assert_eq!(
|
||||
ChachaPolyIetf::aead_cipher()
|
||||
.open_to(&mut op, &out_ciphertext, &[], ock.as_ref(), &[0u8; 12])
|
||||
.ok()?,
|
||||
OUT_PLAINTEXT_SIZE
|
||||
);
|
||||
|
||||
let pk_d = D::extract_pk_d(&op)?;
|
||||
let esk = D::extract_esk(&op)?;
|
||||
|
||||
let shared_secret = D::ka_agree_enc(&esk, &pk_d);
|
||||
// The small-order point check at the point of output parsing rejects
|
||||
// non-canonical encodings, so reencoding here for the KDF should
|
||||
// be okay.
|
||||
let key = D::kdf(shared_secret, &D::epk_bytes(output.epk()));
|
||||
|
||||
let mut plaintext = [0; ENC_CIPHERTEXT_SIZE];
|
||||
assert_eq!(
|
||||
ChachaPolyIetf::aead_cipher()
|
||||
.open_to(
|
||||
&mut plaintext,
|
||||
output.enc_ciphertext(),
|
||||
&[],
|
||||
key.as_ref(),
|
||||
&[0u8; 12]
|
||||
)
|
||||
.ok()?,
|
||||
NOTE_PLAINTEXT_SIZE
|
||||
);
|
||||
|
||||
let (note, to) =
|
||||
domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, output.epk(), &plaintext)?;
|
||||
let memo = domain.extract_memo(&plaintext);
|
||||
|
||||
if let NoteValidity::Valid =
|
||||
check_note_validity::<D>(¬e, output.epk(), &output.cmstar_bytes())
|
||||
{
|
||||
Some((note, to, memo))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ description = "APIs for creating shielded Zcash light clients"
|
|||
version = "0.5.0"
|
||||
authors = [
|
||||
"Jack Grigg <jack@z.cash>",
|
||||
"Kris Nuttycombe <kris@electriccoin.co>"
|
||||
]
|
||||
homepage = "https://github.com/zcash/librustzcash"
|
||||
repository = "https://github.com/zcash/librustzcash"
|
||||
|
@ -21,13 +22,14 @@ group = "0.8"
|
|||
hex = "0.4"
|
||||
jubjub = "0.5.1"
|
||||
nom = "6.1"
|
||||
percent-encoding = "2.1.0"
|
||||
proptest = { version = "0.10.1", optional = true }
|
||||
protobuf = "2.20"
|
||||
rand_core = "0.5.1"
|
||||
subtle = "2.2.3"
|
||||
time = "0.2"
|
||||
zcash_note_encryption = { version = "0.0", path = "../components/zcash_note_encryption" }
|
||||
zcash_primitives = { version = "0.5", path = "../zcash_primitives" }
|
||||
proptest = { version = "0.10.1", optional = true }
|
||||
percent-encoding = "2.1.0"
|
||||
|
||||
[build-dependencies]
|
||||
protobuf-codegen-pure = "2.20"
|
||||
|
|
|
@ -205,7 +205,7 @@ where
|
|||
.unwrap(); //DiversifyHash would have to unexpectedly return the zero point for this to be None
|
||||
|
||||
let note = from
|
||||
.create_note(u64::from(selected.note_value), selected.rseed)
|
||||
.create_note(selected.note_value.into(), selected.rseed)
|
||||
.unwrap();
|
||||
|
||||
let merkle_path = selected.witness.path().expect("the tree is not empty");
|
||||
|
|
|
@ -3,8 +3,10 @@ use std::collections::HashMap;
|
|||
use zcash_primitives::{
|
||||
consensus::{self, BlockHeight},
|
||||
memo::MemoBytes,
|
||||
note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery},
|
||||
sapling::{Note, PaymentAddress},
|
||||
sapling::{
|
||||
note_encryption::{try_sapling_note_decryption, try_sapling_output_recovery},
|
||||
Note, PaymentAddress,
|
||||
},
|
||||
transaction::Transaction,
|
||||
zip32::ExtendedFullViewingKey,
|
||||
};
|
||||
|
@ -47,29 +49,14 @@ pub fn decrypt_transaction<P: consensus::Parameters>(
|
|||
let ovk = extfvk.fvk.ovk;
|
||||
|
||||
for (index, output) in tx.shielded_outputs.iter().enumerate() {
|
||||
let ((note, to, memo), outgoing) = match try_sapling_note_decryption(
|
||||
params,
|
||||
height,
|
||||
&ivk,
|
||||
&output.ephemeral_key,
|
||||
&output.cmu,
|
||||
&output.enc_ciphertext,
|
||||
) {
|
||||
Some(ret) => (ret, false),
|
||||
None => match try_sapling_output_recovery(
|
||||
params,
|
||||
height,
|
||||
&ovk,
|
||||
&output.cv,
|
||||
&output.cmu,
|
||||
&output.ephemeral_key,
|
||||
&output.enc_ciphertext,
|
||||
&output.out_ciphertext,
|
||||
) {
|
||||
Some(ret) => (ret, true),
|
||||
None => continue,
|
||||
},
|
||||
};
|
||||
let ((note, to, memo), outgoing) =
|
||||
match try_sapling_note_decryption(params, height, &ivk, output) {
|
||||
Some(ret) => (ret, false),
|
||||
None => match try_sapling_output_recovery(params, height, &ovk, output) {
|
||||
Some(ret) => (ret, true),
|
||||
None => continue,
|
||||
},
|
||||
};
|
||||
decrypted.push(DecryptedOutput {
|
||||
index,
|
||||
note,
|
||||
|
|
|
@ -2,14 +2,17 @@
|
|||
|
||||
use ff::PrimeField;
|
||||
use group::GroupEncoding;
|
||||
use std::convert::TryInto;
|
||||
use std::convert::{TryFrom, TryInto};
|
||||
|
||||
use zcash_primitives::{
|
||||
block::{BlockHash, BlockHeader},
|
||||
consensus::BlockHeight,
|
||||
sapling::Nullifier,
|
||||
transaction::components::sapling::{CompactOutputDescription, OutputDescription},
|
||||
};
|
||||
|
||||
use zcash_note_encryption::COMPACT_NOTE_SIZE;
|
||||
|
||||
pub mod compact_formats;
|
||||
|
||||
impl compact_formats::CompactBlock {
|
||||
|
@ -98,6 +101,28 @@ impl compact_formats::CompactOutput {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<OutputDescription> for compact_formats::CompactOutput {
|
||||
fn from(out: OutputDescription) -> compact_formats::CompactOutput {
|
||||
let mut result = compact_formats::CompactOutput::new();
|
||||
result.set_cmu(out.cmu.to_repr().to_vec());
|
||||
result.set_epk(out.ephemeral_key.to_bytes().to_vec());
|
||||
result.set_ciphertext(out.enc_ciphertext[..COMPACT_NOTE_SIZE].to_vec());
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<compact_formats::CompactOutput> for CompactOutputDescription {
|
||||
type Error = ();
|
||||
|
||||
fn try_from(value: compact_formats::CompactOutput) -> Result<Self, Self::Error> {
|
||||
Ok(CompactOutputDescription {
|
||||
cmu: value.cmu()?,
|
||||
epk: value.epk()?,
|
||||
enc_ciphertext: value.ciphertext,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl compact_formats::CompactSpend {
|
||||
pub fn nf(&self) -> Result<Nullifier, ()> {
|
||||
Nullifier::from_slice(&self.nf).map_err(|_| ())
|
||||
|
|
|
@ -2,13 +2,17 @@
|
|||
|
||||
use ff::PrimeField;
|
||||
use std::collections::HashSet;
|
||||
use std::convert::TryFrom;
|
||||
use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption};
|
||||
use zcash_note_encryption::ShieldedOutput;
|
||||
use zcash_primitives::{
|
||||
consensus::{self, BlockHeight},
|
||||
merkle_tree::{CommitmentTree, IncrementalWitness},
|
||||
note_encryption::try_sapling_compact_note_decryption,
|
||||
sapling::{Node, Note, Nullifier, PaymentAddress, SaplingIvk},
|
||||
transaction::TxId,
|
||||
sapling::{
|
||||
note_encryption::{try_sapling_compact_note_decryption, SaplingDomain},
|
||||
Node, Note, Nullifier, PaymentAddress, SaplingIvk,
|
||||
},
|
||||
transaction::{components::sapling::CompactOutputDescription, TxId},
|
||||
zip32::ExtendedFullViewingKey,
|
||||
};
|
||||
|
||||
|
@ -36,12 +40,10 @@ fn scan_output<P: consensus::Parameters, K: ScanningKey>(
|
|||
block_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||
new_witnesses: &mut [&mut IncrementalWitness<Node>],
|
||||
) -> Option<WalletShieldedOutput<K::Nf>> {
|
||||
let cmu = output.cmu().ok()?;
|
||||
let epk = output.epk().ok()?;
|
||||
let ct = output.ciphertext;
|
||||
let output = CompactOutputDescription::try_from(output).ok()?;
|
||||
|
||||
// Increment tree and witnesses
|
||||
let node = Node::new(cmu.to_repr());
|
||||
let node = Node::new(output.cmu.to_repr());
|
||||
for witness in existing_witnesses {
|
||||
witness.append(node).unwrap();
|
||||
}
|
||||
|
@ -54,7 +56,7 @@ fn scan_output<P: consensus::Parameters, K: ScanningKey>(
|
|||
tree.append(node).unwrap();
|
||||
|
||||
for (account, vk) in vks.iter() {
|
||||
let (note, to) = match vk.try_decryption(params, height, &epk, &cmu, &ct) {
|
||||
let (note, to) = match vk.try_decryption(params, height, &output) {
|
||||
Some(ret) => ret,
|
||||
None => continue,
|
||||
};
|
||||
|
@ -72,8 +74,8 @@ fn scan_output<P: consensus::Parameters, K: ScanningKey>(
|
|||
|
||||
return Some(WalletShieldedOutput {
|
||||
index,
|
||||
cmu,
|
||||
epk,
|
||||
cmu: output.cmu,
|
||||
epk: output.epk,
|
||||
account: **account,
|
||||
note,
|
||||
to,
|
||||
|
@ -106,13 +108,11 @@ pub trait ScanningKey {
|
|||
|
||||
/// Attempts to decrypt a Sapling note and payment address
|
||||
/// from the specified ciphertext using this scanning key.
|
||||
fn try_decryption<P: consensus::Parameters>(
|
||||
fn try_decryption<P: consensus::Parameters, Output: ShieldedOutput<SaplingDomain<P>>>(
|
||||
&self,
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
epk: &jubjub::ExtendedPoint,
|
||||
cmu: &bls12_381::Scalar,
|
||||
ct: &[u8],
|
||||
output: &Output,
|
||||
) -> Option<(Note, PaymentAddress)>;
|
||||
|
||||
/// Produces the nullifier for the specified note and witness, if possible.
|
||||
|
@ -130,15 +130,13 @@ pub trait ScanningKey {
|
|||
impl ScanningKey for ExtendedFullViewingKey {
|
||||
type Nf = Nullifier;
|
||||
|
||||
fn try_decryption<P: consensus::Parameters>(
|
||||
fn try_decryption<P: consensus::Parameters, Output: ShieldedOutput<SaplingDomain<P>>>(
|
||||
&self,
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
epk: &jubjub::ExtendedPoint,
|
||||
cmu: &bls12_381::Scalar,
|
||||
ct: &[u8],
|
||||
output: &Output,
|
||||
) -> Option<(Note, PaymentAddress)> {
|
||||
try_sapling_compact_note_decryption(params, height, &self.fvk.vk.ivk(), &epk, &cmu, &ct)
|
||||
try_sapling_compact_note_decryption(params, height, &self.fvk.vk.ivk(), output)
|
||||
}
|
||||
|
||||
fn nf(&self, note: &Note, witness: &IncrementalWitness<Node>) -> Self::Nf {
|
||||
|
@ -153,15 +151,13 @@ impl ScanningKey for ExtendedFullViewingKey {
|
|||
impl ScanningKey for SaplingIvk {
|
||||
type Nf = ();
|
||||
|
||||
fn try_decryption<P: consensus::Parameters>(
|
||||
fn try_decryption<P: consensus::Parameters, Output: ShieldedOutput<SaplingDomain<P>>>(
|
||||
&self,
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
epk: &jubjub::ExtendedPoint,
|
||||
cmu: &bls12_381::Scalar,
|
||||
ct: &[u8],
|
||||
output: &Output,
|
||||
) -> Option<(Note, PaymentAddress)> {
|
||||
try_sapling_compact_note_decryption(params, height, self, &epk, &cmu, &ct)
|
||||
try_sapling_compact_note_decryption(params, height, self, output)
|
||||
}
|
||||
|
||||
fn nf(&self, _note: &Note, _witness: &IncrementalWitness<Node>) {}
|
||||
|
@ -305,8 +301,10 @@ mod tests {
|
|||
constants::SPENDING_KEY_GENERATOR,
|
||||
memo::MemoBytes,
|
||||
merkle_tree::CommitmentTree,
|
||||
note_encryption::SaplingNoteEncryption,
|
||||
sapling::{util::generate_random_rseed, Note, Nullifier, SaplingIvk},
|
||||
sapling::{
|
||||
note_encryption::sapling_note_encryption, util::generate_random_rseed, Note, Nullifier,
|
||||
SaplingIvk,
|
||||
},
|
||||
transaction::components::Amount,
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
};
|
||||
|
@ -368,7 +366,7 @@ mod tests {
|
|||
value: value.into(),
|
||||
rseed,
|
||||
};
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
let encryptor = sapling_note_encryption::<_, Network>(
|
||||
Some(extfvk.fvk.ovk),
|
||||
note.clone(),
|
||||
to,
|
||||
|
@ -395,7 +393,7 @@ mod tests {
|
|||
let mut cout = CompactOutput::new();
|
||||
cout.set_cmu(cmu);
|
||||
cout.set_epk(epk);
|
||||
cout.set_ciphertext(enc_ciphertext[..52].to_vec());
|
||||
cout.set_ciphertext(enc_ciphertext.as_ref()[..52].to_vec());
|
||||
let mut ctx = CompactTx::new();
|
||||
let mut txid = vec![0; 32];
|
||||
rng.fill_bytes(&mut txid);
|
||||
|
|
|
@ -4,6 +4,7 @@ description = "An SQLite-based Zcash light client"
|
|||
version = "0.3.0"
|
||||
authors = [
|
||||
"Jack Grigg <jack@z.cash>",
|
||||
"Kris Nuttycombe <kris@electriccoin.co>"
|
||||
]
|
||||
homepage = "https://github.com/zcash/librustzcash"
|
||||
repository = "https://github.com/zcash/librustzcash"
|
||||
|
|
|
@ -563,8 +563,10 @@ mod tests {
|
|||
block::BlockHash,
|
||||
consensus::{BlockHeight, Network, NetworkUpgrade, Parameters},
|
||||
memo::MemoBytes,
|
||||
note_encryption::SaplingNoteEncryption,
|
||||
sapling::{util::generate_random_rseed, Note, Nullifier, PaymentAddress},
|
||||
sapling::{
|
||||
note_encryption::sapling_note_encryption, util::generate_random_rseed, Note, Nullifier,
|
||||
PaymentAddress,
|
||||
},
|
||||
transaction::components::Amount,
|
||||
zip32::ExtendedFullViewingKey,
|
||||
};
|
||||
|
@ -614,7 +616,7 @@ mod tests {
|
|||
value: value.into(),
|
||||
rseed,
|
||||
};
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
let encryptor = sapling_note_encryption::<_, Network>(
|
||||
Some(extfvk.fvk.ovk),
|
||||
note.clone(),
|
||||
to,
|
||||
|
@ -629,7 +631,7 @@ mod tests {
|
|||
let mut cout = CompactOutput::new();
|
||||
cout.set_cmu(cmu);
|
||||
cout.set_epk(epk);
|
||||
cout.set_ciphertext(enc_ciphertext[..52].to_vec());
|
||||
cout.set_ciphertext(enc_ciphertext.as_ref()[..52].to_vec());
|
||||
let mut ctx = CompactTx::new();
|
||||
let mut txid = vec![0; 32];
|
||||
rng.fill_bytes(&mut txid);
|
||||
|
@ -674,7 +676,7 @@ mod tests {
|
|||
value: value.into(),
|
||||
rseed,
|
||||
};
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
let encryptor = sapling_note_encryption::<_, Network>(
|
||||
Some(extfvk.fvk.ovk),
|
||||
note.clone(),
|
||||
to,
|
||||
|
@ -688,7 +690,7 @@ mod tests {
|
|||
let mut cout = CompactOutput::new();
|
||||
cout.set_cmu(cmu);
|
||||
cout.set_epk(epk);
|
||||
cout.set_ciphertext(enc_ciphertext[..52].to_vec());
|
||||
cout.set_ciphertext(enc_ciphertext.as_ref()[..52].to_vec());
|
||||
cout
|
||||
});
|
||||
|
||||
|
@ -702,7 +704,7 @@ mod tests {
|
|||
value: (in_value - value).into(),
|
||||
rseed,
|
||||
};
|
||||
let encryptor = SaplingNoteEncryption::new(
|
||||
let encryptor = sapling_note_encryption::<_, Network>(
|
||||
Some(extfvk.fvk.ovk),
|
||||
note.clone(),
|
||||
change_addr,
|
||||
|
@ -716,7 +718,7 @@ mod tests {
|
|||
let mut cout = CompactOutput::new();
|
||||
cout.set_cmu(cmu);
|
||||
cout.set_epk(epk);
|
||||
cout.set_ciphertext(enc_ciphertext[..52].to_vec());
|
||||
cout.set_ciphertext(enc_ciphertext.as_ref()[..52].to_vec());
|
||||
cout
|
||||
});
|
||||
|
||||
|
|
|
@ -155,8 +155,7 @@ mod tests {
|
|||
block::BlockHash,
|
||||
consensus::BlockHeight,
|
||||
legacy::TransparentAddress,
|
||||
note_encryption::try_sapling_output_recovery,
|
||||
sapling::prover::TxProver,
|
||||
sapling::{note_encryption::try_sapling_output_recovery, prover::TxProver},
|
||||
transaction::{components::Amount, Transaction},
|
||||
zip32::{ExtendedFullViewingKey, ExtendedSpendingKey},
|
||||
};
|
||||
|
@ -635,11 +634,7 @@ mod tests {
|
|||
&network,
|
||||
sapling_activation_height(),
|
||||
&extfvk.fvk.ovk,
|
||||
&output.cv,
|
||||
&output.cmu,
|
||||
&output.ephemeral_key,
|
||||
&output.enc_ciphertext,
|
||||
&output.out_ciphertext,
|
||||
output,
|
||||
)
|
||||
};
|
||||
|
||||
|
|
|
@ -4,6 +4,7 @@ description = "Rust implementations of the Zcash primitives"
|
|||
version = "0.5.0"
|
||||
authors = [
|
||||
"Jack Grigg <jack@z.cash>",
|
||||
"Kris Nuttycombe <kris@electriccoin.co>"
|
||||
]
|
||||
homepage = "https://github.com/zcash/librustzcash"
|
||||
repository = "https://github.com/zcash/librustzcash"
|
||||
|
@ -37,6 +38,7 @@ ripemd160 = { version = "0.9", optional = true }
|
|||
secp256k1 = { version = "0.20", optional = true }
|
||||
sha2 = "0.9"
|
||||
subtle = "2.2.3"
|
||||
zcash_note_encryption = { version = "0.0", path = "../components/zcash_note_encryption" }
|
||||
|
||||
# Temporary workaround for https://github.com/myrrlyn/funty/issues/3
|
||||
funty = "=1.1.0"
|
||||
|
|
|
@ -2,11 +2,12 @@ use criterion::{criterion_group, criterion_main, Criterion};
|
|||
use ff::Field;
|
||||
use rand_core::OsRng;
|
||||
use zcash_primitives::{
|
||||
consensus::{NetworkUpgrade::Canopy, Parameters, TEST_NETWORK},
|
||||
consensus::{NetworkUpgrade::Canopy, Parameters, TestNetwork, TEST_NETWORK},
|
||||
memo::MemoBytes,
|
||||
note_encryption::{try_sapling_note_decryption, SaplingNoteEncryption},
|
||||
sapling::{
|
||||
util::generate_random_rseed, Diversifier, PaymentAddress, SaplingIvk, ValueCommitment,
|
||||
note_encryption::{sapling_note_encryption, try_sapling_note_decryption},
|
||||
util::generate_random_rseed,
|
||||
Diversifier, PaymentAddress, SaplingIvk, ValueCommitment,
|
||||
},
|
||||
transaction::components::{OutputDescription, GROTH_PROOF_SIZE},
|
||||
};
|
||||
|
@ -37,10 +38,11 @@ fn bench_note_decryption(c: &mut Criterion) {
|
|||
let note = pa.create_note(value, rseed).unwrap();
|
||||
let cmu = note.cmu();
|
||||
|
||||
let mut ne = SaplingNoteEncryption::new(None, note, pa, MemoBytes::empty(), &mut rng);
|
||||
let ephemeral_key = ne.epk().clone().into();
|
||||
let ne =
|
||||
sapling_note_encryption::<_, TestNetwork>(None, note, pa, MemoBytes::empty(), &mut rng);
|
||||
let ephemeral_key = *ne.epk();
|
||||
let enc_ciphertext = ne.encrypt_note_plaintext();
|
||||
let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu);
|
||||
let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng);
|
||||
|
||||
OutputDescription {
|
||||
cv,
|
||||
|
@ -55,30 +57,11 @@ fn bench_note_decryption(c: &mut Criterion) {
|
|||
let mut group = c.benchmark_group("Sapling note decryption");
|
||||
|
||||
group.bench_function("valid", |b| {
|
||||
b.iter(|| {
|
||||
try_sapling_note_decryption(
|
||||
&TEST_NETWORK,
|
||||
height,
|
||||
&valid_ivk,
|
||||
&output.ephemeral_key,
|
||||
&output.cmu,
|
||||
&output.enc_ciphertext,
|
||||
)
|
||||
.unwrap()
|
||||
})
|
||||
b.iter(|| try_sapling_note_decryption(&TEST_NETWORK, height, &valid_ivk, &output).unwrap())
|
||||
});
|
||||
|
||||
group.bench_function("invalid", |b| {
|
||||
b.iter(|| {
|
||||
try_sapling_note_decryption(
|
||||
&TEST_NETWORK,
|
||||
height,
|
||||
&invalid_ivk,
|
||||
&output.ephemeral_key,
|
||||
&output.cmu,
|
||||
&output.enc_ciphertext,
|
||||
)
|
||||
})
|
||||
b.iter(|| try_sapling_note_decryption(&TEST_NETWORK, height, &invalid_ivk, &output))
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
@ -15,7 +15,6 @@ pub mod constants;
|
|||
pub mod legacy;
|
||||
pub mod memo;
|
||||
pub mod merkle_tree;
|
||||
pub mod note_encryption;
|
||||
pub mod sapling;
|
||||
pub mod serialize;
|
||||
pub mod transaction;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -2,6 +2,7 @@
|
|||
|
||||
pub mod group_hash;
|
||||
pub mod keys;
|
||||
pub mod note_encryption;
|
||||
pub mod pedersen_hash;
|
||||
pub mod prover;
|
||||
pub mod redjubjub;
|
||||
|
@ -325,10 +326,10 @@ impl PaymentAddress {
|
|||
self.diversifier.g_d()
|
||||
}
|
||||
|
||||
pub fn create_note(&self, value: u64, randomness: Rseed) -> Option<Note> {
|
||||
pub fn create_note(&self, value: u64, rseed: Rseed) -> Option<Note> {
|
||||
self.g_d().map(|g_d| Note {
|
||||
value,
|
||||
rseed: randomness,
|
||||
rseed,
|
||||
g_d,
|
||||
pk_d: self.pk_d,
|
||||
})
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -15,10 +15,10 @@ use crate::{
|
|||
legacy::TransparentAddress,
|
||||
memo::MemoBytes,
|
||||
merkle_tree::MerklePath,
|
||||
note_encryption::SaplingNoteEncryption,
|
||||
sapling::{
|
||||
keys::OutgoingViewingKey, prover::TxProver, redjubjub::PrivateKey, spend_sig_internal,
|
||||
util::generate_random_rseed_internal, Diversifier, Node, Note, PaymentAddress,
|
||||
keys::OutgoingViewingKey, note_encryption::sapling_note_encryption, prover::TxProver,
|
||||
redjubjub::PrivateKey, spend_sig_internal, util::generate_random_rseed_internal,
|
||||
Diversifier, Node, Note, PaymentAddress,
|
||||
},
|
||||
transaction::{
|
||||
components::{
|
||||
|
@ -93,16 +93,17 @@ struct SpendDescriptionInfo {
|
|||
merkle_path: MerklePath<Node>,
|
||||
}
|
||||
|
||||
pub struct SaplingOutput {
|
||||
pub struct SaplingOutput<P: consensus::Parameters> {
|
||||
/// `None` represents the `ovk = ⊥` case.
|
||||
ovk: Option<OutgoingViewingKey>,
|
||||
to: PaymentAddress,
|
||||
note: Note,
|
||||
memo: MemoBytes,
|
||||
_params: PhantomData<P>,
|
||||
}
|
||||
|
||||
impl SaplingOutput {
|
||||
pub fn new<R: RngCore + CryptoRng, P: consensus::Parameters>(
|
||||
impl<P: consensus::Parameters> SaplingOutput<P> {
|
||||
pub fn new<R: RngCore + CryptoRng>(
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
rng: &mut R,
|
||||
|
@ -114,7 +115,7 @@ impl SaplingOutput {
|
|||
Self::new_internal(params, height, rng, ovk, to, value, memo)
|
||||
}
|
||||
|
||||
fn new_internal<R: RngCore, P: consensus::Parameters>(
|
||||
fn new_internal<R: RngCore>(
|
||||
params: &P,
|
||||
height: BlockHeight,
|
||||
rng: &mut R,
|
||||
|
@ -142,25 +143,26 @@ impl SaplingOutput {
|
|||
to,
|
||||
note,
|
||||
memo: memo.unwrap_or_else(MemoBytes::empty),
|
||||
_params: PhantomData::default(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn build<P: TxProver, R: RngCore + CryptoRng>(
|
||||
pub fn build<Pr: TxProver, R: RngCore + CryptoRng>(
|
||||
self,
|
||||
prover: &P,
|
||||
ctx: &mut P::SaplingProvingContext,
|
||||
prover: &Pr,
|
||||
ctx: &mut Pr::SaplingProvingContext,
|
||||
rng: &mut R,
|
||||
) -> OutputDescription {
|
||||
self.build_internal(prover, ctx, rng)
|
||||
}
|
||||
|
||||
fn build_internal<P: TxProver, R: RngCore>(
|
||||
fn build_internal<Pr: TxProver, R: RngCore>(
|
||||
self,
|
||||
prover: &P,
|
||||
ctx: &mut P::SaplingProvingContext,
|
||||
prover: &Pr,
|
||||
ctx: &mut Pr::SaplingProvingContext,
|
||||
rng: &mut R,
|
||||
) -> OutputDescription {
|
||||
let mut encryptor = SaplingNoteEncryption::new_internal(
|
||||
let encryptor = sapling_note_encryption::<R, P>(
|
||||
self.ovk,
|
||||
self.note.clone(),
|
||||
self.to.clone(),
|
||||
|
@ -179,9 +181,8 @@ impl SaplingOutput {
|
|||
let cmu = self.note.cmu();
|
||||
|
||||
let enc_ciphertext = encryptor.encrypt_note_plaintext();
|
||||
let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu);
|
||||
|
||||
let ephemeral_key = encryptor.epk().clone().into();
|
||||
let out_ciphertext = encryptor.encrypt_outgoing_plaintext(&cv, &cmu, rng);
|
||||
let ephemeral_key = *encryptor.epk();
|
||||
|
||||
OutputDescription {
|
||||
cv,
|
||||
|
@ -371,7 +372,7 @@ pub struct Builder<'a, P: consensus::Parameters, R: RngCore> {
|
|||
fee: Amount,
|
||||
anchor: Option<bls12_381::Scalar>,
|
||||
spends: Vec<SpendDescriptionInfo>,
|
||||
outputs: Vec<SaplingOutput>,
|
||||
outputs: Vec<SaplingOutput<P>>,
|
||||
transparent_inputs: TransparentInputs,
|
||||
#[cfg(feature = "zfuture")]
|
||||
tze_inputs: TzeInputs<'a, TransactionData>,
|
||||
|
|
|
@ -3,11 +3,19 @@ use group::GroupEncoding;
|
|||
|
||||
use std::io::{self, Read, Write};
|
||||
|
||||
use crate::sapling::{
|
||||
redjubjub::{PublicKey, Signature},
|
||||
Nullifier,
|
||||
use zcash_note_encryption::ShieldedOutput;
|
||||
|
||||
use crate::{
|
||||
consensus,
|
||||
sapling::{
|
||||
note_encryption::SaplingDomain,
|
||||
redjubjub::{PublicKey, Signature},
|
||||
Nullifier,
|
||||
},
|
||||
};
|
||||
|
||||
use zcash_note_encryption::COMPACT_NOTE_SIZE;
|
||||
|
||||
use super::GROTH_PROOF_SIZE;
|
||||
|
||||
#[derive(Clone)]
|
||||
|
@ -110,6 +118,20 @@ pub struct OutputDescription {
|
|||
pub zkproof: [u8; GROTH_PROOF_SIZE],
|
||||
}
|
||||
|
||||
impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>> for OutputDescription {
|
||||
fn epk(&self) -> &jubjub::ExtendedPoint {
|
||||
&self.ephemeral_key
|
||||
}
|
||||
|
||||
fn cmstar_bytes(&self) -> [u8; 32] {
|
||||
self.cmu.to_repr()
|
||||
}
|
||||
|
||||
fn enc_ciphertext(&self) -> &[u8] {
|
||||
&self.enc_ciphertext
|
||||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for OutputDescription {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> {
|
||||
write!(
|
||||
|
@ -191,3 +213,33 @@ impl OutputDescription {
|
|||
writer.write_all(&self.zkproof)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct CompactOutputDescription {
|
||||
pub epk: jubjub::ExtendedPoint,
|
||||
pub cmu: bls12_381::Scalar,
|
||||
pub enc_ciphertext: Vec<u8>,
|
||||
}
|
||||
|
||||
impl From<OutputDescription> for CompactOutputDescription {
|
||||
fn from(out: OutputDescription) -> CompactOutputDescription {
|
||||
CompactOutputDescription {
|
||||
epk: out.ephemeral_key,
|
||||
cmu: out.cmu,
|
||||
enc_ciphertext: out.enc_ciphertext[..COMPACT_NOTE_SIZE].to_vec(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: consensus::Parameters> ShieldedOutput<SaplingDomain<P>> for CompactOutputDescription {
|
||||
fn epk(&self) -> &jubjub::ExtendedPoint {
|
||||
&self.epk
|
||||
}
|
||||
|
||||
fn cmstar_bytes(&self) -> [u8; 32] {
|
||||
self.cmu.to_repr()
|
||||
}
|
||||
|
||||
fn enc_ciphertext(&self) -> &[u8] {
|
||||
&self.enc_ciphertext
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue