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        // Compute the binding signature and assemble the transaction.
104        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    /// Build the serial transaction this plan describes.
116    pub fn build(
117        self,
118        full_viewing_key: &FullViewingKey,
119        witness_data: &WitnessData,
120        auth_data: &AuthorizationData,
121    ) -> Result<Transaction> {
122        // 1. Build each action.
123        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        // 2. Pass in the prebuilt actions to the build method.
137        let tx = self
138            .clone()
139            .build_unauth_with_actions(actions, witness_data)?;
140
141        // 3. Slot in the authorization data with .apply_auth_data,
142        let tx = self.apply_auth_data(auth_data, tx)?;
143
144        // 4. Return the completed transaction.
145        Ok(tx)
146    }
147
148    #[cfg(feature = "parallel")]
149    /// Build the transaction this plan describes while proving concurrently.
150    /// This can be used in environments that support tokio tasks.
151    pub async fn build_concurrent(
152        self,
153        full_viewing_key: &FullViewingKey,
154        witness_data: &WitnessData,
155        auth_data: &AuthorizationData,
156    ) -> Result<Transaction> {
157        // Clone the witness data into an Arc so it can be shared between tasks.
158        let witness_data = std::sync::Arc::new(witness_data.clone());
159
160        // 1. Build each action (concurrently).
161        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(); // Arc
168                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        // 1.5. Collect all of the actions.
176        let mut actions = Vec::new();
177        for handle in action_handles {
178            actions.push(handle.await??);
179        }
180
181        // 2. Pass in the prebuilt actions to the build method.
182        let tx = self
183            .clone()
184            .build_unauth_with_actions(actions, &*witness_data)?;
185
186        // 3. Slot in the authorization data with .apply_auth_data,
187        let tx = self.apply_auth_data(auth_data, tx)?;
188
189        // 4. Return the completed transaction.
190        Ok(tx)
191    }
192
193    /// Returns a [`WitnessData`], which may be used to build this transaction.
194    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}