1use alloc::vec::Vec;
4use core::fmt;
5
6use blake2b_simd::{Hash, Params};
7use group::ff::PrimeField;
8use zcash_note_encryption::{
9 BatchDomain, Domain, EphemeralKeyBytes, NotePlaintextBytes, OutPlaintextBytes,
10 OutgoingCipherKey, ShieldedOutput, COMPACT_NOTE_SIZE, ENC_CIPHERTEXT_SIZE, NOTE_PLAINTEXT_SIZE,
11 OUT_PLAINTEXT_SIZE,
12};
13
14use crate::{
15 action::Action,
16 keys::{
17 DiversifiedTransmissionKey, Diversifier, EphemeralPublicKey, EphemeralSecretKey,
18 OutgoingViewingKey, PreparedEphemeralPublicKey, PreparedIncomingViewingKey, SharedSecret,
19 },
20 note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho},
21 value::{NoteValue, ValueCommitment},
22 Address, Note,
23};
24
25const PRF_OCK_ORCHARD_PERSONALIZATION: &[u8; 16] = b"Zcash_Orchardock";
26
27pub(crate) fn prf_ock_orchard(
31 ovk: &OutgoingViewingKey,
32 cv: &ValueCommitment,
33 cmx_bytes: &[u8; 32],
34 ephemeral_key: &EphemeralKeyBytes,
35) -> OutgoingCipherKey {
36 OutgoingCipherKey(
37 Params::new()
38 .hash_length(32)
39 .personal(PRF_OCK_ORCHARD_PERSONALIZATION)
40 .to_state()
41 .update(ovk.as_ref())
42 .update(&cv.to_bytes())
43 .update(cmx_bytes)
44 .update(ephemeral_key.as_ref())
45 .finalize()
46 .as_bytes()
47 .try_into()
48 .unwrap(),
49 )
50}
51
52fn orchard_parse_note_plaintext_without_memo<F>(
53 domain: &OrchardDomain,
54 plaintext: &[u8],
55 get_pk_d: F,
56) -> Option<(Note, Address)>
57where
58 F: FnOnce(&Diversifier) -> DiversifiedTransmissionKey,
59{
60 assert!(plaintext.len() >= COMPACT_NOTE_SIZE);
61
62 if plaintext[0] != 0x02 {
64 return None;
65 }
66
67 let diversifier = Diversifier::from_bytes(plaintext[1..12].try_into().unwrap());
69 let value = NoteValue::from_bytes(plaintext[12..20].try_into().unwrap());
70 let rseed = Option::from(RandomSeed::from_bytes(
71 plaintext[20..COMPACT_NOTE_SIZE].try_into().unwrap(),
72 &domain.rho,
73 ))?;
74
75 let pk_d = get_pk_d(&diversifier);
76
77 let recipient = Address::from_parts(diversifier, pk_d);
78 let note = Option::from(Note::from_parts(recipient, value, domain.rho, rseed))?;
79 Some((note, recipient))
80}
81
82#[derive(Debug)]
84pub struct OrchardDomain {
85 rho: Rho,
86}
87
88impl memuse::DynamicUsage for OrchardDomain {
89 fn dynamic_usage(&self) -> usize {
90 self.rho.dynamic_usage()
91 }
92
93 fn dynamic_usage_bounds(&self) -> (usize, Option<usize>) {
94 self.rho.dynamic_usage_bounds()
95 }
96}
97
98impl OrchardDomain {
99 pub fn for_action<T>(act: &Action<T>) -> Self {
101 Self { rho: act.rho() }
102 }
103
104 pub fn for_pczt_action(act: &crate::pczt::Action) -> Self {
106 Self {
107 rho: Rho::from_nf_old(act.spend().nullifier),
108 }
109 }
110
111 pub fn for_compact_action(act: &CompactAction) -> Self {
113 Self { rho: act.rho() }
114 }
115}
116
117impl Domain for OrchardDomain {
118 type EphemeralSecretKey = EphemeralSecretKey;
119 type EphemeralPublicKey = EphemeralPublicKey;
120 type PreparedEphemeralPublicKey = PreparedEphemeralPublicKey;
121 type SharedSecret = SharedSecret;
122 type SymmetricKey = Hash;
123 type Note = Note;
124 type Recipient = Address;
125 type DiversifiedTransmissionKey = DiversifiedTransmissionKey;
126 type IncomingViewingKey = PreparedIncomingViewingKey;
127 type OutgoingViewingKey = OutgoingViewingKey;
128 type ValueCommitment = ValueCommitment;
129 type ExtractedCommitment = ExtractedNoteCommitment;
130 type ExtractedCommitmentBytes = [u8; 32];
131 type Memo = [u8; 512]; fn derive_esk(note: &Self::Note) -> Option<Self::EphemeralSecretKey> {
134 Some(note.esk())
135 }
136
137 fn get_pk_d(note: &Self::Note) -> Self::DiversifiedTransmissionKey {
138 *note.recipient().pk_d()
139 }
140
141 fn prepare_epk(epk: Self::EphemeralPublicKey) -> Self::PreparedEphemeralPublicKey {
142 PreparedEphemeralPublicKey::new(epk)
143 }
144
145 fn ka_derive_public(
146 note: &Self::Note,
147 esk: &Self::EphemeralSecretKey,
148 ) -> Self::EphemeralPublicKey {
149 esk.derive_public(note.recipient().g_d())
150 }
151
152 fn ka_agree_enc(
153 esk: &Self::EphemeralSecretKey,
154 pk_d: &Self::DiversifiedTransmissionKey,
155 ) -> Self::SharedSecret {
156 esk.agree(pk_d)
157 }
158
159 fn ka_agree_dec(
160 ivk: &Self::IncomingViewingKey,
161 epk: &Self::PreparedEphemeralPublicKey,
162 ) -> Self::SharedSecret {
163 epk.agree(ivk)
164 }
165
166 fn kdf(secret: Self::SharedSecret, ephemeral_key: &EphemeralKeyBytes) -> Self::SymmetricKey {
167 secret.kdf_orchard(ephemeral_key)
168 }
169
170 fn note_plaintext_bytes(note: &Self::Note, memo: &Self::Memo) -> NotePlaintextBytes {
171 let mut np = [0; NOTE_PLAINTEXT_SIZE];
172 np[0] = 0x02;
173 np[1..12].copy_from_slice(note.recipient().diversifier().as_array());
174 np[12..20].copy_from_slice(¬e.value().to_bytes());
175 np[20..52].copy_from_slice(note.rseed().as_bytes());
176 np[52..].copy_from_slice(memo);
177 NotePlaintextBytes(np)
178 }
179
180 fn derive_ock(
181 ovk: &Self::OutgoingViewingKey,
182 cv: &Self::ValueCommitment,
183 cmstar_bytes: &Self::ExtractedCommitmentBytes,
184 ephemeral_key: &EphemeralKeyBytes,
185 ) -> OutgoingCipherKey {
186 prf_ock_orchard(ovk, cv, cmstar_bytes, ephemeral_key)
187 }
188
189 fn outgoing_plaintext_bytes(
190 note: &Self::Note,
191 esk: &Self::EphemeralSecretKey,
192 ) -> OutPlaintextBytes {
193 let mut op = [0; OUT_PLAINTEXT_SIZE];
194 op[..32].copy_from_slice(¬e.recipient().pk_d().to_bytes());
195 op[32..].copy_from_slice(&esk.0.to_repr());
196 OutPlaintextBytes(op)
197 }
198
199 fn epk_bytes(epk: &Self::EphemeralPublicKey) -> EphemeralKeyBytes {
200 epk.to_bytes()
201 }
202
203 fn epk(ephemeral_key: &EphemeralKeyBytes) -> Option<Self::EphemeralPublicKey> {
204 EphemeralPublicKey::from_bytes(&ephemeral_key.0).into()
205 }
206
207 fn cmstar(note: &Self::Note) -> Self::ExtractedCommitment {
208 note.commitment().into()
209 }
210
211 fn parse_note_plaintext_without_memo_ivk(
212 &self,
213 ivk: &Self::IncomingViewingKey,
214 plaintext: &[u8],
215 ) -> Option<(Self::Note, Self::Recipient)> {
216 orchard_parse_note_plaintext_without_memo(self, plaintext, |diversifier| {
217 DiversifiedTransmissionKey::derive(ivk, diversifier)
218 })
219 }
220
221 fn parse_note_plaintext_without_memo_ovk(
222 &self,
223 pk_d: &Self::DiversifiedTransmissionKey,
224 plaintext: &NotePlaintextBytes,
225 ) -> Option<(Self::Note, Self::Recipient)> {
226 orchard_parse_note_plaintext_without_memo(self, &plaintext.0, |_| *pk_d)
227 }
228
229 fn extract_memo(&self, plaintext: &NotePlaintextBytes) -> Self::Memo {
230 plaintext.0[COMPACT_NOTE_SIZE..NOTE_PLAINTEXT_SIZE]
231 .try_into()
232 .unwrap()
233 }
234
235 fn extract_pk_d(out_plaintext: &OutPlaintextBytes) -> Option<Self::DiversifiedTransmissionKey> {
236 DiversifiedTransmissionKey::from_bytes(out_plaintext.0[0..32].try_into().unwrap()).into()
237 }
238
239 fn extract_esk(out_plaintext: &OutPlaintextBytes) -> Option<Self::EphemeralSecretKey> {
240 EphemeralSecretKey::from_bytes(out_plaintext.0[32..OUT_PLAINTEXT_SIZE].try_into().unwrap())
241 .into()
242 }
243}
244
245impl BatchDomain for OrchardDomain {
246 fn batch_kdf<'a>(
247 items: impl Iterator<Item = (Option<Self::SharedSecret>, &'a EphemeralKeyBytes)>,
248 ) -> Vec<Option<Self::SymmetricKey>> {
249 let (shared_secrets, ephemeral_keys): (Vec<_>, Vec<_>) = items.unzip();
250
251 SharedSecret::batch_to_affine(shared_secrets)
252 .zip(ephemeral_keys)
253 .map(|(secret, ephemeral_key)| {
254 secret.map(|dhsecret| SharedSecret::kdf_orchard_inner(dhsecret, ephemeral_key))
255 })
256 .collect()
257 }
258}
259
260pub type OrchardNoteEncryption = zcash_note_encryption::NoteEncryption<OrchardDomain>;
262
263impl<T> ShieldedOutput<OrchardDomain, ENC_CIPHERTEXT_SIZE> for Action<T> {
264 fn ephemeral_key(&self) -> EphemeralKeyBytes {
265 EphemeralKeyBytes(self.encrypted_note().epk_bytes)
266 }
267
268 fn cmstar_bytes(&self) -> [u8; 32] {
269 self.cmx().to_bytes()
270 }
271
272 fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] {
273 &self.encrypted_note().enc_ciphertext
274 }
275}
276
277impl ShieldedOutput<OrchardDomain, ENC_CIPHERTEXT_SIZE> for crate::pczt::Action {
278 fn ephemeral_key(&self) -> EphemeralKeyBytes {
279 EphemeralKeyBytes(self.output().encrypted_note().epk_bytes)
280 }
281
282 fn cmstar_bytes(&self) -> [u8; 32] {
283 self.output().cmx().to_bytes()
284 }
285
286 fn enc_ciphertext(&self) -> &[u8; ENC_CIPHERTEXT_SIZE] {
287 &self.output().encrypted_note().enc_ciphertext
288 }
289}
290
291#[derive(Clone)]
293pub struct CompactAction {
294 nullifier: Nullifier,
295 cmx: ExtractedNoteCommitment,
296 ephemeral_key: EphemeralKeyBytes,
297 enc_ciphertext: [u8; 52],
298}
299
300impl fmt::Debug for CompactAction {
301 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
302 write!(f, "CompactAction")
303 }
304}
305
306impl<T> From<&Action<T>> for CompactAction {
307 fn from(action: &Action<T>) -> Self {
308 CompactAction {
309 nullifier: *action.nullifier(),
310 cmx: *action.cmx(),
311 ephemeral_key: action.ephemeral_key(),
312 enc_ciphertext: action.encrypted_note().enc_ciphertext[..52]
313 .try_into()
314 .unwrap(),
315 }
316 }
317}
318
319impl ShieldedOutput<OrchardDomain, COMPACT_NOTE_SIZE> for CompactAction {
320 fn ephemeral_key(&self) -> EphemeralKeyBytes {
321 EphemeralKeyBytes(self.ephemeral_key.0)
322 }
323
324 fn cmstar_bytes(&self) -> [u8; 32] {
325 self.cmx.to_bytes()
326 }
327
328 fn enc_ciphertext(&self) -> &[u8; COMPACT_NOTE_SIZE] {
329 &self.enc_ciphertext
330 }
331}
332
333impl CompactAction {
334 pub fn from_parts(
336 nullifier: Nullifier,
337 cmx: ExtractedNoteCommitment,
338 ephemeral_key: EphemeralKeyBytes,
339 enc_ciphertext: [u8; 52],
340 ) -> Self {
341 Self {
342 nullifier,
343 cmx,
344 ephemeral_key,
345 enc_ciphertext,
346 }
347 }
348
349 pub fn nullifier(&self) -> Nullifier {
351 self.nullifier
352 }
353
354 pub fn cmx(&self) -> ExtractedNoteCommitment {
356 self.cmx
357 }
358
359 pub fn rho(&self) -> Rho {
361 Rho::from_nf_old(self.nullifier)
362 }
363}
364
365#[cfg(feature = "test-dependencies")]
367pub mod testing {
368 use rand::RngCore;
369 use zcash_note_encryption::Domain;
370
371 use crate::{
372 keys::OutgoingViewingKey,
373 note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho},
374 value::NoteValue,
375 Address, Note,
376 };
377
378 use super::{CompactAction, OrchardDomain, OrchardNoteEncryption};
379
380 pub fn fake_compact_action<R: RngCore>(
384 rng: &mut R,
385 nf_old: Nullifier,
386 recipient: Address,
387 value: NoteValue,
388 ovk: Option<OutgoingViewingKey>,
389 ) -> (CompactAction, Note) {
390 let rho = Rho::from_nf_old(nf_old);
391 let rseed = {
392 loop {
393 let mut bytes = [0; 32];
394 rng.fill_bytes(&mut bytes);
395 let rseed = RandomSeed::from_bytes(bytes, &rho);
396 if rseed.is_some().into() {
397 break rseed.unwrap();
398 }
399 }
400 };
401 let note = Note::from_parts(recipient, value, rho, rseed).unwrap();
402 let encryptor = OrchardNoteEncryption::new(ovk, note, [0u8; 512]);
403 let cmx = ExtractedNoteCommitment::from(note.commitment());
404 let ephemeral_key = OrchardDomain::epk_bytes(encryptor.epk());
405 let enc_ciphertext = encryptor.encrypt_note_plaintext();
406
407 (
408 CompactAction {
409 nullifier: nf_old,
410 cmx,
411 ephemeral_key,
412 enc_ciphertext: enc_ciphertext.as_ref()[..52].try_into().unwrap(),
413 },
414 note,
415 )
416 }
417}
418
419#[cfg(test)]
420mod tests {
421 use rand::rngs::OsRng;
422 use zcash_note_encryption::{
423 try_compact_note_decryption, try_note_decryption, try_output_recovery_with_ovk,
424 EphemeralKeyBytes,
425 };
426
427 use super::{prf_ock_orchard, CompactAction, OrchardDomain, OrchardNoteEncryption};
428 use crate::{
429 action::Action,
430 keys::{
431 DiversifiedTransmissionKey, Diversifier, EphemeralSecretKey, IncomingViewingKey,
432 OutgoingViewingKey, PreparedIncomingViewingKey,
433 },
434 note::{ExtractedNoteCommitment, Nullifier, RandomSeed, Rho, TransmittedNoteCiphertext},
435 primitives::redpallas,
436 value::{NoteValue, ValueCommitment},
437 Address, Note,
438 };
439
440 #[test]
441 fn test_vectors() {
442 let test_vectors = crate::test_vectors::note_encryption::test_vectors();
443
444 for tv in test_vectors {
445 let ivk = PreparedIncomingViewingKey::new(
451 &IncomingViewingKey::from_bytes(&tv.incoming_viewing_key).unwrap(),
452 );
453 let ovk = OutgoingViewingKey::from(tv.ovk);
454 let d = Diversifier::from_bytes(tv.default_d);
455 let pk_d = DiversifiedTransmissionKey::from_bytes(&tv.default_pk_d).unwrap();
456
457 let cv_net = ValueCommitment::from_bytes(&tv.cv_net).unwrap();
459 let nf_old = Nullifier::from_bytes(&tv.nf_old).unwrap();
460 let rho = Rho::from_nf_old(nf_old);
461 let cmx = ExtractedNoteCommitment::from_bytes(&tv.cmx).unwrap();
462
463 let esk = EphemeralSecretKey::from_bytes(&tv.esk).unwrap();
464 let ephemeral_key = EphemeralKeyBytes(tv.ephemeral_key);
465
466 let value = NoteValue::from_raw(tv.v);
468 let rseed = RandomSeed::from_bytes(tv.rseed, &rho).unwrap();
469
470 let shared_secret = esk.agree(&pk_d);
475 assert_eq!(shared_secret.to_bytes(), tv.shared_secret);
476
477 let k_enc = shared_secret.kdf_orchard(&ephemeral_key);
478 assert_eq!(k_enc.as_bytes(), tv.k_enc);
479
480 let ock = prf_ock_orchard(&ovk, &cv_net, &cmx.to_bytes(), &ephemeral_key);
481 assert_eq!(ock.as_ref(), tv.ock);
482
483 let recipient = Address::from_parts(d, pk_d);
484 let note = Note::from_parts(recipient, value, rho, rseed).unwrap();
485 assert_eq!(ExtractedNoteCommitment::from(note.commitment()), cmx);
486
487 let action = Action::from_parts(
488 nf_old,
490 redpallas::VerificationKey::dummy(),
492 cmx,
493 TransmittedNoteCiphertext {
494 epk_bytes: ephemeral_key.0,
495 enc_ciphertext: tv.c_enc,
496 out_ciphertext: tv.c_out,
497 },
498 cv_net.clone(),
499 (),
500 );
501
502 let domain = OrchardDomain { rho };
508
509 match try_note_decryption(&domain, &ivk, &action) {
510 Some((decrypted_note, decrypted_to, decrypted_memo)) => {
511 assert_eq!(decrypted_note, note);
512 assert_eq!(decrypted_to, recipient);
513 assert_eq!(&decrypted_memo[..], &tv.memo[..]);
514 }
515 None => panic!("Note decryption failed"),
516 }
517
518 match try_compact_note_decryption(&domain, &ivk, &CompactAction::from(&action)) {
519 Some((decrypted_note, decrypted_to)) => {
520 assert_eq!(decrypted_note, note);
521 assert_eq!(decrypted_to, recipient);
522 }
523 None => panic!("Compact note decryption failed"),
524 }
525
526 match try_output_recovery_with_ovk(&domain, &ovk, &action, &cv_net, &tv.c_out) {
527 Some((decrypted_note, decrypted_to, decrypted_memo)) => {
528 assert_eq!(decrypted_note, note);
529 assert_eq!(decrypted_to, recipient);
530 assert_eq!(&decrypted_memo[..], &tv.memo[..]);
531 }
532 None => panic!("Output recovery failed"),
533 }
534
535 let ne = OrchardNoteEncryption::new_with_esk(esk, Some(ovk), note, tv.memo);
540
541 assert_eq!(ne.encrypt_note_plaintext().as_ref(), &tv.c_enc[..]);
542 assert_eq!(
543 &ne.encrypt_outgoing_plaintext(&cv_net, &cmx, &mut OsRng)[..],
544 &tv.c_out[..]
545 );
546 }
547 }
548}