penumbra_transaction/plan/build.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213
use anyhow::Result;
use ark_ff::Zero;
use decaf377::Fr;
use decaf377_rdsa as rdsa;
use penumbra_keys::FullViewingKey;
use penumbra_txhash::AuthorizingData;
use super::TransactionPlan;
use crate::ActionPlan;
use crate::{action::Action, AuthorizationData, Transaction, TransactionBody, WitnessData};
impl TransactionPlan {
/// Builds a [`TransactionPlan`] by slotting in the
/// provided prebuilt actions instead of using the
/// [`ActionPlan`]s in the TransactionPlan.
pub fn build_unauth_with_actions(
self,
actions: Vec<Action>,
witness_data: &WitnessData,
) -> Result<Transaction> {
// Add the memo if it is planned.
let memo = self
.memo
.as_ref()
.map(|memo_data| memo_data.memo())
.transpose()?;
let detection_data = self.detection_data.as_ref().map(|x| x.detection_data());
let transaction_body = TransactionBody {
actions,
transaction_parameters: self.transaction_parameters,
detection_data,
memo,
};
Ok(Transaction {
transaction_body,
anchor: witness_data.anchor,
binding_sig: [0; 64].into(),
})
}
/// Slot in the [`AuthorizationData`] and derive the synthetic
/// blinding factors needed to compute the binding signature
/// and assemble the transaction.
pub fn apply_auth_data(
&self,
auth_data: &AuthorizationData,
mut transaction: Transaction,
) -> Result<Transaction> {
// Do some basic input sanity-checking.
let spend_count = transaction.spends().count();
if auth_data.spend_auths.len() != spend_count {
anyhow::bail!(
"expected {} spend auths but got {}",
spend_count,
auth_data.spend_auths.len()
);
}
// Derive the synthetic blinding factors from `TransactionPlan`.
let mut synthetic_blinding_factor = Fr::zero();
// Accumulate the blinding factors.
for action_plan in &self.actions {
synthetic_blinding_factor += action_plan.value_blinding();
}
// Overwrite the placeholder authorization signatures with the real `AuthorizationData`.
for (spend, auth_sig) in transaction
.transaction_body
.actions
.iter_mut()
.filter_map(|action| {
if let Action::Spend(s) = action {
Some(s)
} else {
None
}
})
.zip(auth_data.spend_auths.clone().into_iter())
{
spend.auth_sig = auth_sig;
}
for (delegator_vote, auth_sig) in transaction
.transaction_body
.actions
.iter_mut()
.filter_map(|action| {
if let Action::DelegatorVote(s) = action {
Some(s)
} else {
None
}
})
.zip(auth_data.delegator_vote_auths.clone().into_iter())
{
delegator_vote.auth_sig = auth_sig;
}
// Compute the binding signature and assemble the transaction.
let binding_signing_key = rdsa::SigningKey::from(synthetic_blinding_factor);
let auth_hash = transaction.transaction_body.auth_hash();
let binding_sig = binding_signing_key.sign_deterministic(auth_hash.as_bytes());
tracing::debug!(bvk = ?rdsa::VerificationKey::from(&binding_signing_key), ?auth_hash);
transaction.binding_sig = binding_sig;
Ok(transaction)
}
/// Build the serial transaction this plan describes.
pub fn build(
self,
full_viewing_key: &FullViewingKey,
witness_data: &WitnessData,
auth_data: &AuthorizationData,
) -> Result<Transaction> {
// 1. Build each action.
let actions = self
.actions
.iter()
.map(|action_plan| {
ActionPlan::build_unauth(
action_plan.clone(),
full_viewing_key,
witness_data,
self.memo_key(),
)
})
.collect::<Result<Vec<_>>>()?;
// 2. Pass in the prebuilt actions to the build method.
let tx = self
.clone()
.build_unauth_with_actions(actions, witness_data)?;
// 3. Slot in the authorization data with .apply_auth_data,
let tx = self.apply_auth_data(auth_data, tx)?;
// 4. Return the completed transaction.
Ok(tx)
}
#[cfg(feature = "parallel")]
/// Build the transaction this plan describes while proving concurrently.
/// This can be used in environments that support tokio tasks.
pub async fn build_concurrent(
self,
full_viewing_key: &FullViewingKey,
witness_data: &WitnessData,
auth_data: &AuthorizationData,
) -> Result<Transaction> {
// Clone the witness data into an Arc so it can be shared between tasks.
let witness_data = std::sync::Arc::new(witness_data.clone());
// 1. Build each action (concurrently).
let action_handles = self
.actions
.iter()
.cloned()
.map(|action_plan| {
let fvk2 = full_viewing_key.clone();
let witness_data2 = witness_data.clone(); // Arc
let memo_key2 = self.memo_key();
tokio::task::spawn_blocking(move || {
ActionPlan::build_unauth(action_plan, &fvk2, &*witness_data2, memo_key2)
})
})
.collect::<Vec<_>>();
// 1.5. Collect all of the actions.
let mut actions = Vec::new();
for handle in action_handles {
actions.push(handle.await??);
}
// 2. Pass in the prebuilt actions to the build method.
let tx = self
.clone()
.build_unauth_with_actions(actions, &*witness_data)?;
// 3. Slot in the authorization data with .apply_auth_data,
let tx = self.apply_auth_data(auth_data, tx)?;
// 4. Return the completed transaction.
Ok(tx)
}
/// Returns a [`WitnessData`], which may be used to build this transaction.
pub fn witness_data(&self, sct: &penumbra_tct::Tree) -> Result<WitnessData, anyhow::Error> {
let anchor = sct.root();
let witness_note = |spend: &penumbra_shielded_pool::SpendPlan| {
let commitment = spend.note.commit();
sct.witness(commitment)
.ok_or_else(|| anyhow::anyhow!("commitment should exist in tree"))
.map(|proof| (commitment, proof))
};
let state_commitment_proofs = self
.spend_plans()
.map(witness_note)
.collect::<Result<_, _>>()?;
Ok(WitnessData {
anchor,
state_commitment_proofs,
})
}
}