penumbra_sdk_shielded_pool/spend/
action.rs

1use std::convert::{TryFrom, TryInto};
2
3use anyhow::{Context, Error};
4use decaf377_rdsa::{Signature, SpendAuth, VerificationKey};
5use penumbra_sdk_asset::balance;
6use penumbra_sdk_proto::{core::component::shielded_pool::v1 as pb, DomainType};
7use penumbra_sdk_sct::Nullifier;
8use penumbra_sdk_txhash::{EffectHash, EffectingData};
9use serde::{Deserialize, Serialize};
10
11use crate::SpendProof;
12use crate::{backref::ENCRYPTED_BACKREF_LEN, EncryptedBackref};
13
14#[derive(Clone, Debug)]
15pub struct Spend {
16    pub body: Body,
17    pub auth_sig: Signature<SpendAuth>,
18    pub proof: SpendProof,
19}
20
21#[derive(Clone, Debug, Deserialize, Serialize)]
22#[serde(try_from = "pb::SpendBody", into = "pb::SpendBody")]
23pub struct Body {
24    pub balance_commitment: balance::Commitment,
25    pub nullifier: Nullifier,
26    pub rk: VerificationKey<SpendAuth>,
27    pub encrypted_backref: EncryptedBackref,
28}
29
30impl EffectingData for Body {
31    fn effect_hash(&self) -> EffectHash {
32        EffectHash::from_proto_effecting_data(&self.to_proto())
33    }
34}
35
36impl EffectingData for Spend {
37    fn effect_hash(&self) -> EffectHash {
38        // The effecting data is in the body of the spend, so we can
39        // just use hash the proto-encoding of the body.
40        self.body.effect_hash()
41    }
42}
43
44impl DomainType for Spend {
45    type Proto = pb::Spend;
46}
47
48impl From<Spend> for pb::Spend {
49    fn from(msg: Spend) -> Self {
50        pb::Spend {
51            body: Some(msg.body.into()),
52            auth_sig: Some(msg.auth_sig.into()),
53            proof: Some(msg.proof.into()),
54        }
55    }
56}
57
58impl TryFrom<pb::Spend> for Spend {
59    type Error = Error;
60
61    fn try_from(proto: pb::Spend) -> anyhow::Result<Self, Self::Error> {
62        let body = proto
63            .body
64            .ok_or_else(|| anyhow::anyhow!("missing spend body"))?
65            .try_into()
66            .context("malformed spend body")?;
67        let auth_sig = proto
68            .auth_sig
69            .ok_or_else(|| anyhow::anyhow!("missing auth sig"))?
70            .try_into()
71            .context("malformed auth sig")?;
72        let proof = proto
73            .proof
74            .ok_or_else(|| anyhow::anyhow!("missing proof"))?
75            .try_into()
76            .context("malformed spend proof")?;
77
78        Ok(Spend {
79            body,
80            auth_sig,
81            proof,
82        })
83    }
84}
85
86impl DomainType for Body {
87    type Proto = pb::SpendBody;
88}
89
90impl From<Body> for pb::SpendBody {
91    fn from(msg: Body) -> Self {
92        pb::SpendBody {
93            balance_commitment: Some(msg.balance_commitment.into()),
94            nullifier: Some(msg.nullifier.into()),
95            rk: Some(msg.rk.into()),
96            encrypted_backref: msg.encrypted_backref.into(),
97        }
98    }
99}
100
101impl TryFrom<pb::SpendBody> for Body {
102    type Error = Error;
103
104    fn try_from(proto: pb::SpendBody) -> anyhow::Result<Self, Self::Error> {
105        let balance_commitment: balance::Commitment = proto
106            .balance_commitment
107            .ok_or_else(|| anyhow::anyhow!("missing balance commitment"))?
108            .try_into()
109            .context("malformed balance commitment")?;
110
111        let nullifier = proto
112            .nullifier
113            .ok_or_else(|| anyhow::anyhow!("missing nullifier"))?
114            .try_into()
115            .context("malformed nullifier")?;
116
117        let rk = proto
118            .rk
119            .ok_or_else(|| anyhow::anyhow!("missing rk"))?
120            .try_into()
121            .context("malformed rk")?;
122
123        // `EncryptedBackref` must have 0 or `ENCRYPTED_BACKREF_LEN` bytes.
124        let encrypted_backref: EncryptedBackref;
125        if proto.encrypted_backref.len() == ENCRYPTED_BACKREF_LEN {
126            let bytes: [u8; ENCRYPTED_BACKREF_LEN] = proto
127                .encrypted_backref
128                .try_into()
129                .map_err(|_| anyhow::anyhow!("invalid encrypted backref"))?;
130            encrypted_backref = EncryptedBackref::try_from(bytes)
131                .map_err(|_| anyhow::anyhow!("invalid encrypted backref"))?;
132        } else if proto.encrypted_backref.len() == 0 {
133            encrypted_backref = EncryptedBackref::dummy();
134        } else {
135            return Err(anyhow::anyhow!("invalid encrypted backref length"));
136        }
137
138        Ok(Body {
139            balance_commitment,
140            nullifier,
141            rk,
142            encrypted_backref,
143        })
144    }
145}