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 let binding_signing_key = rdsa::SigningKey::from(synthetic_blinding_factor);
105 let auth_hash = transaction.transaction_body.auth_hash();
106
107 let binding_sig = binding_signing_key.sign_deterministic(auth_hash.as_bytes());
108 tracing::debug!(bvk = ?rdsa::VerificationKey::from(&binding_signing_key), ?auth_hash);
109
110 transaction.binding_sig = binding_sig;
111
112 Ok(transaction)
113 }
114
115 pub fn build(
117 self,
118 full_viewing_key: &FullViewingKey,
119 witness_data: &WitnessData,
120 auth_data: &AuthorizationData,
121 ) -> Result<Transaction> {
122 let actions = self
124 .actions
125 .iter()
126 .map(|action_plan| {
127 ActionPlan::build_unauth(
128 action_plan.clone(),
129 full_viewing_key,
130 witness_data,
131 self.memo_key(),
132 )
133 })
134 .collect::<Result<Vec<_>>>()?;
135
136 let tx = self
138 .clone()
139 .build_unauth_with_actions(actions, witness_data)?;
140
141 let tx = self.apply_auth_data(auth_data, tx)?;
143
144 Ok(tx)
146 }
147
148 #[cfg(feature = "parallel")]
149 pub async fn build_concurrent(
152 self,
153 full_viewing_key: &FullViewingKey,
154 witness_data: &WitnessData,
155 auth_data: &AuthorizationData,
156 ) -> Result<Transaction> {
157 let witness_data = std::sync::Arc::new(witness_data.clone());
159
160 let action_handles = self
162 .actions
163 .iter()
164 .cloned()
165 .map(|action_plan| {
166 let fvk2 = full_viewing_key.clone();
167 let witness_data2 = witness_data.clone(); let memo_key2 = self.memo_key();
169 tokio::task::spawn_blocking(move || {
170 ActionPlan::build_unauth(action_plan, &fvk2, &*witness_data2, memo_key2)
171 })
172 })
173 .collect::<Vec<_>>();
174
175 let mut actions = Vec::new();
177 for handle in action_handles {
178 actions.push(handle.await??);
179 }
180
181 let tx = self
183 .clone()
184 .build_unauth_with_actions(actions, &*witness_data)?;
185
186 let tx = self.apply_auth_data(auth_data, tx)?;
188
189 Ok(tx)
191 }
192
193 pub fn witness_data(&self, sct: &penumbra_sdk_tct::Tree) -> Result<WitnessData, anyhow::Error> {
195 let anchor = sct.root();
196
197 let witness_note = |spend: &penumbra_sdk_shielded_pool::SpendPlan| {
198 let commitment = spend.note.commit();
199 sct.witness(commitment)
200 .ok_or_else(|| anyhow::anyhow!("commitment should exist in tree"))
201 .map(|proof| (commitment, proof))
202 };
203 let state_commitment_proofs = self
204 .spend_plans()
205 .map(witness_note)
206 .collect::<Result<_, _>>()?;
207
208 Ok(WitnessData {
209 anchor,
210 state_commitment_proofs,
211 })
212 }
213}