penumbra_sdk_shielded_pool/
note.rs

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/// A plaintext Penumbra note.
33#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
34#[serde(into = "pb::Note", try_from = "pb::Note")]
35pub struct Note {
36    /// The typed value recorded by this note.
37    value: Value,
38    /// A uniformly random 32-byte sequence used to derive an ephemeral secret key
39    /// and note blinding factor.
40    rseed: Rseed,
41    /// The address controlling this note.
42    address: Address,
43    /// The s-component of the transmission key of the destination address.
44    /// We store this separately to ensure that every `Note` is constructed
45    /// with a valid transmission key (the `ka::Public` does not validate
46    /// the curve point until it is used, since validation is not free).
47    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/// A note ciphertext.
83#[derive(Clone, PartialEq, Eq, Serialize, Deserialize)]
84#[serde(into = "pb::NoteCiphertext", try_from = "pb::NoteCiphertext")]
85pub struct NoteCiphertext(pub [u8; NOTE_CIPHERTEXT_BYTES]);
86
87/// The domain separator used to generate note commitments.
88pub(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            // Check if this note is associated with the wallet's transparent address.
112            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            // Get the expected clue key and check it matches what is on the provided note address.
123            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    /// Obtain a note corresponding to this allocation.
138    ///
139    /// Note: to ensure determinism, this uses a zero rseed when
140    /// creating the note.
141    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    /// Generate a fresh note representing the given value for the given destination address, with a
175    /// random blinding factor.
176    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    /// Encrypt a note, returning its ciphertext.
236    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    /// Generate encrypted outgoing cipher key for use with this note.
255    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    /// Decrypt wrapped OVK to generate the transmission key and ephemeral secret
273    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    /// Decrypt a note ciphertext using the wrapped OVK to generate a plaintext `Note`.
297    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    /// Decrypt a note ciphertext using the IVK and ephemeral public key to generate a plaintext `Note`.
313    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    /// Decrypt a note ciphertext using the [`PayloadKey`].
327    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        // Ephemeral public key integrity check. See ZIP 212 or Penumbra issue #1688.
344        if note.ephemeral_public_key() != *epk {
345            return Err(Error::DecryptionError);
346        }
347
348        Ok(note)
349    }
350
351    /// Create the note commitment for this note.
352    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
367/// Create a note commitment from its parts.
368pub 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
390/// Create a note commitment from the blinding factor, value, and address.
391pub 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(&note.address.to_vec());
485        bytes[80..96].copy_from_slice(&note.value.amount.to_le_bytes());
486        bytes[96..128].copy_from_slice(&note.value.asset_id.0.to_bytes());
487        bytes[128..160].copy_from_slice(&note.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        (&note).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(&note.address().to_vec());
502        bytes.extend_from_slice(&note.value.amount.to_le_bytes());
503        bytes.extend_from_slice(&note.value.asset_id.0.to_bytes());
504        bytes.extend_from_slice(&note.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}