penumbra_sdk_dex/swap_claim/
plan.rs1use decaf377::Fq;
2use penumbra_sdk_asset::{Balance, Value};
3use penumbra_sdk_keys::{keys::IncomingViewingKey, FullViewingKey};
4use penumbra_sdk_proof_params::SWAPCLAIM_PROOF_PROVING_KEY;
5use penumbra_sdk_proto::{penumbra::core::component::dex::v1 as pb, DomainType};
6use penumbra_sdk_sct::Nullifier;
7use penumbra_sdk_tct as tct;
8
9use serde::{Deserialize, Serialize};
10use tct::Position;
11
12use crate::{swap::SwapPlaintext, BatchSwapOutputData};
13
14use super::{
15 action as swap_claim,
16 proof::{SwapClaimProof, SwapClaimProofPrivate, SwapClaimProofPublic},
17 SwapClaim,
18};
19
20#[derive(Clone, Debug, Deserialize, Serialize)]
22#[serde(try_from = "pb::SwapClaimPlan", into = "pb::SwapClaimPlan")]
23pub struct SwapClaimPlan {
24 pub swap_plaintext: SwapPlaintext,
25 pub position: Position,
26 pub output_data: BatchSwapOutputData,
27 pub epoch_duration: u64,
28 pub proof_blinding_r: Fq,
30 pub proof_blinding_s: Fq,
32}
33
34impl SwapClaimPlan {
35 pub fn swap_claim(
38 &self,
39 fvk: &FullViewingKey,
40 state_commitment_proof: &tct::Proof,
41 ) -> SwapClaim {
42 SwapClaim {
43 body: self.swap_claim_body(fvk),
44 proof: self.swap_claim_proof(state_commitment_proof, fvk),
45 epoch_duration: self.epoch_duration,
46 }
47 }
48
49 pub fn swap_claim_proof(
52 &self,
53 state_commitment_proof: &tct::Proof,
54 fvk: &FullViewingKey,
55 ) -> SwapClaimProof {
56 let (lambda_1, lambda_2) = self
57 .output_data
58 .pro_rata_outputs((self.swap_plaintext.delta_1_i, self.swap_plaintext.delta_2_i));
59 let (output_rseed_1, output_rseed_2) = self.swap_plaintext.output_rseeds();
60 let note_blinding_1 = output_rseed_1.derive_note_blinding();
61 let note_blinding_2 = output_rseed_2.derive_note_blinding();
62 let (output_1_note, output_2_note) = self.swap_plaintext.output_notes(&self.output_data);
63 let note_commitment_1 = output_1_note.commit();
64 let note_commitment_2 = output_2_note.commit();
65
66 let nullifier = Nullifier::derive(
67 fvk.nullifier_key(),
68 self.position,
69 &self.swap_plaintext.swap_commitment(),
70 );
71 SwapClaimProof::prove(
72 self.proof_blinding_r,
73 self.proof_blinding_s,
74 &SWAPCLAIM_PROOF_PROVING_KEY,
75 SwapClaimProofPublic {
76 anchor: state_commitment_proof.root(),
77 nullifier,
78 claim_fee: self.swap_plaintext.claim_fee.clone(),
79 output_data: self.output_data,
80 note_commitment_1,
81 note_commitment_2,
82 },
83 SwapClaimProofPrivate {
84 swap_plaintext: self.swap_plaintext.clone(),
85 state_commitment_proof: state_commitment_proof.clone(),
86 nk: *fvk.nullifier_key(),
87 ak: *fvk.spend_verification_key(),
88 lambda_1,
89 lambda_2,
90 note_blinding_1,
91 note_blinding_2,
92 },
93 )
94 .expect("can generate ZKSwapClaimProof")
95 }
96
97 pub fn swap_claim_body(&self, fvk: &FullViewingKey) -> swap_claim::Body {
99 let (output_1_note, output_2_note) = self.swap_plaintext.output_notes(&self.output_data);
100 tracing::debug!(?output_1_note, ?output_2_note);
101
102 let output_1_commitment = output_1_note.commit();
104 let output_2_commitment = output_2_note.commit();
105
106 let nullifier = Nullifier::derive(
107 fvk.nullifier_key(),
108 self.position,
109 &self.swap_plaintext.swap_commitment(),
110 );
111
112 swap_claim::Body {
113 nullifier,
114 fee: self.swap_plaintext.claim_fee.clone(),
115 output_1_commitment,
116 output_2_commitment,
117 output_data: self.output_data,
118 }
119 }
120
121 pub fn is_viewed_by(&self, ivk: &IncomingViewingKey) -> bool {
123 ivk.views_address(&self.swap_plaintext.claim_address)
124 }
125
126 pub fn balance(&self) -> Balance {
127 let value_fee = Value {
130 amount: self.swap_plaintext.claim_fee.amount(),
131 asset_id: self.swap_plaintext.claim_fee.asset_id(),
132 };
133
134 value_fee.into()
135 }
136}
137
138impl DomainType for SwapClaimPlan {
139 type Proto = pb::SwapClaimPlan;
140}
141
142impl From<SwapClaimPlan> for pb::SwapClaimPlan {
143 fn from(msg: SwapClaimPlan) -> Self {
144 Self {
145 swap_plaintext: Some(msg.swap_plaintext.into()),
146 position: msg.position.into(),
147 output_data: Some(msg.output_data.into()),
148 epoch_duration: msg.epoch_duration,
149 proof_blinding_r: msg.proof_blinding_r.to_bytes().to_vec(),
150 proof_blinding_s: msg.proof_blinding_s.to_bytes().to_vec(),
151 }
152 }
153}
154
155impl TryFrom<pb::SwapClaimPlan> for SwapClaimPlan {
156 type Error = anyhow::Error;
157 fn try_from(msg: pb::SwapClaimPlan) -> Result<Self, Self::Error> {
158 let proof_blinding_r_bytes: [u8; 32] = msg
159 .proof_blinding_r
160 .try_into()
161 .map_err(|_| anyhow::anyhow!("malformed r in `SwapClaimPlan`"))?;
162 let proof_blinding_s_bytes: [u8; 32] = msg
163 .proof_blinding_s
164 .try_into()
165 .map_err(|_| anyhow::anyhow!("malformed s in `SwapClaimPlan`"))?;
166
167 Ok(Self {
168 swap_plaintext: msg
169 .swap_plaintext
170 .ok_or_else(|| anyhow::anyhow!("missing swap_plaintext"))?
171 .try_into()?,
172 position: msg.position.into(),
173 output_data: msg
174 .output_data
175 .ok_or_else(|| anyhow::anyhow!("missing output_data"))?
176 .try_into()?,
177 epoch_duration: msg.epoch_duration,
178 proof_blinding_r: Fq::from_bytes_checked(&proof_blinding_r_bytes)
179 .expect("proof_blinding_r malformed"),
180 proof_blinding_s: Fq::from_bytes_checked(&proof_blinding_s_bytes)
181 .expect("proof_blinding_s malformed"),
182 })
183 }
184}