diff --git a/components/zcash_note_encryption/src/lib.rs b/components/zcash_note_encryption/src/lib.rs index 887c1f155..36ccc765e 100644 --- a/components/zcash_note_encryption/src/lib.rs +++ b/components/zcash_note_encryption/src/lib.rs @@ -5,7 +5,7 @@ use crypto_api_chachapoly::{ChaCha20Ietf, ChachaPolyIetf}; use rand_core::RngCore; -use subtle::{ConstantTimeEq, Choice}; +use subtle::{Choice, ConstantTimeEq}; pub const COMPACT_NOTE_SIZE: usize = 1 + // version 11 + // diversifier @@ -148,10 +148,10 @@ pub trait Domain { fn extract_esk(out_plaintext: &[u8; OUT_CIPHERTEXT_SIZE]) -> Option; } -pub trait ShieldedOutput<'a, D: Domain> { - fn ivk(&'a self) -> &'a D::IncomingViewingKey; - fn epk(&'a self) -> &'a D::EphemeralPublicKey; - fn cmstar(&'a self) -> &'a D::ExtractedCommitment; +pub trait ShieldedOutput { + fn epk(&self) -> &D::EphemeralPublicKey; + fn cmstar(&self) -> D::ExtractedCommitment; + fn enc_ciphertext(&self) -> &[u8]; } /// A struct containing context required for encrypting Sapling and Orchard notes. @@ -315,25 +315,22 @@ impl NoteEncryption { /// `PaymentAddress` to which the note was sent. /// /// Implements section 4.17.2 of the Zcash Protocol Specification. -pub fn try_note_decryption( +pub fn try_note_decryption>( domain: &D, - //output: &ShieldedOutput, ivk: &D::IncomingViewingKey, - epk: &D::EphemeralPublicKey, - cmstar: &D::ExtractedCommitment, - enc_ciphertext: &[u8], + output: &Output, ) -> Option<(D::Note, D::Recipient, D::Memo)> { - assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); + assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE); - let shared_secret = D::ka_agree_dec(ivk, epk); - let key = D::kdf(shared_secret, epk); + let shared_secret = D::ka_agree_dec(ivk, output.epk()); + let key = D::kdf(shared_secret, output.epk()); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; assert_eq!( ChachaPolyIetf::aead_cipher() .open_to( &mut plaintext, - &enc_ciphertext, + output.enc_ciphertext(), &[], key.as_ref(), &[0u8; 12] @@ -342,7 +339,13 @@ pub fn try_note_decryption( NOTE_PLAINTEXT_SIZE ); - let (note, to) = parse_note_plaintext_without_memo_ivk(domain, ivk, epk, cmstar, &plaintext)?; + let (note, to) = parse_note_plaintext_without_memo_ivk( + domain, + ivk, + output.epk(), + &output.cmstar(), + &plaintext, + )?; let memo = domain.extract_memo(&plaintext); Some((note, to, memo)) @@ -375,7 +378,10 @@ fn check_note_validity( } else { 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() { + if D::epk_bytes(&D::ka_derive_public(¬e, &derived_esk)) + .ct_eq(&epk_bytes) + .into() + { NoteValidity::Valid } else { NoteValidity::Invalid @@ -393,24 +399,22 @@ fn check_note_validity( /// Implements the procedure specified in [`ZIP 307`]. /// /// [`ZIP 307`]: https://zips.z.cash/zip-0307 -pub fn try_compact_note_decryption( +pub fn try_compact_note_decryption>( domain: &D, ivk: &D::IncomingViewingKey, - epk: &D::EphemeralPublicKey, - cmstar: &D::ExtractedCommitment, - enc_ciphertext: &[u8], + output: &Output, ) -> Option<(D::Note, D::Recipient)> { - assert_eq!(enc_ciphertext.len(), COMPACT_NOTE_SIZE); + assert_eq!(output.enc_ciphertext().len(), COMPACT_NOTE_SIZE); - let shared_secret = D::ka_agree_dec(&ivk, epk); - let key = D::kdf(shared_secret, &epk); + let shared_secret = D::ka_agree_dec(&ivk, output.epk()); + let key = D::kdf(shared_secret, output.epk()); // Start from block 1 to skip over Poly1305 keying output let mut plaintext = [0; COMPACT_NOTE_SIZE]; - plaintext.copy_from_slice(&enc_ciphertext); + 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, epk, cmstar, &plaintext) + parse_note_plaintext_without_memo_ivk(domain, ivk, output.epk(), &output.cmstar(), &plaintext) } /// Recovery of the full note plaintext by the sender. @@ -421,15 +425,13 @@ pub fn try_compact_note_decryption( /// /// Implements part of section 4.17.3 of the Zcash Protocol Specification. /// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. -pub fn try_output_recovery_with_ock( +pub fn try_output_recovery_with_ock>( domain: &D, ock: &OutgoingCipherKey, - cmstar: &D::ExtractedCommitment, - epk: &D::EphemeralPublicKey, - enc_ciphertext: &[u8], + output: &Output, out_ciphertext: &[u8], ) -> Option<(D::Note, D::Recipient, D::Memo)> { - assert_eq!(enc_ciphertext.len(), ENC_CIPHERTEXT_SIZE); + assert_eq!(output.enc_ciphertext().len(), ENC_CIPHERTEXT_SIZE); assert_eq!(out_ciphertext.len(), OUT_CIPHERTEXT_SIZE); let mut op = [0; OUT_CIPHERTEXT_SIZE]; @@ -444,14 +446,14 @@ pub fn try_output_recovery_with_ock( let esk = D::extract_esk(&op)?; let shared_secret = D::ka_agree_enc(&esk, &pk_d); - let key = D::kdf(shared_secret, &epk); + let key = D::kdf(shared_secret, output.epk()); let mut plaintext = [0; ENC_CIPHERTEXT_SIZE]; assert_eq!( ChachaPolyIetf::aead_cipher() .open_to( &mut plaintext, - &enc_ciphertext, + output.enc_ciphertext(), &[], key.as_ref(), &[0u8; 12] @@ -460,10 +462,11 @@ pub fn try_output_recovery_with_ock( NOTE_PLAINTEXT_SIZE ); - let (note, to) = domain.parse_note_plaintext_without_memo_ovk(&pk_d, &esk, &epk, &plaintext)?; + 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::(¬e, epk, cmstar) { + if let NoteValidity::Valid = check_note_validity::(¬e, output.epk(), &output.cmstar()) { Some((note, to, memo)) } else { None diff --git a/zcash_client_backend/Cargo.toml b/zcash_client_backend/Cargo.toml index 527311658..da0518aa7 100644 --- a/zcash_client_backend/Cargo.toml +++ b/zcash_client_backend/Cargo.toml @@ -22,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" diff --git a/zcash_client_backend/src/decrypt.rs b/zcash_client_backend/src/decrypt.rs index 3e76a7c5f..6a82f1c1f 100644 --- a/zcash_client_backend/src/decrypt.rs +++ b/zcash_client_backend/src/decrypt.rs @@ -49,29 +49,21 @@ pub fn decrypt_transaction( 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.cv, + output, + &output.out_ciphertext, + ) { + Some(ret) => (ret, true), + None => continue, + }, + }; decrypted.push(DecryptedOutput { index, note, diff --git a/zcash_client_backend/src/proto.rs b/zcash_client_backend/src/proto.rs index 8247abdc9..9493c4c26 100644 --- a/zcash_client_backend/src/proto.rs +++ b/zcash_client_backend/src/proto.rs @@ -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 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 for CompactOutputDescription { + type Error = (); + + fn try_from(value: compact_formats::CompactOutput) -> Result { + Ok(CompactOutputDescription { + cmu: value.cmu()?, + epk: value.epk()?, + enc_ciphertext: value.ciphertext, + }) + } +} + impl compact_formats::CompactSpend { pub fn nf(&self) -> Result { Nullifier::from_slice(&self.nf).map_err(|_| ()) diff --git a/zcash_client_backend/src/welding_rig.rs b/zcash_client_backend/src/welding_rig.rs index 0eb076454..bff4fd659 100644 --- a/zcash_client_backend/src/welding_rig.rs +++ b/zcash_client_backend/src/welding_rig.rs @@ -2,15 +2,16 @@ use ff::PrimeField; use std::collections::HashSet; +use std::convert::TryFrom; use subtle::{ConditionallySelectable, ConstantTimeEq, CtOption}; use zcash_primitives::{ consensus::{self, BlockHeight}, merkle_tree::{CommitmentTree, IncrementalWitness}, sapling::{ - note_encryption::try_sapling_compact_note_decryption, Node, Note, Nullifier, - PaymentAddress, SaplingIvk, + note_encryption::{try_sapling_compact_note_decryption, SaplingShieldedOutput}, + Node, Note, Nullifier, PaymentAddress, SaplingIvk, }, - transaction::TxId, + transaction::{components::sapling::CompactOutputDescription, TxId}, zip32::ExtendedFullViewingKey, }; @@ -38,12 +39,10 @@ fn scan_output( block_witnesses: &mut [&mut IncrementalWitness], new_witnesses: &mut [&mut IncrementalWitness], ) -> Option> { - 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(); } @@ -56,7 +55,7 @@ fn scan_output( 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, }; @@ -74,8 +73,8 @@ fn scan_output( return Some(WalletShieldedOutput { index, - cmu, - epk, + cmu: output.cmu, + epk: output.epk, account: **account, note, to, @@ -108,13 +107,11 @@ pub trait ScanningKey { /// Attempts to decrypt a Sapling note and payment address /// from the specified ciphertext using this scanning key. - fn try_decryption( + fn try_decryption>( &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. @@ -132,15 +129,13 @@ pub trait ScanningKey { impl ScanningKey for ExtendedFullViewingKey { type Nf = Nullifier; - fn try_decryption( + fn try_decryption>( &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) -> Self::Nf { @@ -155,15 +150,13 @@ impl ScanningKey for ExtendedFullViewingKey { impl ScanningKey for SaplingIvk { type Nf = (); - fn try_decryption( + fn try_decryption>( &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) {} diff --git a/zcash_client_sqlite/src/wallet/transact.rs b/zcash_client_sqlite/src/wallet/transact.rs index e4b52e31e..768558a90 100644 --- a/zcash_client_sqlite/src/wallet/transact.rs +++ b/zcash_client_sqlite/src/wallet/transact.rs @@ -635,9 +635,7 @@ mod tests { sapling_activation_height(), &extfvk.fvk.ovk, &output.cv, - &output.cmu, - &output.ephemeral_key, - &output.enc_ciphertext, + output, &output.out_ciphertext, ) }; diff --git a/zcash_primitives/benches/note_decryption.rs b/zcash_primitives/benches/note_decryption.rs index f9e142ed7..6200d5202 100644 --- a/zcash_primitives/benches/note_decryption.rs +++ b/zcash_primitives/benches/note_decryption.rs @@ -57,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)) }); } diff --git a/zcash_primitives/src/sapling/note_encryption.rs b/zcash_primitives/src/sapling/note_encryption.rs index dd1e2e4dc..fe14133ec 100644 --- a/zcash_primitives/src/sapling/note_encryption.rs +++ b/zcash_primitives/src/sapling/note_encryption.rs @@ -9,7 +9,7 @@ use std::convert::TryInto; use zcash_note_encryption::{ try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ock, Domain, EphemeralKeyBytes, NoteEncryption, NotePlaintextBytes, NoteValidity, OutPlaintextBytes, - OutgoingCipherKey, COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, + OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, }; @@ -290,6 +290,12 @@ impl Domain for SaplingDomain

{ } } +pub trait SaplingShieldedOutput: + ShieldedOutput> +{ + fn cmu(&self) -> &bls12_381::Scalar; +} + /// Creates a new encryption context for the given note. /// /// Setting `ovk` to `None` represents the `ovk = ⊥` case, where the note cannot be @@ -331,35 +337,34 @@ pub fn plaintext_version_is_valid( } } -pub fn try_sapling_note_decryption( +pub fn try_sapling_note_decryption>( params: &P, height: BlockHeight, ivk: &SaplingIvk, - epk: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - enc_ciphertext: &[u8], + output: &Output, ) -> Option<(Note, PaymentAddress, MemoBytes)> { let domain = SaplingDomain { params: params.clone(), height, }; - try_note_decryption(&domain, ivk, epk, &cmu.to_bytes(), enc_ciphertext) + try_note_decryption(&domain, ivk, output) } -pub fn try_sapling_compact_note_decryption( +pub fn try_sapling_compact_note_decryption< + P: consensus::Parameters, + Output: SaplingShieldedOutput

, +>( params: &P, height: BlockHeight, ivk: &SaplingIvk, - epk: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - enc_ciphertext: &[u8], + output: &Output, ) -> Option<(Note, PaymentAddress)> { let domain = SaplingDomain { params: params.clone(), height, }; - try_compact_note_decryption(&domain, ivk, epk, &cmu.to_bytes(), enc_ciphertext) + try_compact_note_decryption(&domain, ivk, output) } /// Recovery of the full note plaintext by the sender. @@ -370,13 +375,14 @@ pub fn try_sapling_compact_note_decryption( /// /// Implements part of section 4.17.3 of the Zcash Protocol Specification. /// For decryption using a Full Viewing Key see [`try_sapling_output_recovery`]. -pub fn try_sapling_output_recovery_with_ock( +pub fn try_sapling_output_recovery_with_ock< + P: consensus::Parameters, + Output: SaplingShieldedOutput

, +>( params: &P, height: BlockHeight, ock: &OutgoingCipherKey, - cmu: &bls12_381::Scalar, - epk: &jubjub::ExtendedPoint, - enc_ciphertext: &[u8], + output: &Output, out_ciphertext: &[u8], ) -> Option<(Note, PaymentAddress, MemoBytes)> { let domain = SaplingDomain { @@ -384,14 +390,7 @@ pub fn try_sapling_output_recovery_with_ock( height, }; - try_output_recovery_with_ock( - &domain, - ock, - &cmu.to_bytes(), - epk, - enc_ciphertext, - out_ciphertext, - ) + try_output_recovery_with_ock(&domain, ock, output, out_ciphertext) } /// Recovery of the full note plaintext by the sender. @@ -402,23 +401,19 @@ pub fn try_sapling_output_recovery_with_ock( /// /// Implements section 4.17.3 of the Zcash Protocol Specification. #[allow(clippy::too_many_arguments)] -pub fn try_sapling_output_recovery( +pub fn try_sapling_output_recovery>( params: &P, height: BlockHeight, ovk: &OutgoingViewingKey, cv: &jubjub::ExtendedPoint, - cmu: &bls12_381::Scalar, - epk: &jubjub::ExtendedPoint, - enc_ciphertext: &[u8], + output: &Output, out_ciphertext: &[u8], ) -> Option<(Note, PaymentAddress, MemoBytes)> { - try_sapling_output_recovery_with_ock::

( + try_sapling_output_recovery_with_ock( params, height, - &prf_ock(&ovk, &cv, &cmu, &epk), - cmu, - epk, - enc_ciphertext, + &prf_ock(&ovk, &cv, output.cmu(), output.epk()), + output, out_ciphertext, ) } @@ -434,8 +429,8 @@ mod tests { use std::convert::TryInto; use zcash_note_encryption::{ - NoteEncryption, OutgoingCipherKey, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, - NOTE_PLAINTEXT_SIZE, OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, + NoteEncryption, OutgoingCipherKey, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE, + OUT_CIPHERTEXT_SIZE, OUT_PLAINTEXT_SIZE, }; use super::{ @@ -456,7 +451,11 @@ mod tests { keys::OutgoingViewingKey, Diversifier, PaymentAddress, Rseed, SaplingIvk, ValueCommitment, }, - transaction::components::amount::Amount, + transaction::components::{ + amount::Amount, + sapling::{CompactOutputDescription, OutputDescription}, + GROTH_PROOF_SIZE, + }, }; fn random_enc_ciphertext( @@ -466,33 +465,18 @@ mod tests { OutgoingViewingKey, OutgoingCipherKey, SaplingIvk, - jubjub::ExtendedPoint, - bls12_381::Scalar, - jubjub::ExtendedPoint, - [u8; ENC_CIPHERTEXT_SIZE], - [u8; OUT_CIPHERTEXT_SIZE], + OutputDescription, ) { let ivk = SaplingIvk(jubjub::Fr::random(&mut rng)); - let (ovk, ock, cv, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext_with(height, &ivk, rng); + let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, rng); - assert!(try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ) - .is_some()); + assert!(try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output).is_some()); assert!(try_sapling_compact_note_decryption( &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output.clone()), ) .is_some()); @@ -500,42 +484,30 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext, + &output.cv, + &output, + &output.out_ciphertext, ); let ock_output_recovery = try_sapling_output_recovery_with_ock( &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext, + &output, + &output.out_ciphertext, ); assert!(ovk_output_recovery.is_some()); assert!(ock_output_recovery.is_some()); assert_eq!(ovk_output_recovery, ock_output_recovery); - (ovk, ock, ivk, cv, cmu, epk, enc_ciphertext, out_ciphertext) + (ovk, ock, ivk, output) } fn random_enc_ciphertext_with( height: BlockHeight, ivk: &SaplingIvk, mut rng: &mut R, - ) -> ( - OutgoingViewingKey, - OutgoingCipherKey, - jubjub::ExtendedPoint, - bls12_381::Scalar, - jubjub::ExtendedPoint, - [u8; ENC_CIPHERTEXT_SIZE], - [u8; OUT_CIPHERTEXT_SIZE], - ) { + ) -> (OutgoingViewingKey, OutgoingCipherKey, OutputDescription) { let diversifier = Diversifier([0; 11]); let pk_d = diversifier.g_d().unwrap() * ivk.0; let pa = PaymentAddress::from_parts_unchecked(diversifier, pk_d); @@ -561,12 +533,19 @@ mod tests { MemoBytes::empty(), &mut rng, ); - let epk = *ne.epk(); - let enc_ciphertext = ne.encrypt_note_plaintext(); - let out_ciphertext = ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng); - let ock = prf_ock(&ovk, &cv, &cmu, &epk); + let ephemeral_key = *ne.epk(); + let ock = prf_ock(&ovk, &cv, &cmu, &ephemeral_key); + + let output = OutputDescription { + cv, + cmu, + ephemeral_key, + enc_ciphertext: ne.encrypt_note_plaintext(), + out_ciphertext: ne.encrypt_outgoing_plaintext(&cv, &cmu, &mut rng), + zkproof: [0u8; GROTH_PROOF_SIZE], + }; - (ovk, ock, cv, cmu, epk, enc_ciphertext, out_ciphertext) + (ovk, ock, output) } fn reencrypt_enc_ciphertext( @@ -661,16 +640,14 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, _, output) = random_enc_ciphertext(height, &mut rng); assert_eq!( try_sapling_note_decryption( &TEST_NETWORK, height, &SaplingIvk(jubjub::Fr::random(&mut rng)), - &epk, - &cmu, - &enc_ciphertext + &output ), None ); @@ -686,17 +663,12 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &jubjub::ExtendedPoint::random(&mut rng), - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output,), None ); } @@ -711,17 +683,11 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.cmu = bls12_381::Scalar::random(&mut rng); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &bls12_381::Scalar::random(&mut rng), - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -736,19 +702,11 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, cmu, epk, mut enc_ciphertext, _) = - random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; - enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -766,27 +724,19 @@ mod tests { let leadbytes = [0x02, 0x03, 0x01]; for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[0] = leadbyte, ); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -801,27 +751,19 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), ); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -836,28 +778,20 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), ); assert_eq!( - try_sapling_note_decryption( - &TEST_NETWORK, - height, - &ivk, - &epk, - &cmu, - &enc_ciphertext - ), + try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output), None ); } @@ -872,16 +806,14 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, _, _, cmu, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, _, output) = random_enc_ciphertext(height, &mut rng); assert_eq!( try_sapling_compact_note_decryption( &TEST_NETWORK, height, &SaplingIvk(jubjub::Fr::random(&mut rng)), - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -897,16 +829,15 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, cmu, _, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); assert_eq!( try_sapling_compact_note_decryption( &TEST_NETWORK, height, &ivk, - &jubjub::ExtendedPoint::random(&mut rng), - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -922,16 +853,15 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, ivk, _, _, epk, enc_ciphertext, _) = random_enc_ciphertext(height, &mut rng); + let (_, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); + output.cmu = bls12_381::Scalar::random(&mut rng); assert_eq!( try_sapling_compact_note_decryption( &TEST_NETWORK, height, &ivk, - &epk, - &bls12_381::Scalar::random(&mut rng), - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -950,16 +880,15 @@ mod tests { let leadbytes = [0x02, 0x03, 0x01]; for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[0] = leadbyte, ); assert_eq!( @@ -967,9 +896,7 @@ mod tests { &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -985,16 +912,15 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), ); assert_eq!( @@ -1002,9 +928,7 @@ mod tests { &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -1020,16 +944,15 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, ivk, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, ivk, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), ); assert_eq!( @@ -1037,9 +960,7 @@ mod tests { &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &enc_ciphertext[..COMPACT_NOTE_SIZE] + &CompactOutputDescription::from(output) ), None ); @@ -1055,8 +976,7 @@ mod tests { ]; for &height in heights.iter() { - let (mut ovk, _, _, cv, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (mut ovk, _, _, output) = random_enc_ciphertext(height, &mut rng); ovk.0[0] ^= 0xff; assert_eq!( @@ -1064,11 +984,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1084,18 +1002,15 @@ mod tests { ]; for &height in heights.iter() { - let (_, _, _, _, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (_, _, _, output) = random_enc_ciphertext(height, &mut rng); assert_eq!( try_sapling_output_recovery_with_ock( &TEST_NETWORK, height, &OutgoingCipherKey([0u8; 32]), - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1111,8 +1026,7 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, _, _, _, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, _, _, output) = random_enc_ciphertext(height, &mut rng); assert_eq!( try_sapling_output_recovery( @@ -1120,10 +1034,8 @@ mod tests { height, &ovk, &jubjub::ExtendedPoint::random(&mut rng), - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1139,19 +1051,17 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, _, epk, enc_ctext, out_ctext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + output.cmu = bls12_381::Scalar::random(&mut rng); assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &bls12_381::Scalar::random(&mut rng), - &epk, - &enc_ctext, - &out_ctext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1161,10 +1071,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &bls12_381::Scalar::random(&mut rng), - &epk, - &enc_ctext, - &out_ctext + &output, + &output.out_ciphertext ), None ); @@ -1180,19 +1088,17 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, _, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); + output.ephemeral_key = jubjub::ExtendedPoint::random(&mut rng); assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &jubjub::ExtendedPoint::random(&mut rng), - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1202,10 +1108,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &jubjub::ExtendedPoint::random(&mut rng), - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1221,20 +1125,17 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); - enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; + output.enc_ciphertext[ENC_CIPHERTEXT_SIZE - 1] ^= 0xff; assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1243,10 +1144,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1262,20 +1161,17 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, epk, enc_ciphertext, mut out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); - out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff; + output.out_ciphertext[OUT_CIPHERTEXT_SIZE - 1] ^= 0xff; assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1284,10 +1180,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1306,16 +1200,15 @@ mod tests { let leadbytes = [0x02, 0x03, 0x01]; for (&height, &leadbyte) in heights.iter().zip(leadbytes.iter()) { - let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[0] = leadbyte, ); assert_eq!( @@ -1323,11 +1216,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1336,10 +1227,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1355,16 +1244,15 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_invalid_diversifier().0), ); assert_eq!( @@ -1372,11 +1260,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1385,10 +1271,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1404,16 +1288,15 @@ mod tests { ]; for &height in heights.iter() { - let (ovk, ock, _, cv, cmu, epk, mut enc_ciphertext, out_ciphertext) = - random_enc_ciphertext(height, &mut rng); + let (ovk, ock, _, mut output) = random_enc_ciphertext(height, &mut rng); reencrypt_enc_ciphertext( &ovk, - &cv, - &cmu, - &epk, - &mut enc_ciphertext, - &out_ciphertext, + &output.cv, + &output.cmu, + &output.ephemeral_key, + &mut output.enc_ciphertext, + &output.out_ciphertext, |pt| pt[1..12].copy_from_slice(&find_valid_diversifier().0), ); assert_eq!( @@ -1421,11 +1304,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1434,10 +1315,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1454,19 +1333,16 @@ mod tests { for &height in heights.iter() { let ivk = SaplingIvk(jubjub::Fr::zero()); - let (ovk, ock, cv, cmu, epk, enc_ciphertext, out_ciphertext) = - random_enc_ciphertext_with(height, &ivk, &mut rng); + let (ovk, ock, output) = random_enc_ciphertext_with(height, &ivk, &mut rng); assert_eq!( try_sapling_output_recovery( &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output.cv, + &output, + &output.out_ciphertext ), None ); @@ -1475,10 +1351,8 @@ mod tests { &TEST_NETWORK, height, &ock, - &cmu, - &epk, - &enc_ciphertext, - &out_ciphertext + &output, + &output.out_ciphertext ), None ); @@ -1540,12 +1414,21 @@ mod tests { let note = to.create_note(tv.v, Rseed::BeforeZip212(rcm)).unwrap(); assert_eq!(note.cmu(), cmu); + let output = OutputDescription { + cv, + cmu, + ephemeral_key: epk, + enc_ciphertext: tv.c_enc, + out_ciphertext: tv.c_out, + zkproof: [0u8; GROTH_PROOF_SIZE], + }; + // // Test decryption // (Tested first because it only requires immutable references.) // - match try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &epk, &cmu, &tv.c_enc) { + match try_sapling_note_decryption(&TEST_NETWORK, height, &ivk, &output) { Some((decrypted_note, decrypted_to, decrypted_memo)) => { assert_eq!(decrypted_note, note); assert_eq!(decrypted_to, to); @@ -1558,9 +1441,7 @@ mod tests { &TEST_NETWORK, height, &ivk, - &epk, - &cmu, - &tv.c_enc[..COMPACT_NOTE_SIZE], + &CompactOutputDescription::from(output.clone()), ) { Some((decrypted_note, decrypted_to)) => { assert_eq!(decrypted_note, note); @@ -1573,11 +1454,9 @@ mod tests { &TEST_NETWORK, height, &ovk, - &cv, - &cmu, - &epk, - &tv.c_enc, - &tv.c_out, + &output.cv, + &output, + &output.out_ciphertext, ) { Some((decrypted_note, decrypted_to, decrypted_memo)) => { assert_eq!(decrypted_note, note); diff --git a/zcash_primitives/src/transaction/components/sapling.rs b/zcash_primitives/src/transaction/components/sapling.rs index e5184948a..ed48c18ff 100644 --- a/zcash_primitives/src/transaction/components/sapling.rs +++ b/zcash_primitives/src/transaction/components/sapling.rs @@ -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, SaplingShieldedOutput}, + redjubjub::{PublicKey, Signature}, + Nullifier, + }, }; +use zcash_note_encryption::COMPACT_NOTE_SIZE; + use super::GROTH_PROOF_SIZE; #[derive(Clone)] @@ -110,6 +118,26 @@ pub struct OutputDescription { pub zkproof: [u8; GROTH_PROOF_SIZE], } +impl ShieldedOutput> for OutputDescription { + fn epk(&self) -> &jubjub::ExtendedPoint { + &self.ephemeral_key + } + + fn cmstar(&self) -> [u8; 32] { + self.cmu.to_repr() + } + + fn enc_ciphertext(&self) -> &[u8] { + &self.enc_ciphertext + } +} + +impl SaplingShieldedOutput

for OutputDescription { + fn cmu(&self) -> &bls12_381::Scalar { + &self.cmu + } +} + impl std::fmt::Debug for OutputDescription { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> Result<(), std::fmt::Error> { write!( @@ -191,3 +219,39 @@ impl OutputDescription { writer.write_all(&self.zkproof) } } + +pub struct CompactOutputDescription { + pub epk: jubjub::ExtendedPoint, + pub cmu: bls12_381::Scalar, + pub enc_ciphertext: Vec, +} + +impl From 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 ShieldedOutput> for CompactOutputDescription { + fn epk(&self) -> &jubjub::ExtendedPoint { + &self.epk + } + + fn cmstar(&self) -> [u8; 32] { + self.cmu.to_repr() + } + + fn enc_ciphertext(&self) -> &[u8] { + &self.enc_ciphertext + } +} + +impl SaplingShieldedOutput

for CompactOutputDescription { + fn cmu(&self) -> &bls12_381::Scalar { + &self.cmu + } +}