penumbra_sdk_shielded_pool/spend/
action.rs1use 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 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 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}