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(