penumbra_sdk_transaction/plan/
build.rs1use anyhow::Result;
2use ark_ff::Zero;
3use decaf377::Fr;
4use decaf377_rdsa as rdsa;
5use penumbra_sdk_keys::FullViewingKey;
6use penumbra_sdk_txhash::AuthorizingData;
7
8use super::TransactionPlan;
9use crate::ActionPlan;
10use crate::{action::Action, AuthorizationData, Transaction, TransactionBody, WitnessData};
11
12impl TransactionPlan {
13 pub fn build_unauth_with_actions(
17 self,
18 actions: Vec<Action>,
19 witness_data: &WitnessData,
20 ) -> Result<Transaction> {
21 let memo = self
23 .memo
24 .as_ref()
25 .map(|memo_data| memo_data.memo())
26 .transpose()?;
27
28 let detection_data = self.detection_data.as_ref().map(|x| x.detection_data());
29
30 let transaction_body = TransactionBody {
31 actions,
32 transaction_parameters: self.transaction_parameters,
33 detection_data,
34 memo,
35 };
36
37 Ok(Transaction {
38 transaction_body,
39 anchor: witness_data.anchor,
40 binding_sig: [0; 64].into(),
41 })
42 }
43
44 pub fn apply_auth_data(
48 &self,
49 auth_data: &AuthorizationData,
50 mut transaction: Transaction,
51 ) -> Result<Transaction> {
52 let spend_count = transaction.spends().count();
54 if auth_data.spend_auths.len() != spend_count {
55 anyhow::bail!(
56 "expected {} spend auths but got {}",
57 spend_count,
58 auth_data.spend_auths.len()
59 );
60 }
61
62 let mut synthetic_blinding_factor = Fr::zero();
64
65 for action_plan in &self.actions {
67 synthetic_blinding_factor += action_plan.value_blinding();
68 }
69
70 for (spend, auth_sig) in transaction
72 .transaction_body
73 .actions
74 .iter_mut()
75 .filter_map(|action| {
76 if let Action::Spend(s) = action {
77 Some(s)
78 } else {
79 None
80 }
81 })
82 .zip(auth_data.spend_auths.clone().into_iter())
83 {
84 spend.auth_sig = auth_sig;
85 }
86
87 for (delegator_vote, auth_sig) in transaction
88 .transaction_body
89 .actions
90 .iter_mut()
91 .filter_map(|action| {
92 if let Action::DelegatorVote(s) = action {
93 Some(s)
94 } else {
95 None
96 }
97 })
98 .zip(auth_data.delegator_vote_auths.clone().into_iter())
99 {
100 delegator_vote.auth_sig = auth_sig;
101 }
102
103 for (lqt_vote, auth_sig) in transaction
104 .transaction_body
105 .actions
106 .iter_mut()
107 .filter_map(|action| {
108 if let Action::ActionLiquidityTournamentVote(s) = action {
109 Some(s)
110 } else {
111 None
112 }
113 })
114 .zip(auth_data.lqt_vote_auths.clone().into_iter())
115 {
116 lqt_vote.auth_sig = auth_sig;
117 }
118
119 let binding_signing_key = rdsa::SigningKey::from(synthetic_blinding_factor);
121 let auth_hash = transaction.transaction_body.auth_hash();
122
123 let binding_sig = binding_signing_key.sign_deterministic(auth_hash.as_bytes());
124 tracing::debug!(bvk = ?rdsa::VerificationKey::from(&binding_signing_key), ?auth_hash);
125
126 transaction.binding_sig = binding_sig;
127
128 Ok(transaction)
129 }
130
131 pub fn build(
133 self,
134 full_viewing_key: &FullViewingKey,
135 witness_data: &WitnessData,
136 auth_data: &AuthorizationData,
137 ) -> Result<Transaction> {
138 let actions = self
140 .actions
141 .iter()
142 .map(|action_plan| {
143 ActionPlan::build_unauth(
144 action_plan.clone(),
145 full_viewing_key,
146 witness_data,
147 self.memo_key(),
148 )
149 })
150 .collect::<Result<Vec<_>>>()?;
151
152 let tx = self
154 .clone()
155 .build_unauth_with_actions(actions, witness_data)?;
156
157 let tx = self.apply_auth_data(auth_data, tx)?;
159
160 Ok(tx)
162 }
163
164 #[cfg(feature = "parallel")]
165 pub async fn build_concurrent(
168 self,
169 full_viewing_key: &FullViewingKey,
170 witness_data: &WitnessData,
171 auth_data: &AuthorizationData,
172 ) -> Result<Transaction> {
173 let witness_data = std::sync::Arc::new(witness_data.clone());
175
176 let action_handles = self
178 .actions
179 .iter()
180 .cloned()
181 .map(|action_plan| {
182 let fvk2 = full_viewing_key.clone();
183 let witness_data2 = witness_data.clone(); let memo_key2 = self.memo_key();
185 tokio::task::spawn_blocking(move || {
186 ActionPlan::build_unauth(action_plan, &fvk2, &*witness_data2, memo_key2)
187 })
188 })
189 .collect::<Vec<_>>();
190
191 let mut actions = Vec::new();
193 for handle in action_handles {
194 actions.push(handle.await??);
195 }
196
197 let tx = self
199 .clone()
200 .build_unauth_with_actions(actions, &*witness_data)?;
201
202 let tx = self.apply_auth_data(auth_data, tx)?;
204
205 Ok(tx)
207 }
208
209 pub fn witness_data(&self, sct: &penumbra_sdk_tct::Tree) -> Result<WitnessData, anyhow::Error> {
211 let anchor = sct.root();
212
213 let witness_note = |spend: &penumbra_sdk_shielded_pool::SpendPlan| {
214 let commitment = spend.note.commit();
215 sct.witness(commitment)
216 .ok_or_else(|| anyhow::anyhow!("commitment should exist in tree"))
217 .map(|proof| (commitment, proof))
218 };
219 let state_commitment_proofs = self
220 .spend_plans()
221 .map(witness_note)
222 .collect::<Result<_, _>>()?;
223
224 Ok(WitnessData {
225 anchor,
226 state_commitment_proofs,
227 })
228 }
229}