diff --git a/app/rust/src/parser/note.rs b/app/rust/src/parser/note.rs index 944d1d3..70ff1be 100644 --- a/app/rust/src/parser/note.rs +++ b/app/rust/src/parser/note.rs @@ -2,11 +2,26 @@ use crate::address::Address; use crate::parser::{ address::AddressC, bytes::BytesC, - plans::{amount::Amount, rseed::Rseed, value::ValueC}, + plans::{rseed::Rseed, value::{Value, ValueC}}, }; use crate::ParserError; use decaf377::Fq; +pub struct Note { + /// The typed value recorded by this note. + value: Value, + /// A uniformly random 32-byte sequence used to derive an ephemeral secret key + /// and note blinding factor. + rseed: Rseed, + /// The address controlling this note. + address: Address, + /// The s-component of the transmission key of the destination address. + /// We store this separately to ensure that every `Note` is constructed + /// with a valid transmission key (the `ka::Public` does not validate + /// the curve point until it is used, since validation is not free). + transmission_key_s: Fq, +} + #[repr(C)] #[derive(Clone)] #[cfg_attr(any(feature = "derive-debug", test), derive(Debug))] @@ -16,16 +31,34 @@ pub struct NoteC { pub address: AddressC, } -impl NoteC { +impl TryFrom for Note { + type Error = ParserError; + + fn try_from(note_c: NoteC) -> Result { + let value = Value::try_from(note_c.value)?; + let rseed = Rseed::try_from(note_c.rseed)?; + let address = Address::try_from(note_c.address.inner.get_bytes()?)?; + let transmission_key_s = Fq::from_bytes_checked(&address.transmission_key().0).map_err(|_| ParserError::InvalidFvk).unwrap(); + + Ok(Note { + value, + rseed, + address, + transmission_key_s, + }) + } +} + +impl Note { pub fn commit(&self) -> Result { let commit = poseidon377::hash_6( &Self::notecommit_domain_sep(), ( self.note_blinding()?, - Amount::try_from(self.value.amount.clone())?.into(), - self.get_asset_id_fq()?, + self.value.amount.clone().into(), + self.value.asset_id.0, self.diversified_generator()?, - self.transmission_key_s()?, + self.transmission_key_s, self.clue_key()?, ), ); @@ -33,61 +66,21 @@ impl NoteC { } pub fn note_blinding(&self) -> Result { - let rseed_array: &[u8; 32] = self - .get_rseed()? - .try_into() - .expect("rseed should be 32 bytes"); - let rseed = Rseed(*rseed_array).derive_note_blinding()?; + let rseed = self.rseed.derive_note_blinding()?; Ok(rseed) } pub fn diversified_generator(&self) -> Result { - let diversifier_generator = Address::try_from(self.get_address()?) - .map_err(|_| ParserError::InvalidAddress) - .unwrap() - .diversifier() - .diversified_generator() - .vartime_compress_to_field(); + let diversifier_generator = self.address.diversifier().diversified_generator().vartime_compress_to_field(); Ok(diversifier_generator) } - pub fn transmission_key_s(&self) -> Result { - let transmission_key_s = Fq::from_bytes_checked( - &Address::try_from(self.get_address()?) - .unwrap() - .transmission_key() - .0, - ) - .map_err(|_| ParserError::InvalidFvk) - .unwrap(); - Ok(transmission_key_s) - } - pub fn clue_key(&self) -> Result { - let address = - Address::try_from(self.get_address()?).map_err(|_| ParserError::InvalidAddress)?; - let clue_key = address.clue_key(); + let clue_key = self.address.clue_key(); Ok(Fq::from_le_bytes_mod_order(&clue_key.0[..])) } pub fn notecommit_domain_sep() -> Fq { Fq::from_le_bytes_mod_order(blake2b_simd::blake2b(b"penumbra.notecommit").as_bytes()) } - - pub fn get_rseed(&self) -> Result<&[u8], ParserError> { - self.rseed.get_bytes() - } - - pub fn get_asset_id(&self) -> Result<&[u8], ParserError> { - self.value.asset_id.inner.get_bytes() - } - - pub fn get_address(&self) -> Result<&[u8], ParserError> { - self.address.inner.get_bytes() - } - - pub fn get_asset_id_fq(&self) -> Result { - let asset_id_bytes = self.get_asset_id()?; - Ok(Fq::from_le_bytes_mod_order(asset_id_bytes)) - } } diff --git a/app/rust/src/parser/plans/rseed.rs b/app/rust/src/parser/plans/rseed.rs index 00c7b4a..67aaaff 100644 --- a/app/rust/src/parser/plans/rseed.rs +++ b/app/rust/src/parser/plans/rseed.rs @@ -1,5 +1,6 @@ use decaf377::Fq; use crate::{ParserError, utils::prf}; +use crate::parser::bytes::BytesC; pub struct Rseed(pub [u8; 32]); @@ -9,4 +10,15 @@ impl Rseed { let hash_result = prf::expand(b"Penumbra_DeriRcm", &self.0, &[5u8])?; Ok(Fq::from_le_bytes_mod_order(&hash_result)) } -} \ No newline at end of file +} + +impl TryFrom for Rseed { + type Error = ParserError; + + fn try_from(value: BytesC) -> Result { + assert_eq!(value.len, 32, "Invalid rseed length"); + let mut rseed = [0u8; 32]; + rseed.copy_from_slice(value.get_bytes()?); + Ok(Rseed(rseed)) + } +} diff --git a/app/rust/src/parser/plans/spend.rs b/app/rust/src/parser/plans/spend.rs index 8553863..2cd0fda 100644 --- a/app/rust/src/parser/plans/spend.rs +++ b/app/rust/src/parser/plans/spend.rs @@ -2,7 +2,7 @@ use crate::effect_hash::{create_personalized_state, EffectHash}; use crate::keys::FullViewingKey; use crate::parser::{ bytes::BytesC, - note::NoteC, + note::{Note, NoteC}, plans::{nullifier::Nullifier, value::Value}, }; use crate::ParserError; @@ -134,7 +134,8 @@ impl SpendPlanC { pub fn nullifier(&self, fvk: &FullViewingKey) -> Result<[u8; 32], ParserError> { let nk = fvk.nullifier_key(); - let nullifier = Nullifier::derive(&nk, self.position, &self.note.commit()?); + let note = Note::try_from(self.note.clone())?; + let nullifier = Nullifier::derive(&nk, self.position, ¬e.commit()?); Ok(nullifier.0.to_bytes()) }