From 0eed0c1db31faeeb9210bac234888ba2553252ed Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Fri, 22 Nov 2024 11:21:21 -0500 Subject: [PATCH] UIP-4 Spend Backreferences (#4922) This implements UIP-4 as described in https://github.com/penumbra-zone/UIPs/pull/2 https://github.com/penumbra-zone/UIPs/pull/2 - [ ] I have added guiding text to explain how a reviewer should test these changes. - [x] If this code contains consensus-breaking changes, I have added the "consensus-breaking" label. Otherwise, I declare my belief that there are not consensus-breaking changes, for the following reason: --- crates/bin/pcli/src/terminal.rs | 4 +- .../component/shielded-pool/src/backref.rs | 217 ++++++++++++++++++ .../core/component/shielded-pool/src/lib.rs | 3 + .../shielded-pool/src/spend/action.rs | 19 ++ .../component/shielded-pool/src/spend/plan.rs | 6 +- crates/core/keys/src/keys/fvk.rs | 7 +- crates/core/keys/src/lib.rs | 2 +- crates/core/keys/src/symmetric.rs | 17 ++ ...enumbra.core.component.shielded_pool.v1.rs | 3 + ...a.core.component.shielded_pool.v1.serde.rs | 21 ++ .../proto/src/gen/proto_descriptor.bin.no_lfs | Bin 648110 -> 648288 bytes .../shielded_pool/v1/shielded_pool.proto | 4 +- 12 files changed, 298 insertions(+), 5 deletions(-) create mode 100644 crates/core/component/shielded-pool/src/backref.rs diff --git a/crates/bin/pcli/src/terminal.rs b/crates/bin/pcli/src/terminal.rs index a13f03d65b..95bf433242 100644 --- a/crates/bin/pcli/src/terminal.rs +++ b/crates/bin/pcli/src/terminal.rs @@ -11,7 +11,7 @@ use penumbra_keys::{ }; use penumbra_proof_params::GROTH16_PROOF_LENGTH_BYTES; use penumbra_sct::Nullifier; -use penumbra_shielded_pool::{Note, NoteView}; +use penumbra_shielded_pool::{EncryptedBackref, Note, NoteView}; use penumbra_tct::structure::Hash; use penumbra_transaction::{view, ActionPlan, ActionView, TransactionPlan, TransactionView}; use termion::{color, input::TermRead}; @@ -82,6 +82,8 @@ fn pretty_print_transaction_plan( balance_commitment: dummy_commitment(), nullifier: Nullifier(Fq::default()), rk: dummy_pk(), + encrypted_backref: EncryptedBackref::try_from([0u8; 0]) + .expect("can create dummy encrypted backref"), }, auth_sig: dummy_sig(), proof: dummy_proof_spend(), diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs new file mode 100644 index 0000000000..7eef7f4d96 --- /dev/null +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -0,0 +1,217 @@ +use anyhow::Result; +use chacha20poly1305::{ + aead::{Aead, NewAead}, + ChaCha20Poly1305, Nonce, +}; + +use penumbra_keys::BackreferenceKey; +use penumbra_sct::Nullifier; +use penumbra_tct as tct; + +pub const ENCRYPTED_BACKREF_LEN: usize = 48; + +#[derive(Clone, Debug, Eq, PartialEq)] +pub struct Backref { + note_commitment: tct::StateCommitment, +} + +#[derive(Clone, Debug)] +pub struct EncryptedBackref { + /// The inner bytes can either have 0 or `ENCRYPTED_BACKREF_LEN` bytes. + bytes: Vec, +} + +impl Backref { + pub fn new(note_commitment: tct::StateCommitment) -> Self { + Self { note_commitment } + } + + pub fn encrypt(&self, brk: &BackreferenceKey, nullifier: &Nullifier) -> EncryptedBackref { + let cipher = ChaCha20Poly1305::new(&brk.0); + + // Nonce is the first 12 bytes of the nullifier + let nonce_bytes = &nullifier.to_bytes()[..12]; + let nonce = Nonce::from_slice(&nonce_bytes); + + let plaintext = self.note_commitment.0.to_bytes(); + + let ciphertext = cipher + .encrypt(nonce, plaintext.as_ref()) + .expect("encryption should succeed "); + + EncryptedBackref { bytes: ciphertext } + } +} + +impl EncryptedBackref { + pub fn is_empty(&self) -> bool { + self.bytes.is_empty() + } + + pub fn len(&self) -> usize { + self.bytes.len() + } + + pub fn dummy() -> Self { + Self { bytes: vec![] } + } + + /// Decrypts the encrypted backref, returning a backref if the decryption is successful, + /// or `None` if the encrypted backref is zero-length. + pub fn decrypt( + &self, + brk: &BackreferenceKey, + nullifier: &Nullifier, + ) -> Result> { + // We might have a 0-length encrypted backref, which + // is treated as a valid value and means that the note has no backref. + if self.is_empty() { + return Ok(None); + } + + let cipher = ChaCha20Poly1305::new(&brk.0); + + let nonce_bytes = &nullifier.to_bytes()[..12]; + let nonce = Nonce::from_slice(&nonce_bytes); + + let plaintext = cipher + .decrypt(nonce, self.bytes.as_ref()) + .map_err(|_| anyhow::anyhow!("decryption error"))?; + + let note_commitment_bytes: [u8; 32] = plaintext + .try_into() + .map_err(|_| anyhow::anyhow!("decryption error"))?; + + let backref = Backref::try_from(note_commitment_bytes) + .map_err(|_| anyhow::anyhow!("decryption error"))?; + + Ok(Some(backref)) + } +} + +impl TryFrom<[u8; 32]> for Backref { + type Error = anyhow::Error; + + fn try_from(bytes: [u8; 32]) -> Result { + Ok(Self { + note_commitment: tct::StateCommitment::try_from(bytes) + .map_err(|_| anyhow::anyhow!("invalid note commitment"))?, + }) + } +} + +// EncryptedBackrefs can either have 0 or ENCRYPTED_BACKREF_LEN bytes. + +impl TryFrom<[u8; ENCRYPTED_BACKREF_LEN]> for EncryptedBackref { + type Error = anyhow::Error; + + fn try_from(bytes: [u8; ENCRYPTED_BACKREF_LEN]) -> Result { + Ok(Self { + bytes: bytes.to_vec(), + }) + } +} + +impl TryFrom<[u8; 0]> for EncryptedBackref { + type Error = anyhow::Error; + + fn try_from(bytes: [u8; 0]) -> Result { + Ok(Self { + bytes: bytes.to_vec(), + }) + } +} + +impl From for Vec { + fn from(encrypted_backref: EncryptedBackref) -> Vec { + encrypted_backref.bytes + } +} + +#[cfg(test)] +mod tests { + use super::*; + use proptest::prelude::*; + + use penumbra_asset::{asset, Value}; + use penumbra_keys::keys::{Bip44Path, SeedPhrase, SpendKey}; + + use crate::{Note, Rseed}; + + proptest! { + #[test] + fn encrypted_backref_zero_length(seed_phrase_randomness in any::<[u8; 32]>(), amount_to_send in any::(), rseed_randomness in any::<[u8; 32]>()) { + let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness); + let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); + let fvk = sk.full_viewing_key(); + let brk = fvk.backref_key(); + + let ivk = fvk.incoming(); + let (sender, _dtk_d) = ivk.payment_address(0u32.into()); + + let value_to_send = Value { + amount: amount_to_send.into(), + asset_id: asset::Cache::with_known_assets() + .get_unit("upenumbra") + .unwrap() + .id(), + }; + let rseed = Rseed(rseed_randomness); + + let note = Note::from_parts(sender, value_to_send, rseed).expect("valid note"); + let note_commitment: penumbra_tct::StateCommitment = note.commit(); + let nk = *sk.nullifier_key(); + let mut sct = tct::Tree::new(); + + sct.insert(tct::Witness::Keep, note_commitment).unwrap(); + let state_commitment_proof = sct.witness(note_commitment).unwrap(); + let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), ¬e_commitment); + + let encrypted_backref = EncryptedBackref::dummy(); + assert!(encrypted_backref.is_empty()); + assert_eq!(encrypted_backref.len(), 0); + + // Decrypting a zero-length encrypted backref should return `None`. + let decrypted_backref = encrypted_backref.decrypt(&brk, &nullifier).unwrap(); + assert_eq!(decrypted_backref, None); + } + } + + proptest! { + #[test] + fn encrypted_backref_round_trip(seed_phrase_randomness in any::<[u8; 32]>(), amount_to_send in any::(), rseed_randomness in any::<[u8; 32]>()) { + let seed_phrase = SeedPhrase::from_randomness(&seed_phrase_randomness); + let sk = SpendKey::from_seed_phrase_bip44(seed_phrase, &Bip44Path::new(0)); + let fvk = sk.full_viewing_key(); + let brk = fvk.backref_key(); + + let ivk = fvk.incoming(); + let (sender, _dtk_d) = ivk.payment_address(0u32.into()); + + let value_to_send = Value { + amount: amount_to_send.into(), + asset_id: asset::Cache::with_known_assets() + .get_unit("upenumbra") + .unwrap() + .id(), + }; + let rseed = Rseed(rseed_randomness); + + let note = Note::from_parts(sender, value_to_send, rseed).expect("valid note"); + let note_commitment: penumbra_tct::StateCommitment = note.commit(); + let nk = *sk.nullifier_key(); + let mut sct = tct::Tree::new(); + + sct.insert(tct::Witness::Keep, note_commitment).unwrap(); + let state_commitment_proof = sct.witness(note_commitment).unwrap(); + let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), ¬e_commitment); + + let backref = Backref::new(note_commitment); + let encrypted_backref = backref.encrypt(&brk, &nullifier); + + let decrypted_backref = encrypted_backref.decrypt(&brk, &nullifier).unwrap(); + + assert_eq!(Some(backref), decrypted_backref); + } + } +} diff --git a/crates/core/component/shielded-pool/src/lib.rs b/crates/core/component/shielded-pool/src/lib.rs index 84d112ed52..04109d614e 100644 --- a/crates/core/component/shielded-pool/src/lib.rs +++ b/crates/core/component/shielded-pool/src/lib.rs @@ -25,6 +25,9 @@ pub mod nullifier_derivation; pub mod output; pub mod spend; +pub mod backref; +pub use backref::{Backref, EncryptedBackref}; + pub use convert::{ConvertCircuit, ConvertProof, ConvertProofPrivate, ConvertProofPublic}; pub use nullifier_derivation::{ NullifierDerivationCircuit, NullifierDerivationProof, NullifierDerivationProofPrivate, diff --git a/crates/core/component/shielded-pool/src/spend/action.rs b/crates/core/component/shielded-pool/src/spend/action.rs index 2a548a592a..83517d6748 100644 --- a/crates/core/component/shielded-pool/src/spend/action.rs +++ b/crates/core/component/shielded-pool/src/spend/action.rs @@ -9,6 +9,7 @@ use penumbra_txhash::{EffectHash, EffectingData}; use serde::{Deserialize, Serialize}; use crate::SpendProof; +use crate::{backref::ENCRYPTED_BACKREF_LEN, EncryptedBackref}; #[derive(Clone, Debug)] pub struct Spend { @@ -23,6 +24,7 @@ pub struct Body { pub balance_commitment: balance::Commitment, pub nullifier: Nullifier, pub rk: VerificationKey, + pub encrypted_backref: EncryptedBackref, } impl EffectingData for Body { @@ -91,6 +93,7 @@ impl From for pb::SpendBody { balance_commitment: Some(msg.balance_commitment.into()), nullifier: Some(msg.nullifier.into()), rk: Some(msg.rk.into()), + encrypted_backref: msg.encrypted_backref.into(), } } } @@ -117,10 +120,26 @@ impl TryFrom for Body { .try_into() .context("malformed rk")?; + // `EncryptedBackref` must have 0 or `ENCRYPTED_BACKREF_LEN` bytes. + let encrypted_backref: EncryptedBackref; + if proto.encrypted_backref.len() == ENCRYPTED_BACKREF_LEN { + let bytes: [u8; ENCRYPTED_BACKREF_LEN] = proto + .encrypted_backref + .try_into() + .map_err(|_| anyhow::anyhow!("invalid encrypted backref"))?; + encrypted_backref = EncryptedBackref::try_from(bytes) + .map_err(|_| anyhow::anyhow!("invalid encrypted backref"))?; + } else if proto.encrypted_backref.len() == 0 { + encrypted_backref = EncryptedBackref::dummy(); + } else { + return Err(anyhow::anyhow!("invalid encrypted backref length")); + } + Ok(Body { balance_commitment, nullifier, rk, + encrypted_backref, }) } } diff --git a/crates/core/component/shielded-pool/src/spend/plan.rs b/crates/core/component/shielded-pool/src/spend/plan.rs index a79fd41d99..1602badd0b 100644 --- a/crates/core/component/shielded-pool/src/spend/plan.rs +++ b/crates/core/component/shielded-pool/src/spend/plan.rs @@ -9,7 +9,7 @@ use rand_core::{CryptoRng, RngCore}; use serde::{Deserialize, Serialize}; use super::{Body, Spend, SpendProof}; -use crate::{Note, Rseed, SpendProofPrivate, SpendProofPublic}; +use crate::{Backref, Note, Rseed, SpendProofPrivate, SpendProofPublic}; /// A planned [`Spend`](Spend). #[derive(Clone, Debug, Deserialize, Serialize)] @@ -75,10 +75,14 @@ impl SpendPlan { /// Construct the [`spend::Body`] described by this [`SpendPlan`]. pub fn spend_body(&self, fvk: &FullViewingKey) -> Body { + // Construct the backreference for this spend. + let backref = Backref::new(self.note.commit()); + let encrypted_backref = backref.encrypt(&fvk.backref_key(), &self.nullifier(fvk)); Body { balance_commitment: self.balance().commit(self.value_blinding), nullifier: self.nullifier(fvk), rk: self.rk(fvk), + encrypted_backref, } } diff --git a/crates/core/keys/src/keys/fvk.rs b/crates/core/keys/src/keys/fvk.rs index e9a029578f..44239700bf 100644 --- a/crates/core/keys/src/keys/fvk.rs +++ b/crates/core/keys/src/keys/fvk.rs @@ -12,7 +12,7 @@ use crate::keys::wallet_id::WalletId; use crate::{ fmd, ka, prf, rdsa::{SpendAuth, VerificationKey}, - Address, AddressView, + Address, AddressView, BackreferenceKey, }; use super::{AddressIndex, DiversifierKey, IncomingViewingKey, NullifierKey, OutgoingViewingKey}; @@ -120,6 +120,11 @@ impl FullViewingKey { &self.ak } + /// Construct the backreference key for this full viewing key. + pub fn backref_key(&self) -> BackreferenceKey { + BackreferenceKey::derive(self.outgoing()).clone() + } + /// Hashes the full viewing key into an [`WalletId`]. pub fn wallet_id(&self) -> WalletId { let hash_result = hash_2( diff --git a/crates/core/keys/src/lib.rs b/crates/core/keys/src/lib.rs index 81c8c8be7f..a880e51832 100644 --- a/crates/core/keys/src/lib.rs +++ b/crates/core/keys/src/lib.rs @@ -14,7 +14,7 @@ pub mod test_keys; pub use address::{Address, AddressVar, AddressView}; pub use keys::FullViewingKey; -pub use symmetric::PayloadKey; +pub use symmetric::{BackreferenceKey, PayloadKey}; fn fmt_hex>(data: T, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", hex::encode(data)) diff --git a/crates/core/keys/src/symmetric.rs b/crates/core/keys/src/symmetric.rs index 4e383d15c3..cc04585ed4 100644 --- a/crates/core/keys/src/symmetric.rs +++ b/crates/core/keys/src/symmetric.rs @@ -344,3 +344,20 @@ impl TryFrom<&[u8]> for WrappedMemoKey { Ok(Self(bytes)) } } + +/// Represents a symmetric `ChaCha20Poly1305` key used for Spend backreferences. +#[derive(Debug, Copy, Clone, PartialEq, Eq)] +pub struct BackreferenceKey(pub Key); + +impl BackreferenceKey { + pub fn derive(ovk: &OutgoingViewingKey) -> Self { + let mut kdf_params = blake2b_simd::Params::new(); + kdf_params.personal(b"Penumbra_Backref"); + kdf_params.hash_length(32); + let mut kdf = kdf_params.to_state(); + kdf.update(&ovk.to_bytes()); + + let key = kdf.finalize(); + Self(*Key::from_slice(key.as_bytes())) + } +} diff --git a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs index 1362033de8..e287e3e5e6 100644 --- a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs +++ b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.rs @@ -420,6 +420,9 @@ pub struct SpendBody { pub rk: ::core::option::Option< super::super::super::super::crypto::decaf377_rdsa::v1::SpendVerificationKey, >, + /// An encryption of the commitment of the input note to the sender's OVK. + #[prost(bytes = "vec", tag = "7")] + pub encrypted_backref: ::prost::alloc::vec::Vec, } impl ::prost::Name for SpendBody { const NAME: &'static str = "SpendBody"; diff --git a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs index 85e3edf22e..c77243e1d0 100644 --- a/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs +++ b/crates/proto/src/gen/penumbra.core.component.shielded_pool.v1.serde.rs @@ -3789,6 +3789,9 @@ impl serde::Serialize for SpendBody { if self.rk.is_some() { len += 1; } + if !self.encrypted_backref.is_empty() { + len += 1; + } let mut struct_ser = serializer.serialize_struct("penumbra.core.component.shielded_pool.v1.SpendBody", len)?; if let Some(v) = self.balance_commitment.as_ref() { struct_ser.serialize_field("balanceCommitment", v)?; @@ -3799,6 +3802,10 @@ impl serde::Serialize for SpendBody { if let Some(v) = self.rk.as_ref() { struct_ser.serialize_field("rk", v)?; } + if !self.encrypted_backref.is_empty() { + #[allow(clippy::needless_borrow)] + struct_ser.serialize_field("encryptedBackref", pbjson::private::base64::encode(&self.encrypted_backref).as_str())?; + } struct_ser.end() } } @@ -3813,6 +3820,8 @@ impl<'de> serde::Deserialize<'de> for SpendBody { "balanceCommitment", "nullifier", "rk", + "encrypted_backref", + "encryptedBackref", ]; #[allow(clippy::enum_variant_names)] @@ -3820,6 +3829,7 @@ impl<'de> serde::Deserialize<'de> for SpendBody { BalanceCommitment, Nullifier, Rk, + EncryptedBackref, __SkipField__, } impl<'de> serde::Deserialize<'de> for GeneratedField { @@ -3845,6 +3855,7 @@ impl<'de> serde::Deserialize<'de> for SpendBody { "balanceCommitment" | "balance_commitment" => Ok(GeneratedField::BalanceCommitment), "nullifier" => Ok(GeneratedField::Nullifier), "rk" => Ok(GeneratedField::Rk), + "encryptedBackref" | "encrypted_backref" => Ok(GeneratedField::EncryptedBackref), _ => Ok(GeneratedField::__SkipField__), } } @@ -3867,6 +3878,7 @@ impl<'de> serde::Deserialize<'de> for SpendBody { let mut balance_commitment__ = None; let mut nullifier__ = None; let mut rk__ = None; + let mut encrypted_backref__ = None; while let Some(k) = map_.next_key()? { match k { GeneratedField::BalanceCommitment => { @@ -3887,6 +3899,14 @@ impl<'de> serde::Deserialize<'de> for SpendBody { } rk__ = map_.next_value()?; } + GeneratedField::EncryptedBackref => { + if encrypted_backref__.is_some() { + return Err(serde::de::Error::duplicate_field("encryptedBackref")); + } + encrypted_backref__ = + Some(map_.next_value::<::pbjson::private::BytesDeserialize<_>>()?.0) + ; + } GeneratedField::__SkipField__ => { let _ = map_.next_value::()?; } @@ -3896,6 +3916,7 @@ impl<'de> serde::Deserialize<'de> for SpendBody { balance_commitment: balance_commitment__, nullifier: nullifier__, rk: rk__, + encrypted_backref: encrypted_backref__.unwrap_or_default(), }) } } diff --git a/crates/proto/src/gen/proto_descriptor.bin.no_lfs b/crates/proto/src/gen/proto_descriptor.bin.no_lfs index 07410da27bbcc3d19b4f110072ab6deee0e843dc..d0511fd1ddedf5cf99077bb3b1f61779d626627a 100644 GIT binary patch delta 3832 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 diff --git a/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto b/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto index f93427e8e8..d2c9cb55f7 100644 --- a/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto +++ b/proto/penumbra/penumbra/core/component/shielded_pool/v1/shielded_pool.proto @@ -42,7 +42,7 @@ message FmdMetaParameters { } // How much time users have to transition to new parameters. - uint64 fmd_grace_period_blocks = 1; + uint64 fmd_grace_period_blocks = 1; // The algorithm governing how the parameters change. oneof algorithm { uint32 fixed_precision_bits = 2; @@ -154,6 +154,8 @@ message SpendBody { sct.v1.Nullifier nullifier = 6; // The randomized validating key for the spend authorization signature. crypto.decaf377_rdsa.v1.SpendVerificationKey rk = 4; + // An encryption of the commitment of the input note to the sender's OVK. + bytes encrypted_backref = 7; } message SpendView { message Visible {