From 17db3bc8ab28887e4330a303f4122df04112a84f Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Tue, 12 Nov 2024 19:32:26 -0500 Subject: [PATCH 01/13] UIP 4: add `encrypted_backref` field to `SpendBody` --- ...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 627367 -> 627545 bytes .../shielded_pool/v1/shielded_pool.proto | 4 +++- 4 files changed, 27 insertions(+), 1 deletion(-) 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 f2452b1c4b474c5738ad69f0c7d1b1e5f47508b9..70880248d1fa01386236afd3ab38f2d09499eaa5 100644 GIT binary patch delta 3815 zcmYjUZ%kI_6@Sm+a=BhmcrUy!@WP8Pv}zSE40LL>?X+v0)^;XMYnLtEvRS${jc6=e zvg`v^XAE1Kur&7R2ZvCLh>b-Qf)M{imX1)M(jZd*qBhnUW86}+FU(}W^W6LJ_QU(( zcb@b6o%5XYoWrG~ec|Q93wQO)tBLO3_m}ujI`hJRjKmUOr4+^W1%JxLICiBr?XTyNC)@$v!K)Z$U5Z zh*C@6hySD~ds|$~%Hs^r`a5x1Vnb?Oz3Q&!{Kl=@w{3g7VOw2&Lu&h5sfORxrQWXJ z@%x5U{q}~sRKs>{dAF|q&AOe7-c4=ZvZ=~-=en69m8rF|3?zY0v4J{LY0}Mc*@}Ah zL5h4zF zf+}fwz-$BIdxUcG4TSU&iWi1d9Cpg}rE^i1%LD30t`)40v!mu1+)lZ;w9fPFV-y(( zTj#;^n6z#=GV&y&F16{j1?LC^Tz#7;<& z1iEQXkbuVoO1C5sL}AZ5sWy)w%1Pqjxp~Z>oRr$gl>k9GNwK*5f{p^zt7C6Vw8y;!4E{cY`Kcp4CU3OsrPXi8I~tUgbi8~R9X&r@E^Kv2$8QL#Hn z2wvdXLlpT+#lNKUxkFOUSD;iN!H3niU(&1oF!5YXg%L`_G%sNwl!hr;=Ke%ea5$oV zdx19kBgCblj|3l~WVL~yj8N?|_i0T*W5?9>3$&(c%+#ttH+Ia_3J{brsnvU=Ie_D8 z{W$Gu98Ytkr{m-lhGC?q;!86BM}>KHwNUC*+>8dt$PBE)stoP8=|fFPhZ@ zD213bdqZQ44h*iF+lXbPEuc&ihDR2OHX!`?p-EJkhbbzoNV`B|iWV#kZ;JJGh4|X+O(o_Pv%X>(dVO6@ z3kK^82HaHND_CDw(-&fct@7^@2V)r53EU;GEL4yL z?oxS$9^^QqclXrz1P1w@d9g4;=pH%2XaRz9kMg2=eZ>Xcm-V&2_I{cokNmz_T<{Sm zNlf-m9E;j|Ad72_EG}Pdo}`#wTrdWU3oa0NU?vL?0uN-eUep3uTn|<0B)wGoFinxg z^)OA5#r04q>s+?c?(Zn_Kcd7k<-e9uqIxH(eA9R26iCBuJv)zxw@6h4LFy6NdPW7J zERU$@2j&#Yk7&)S?gVQtoM-yVFVSoC+zi=z*MdYK%}{Y|NRo#zL(l%$KxoX+3%_)q zLE{TOdlyIcuo~ZNugEWi-Y#}h?n>OXP#V8mJ)WYcH|}N*wg!yw+08{MQ4=4i?dB(* z)X%8Mv-fi3FC2Wp6v1;ZJLT?P*-1qr*Q6T1rhT^DgR zLT$e!JtluFN<8}jN7~eXuFz=i0d^|2Qi&+FsL`vm&TrxHPdbcHYT-G-Q~^S%h4TyC zY6LHl*WapEU!$6rThkPo<5mv80^kEmD~EGj>e+`l@@aULQg|L>XNmhP)Ji3RcIH%~ zTJ_(eq?)`&NgsybPaclp-Olk6T_Ql}wsYBh*VkkmcBuK+soL*g4yL|TPjm+tCPRv( z(!r^6_a#liE9_K%x=ufB>@?Yz>WS_&*#m;o8DuXh0v%-z{tg16hky%0d712^yl{zu z+S#wtJxt>JizELCFP?%~hI*H4Gp=x)u{;)se&C%@r*2SX#R;sESw9)L6O7NQ=R!Z=9+f#wYg0XJ<<0uZ zlIxN7E=GZp67`g-o2E+tlxYKuh;hoa0T8!5C2d%uDL5QZXQpYXKVaIB)NL3rZ2$yi zK-#cYQxNmCir=K?{nMrmN!^CirVW6goR&5$)fAw!YVS>|sydsdNcqq5oZy2gsTb>6 z#{UjYGKERmtb^*-P5SGGLDMoA!5K6y1BCD(hb=4f?9UnhiUrG}44$8}vsl0UGAYfF z>bXTV{*WmRjPMyUr2&N6kd#I~>&j$UhLv}lR{FyxX&A+7k_H52Sdy;P6qIH}{oywG zKOZsGEYqbK;lla)nE`?_!WB=*wnh}7QS(B9@InED7Yc}IqvpDR;=J$iZNiW7%fQdV RFB3l-zpTE;x1FP1{|CP(5gY&j delta 3713 zcmZu!T}&L;72b1p7>40@2iRp77Go0QU_4M`V(d6+LmQ=Wn?y~NHfmJ0RUfLVeW?1{ zheG03q_RpBVd+{C!Ep%S)Uh#kVUc5GQil+N!FB-4*brKh-dc_AEBsQ=;jEwCbuN4lBCMMY?Z*+0fBZ? zCL*BpWy?eWTl58Gq7e=n(NBETY~e_LKRKRTAbpYz2FR)ix}@a+^9@Aq02P!P2;~8a zC)|RcX)aa=<@mb8EQbel4b-doF$2LJl!MFUJkLHxk+E=c9x{)~Fg&(E9-+!Acb_KbcveO| zJ4@eh%w#D!pk%U?98fYs*^VxkdiMJinNU-+)KL6BIYn+Wgi58s|gU?Y(xq+aZql%>acf#gno;^vCi$tl@ zq@J6g2h~45qJyPn;7m$~pNDRllrp6bU8EQMDdIvcgdlRJs3>J1bf+j??S5BNusE%f zmuR0qOi-?$H#?dPco=iS{DHO zKbL5K`&^bI)14zH5pE;XougExzSHHNeVHPw;hio==4CmjxKX*d(Wk^$!bb><-3 zvT-y(6u+z;O~xc=Tanu24FG|iu@~_ejJ%MW%@fH z8<&dK)c8E@^w)?>v^s*wS)*9686ecw2u`s}Q?Phjr59+of15aXlyP0aZStx^1u5V* zZLZbNI?kBg9relrwKU%`Hx@w%-61DQ$g3QD`&1xvKRcV5yauUI6o*85ZiR|=6byiAhzk^?Y^!$<=L-uU;wcgD zRy(g#TT{0w8bRcAo1y`s)-6TP%HNnu&whg=hgIKox={QEI}KW?Qj~hsAC{@j@8R%s z06{4Aa9;5I0HM^wrDbjtHm{Ut(yRWtOgo6wz-c%tx5{$W3EXxso&h9wCY@;w2wgWmlDg!?&El+ZV@1K`?z|O>uWL=`_<(Y zYV!M;1AC~_PrsiN>5w9&^mEN-_i0VRZ5&koXY_3QpsBt}Km9>dJs>E9LG@B1&{5{# z4<-<12)HGbm)Cxj>$VxFll?|L!!*9#IPyVw@H8@yvGb62|FpFQZ}UCo(;R$ur!6_^ zdz@S7ZpCLeptRxSSl=?6ayp!tJtJF|?685r@ClBu=*HM+YK@I+>83TgYW7LcR5T_iKF>KQmERLz) z-k|ONm>EM_k73M=0T7fi8N=h6f<4cuk8jYo{WE3^X+4HBW(a6 Date: Tue, 12 Nov 2024 20:40:24 -0500 Subject: [PATCH 02/13] UIP 4: add `EncryptedBackref` struct --- crates/bin/pcli/src/terminal.rs | 4 +++- crates/core/component/shielded-pool/src/backref.rs | 12 ++++++++++++ crates/core/component/shielded-pool/src/lib.rs | 3 +++ .../component/shielded-pool/src/spend/action.rs | 14 ++++++++++++++ .../core/component/shielded-pool/src/spend/plan.rs | 4 +++- 5 files changed, 35 insertions(+), 2 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..7df9ead9b8 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(), + // todo: populate property + encrypted_backref: EncryptedBackref { bytes: vec![] }, }, 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..706ff17a2e --- /dev/null +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -0,0 +1,12 @@ +use penumbra_tct as tct; + +pub const ENCRYPTED_BACKREF_LEN: usize = 48; + +pub struct Backref { + pub note_commitment: tct::StateCommitment, +} + +#[derive(Clone, Debug)] +pub struct EncryptedBackref { + pub bytes: Vec, +} 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..0a77949695 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.bytes, } } } @@ -117,10 +120,21 @@ impl TryFrom for Body { .try_into() .context("malformed rk")?; + // `EncryptedBackref` must have 0 or `ENCRYPTED_BACKREF_LEN` bytes. + if proto.encrypted_backref.len() != ENCRYPTED_BACKREF_LEN + && proto.encrypted_backref.len() != 0 + { + return Err(anyhow::anyhow!("invalid encrypted backref length")); + } + let encrypted_backref = EncryptedBackref { + bytes: proto.encrypted_backref, + }; + 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..a8adaf18b2 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::{EncryptedBackref, Note, Rseed, SpendProofPrivate, SpendProofPublic}; /// A planned [`Spend`](Spend). #[derive(Clone, Debug, Deserialize, Serialize)] @@ -79,6 +79,8 @@ impl SpendPlan { balance_commitment: self.balance().commit(self.value_blinding), nullifier: self.nullifier(fvk), rk: self.rk(fvk), + // todo: populate property + encrypted_backref: EncryptedBackref { bytes: vec![] }, } } From d330dd7c5efcf6d0fe607d50c59e9411108cf2e2 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Wed, 13 Nov 2024 15:33:44 -0500 Subject: [PATCH 03/13] UIP 4: Backreference Key derivation --- .../core/component/shielded-pool/src/backref.rs | 1 + crates/core/keys/src/lib.rs | 2 +- crates/core/keys/src/symmetric.rs | 17 +++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs index 706ff17a2e..3dc20490eb 100644 --- a/crates/core/component/shielded-pool/src/backref.rs +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -1,3 +1,4 @@ +use penumbra_keys::BackreferenceKey; use penumbra_tct as tct; pub const ENCRYPTED_BACKREF_LEN: usize = 48; 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..d6194035ec 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(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(64); + let mut kdf = kdf_params.to_state(); + kdf.update(&ovk.to_bytes()); + + let key = kdf.finalize(); + Self(*Key::from_slice(key.as_bytes())) + } +} From 9a0df181aa1f957f4da42e6a9bd38f71c8e4eb25 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Wed, 13 Nov 2024 15:44:02 -0500 Subject: [PATCH 04/13] UIP 4: Implement backreference encryption --- .../component/shielded-pool/src/backref.rs | 29 +++++++++++++++++++ crates/core/keys/src/symmetric.rs | 2 +- 2 files changed, 30 insertions(+), 1 deletion(-) diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs index 3dc20490eb..6f2d3d8e3b 100644 --- a/crates/core/component/shielded-pool/src/backref.rs +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -1,4 +1,11 @@ +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; @@ -11,3 +18,25 @@ pub struct Backref { pub struct EncryptedBackref { pub bytes: Vec, } + +impl Backref { + pub fn encrypt( + &self, + brk: &BackreferenceKey, + nullifier: &Nullifier, + ) -> Result { + 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()) + .map_err(|_| anyhow::anyhow!("encryption error"))?; + + Ok(EncryptedBackref { bytes: ciphertext }) + } +} diff --git a/crates/core/keys/src/symmetric.rs b/crates/core/keys/src/symmetric.rs index d6194035ec..24c1f40e18 100644 --- a/crates/core/keys/src/symmetric.rs +++ b/crates/core/keys/src/symmetric.rs @@ -347,7 +347,7 @@ impl TryFrom<&[u8]> for WrappedMemoKey { /// Represents a symmetric `ChaCha20Poly1305` key used for Spend backreferences. #[derive(Debug, Copy, Clone, PartialEq, Eq)] -pub struct BackreferenceKey(Key); +pub struct BackreferenceKey(pub Key); impl BackreferenceKey { pub fn derive(ovk: &OutgoingViewingKey) -> Self { From c3009e0b76bbb8522beacd0c6a996fdfc9d181f5 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Wed, 13 Nov 2024 15:47:42 -0500 Subject: [PATCH 05/13] UIP 4: Implement Backreference decryption --- .../component/shielded-pool/src/backref.rs | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs index 6f2d3d8e3b..a61ccb0c2c 100644 --- a/crates/core/component/shielded-pool/src/backref.rs +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -40,3 +40,25 @@ impl Backref { Ok(EncryptedBackref { bytes: ciphertext }) } } + +impl EncryptedBackref { + pub fn decrypt(&self, brk: &BackreferenceKey, nullifier: &Nullifier) -> Result { + 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"))?; + + Ok(Backref { + note_commitment: tct::StateCommitment::try_from(note_commitment_bytes) + .map_err(|_| anyhow::anyhow!("decryption error"))?, + }) + } +} From 398de20a5ce8df5e6d6a858878a64e5e25dfe31a Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Wed, 13 Nov 2024 18:49:32 -0500 Subject: [PATCH 06/13] UIP 4: Thread spend backrefs through planning/pcli --- crates/bin/pcli/src/terminal.rs | 4 +- .../component/shielded-pool/src/backref.rs | 58 +++++++++++++++++-- .../shielded-pool/src/spend/action.rs | 20 ++++--- .../component/shielded-pool/src/spend/plan.rs | 11 +++- crates/core/keys/src/keys/fvk.rs | 7 ++- 5 files changed, 82 insertions(+), 18 deletions(-) diff --git a/crates/bin/pcli/src/terminal.rs b/crates/bin/pcli/src/terminal.rs index 7df9ead9b8..95bf433242 100644 --- a/crates/bin/pcli/src/terminal.rs +++ b/crates/bin/pcli/src/terminal.rs @@ -82,8 +82,8 @@ fn pretty_print_transaction_plan( balance_commitment: dummy_commitment(), nullifier: Nullifier(Fq::default()), rk: dummy_pk(), - // todo: populate property - encrypted_backref: EncryptedBackref { bytes: vec![] }, + 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 index a61ccb0c2c..d1bf3d3438 100644 --- a/crates/core/component/shielded-pool/src/backref.rs +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -11,15 +11,20 @@ use penumbra_tct as tct; pub const ENCRYPTED_BACKREF_LEN: usize = 48; pub struct Backref { - pub note_commitment: tct::StateCommitment, + note_commitment: tct::StateCommitment, } #[derive(Clone, Debug)] pub struct EncryptedBackref { - pub bytes: Vec, + /// 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, @@ -43,6 +48,13 @@ impl Backref { impl EncryptedBackref { 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.bytes.is_empty() { + let zero_commitment = tct::StateCommitment::try_from([0u8; 32])?; + return Ok(Backref::new(zero_commitment)); + } + let cipher = ChaCha20Poly1305::new(&brk.0); let nonce_bytes = &nullifier.to_bytes()[..12]; @@ -56,9 +68,45 @@ impl EncryptedBackref { .try_into() .map_err(|_| anyhow::anyhow!("decryption error"))?; - Ok(Backref { - note_commitment: tct::StateCommitment::try_from(note_commitment_bytes) - .map_err(|_| anyhow::anyhow!("decryption error"))?, + Backref::try_from(note_commitment_bytes).map_err(|_| anyhow::anyhow!("decryption error")) + } +} + +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 + } +} diff --git a/crates/core/component/shielded-pool/src/spend/action.rs b/crates/core/component/shielded-pool/src/spend/action.rs index 0a77949695..868db1f466 100644 --- a/crates/core/component/shielded-pool/src/spend/action.rs +++ b/crates/core/component/shielded-pool/src/spend/action.rs @@ -93,7 +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.bytes, + encrypted_backref: msg.encrypted_backref.into(), } } } @@ -121,14 +121,20 @@ impl TryFrom for Body { .context("malformed rk")?; // `EncryptedBackref` must have 0 or `ENCRYPTED_BACKREF_LEN` bytes. - if proto.encrypted_backref.len() != ENCRYPTED_BACKREF_LEN - && proto.encrypted_backref.len() != 0 - { + 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::try_from([0u8; ENCRYPTED_BACKREF_LEN]) + .context("invalid encrypted backref")?; + } else { return Err(anyhow::anyhow!("invalid encrypted backref length")); } - let encrypted_backref = EncryptedBackref { - bytes: proto.encrypted_backref, - }; Ok(Body { balance_commitment, diff --git a/crates/core/component/shielded-pool/src/spend/plan.rs b/crates/core/component/shielded-pool/src/spend/plan.rs index a8adaf18b2..b897a453a1 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::{EncryptedBackref, Note, Rseed, SpendProofPrivate, SpendProofPublic}; +use crate::{Backref, Note, Rseed, SpendProofPrivate, SpendProofPublic}; /// A planned [`Spend`](Spend). #[derive(Clone, Debug, Deserialize, Serialize)] @@ -75,12 +75,17 @@ 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()); + // TODO: This is fallible + let encrypted_backref = backref + .encrypt(&fvk.backref_key(), &self.nullifier(fvk)) + .expect("can encrypt"); Body { balance_commitment: self.balance().commit(self.value_blinding), nullifier: self.nullifier(fvk), rk: self.rk(fvk), - // todo: populate property - encrypted_backref: EncryptedBackref { bytes: vec![] }, + 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( From 89b547922a2fb9ad73dbd124405e50e458f12ace Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Thu, 14 Nov 2024 17:22:37 -0500 Subject: [PATCH 07/13] UIP 4: Use Blake2b-256 for derivation of backreference key --- crates/core/keys/src/symmetric.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/core/keys/src/symmetric.rs b/crates/core/keys/src/symmetric.rs index 24c1f40e18..cc04585ed4 100644 --- a/crates/core/keys/src/symmetric.rs +++ b/crates/core/keys/src/symmetric.rs @@ -353,7 +353,7 @@ 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(64); + kdf_params.hash_length(32); let mut kdf = kdf_params.to_state(); kdf.update(&ovk.to_bytes()); From 7320865c1e7f28f3b33f611d32acbb6dd779cab8 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Thu, 14 Nov 2024 17:22:55 -0500 Subject: [PATCH 08/13] test: add round trip test for UIP 4 backreferences --- .../component/shielded-pool/src/backref.rs | 49 +++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs index d1bf3d3438..b770692131 100644 --- a/crates/core/component/shielded-pool/src/backref.rs +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -10,6 +10,7 @@ use penumbra_tct as tct; pub const ENCRYPTED_BACKREF_LEN: usize = 48; +#[derive(Clone, Debug, Eq, PartialEq)] pub struct Backref { note_commitment: tct::StateCommitment, } @@ -110,3 +111,51 @@ impl From for Vec { encrypted_backref.bytes } } + +#[cfg(test)] +mod tests { + use super::*; + + use penumbra_asset::{asset, Value}; + use penumbra_keys::keys::{Bip44Path, SeedPhrase, SpendKey}; + use rand_core::OsRng; + + use crate::Note; + + #[test] + fn encrypted_backref_round_trip() { + let rng = OsRng; + + let seed_phrase = SeedPhrase::generate(rng); + 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: 1u64.into(), + asset_id: asset::Cache::with_known_assets() + .get_unit("upenumbra") + .unwrap() + .id(), + }; + + let note = Note::generate(&mut OsRng, &sender, value_to_send); + 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).unwrap(); + + let decrypted_backref = encrypted_backref.decrypt(&brk, &nullifier).unwrap(); + + assert_eq!(backref, decrypted_backref); + } +} From 128345b64e62f3fb218c32db25ad4e6757bdbb16 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Thu, 14 Nov 2024 17:59:20 -0500 Subject: [PATCH 09/13] UIP 4: Return `Option` from encryption method, add helper functions --- .../component/shielded-pool/src/backref.rs | 30 +++++++++++++++---- 1 file changed, 25 insertions(+), 5 deletions(-) diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs index b770692131..a110d946c7 100644 --- a/crates/core/component/shielded-pool/src/backref.rs +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -48,12 +48,29 @@ impl Backref { } impl EncryptedBackref { - pub fn decrypt(&self, brk: &BackreferenceKey, nullifier: &Nullifier) -> Result { + 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.bytes.is_empty() { - let zero_commitment = tct::StateCommitment::try_from([0u8; 32])?; - return Ok(Backref::new(zero_commitment)); + if self.is_empty() { + return Ok(None); } let cipher = ChaCha20Poly1305::new(&brk.0); @@ -69,7 +86,10 @@ impl EncryptedBackref { .try_into() .map_err(|_| anyhow::anyhow!("decryption error"))?; - Backref::try_from(note_commitment_bytes).map_err(|_| anyhow::anyhow!("decryption error")) + let backref = Backref::try_from(note_commitment_bytes) + .map_err(|_| anyhow::anyhow!("decryption error"))?; + + Ok(Some(backref)) } } From e6e5158379dd53b2b0976a1da1246805102187a9 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Thu, 14 Nov 2024 17:59:45 -0500 Subject: [PATCH 10/13] test: add decryption for 0 length UIP 4 encrypted backrefs --- .../component/shielded-pool/src/backref.rs | 40 ++++++++++++++++++- 1 file changed, 39 insertions(+), 1 deletion(-) diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs index a110d946c7..f8cf15613b 100644 --- a/crates/core/component/shielded-pool/src/backref.rs +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -142,6 +142,44 @@ mod tests { use crate::Note; + #[test] + fn encrypted_backref_zero_length() { + let rng = OsRng; + + let seed_phrase = SeedPhrase::generate(rng); + 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: 1u64.into(), + asset_id: asset::Cache::with_known_assets() + .get_unit("upenumbra") + .unwrap() + .id(), + }; + + let note = Note::generate(&mut OsRng, &sender, value_to_send); + 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); + } + #[test] fn encrypted_backref_round_trip() { let rng = OsRng; @@ -176,6 +214,6 @@ mod tests { let decrypted_backref = encrypted_backref.decrypt(&brk, &nullifier).unwrap(); - assert_eq!(backref, decrypted_backref); + assert_eq!(Some(backref), decrypted_backref); } } From 24de4dd17bbf84a2bdb8b7dab35726d9cad46555 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Thu, 14 Nov 2024 18:04:30 -0500 Subject: [PATCH 11/13] test: convert UIP 4 tests to proptests --- .../component/shielded-pool/src/backref.rs | 152 +++++++++--------- 1 file changed, 77 insertions(+), 75 deletions(-) diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs index f8cf15613b..7f48fe3922 100644 --- a/crates/core/component/shielded-pool/src/backref.rs +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -135,85 +135,87 @@ impl From for Vec { #[cfg(test)] mod tests { use super::*; + use proptest::prelude::*; use penumbra_asset::{asset, Value}; use penumbra_keys::keys::{Bip44Path, SeedPhrase, SpendKey}; - use rand_core::OsRng; - - use crate::Note; - - #[test] - fn encrypted_backref_zero_length() { - let rng = OsRng; - - let seed_phrase = SeedPhrase::generate(rng); - 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: 1u64.into(), - asset_id: asset::Cache::with_known_assets() - .get_unit("upenumbra") - .unwrap() - .id(), - }; - - let note = Note::generate(&mut OsRng, &sender, value_to_send); - 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); - } - - #[test] - fn encrypted_backref_round_trip() { - let rng = OsRng; - - let seed_phrase = SeedPhrase::generate(rng); - 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: 1u64.into(), - asset_id: asset::Cache::with_known_assets() - .get_unit("upenumbra") - .unwrap() - .id(), - }; - - let note = Note::generate(&mut OsRng, &sender, value_to_send); - 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).unwrap(); - - let decrypted_backref = encrypted_backref.decrypt(&brk, &nullifier).unwrap(); + 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); + } + } - assert_eq!(Some(backref), decrypted_backref); + 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).unwrap(); + + let decrypted_backref = encrypted_backref.decrypt(&brk, &nullifier).unwrap(); + + assert_eq!(Some(backref), decrypted_backref); + } } } From 97630a78339f031579fbfb6aea20e9b85a4f8d82 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Fri, 15 Nov 2024 20:44:19 -0500 Subject: [PATCH 12/13] fix: return `EncryptedBackref::dummy` when parsing 0 byte array --- crates/core/component/shielded-pool/src/spend/action.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/crates/core/component/shielded-pool/src/spend/action.rs b/crates/core/component/shielded-pool/src/spend/action.rs index 868db1f466..83517d6748 100644 --- a/crates/core/component/shielded-pool/src/spend/action.rs +++ b/crates/core/component/shielded-pool/src/spend/action.rs @@ -130,8 +130,7 @@ impl TryFrom for Body { encrypted_backref = EncryptedBackref::try_from(bytes) .map_err(|_| anyhow::anyhow!("invalid encrypted backref"))?; } else if proto.encrypted_backref.len() == 0 { - encrypted_backref = EncryptedBackref::try_from([0u8; ENCRYPTED_BACKREF_LEN]) - .context("invalid encrypted backref")?; + encrypted_backref = EncryptedBackref::dummy(); } else { return Err(anyhow::anyhow!("invalid encrypted backref length")); } From 3ad5fa4fe61790dcb7883866c399d1801ba4fa10 Mon Sep 17 00:00:00 2001 From: redshiftzero Date: Fri, 15 Nov 2024 20:56:59 -0500 Subject: [PATCH 13/13] UIP 4: make backreference encryption infallible This is safe because we know the arrays that have been allocated are the correct size --- crates/core/component/shielded-pool/src/backref.rs | 12 ++++-------- .../core/component/shielded-pool/src/spend/plan.rs | 5 +---- 2 files changed, 5 insertions(+), 12 deletions(-) diff --git a/crates/core/component/shielded-pool/src/backref.rs b/crates/core/component/shielded-pool/src/backref.rs index 7f48fe3922..7eef7f4d96 100644 --- a/crates/core/component/shielded-pool/src/backref.rs +++ b/crates/core/component/shielded-pool/src/backref.rs @@ -26,11 +26,7 @@ impl Backref { Self { note_commitment } } - pub fn encrypt( - &self, - brk: &BackreferenceKey, - nullifier: &Nullifier, - ) -> Result { + pub fn encrypt(&self, brk: &BackreferenceKey, nullifier: &Nullifier) -> EncryptedBackref { let cipher = ChaCha20Poly1305::new(&brk.0); // Nonce is the first 12 bytes of the nullifier @@ -41,9 +37,9 @@ impl Backref { let ciphertext = cipher .encrypt(nonce, plaintext.as_ref()) - .map_err(|_| anyhow::anyhow!("encryption error"))?; + .expect("encryption should succeed "); - Ok(EncryptedBackref { bytes: ciphertext }) + EncryptedBackref { bytes: ciphertext } } } @@ -211,7 +207,7 @@ mod tests { let nullifier = Nullifier::derive(&nk, state_commitment_proof.position(), ¬e_commitment); let backref = Backref::new(note_commitment); - let encrypted_backref = backref.encrypt(&brk, &nullifier).unwrap(); + let encrypted_backref = backref.encrypt(&brk, &nullifier); let decrypted_backref = encrypted_backref.decrypt(&brk, &nullifier).unwrap(); diff --git a/crates/core/component/shielded-pool/src/spend/plan.rs b/crates/core/component/shielded-pool/src/spend/plan.rs index b897a453a1..1602badd0b 100644 --- a/crates/core/component/shielded-pool/src/spend/plan.rs +++ b/crates/core/component/shielded-pool/src/spend/plan.rs @@ -77,10 +77,7 @@ impl SpendPlan { pub fn spend_body(&self, fvk: &FullViewingKey) -> Body { // Construct the backreference for this spend. let backref = Backref::new(self.note.commit()); - // TODO: This is fallible - let encrypted_backref = backref - .encrypt(&fvk.backref_key(), &self.nullifier(fvk)) - .expect("can encrypt"); + let encrypted_backref = backref.encrypt(&fvk.backref_key(), &self.nullifier(fvk)); Body { balance_commitment: self.balance().commit(self.value_blinding), nullifier: self.nullifier(fvk),