penumbra_sdk_transaction/plan/
build.rs

1use 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    /// Builds a [`TransactionPlan`] by slotting in the
14    /// provided prebuilt actions instead of using the
15    /// [`ActionPlan`]s in the TransactionPlan.
16    pub fn build_unauth_with_actions(
17        self,
18        actions: Vec<Action>,
19        witness_data: &WitnessData,
20    ) -> Result<Transaction> {
21        // Add the memo if it is planned.
22        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    /// Slot in the [`AuthorizationData`] and derive the synthetic
45    /// blinding factors needed to compute the binding signature
46    /// and assemble the transaction.
47    pub fn apply_auth_data(
48        &self,
49        auth_data: &AuthorizationData,
50        mut transaction: Transaction,
51    ) -> Result<Transaction> {
52        // Do some basic input sanity-checking.
53        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        // Derive the synthetic blinding factors from `TransactionPlan`.
63        let mut synthetic_blinding_factor = Fr::zero();
64
65        // Accumulate the blinding factors.
66        for action_plan in &self.actions {
67            synthetic_blinding_factor += action_plan.value_blinding();
68        }
69
70        // Overwrite the placeholder authorization signatures with the real `AuthorizationData`.
71        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        // Compute the binding signature and assemble the transaction.
120        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    /// Build the serial transaction this plan describes.
132    pub fn build(
133        self,
134        full_viewing_key: &FullViewingKey,
135        witness_data: &WitnessData,
136        auth_data: &AuthorizationData,
137    ) -> Result<Transaction> {
138        // 1. Build each action.
139        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        // 2. Pass in the prebuilt actions to the build method.
153        let tx = self
154            .clone()
155            .build_unauth_with_actions(actions, witness_data)?;
156
157        // 3. Slot in the authorization data with .apply_auth_data,
158        let tx = self.apply_auth_data(auth_data, tx)?;
159
160        // 4. Return the completed transaction.
161        Ok(tx)
162    }
163
164    #[cfg(feature = "parallel")]
165    /// Build the transaction this plan describes while proving concurrently.
166    /// This can be used in environments that support tokio tasks.
167    pub async fn build_concurrent(
168        self,
169        full_viewing_key: &FullViewingKey,
170        witness_data: &WitnessData,
171        auth_data: &AuthorizationData,
172    ) -> Result<Transaction> {
173        // Clone the witness data into an Arc so it can be shared between tasks.
174        let witness_data = std::sync::Arc::new(witness_data.clone());
175
176        // 1. Build each action (concurrently).
177        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(); // Arc
184                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        // 1.5. Collect all of the actions.
192        let mut actions = Vec::new();
193        for handle in action_handles {
194            actions.push(handle.await??);
195        }
196
197        // 2. Pass in the prebuilt actions to the build method.
198        let tx = self
199            .clone()
200            .build_unauth_with_actions(actions, &*witness_data)?;
201
202        // 3. Slot in the authorization data with .apply_auth_data,
203        let tx = self.apply_auth_data(auth_data, tx)?;
204
205        // 4. Return the completed transaction.
206        Ok(tx)
207    }
208
209    /// Returns a [`WitnessData`], which may be used to build this transaction.
210    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}