1use std::convert::{TryFrom, TryInto};
2
3use crate::genesis::Allocation;
4use blake2b_simd;
5use decaf377::Fq;
6use decaf377_fmd as fmd;
7use decaf377_ka as ka;
8use once_cell::sync::Lazy;
9use penumbra_sdk_keys::{
10 keys::{Diversifier, FullViewingKey, IncomingViewingKey, OutgoingViewingKey},
11 symmetric::{OutgoingCipherKey, OvkWrappedKey, PayloadKey, PayloadKind},
12 Address, AddressView,
13};
14use penumbra_sdk_proto::penumbra::core::component::shielded_pool::v1 as pb;
15use rand::{CryptoRng, Rng};
16use serde::{Deserialize, Serialize};
17use thiserror;
18
19mod r1cs;
20pub use r1cs::NoteVar;
21
22pub use penumbra_sdk_tct::StateCommitment;
23
24use penumbra_sdk_asset::{asset, balance, Value, ValueView};
25use penumbra_sdk_num::Amount;
26
27use crate::{NotePayload, Rseed};
28
29pub const NOTE_LEN_BYTES: usize = 160;
30pub const NOTE_CIPHERTEXT_BYTES: usize = 176;
31
32#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(into = "pb::Note", try_from = "pb::Note")]
35pub struct Note {
36 value: Value,
38 rseed: Rseed,
41 address: Address,
43 transmission_key_s: Fq,
48}
49
50#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
51#[serde(into = "pb::NoteView", try_from = "pb::NoteView")]
52pub struct NoteView {
53 pub value: ValueView,
54 pub rseed: Rseed,
55 pub address: AddressView,
56}
57
58impl NoteView {
59 pub fn note(&self) -> Result<Note, Error> {
60 self.clone().try_into()
61 }
62
63 pub fn address(&self) -> Address {
64 self.address.address()
65 }
66
67 pub fn asset_id(&self) -> asset::Id {
68 self.value.asset_id()
69 }
70}
71
72impl TryFrom<NoteView> for Note {
73 type Error = Error;
74
75 fn try_from(view: NoteView) -> Result<Self, Self::Error> {
76 let value = view.value.value();
77 let address = view.address.address();
78 Note::from_parts(address, value, view.rseed)
79 }
80}
81
82#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(into = "pb::NoteCiphertext", try_from = "pb::NoteCiphertext")]
85pub struct NoteCiphertext(pub [u8; NOTE_CIPHERTEXT_BYTES]);
86
87pub(crate) static NOTECOMMIT_DOMAIN_SEP: Lazy<Fq> = Lazy::new(|| {
89 Fq::from_le_bytes_mod_order(blake2b_simd::blake2b(b"penumbra.notecommit").as_bytes())
90});
91
92#[derive(thiserror::Error, Debug)]
93pub enum Error {
94 #[error("Invalid note commitment")]
95 InvalidNoteCommitment,
96 #[error("Invalid transmission key")]
97 InvalidTransmissionKey,
98 #[error("Note type unsupported")]
99 NoteTypeUnsupported,
100 #[error("Note deserialization error")]
101 NoteDeserializationError,
102 #[error("Invalid note ciphertext")]
103 InvalidNoteCiphertext,
104 #[error("Decryption error")]
105 DecryptionError,
106}
107
108impl Note {
109 pub fn controlled_by(&self, fvk: &FullViewingKey) -> bool {
110 if let Some(address_index) = fvk.address_index(&self.address()) {
111 if fvk
113 .incoming()
114 .transparent_address()
115 .parse::<Address>()
116 .expect("constructed transparent address is always valid")
117 == self.address()
118 {
119 return true;
120 }
121
122 let (expected_address, _) = fvk.incoming().payment_address(address_index);
124 let expected_ck_d = expected_address.clue_key();
125
126 let transmission_key_matches = *self.transmission_key()
127 == fvk
128 .incoming()
129 .diversified_public(&self.diversified_generator());
130
131 return transmission_key_matches && self.clue_key() == expected_ck_d;
132 } else {
133 false
134 }
135 }
136
137 pub fn from_allocation(allocation: Allocation) -> anyhow::Result<Note> {
142 Note::from_parts(
143 allocation.address,
144 Value {
145 amount: allocation.raw_amount,
146 asset_id: asset::REGISTRY
147 .parse_denom(&allocation.raw_denom)
148 .ok_or_else(|| anyhow::anyhow!("invalid denomination"))?
149 .id(),
150 },
151 Rseed([0u8; 32]),
152 )
153 .map_err(Into::into)
154 }
155
156 pub fn from_parts(address: Address, value: Value, rseed: Rseed) -> Result<Self, Error> {
157 Ok(Note {
158 value,
159 rseed,
160 address: address.clone(),
161 transmission_key_s: Fq::from_bytes_checked(&address.transmission_key().0)
162 .map_err(|_| Error::InvalidTransmissionKey)?,
163 })
164 }
165
166 pub fn payload(&self) -> NotePayload {
167 NotePayload {
168 note_commitment: self.commit(),
169 ephemeral_key: self.ephemeral_public_key(),
170 encrypted_note: self.encrypt(),
171 }
172 }
173
174 pub fn generate(rng: &mut (impl Rng + CryptoRng), address: &Address, value: Value) -> Self {
177 let rseed = Rseed::generate(rng);
178 Note::from_parts(address.clone(), value, rseed)
179 .expect("transmission key in address is always valid")
180 }
181
182 pub fn address(&self) -> Address {
183 self.address.clone()
184 }
185
186 pub fn diversified_generator(&self) -> decaf377::Element {
187 self.address.diversifier().diversified_generator()
188 }
189
190 pub fn transmission_key(&self) -> &ka::Public {
191 self.address.transmission_key()
192 }
193
194 pub fn transmission_key_s(&self) -> Fq {
195 self.transmission_key_s
196 }
197
198 pub fn clue_key(&self) -> &fmd::ClueKey {
199 self.address.clue_key()
200 }
201
202 pub fn diversifier(&self) -> &Diversifier {
203 self.address.diversifier()
204 }
205
206 pub fn ephemeral_secret_key(&self) -> ka::Secret {
207 self.rseed.derive_esk()
208 }
209
210 pub fn ephemeral_public_key(&self) -> ka::Public {
211 self.ephemeral_secret_key()
212 .diversified_public(&self.diversified_generator())
213 }
214
215 pub fn note_blinding(&self) -> Fq {
216 self.rseed.derive_note_blinding()
217 }
218
219 pub fn value(&self) -> Value {
220 self.value
221 }
222
223 pub fn asset_id(&self) -> asset::Id {
224 self.value.asset_id
225 }
226
227 pub fn amount(&self) -> Amount {
228 self.value.amount
229 }
230
231 pub fn rseed(&self) -> Rseed {
232 self.rseed
233 }
234
235 pub fn encrypt(&self) -> NoteCiphertext {
237 let esk = self.ephemeral_secret_key();
238 let epk = esk.diversified_public(&self.diversified_generator());
239 let shared_secret = esk
240 .key_agreement_with(self.transmission_key())
241 .expect("key agreement succeeded");
242
243 let key = PayloadKey::derive(&shared_secret, &epk);
244 let note_plaintext: Vec<u8> = self.into();
245 let encryption_result = key.encrypt(note_plaintext, PayloadKind::Note);
246
247 let ciphertext: [u8; NOTE_CIPHERTEXT_BYTES] = encryption_result
248 .try_into()
249 .expect("note encryption result fits in ciphertext len");
250
251 NoteCiphertext(ciphertext)
252 }
253
254 pub fn encrypt_key(&self, ovk: &OutgoingViewingKey, cv: balance::Commitment) -> OvkWrappedKey {
256 let esk = self.ephemeral_secret_key();
257 let epk = esk.diversified_public(&self.diversified_generator());
258 let ock = OutgoingCipherKey::derive(ovk, cv, self.commit(), &epk);
259 let shared_secret = esk
260 .key_agreement_with(self.transmission_key())
261 .expect("key agreement succeeded");
262
263 let encryption_result = ock.encrypt(shared_secret.0.to_vec(), PayloadKind::Note);
264
265 OvkWrappedKey(
266 encryption_result
267 .try_into()
268 .expect("OVK encryption result fits in ciphertext len"),
269 )
270 }
271
272 pub fn decrypt_key(
274 wrapped_ovk: OvkWrappedKey,
275 cm: StateCommitment,
276 cv: balance::Commitment,
277 ovk: &OutgoingViewingKey,
278 epk: &ka::Public,
279 ) -> Result<ka::SharedSecret, Error> {
280 let ock = OutgoingCipherKey::derive(ovk, cv, cm, epk);
281
282 let plaintext = ock
283 .decrypt(wrapped_ovk.to_vec(), PayloadKind::Note)
284 .map_err(|_| Error::DecryptionError)?;
285
286 let shared_secret_bytes: [u8; 32] = plaintext[0..32]
287 .try_into()
288 .map_err(|_| Error::DecryptionError)?;
289 let shared_secret: ka::SharedSecret = shared_secret_bytes
290 .try_into()
291 .map_err(|_| Error::DecryptionError)?;
292
293 Ok(shared_secret)
294 }
295
296 pub fn decrypt_outgoing(
298 ciphertext: &NoteCiphertext,
299 wrapped_ovk: OvkWrappedKey,
300 cm: StateCommitment,
301 cv: balance::Commitment,
302 ovk: &OutgoingViewingKey,
303 epk: &ka::Public,
304 ) -> Result<Note, Error> {
305 let shared_secret =
306 Note::decrypt_key(wrapped_ovk, cm, cv, ovk, epk).map_err(|_| Error::DecryptionError)?;
307
308 let key = PayloadKey::derive(&shared_secret, epk);
309 Note::decrypt_with_payload_key(ciphertext, &key, epk)
310 }
311
312 pub fn decrypt(
314 ciphertext: &NoteCiphertext,
315 ivk: &IncomingViewingKey,
316 epk: &ka::Public,
317 ) -> Result<Note, Error> {
318 let shared_secret = ivk
319 .key_agreement_with(epk)
320 .map_err(|_| Error::DecryptionError)?;
321
322 let key = PayloadKey::derive(&shared_secret, epk);
323 Note::decrypt_with_payload_key(ciphertext, &key, epk)
324 }
325
326 pub fn decrypt_with_payload_key(
328 ciphertext: &NoteCiphertext,
329 payload_key: &PayloadKey,
330 epk: &ka::Public,
331 ) -> Result<Note, Error> {
332 let plaintext = payload_key
333 .decrypt(ciphertext.0.to_vec(), PayloadKind::Note)
334 .map_err(|_| Error::DecryptionError)?;
335
336 let plaintext_bytes: [u8; NOTE_LEN_BYTES] =
337 plaintext.try_into().map_err(|_| Error::DecryptionError)?;
338
339 let note: Note = plaintext_bytes
340 .try_into()
341 .map_err(|_| Error::DecryptionError)?;
342
343 if note.ephemeral_public_key() != *epk {
345 return Err(Error::DecryptionError);
346 }
347
348 Ok(note)
349 }
350
351 pub fn commit(&self) -> StateCommitment {
353 self::commitment(
354 self.note_blinding(),
355 self.value,
356 self.diversified_generator(),
357 self.transmission_key_s,
358 self.address.clue_key(),
359 )
360 }
361
362 pub fn to_bytes(&self) -> [u8; NOTE_LEN_BYTES] {
363 self.into()
364 }
365}
366
367pub fn commitment(
369 note_blinding: Fq,
370 value: Value,
371 diversified_generator: decaf377::Element,
372 transmission_key_s: Fq,
373 clue_key: &fmd::ClueKey,
374) -> StateCommitment {
375 let commit = poseidon377::hash_6(
376 &NOTECOMMIT_DOMAIN_SEP,
377 (
378 note_blinding,
379 value.amount.into(),
380 value.asset_id.0,
381 diversified_generator.vartime_compress_to_field(),
382 transmission_key_s,
383 Fq::from_le_bytes_mod_order(&clue_key.0[..]),
384 ),
385 );
386
387 StateCommitment(commit)
388}
389
390pub fn commitment_from_address(
392 address: Address,
393 value: Value,
394 note_blinding: Fq,
395) -> Result<StateCommitment, Error> {
396 let transmission_key_s = Fq::from_bytes_checked(&address.transmission_key().0)
397 .map_err(|_| Error::InvalidTransmissionKey)?;
398 let commit = poseidon377::hash_6(
399 &NOTECOMMIT_DOMAIN_SEP,
400 (
401 note_blinding,
402 value.amount.into(),
403 value.asset_id.0,
404 address.diversified_generator().vartime_compress_to_field(),
405 transmission_key_s,
406 Fq::from_le_bytes_mod_order(&address.clue_key().0[..]),
407 ),
408 );
409
410 Ok(StateCommitment(commit))
411}
412
413impl std::fmt::Debug for Note {
414 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
415 f.debug_struct("Note")
416 .field("value", &self.value)
417 .field("address", &self.address())
418 .field("rseed", &hex::encode(self.rseed.to_bytes()))
419 .finish()
420 }
421}
422
423impl TryFrom<pb::Note> for Note {
424 type Error = anyhow::Error;
425 fn try_from(msg: pb::Note) -> Result<Self, Self::Error> {
426 let address = msg
427 .address
428 .ok_or_else(|| anyhow::anyhow!("missing value"))?
429 .try_into()?;
430 let value = msg
431 .value
432 .ok_or_else(|| anyhow::anyhow!("missing value"))?
433 .try_into()?;
434 let rseed = Rseed(msg.rseed.as_slice().try_into()?);
435
436 Ok(Note::from_parts(address, value, rseed)?)
437 }
438}
439
440impl From<Note> for pb::Note {
441 fn from(msg: Note) -> Self {
442 pb::Note {
443 address: Some(msg.address().into()),
444 value: Some(msg.value().into()),
445 rseed: msg.rseed.to_bytes().to_vec(),
446 }
447 }
448}
449
450impl From<NoteView> for pb::NoteView {
451 fn from(msg: NoteView) -> Self {
452 pb::NoteView {
453 address: Some(msg.address.into()),
454 value: Some(msg.value.into()),
455 rseed: msg.rseed.to_bytes().to_vec(),
456 }
457 }
458}
459
460impl TryFrom<pb::NoteView> for NoteView {
461 type Error = anyhow::Error;
462 fn try_from(msg: pb::NoteView) -> Result<Self, Self::Error> {
463 let address = msg
464 .address
465 .ok_or_else(|| anyhow::anyhow!("missing value"))?
466 .try_into()?;
467 let value = msg
468 .value
469 .ok_or_else(|| anyhow::anyhow!("missing value"))?
470 .try_into()?;
471 let rseed = Rseed(msg.rseed.as_slice().try_into()?);
472
473 Ok(NoteView {
474 address,
475 value,
476 rseed,
477 })
478 }
479}
480
481impl From<&Note> for [u8; NOTE_LEN_BYTES] {
482 fn from(note: &Note) -> [u8; NOTE_LEN_BYTES] {
483 let mut bytes = [0u8; NOTE_LEN_BYTES];
484 bytes[0..80].copy_from_slice(¬e.address.to_vec());
485 bytes[80..96].copy_from_slice(¬e.value.amount.to_le_bytes());
486 bytes[96..128].copy_from_slice(¬e.value.asset_id.0.to_bytes());
487 bytes[128..160].copy_from_slice(¬e.rseed.to_bytes());
488 bytes
489 }
490}
491
492impl From<Note> for [u8; NOTE_LEN_BYTES] {
493 fn from(note: Note) -> [u8; NOTE_LEN_BYTES] {
494 (¬e).into()
495 }
496}
497
498impl From<&Note> for Vec<u8> {
499 fn from(note: &Note) -> Vec<u8> {
500 let mut bytes = vec![];
501 bytes.extend_from_slice(¬e.address().to_vec());
502 bytes.extend_from_slice(¬e.value.amount.to_le_bytes());
503 bytes.extend_from_slice(¬e.value.asset_id.0.to_bytes());
504 bytes.extend_from_slice(¬e.rseed.to_bytes());
505 bytes
506 }
507}
508
509impl TryFrom<&[u8]> for Note {
510 type Error = Error;
511
512 fn try_from(bytes: &[u8]) -> Result<Self, Self::Error> {
513 if bytes.len() != NOTE_LEN_BYTES {
514 return Err(Error::NoteDeserializationError);
515 }
516
517 let amount_bytes: [u8; 16] = bytes[80..96]
518 .try_into()
519 .map_err(|_| Error::NoteDeserializationError)?;
520 let asset_id_bytes: [u8; 32] = bytes[96..128]
521 .try_into()
522 .map_err(|_| Error::NoteDeserializationError)?;
523 let rseed_bytes: [u8; 32] = bytes[128..160]
524 .try_into()
525 .map_err(|_| Error::NoteDeserializationError)?;
526
527 Note::from_parts(
528 bytes[0..80]
529 .try_into()
530 .map_err(|_| Error::NoteDeserializationError)?,
531 Value {
532 amount: Amount::from_le_bytes(amount_bytes),
533 asset_id: asset::Id(
534 Fq::from_bytes_checked(&asset_id_bytes)
535 .map_err(|_| Error::NoteDeserializationError)?,
536 ),
537 },
538 Rseed(rseed_bytes),
539 )
540 }
541}
542
543impl TryFrom<[u8; NOTE_LEN_BYTES]> for Note {
544 type Error = Error;
545
546 fn try_from(bytes: [u8; NOTE_LEN_BYTES]) -> Result<Note, Self::Error> {
547 (&bytes[..]).try_into()
548 }
549}
550
551impl TryFrom<pb::NoteCiphertext> for NoteCiphertext {
552 type Error = Error;
553
554 fn try_from(msg: pb::NoteCiphertext) -> Result<Self, Self::Error> {
555 if msg.inner.len() != NOTE_CIPHERTEXT_BYTES {
556 return Err(Error::InvalidNoteCiphertext);
557 }
558
559 let inner_bytes: [u8; NOTE_CIPHERTEXT_BYTES] = msg
560 .inner
561 .try_into()
562 .map_err(|_| Error::InvalidNoteCiphertext)?;
563
564 Ok(NoteCiphertext(inner_bytes))
565 }
566}
567
568impl From<NoteCiphertext> for pb::NoteCiphertext {
569 fn from(msg: NoteCiphertext) -> Self {
570 pb::NoteCiphertext {
571 inner: msg.0.to_vec(),
572 }
573 }
574}
575
576#[cfg(test)]
577mod tests {
578 use decaf377::Fr;
579 use rand_core::OsRng;
580
581 use super::*;
582 use penumbra_sdk_keys::keys::{Bip44Path, SeedPhrase, SpendKey};
583
584 #[test]
585 fn note_encryption_and_decryption() {
586 let mut rng = OsRng;
587
588 let seed_phrase = SeedPhrase::generate(rng);
589 let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
590 let fvk = sk.full_viewing_key();
591 let ivk = fvk.incoming();
592 let (dest, _dtk_d) = ivk.payment_address(0u32.into());
593
594 let value = Value {
595 amount: 10u64.into(),
596 asset_id: asset::Cache::with_known_assets()
597 .get_unit("upenumbra")
598 .unwrap()
599 .id(),
600 };
601 let note = Note::generate(&mut rng, &dest, value);
602
603 let ciphertext = note.encrypt();
604
605 let esk = note.ephemeral_secret_key();
606 let epk = esk.diversified_public(dest.diversified_generator());
607 let plaintext = Note::decrypt(&ciphertext, ivk, &epk).expect("can decrypt note");
608
609 assert_eq!(plaintext, note);
610
611 let seed_phrase = SeedPhrase::generate(rng);
612 let sk2 = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
613 let fvk2 = sk2.full_viewing_key();
614 let ivk2 = fvk2.incoming();
615
616 assert!(Note::decrypt(&ciphertext, ivk2, &epk).is_err());
617 }
618
619 #[test]
620 fn note_encryption_and_sender_decryption() {
621 let mut rng = OsRng;
622
623 let seed_phrase = SeedPhrase::generate(rng);
624 let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
625 let fvk = sk.full_viewing_key();
626 let ivk = fvk.incoming();
627 let ovk = fvk.outgoing();
628 let (dest, _dtk_d) = ivk.payment_address(0u32.into());
629
630 let value = Value {
631 amount: 10u64.into(),
632 asset_id: asset::Cache::with_known_assets()
633 .get_unit("upenumbra")
634 .unwrap()
635 .id(),
636 };
637 let note = Note::generate(&mut rng, &dest, value);
638
639 let value_blinding = Fr::rand(&mut rng);
640 let cv = note.value.commit(value_blinding);
641
642 let wrapped_ovk = note.encrypt_key(ovk, cv);
643 let ciphertext = note.encrypt();
644
645 let esk = note.ephemeral_secret_key();
646 let epk = esk.diversified_public(dest.diversified_generator());
647 let plaintext =
648 Note::decrypt_outgoing(&ciphertext, wrapped_ovk, note.commit(), cv, ovk, &epk)
649 .expect("can decrypt note");
650
651 assert_eq!(plaintext, note);
652 }
653
654 #[test]
655 fn note_decryption_fails_with_incorrect_epk() {
656 let mut rng = OsRng;
657
658 let seed_phrase = SeedPhrase::generate(rng);
659 let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0));
660 let fvk = sk.full_viewing_key();
661 let ivk = fvk.incoming();
662 let (dest, _dtk_d) = ivk.payment_address(0u32.into());
663
664 let value = Value {
665 amount: 10u64.into(),
666 asset_id: asset::Cache::with_known_assets()
667 .get_unit("upenumbra")
668 .unwrap()
669 .id(),
670 };
671 let note = Note::generate(&mut rng, &dest, value);
672
673 let ciphertext = note.encrypt();
674
675 let wrong_esk = ka::Secret::new(&mut rng);
676 let wrong_epk = wrong_esk.diversified_public(dest.diversified_generator());
677 let decryption_result = Note::decrypt(&ciphertext, ivk, &wrong_epk);
678
679 assert!(decryption_result.is_err());
680 }
681}