penumbra_sdk_dex/swap/
plan.rs1use anyhow::{anyhow, Context, Result};
2use ark_ff::Zero;
3
4use decaf377::{Fq, Fr};
5use penumbra_sdk_asset::{balance, Balance, Value};
6use penumbra_sdk_keys::FullViewingKey;
7use penumbra_sdk_proto::{penumbra::core::component::dex::v1 as pb, DomainType};
8use rand_core::{CryptoRng, RngCore};
9use serde::{Deserialize, Serialize};
10
11use crate::swap::proof::{SwapProofPrivate, SwapProofPublic};
12
13use super::{action as swap, proof::SwapProof, Swap, SwapPlaintext};
15
16#[derive(Clone, Debug, Deserialize, Serialize)]
18#[serde(try_from = "pb::SwapPlan", into = "pb::SwapPlan")]
19pub struct SwapPlan {
20 pub swap_plaintext: SwapPlaintext,
21 pub fee_blinding: Fr,
22 pub proof_blinding_r: Fq,
23 pub proof_blinding_s: Fq,
24}
25
26impl SwapPlan {
27 pub fn new<R: CryptoRng + RngCore>(rng: &mut R, swap_plaintext: SwapPlaintext) -> SwapPlan {
29 let fee_blinding = Fr::rand(rng);
30
31 SwapPlan {
32 fee_blinding,
33 swap_plaintext,
34 proof_blinding_r: Fq::rand(rng),
35 proof_blinding_s: Fq::rand(rng),
36 }
37 }
38
39 pub fn swap(&self, fvk: &FullViewingKey) -> Swap {
41 Swap {
42 body: self.swap_body(fvk),
43 proof: self.swap_proof(),
44 }
45 }
46
47 pub fn swap_body(&self, fvk: &FullViewingKey) -> swap::Body {
49 swap::Body {
50 trading_pair: self.swap_plaintext.trading_pair,
51 delta_1_i: self.swap_plaintext.delta_1_i,
52 delta_2_i: self.swap_plaintext.delta_2_i,
53 fee_commitment: self.fee_commitment(),
54 payload: self.swap_plaintext.encrypt(fvk.outgoing()),
55 }
56 }
57
58 pub fn swap_proof(&self) -> SwapProof {
60 use penumbra_sdk_proof_params::SWAP_PROOF_PROVING_KEY;
61
62 let balance_commitment =
63 self.transparent_balance().commit(Fr::zero()) + self.fee_commitment();
64 SwapProof::prove(
65 self.proof_blinding_r,
66 self.proof_blinding_s,
67 &SWAP_PROOF_PROVING_KEY,
68 SwapProofPublic {
69 balance_commitment,
70 swap_commitment: self.swap_plaintext.swap_commitment(),
71 fee_commitment: self.fee_commitment(),
72 },
73 SwapProofPrivate {
74 fee_blinding: self.fee_blinding,
75 swap_plaintext: self.swap_plaintext.clone(),
76 },
77 )
78 .expect("can generate ZKSwapProof")
79 }
80
81 pub fn fee_commitment(&self) -> balance::Commitment {
82 self.swap_plaintext.claim_fee.commit(self.fee_blinding)
83 }
84
85 pub fn transparent_balance(&self) -> Balance {
86 let value_1 = Value {
87 amount: self.swap_plaintext.delta_1_i,
88 asset_id: self.swap_plaintext.trading_pair.asset_1(),
89 };
90 let value_2 = Value {
91 amount: self.swap_plaintext.delta_2_i,
92 asset_id: self.swap_plaintext.trading_pair.asset_2(),
93 };
94
95 let mut balance = Balance::default();
96 balance -= value_1;
97 balance -= value_2;
98 balance
99 }
100
101 pub fn balance(&self) -> Balance {
102 let value_fee = Value {
107 amount: self.swap_plaintext.claim_fee.amount(),
108 asset_id: self.swap_plaintext.claim_fee.asset_id(),
109 };
110
111 let mut balance = self.transparent_balance();
112 balance -= value_fee;
113 balance
114 }
115}
116
117impl DomainType for SwapPlan {
118 type Proto = pb::SwapPlan;
119}
120
121impl From<SwapPlan> for pb::SwapPlan {
122 fn from(msg: SwapPlan) -> Self {
123 Self {
124 swap_plaintext: Some(msg.swap_plaintext.into()),
125 fee_blinding: msg.fee_blinding.to_bytes().to_vec(),
126 proof_blinding_r: msg.proof_blinding_r.to_bytes().to_vec(),
127 proof_blinding_s: msg.proof_blinding_s.to_bytes().to_vec(),
128 }
129 }
130}
131
132impl TryFrom<pb::SwapPlan> for SwapPlan {
133 type Error = anyhow::Error;
134 fn try_from(msg: pb::SwapPlan) -> Result<Self, Self::Error> {
135 let proof_blinding_r_bytes: [u8; 32] = msg
136 .proof_blinding_r
137 .try_into()
138 .map_err(|_| anyhow::anyhow!("malformed r in `SwapPlan`"))?;
139 let proof_blinding_s_bytes: [u8; 32] = msg
140 .proof_blinding_s
141 .try_into()
142 .map_err(|_| anyhow::anyhow!("malformed s in `SwapPlan`"))?;
143
144 let fee_blinding_bytes: [u8; 32] = msg.fee_blinding[..]
145 .try_into()
146 .map_err(|_| anyhow!("expected 32 byte fee blinding"))?;
147 Ok(Self {
148 fee_blinding: Fr::from_bytes_checked(&fee_blinding_bytes)
149 .expect("fee blinding malformed"),
150 swap_plaintext: msg
151 .swap_plaintext
152 .ok_or_else(|| anyhow!("missing swap_plaintext"))?
153 .try_into()
154 .context("swap plaintext malformed")?,
155 proof_blinding_r: Fq::from_bytes_checked(&proof_blinding_r_bytes)
156 .expect("proof_blinding_r malformed"),
157 proof_blinding_s: Fq::from_bytes_checked(&proof_blinding_s_bytes)
158 .expect("proof_blinding_r malformed"),
159 })
160 }
161}