penumbra_sdk_transaction/
transaction.rs

1use std::{
2    collections::BTreeMap,
3    convert::{TryFrom, TryInto},
4};
5
6use anyhow::{Context, Error};
7use ark_ff::Zero;
8use decaf377::Fr;
9use decaf377_rdsa::{Binding, Signature, VerificationKey, VerificationKeyBytes};
10use penumbra_sdk_asset::Balance;
11use penumbra_sdk_community_pool::{CommunityPoolDeposit, CommunityPoolOutput, CommunityPoolSpend};
12use penumbra_sdk_dex::{
13    lp::action::{PositionClose, PositionOpen},
14    swap::Swap,
15};
16use penumbra_sdk_governance::{DelegatorVote, ProposalSubmit, ProposalWithdraw, ValidatorVote};
17use penumbra_sdk_ibc::IbcRelay;
18use penumbra_sdk_keys::{AddressView, FullViewingKey, PayloadKey};
19use penumbra_sdk_proto::{
20    core::transaction::v1::{self as pbt},
21    DomainType, Message,
22};
23use penumbra_sdk_sct::Nullifier;
24use penumbra_sdk_shielded_pool::{Note, Output, Spend};
25use penumbra_sdk_stake::{Delegate, Undelegate, UndelegateClaim};
26use penumbra_sdk_tct as tct;
27use penumbra_sdk_tct::StateCommitment;
28use penumbra_sdk_txhash::{
29    AuthHash, AuthorizingData, EffectHash, EffectingData, TransactionContext, TransactionId,
30};
31use serde::{Deserialize, Serialize};
32
33use crate::{
34    memo::{MemoCiphertext, MemoPlaintext},
35    view::{action_view::OutputView, MemoView, TransactionBodyView},
36    Action, ActionView, DetectionData, IsAction, MemoPlaintextView, TransactionParameters,
37    TransactionPerspective, TransactionView,
38};
39
40#[derive(Clone, Debug, Default)]
41pub struct TransactionBody {
42    pub actions: Vec<Action>,
43    pub transaction_parameters: TransactionParameters,
44    pub detection_data: Option<DetectionData>,
45    pub memo: Option<MemoCiphertext>,
46}
47
48/// Represents a transaction summary containing multiple effects.
49#[derive(Debug, Clone, Default, Serialize, Deserialize)]
50#[serde(try_from = "pbt::TransactionSummary", into = "pbt::TransactionSummary")]
51pub struct TransactionSummary {
52    pub effects: Vec<TransactionEffect>,
53}
54
55/// Represents an individual effect of a transaction.
56#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
57pub struct TransactionEffect {
58    pub address: AddressView,
59    pub balance: Balance,
60}
61
62impl EffectingData for TransactionBody {
63    fn effect_hash(&self) -> EffectHash {
64        let mut state = blake2b_simd::Params::new()
65            .personal(b"PenumbraEfHs")
66            .to_state();
67
68        let parameters_hash = self.transaction_parameters.effect_hash();
69        let memo_hash = self
70            .memo
71            .as_ref()
72            .map(|memo| memo.effect_hash())
73            // If the memo is not present, use the all-zero hash to record its absence in
74            // the overall effect hash.
75            .unwrap_or_default();
76        let detection_data_hash = self
77            .detection_data
78            .as_ref()
79            .map(|detection_data| detection_data.effect_hash())
80            // If the detection data is not present, use the all-zero hash to
81            // record its absence in the overall effect hash.
82            .unwrap_or_default();
83
84        // Hash the fixed data of the transaction body.
85        state.update(parameters_hash.as_bytes());
86        state.update(memo_hash.as_bytes());
87        state.update(detection_data_hash.as_bytes());
88
89        // Hash the number of actions, then each action.
90        let num_actions = self.actions.len() as u32;
91        state.update(&num_actions.to_le_bytes());
92        for action in &self.actions {
93            state.update(action.effect_hash().as_bytes());
94        }
95
96        EffectHash(state.finalize().as_array().clone())
97    }
98}
99
100impl EffectingData for Transaction {
101    fn effect_hash(&self) -> EffectHash {
102        self.transaction_body.effect_hash()
103    }
104}
105
106impl AuthorizingData for TransactionBody {
107    fn auth_hash(&self) -> AuthHash {
108        AuthHash(
109            blake2b_simd::Params::default()
110                .hash(&self.encode_to_vec())
111                .as_bytes()[0..32]
112                .try_into()
113                .expect("blake2b output is always 32 bytes long"),
114        )
115    }
116}
117
118impl AuthorizingData for Transaction {
119    fn auth_hash(&self) -> AuthHash {
120        self.transaction_body.auth_hash()
121    }
122}
123
124#[derive(Clone, Debug, Serialize, Deserialize)]
125#[serde(try_from = "pbt::Transaction", into = "pbt::Transaction")]
126pub struct Transaction {
127    pub transaction_body: TransactionBody,
128    pub binding_sig: Signature<Binding>,
129    pub anchor: tct::Root,
130}
131
132impl Default for Transaction {
133    fn default() -> Self {
134        Transaction {
135            transaction_body: Default::default(),
136            binding_sig: [0u8; 64].into(),
137            anchor: tct::Tree::new().root(),
138        }
139    }
140}
141
142impl Transaction {
143    pub fn context(&self) -> TransactionContext {
144        TransactionContext {
145            anchor: self.anchor,
146            effect_hash: self.effect_hash(),
147        }
148    }
149
150    pub fn num_proofs(&self) -> usize {
151        self.transaction_body
152            .actions
153            .iter()
154            .map(|action| match action {
155                Action::Spend(_) => 1,
156                Action::Output(_) => 1,
157                Action::Swap(_) => 1,
158                Action::SwapClaim(_) => 1,
159                Action::UndelegateClaim(_) => 1,
160                Action::DelegatorVote(_) => 1,
161                _ => 0,
162            })
163            .sum()
164    }
165
166    /// Helper function for decrypting the memo on the transaction given an FVK.
167    ///
168    /// Will return an Error if there is no memo.
169    pub fn decrypt_memo(&self, fvk: &FullViewingKey) -> anyhow::Result<MemoPlaintext> {
170        // Error if we don't have an encrypted memo field to decrypt.
171        if self.transaction_body().memo.is_none() {
172            return Err(anyhow::anyhow!("no memo"));
173        }
174
175        // Any output will let us decrypt the memo.
176        if let Some(output) = self.outputs().next() {
177            // First decrypt the wrapped memo key on the output.
178            let ovk_wrapped_key = output.body.ovk_wrapped_key.clone();
179            let shared_secret = Note::decrypt_key(
180                ovk_wrapped_key,
181                output.body.note_payload.note_commitment,
182                output.body.balance_commitment,
183                fvk.outgoing(),
184                &output.body.note_payload.ephemeral_key,
185            );
186
187            let wrapped_memo_key = &output.body.wrapped_memo_key;
188            let memo_key: PayloadKey = match shared_secret {
189                Ok(shared_secret) => {
190                    let payload_key =
191                        PayloadKey::derive(&shared_secret, &output.body.note_payload.ephemeral_key);
192                    wrapped_memo_key.decrypt_outgoing(&payload_key)?
193                }
194                Err(_) => wrapped_memo_key
195                    .decrypt(output.body.note_payload.ephemeral_key, fvk.incoming())?,
196            };
197
198            // Now we can use the memo key to decrypt the memo.
199            let tx_body = self.transaction_body();
200            let memo_ciphertext = tx_body
201                .memo
202                .as_ref()
203                .expect("memo field exists on this transaction");
204            let decrypted_memo = MemoCiphertext::decrypt(&memo_key, memo_ciphertext.clone())?;
205
206            // The memo is shared across all outputs, so we can stop here.
207            return Ok(decrypted_memo);
208        }
209
210        // If we got here, we were unable to decrypt the memo.
211        Err(anyhow::anyhow!("unable to decrypt memo"))
212    }
213
214    pub fn payload_keys(
215        &self,
216        fvk: &FullViewingKey,
217    ) -> anyhow::Result<BTreeMap<StateCommitment, PayloadKey>> {
218        let mut result = BTreeMap::new();
219
220        for action in self.actions() {
221            match action {
222                Action::Swap(swap) => {
223                    let commitment = swap.body.payload.commitment;
224                    let payload_key = PayloadKey::derive_swap(fvk.outgoing(), commitment);
225
226                    result.insert(commitment, payload_key);
227                }
228                Action::Output(output) => {
229                    // Outputs may be either incoming or outgoing; for an outgoing output
230                    // we need to use the ovk_wrapped_key, and for an incoming output we need to
231                    // use the IVK to perform key agreement with the ephemeral key.
232                    let ovk_wrapped_key = output.body.ovk_wrapped_key.clone();
233                    let commitment = output.body.note_payload.note_commitment;
234                    let epk = &output.body.note_payload.ephemeral_key;
235                    let cv = output.body.balance_commitment;
236                    let ovk = fvk.outgoing();
237                    let shared_secret =
238                        Note::decrypt_key(ovk_wrapped_key, commitment, cv, ovk, epk);
239
240                    match shared_secret {
241                        Ok(shared_secret) => {
242                            // This is an outgoing output.
243                            let payload_key = PayloadKey::derive(&shared_secret, epk);
244                            result.insert(commitment, payload_key);
245                        }
246                        Err(_) => {
247                            // This is (maybe) an incoming output, use the ivk.
248                            let shared_secret = fvk.incoming().key_agreement_with(epk)?;
249                            let payload_key = PayloadKey::derive(&shared_secret, epk);
250
251                            result.insert(commitment, payload_key);
252                        }
253                    }
254                }
255                // These actions have no payload keys; they're listed explicitly
256                // for exhaustiveness.
257                Action::SwapClaim(_)
258                | Action::Spend(_)
259                | Action::Delegate(_)
260                | Action::Undelegate(_)
261                | Action::UndelegateClaim(_)
262                | Action::ValidatorDefinition(_)
263                | Action::IbcRelay(_)
264                | Action::ProposalSubmit(_)
265                | Action::ProposalWithdraw(_)
266                | Action::ValidatorVote(_)
267                | Action::DelegatorVote(_)
268                | Action::ProposalDepositClaim(_)
269                | Action::PositionOpen(_)
270                | Action::PositionClose(_)
271                | Action::PositionWithdraw(_)
272                | Action::Ics20Withdrawal(_)
273                | Action::CommunityPoolSpend(_)
274                | Action::CommunityPoolOutput(_)
275                | Action::CommunityPoolDeposit(_)
276                | Action::ActionDutchAuctionSchedule(_)
277                | Action::ActionDutchAuctionEnd(_)
278                | Action::ActionDutchAuctionWithdraw(_)
279                | Action::ActionLiquidityTournamentVote(_) => {}
280            }
281        }
282
283        Ok(result)
284    }
285
286    pub fn view_from_perspective(&self, txp: &TransactionPerspective) -> TransactionView {
287        let mut action_views = Vec::new();
288
289        let mut memo_plaintext: Option<MemoPlaintext> = None;
290        let mut memo_ciphertext: Option<MemoCiphertext> = None;
291
292        for action in self.actions() {
293            let action_view = action.view_from_perspective(txp);
294
295            // In the case of Output actions, decrypt the transaction memo if this hasn't already been done.
296            if let ActionView::Output(output) = &action_view {
297                if memo_plaintext.is_none() {
298                    memo_plaintext = match self.transaction_body().memo {
299                        Some(ciphertext) => {
300                            memo_ciphertext = Some(ciphertext.clone());
301                            match output {
302                                OutputView::Visible {
303                                    output: _,
304                                    note: _,
305                                    payload_key: decrypted_memo_key,
306                                } => MemoCiphertext::decrypt(decrypted_memo_key, ciphertext).ok(),
307                                OutputView::Opaque { output: _ } => None,
308                            }
309                        }
310                        None => None,
311                    }
312                }
313            }
314
315            action_views.push(action_view);
316        }
317
318        let memo_view = match memo_ciphertext {
319            Some(ciphertext) => match memo_plaintext {
320                Some(plaintext) => {
321                    let plaintext_view: MemoPlaintextView = MemoPlaintextView {
322                        return_address: txp.view_address(plaintext.return_address()),
323                        text: plaintext.text().to_owned(),
324                    };
325                    Some(MemoView::Visible {
326                        plaintext: plaintext_view,
327                        ciphertext,
328                    })
329                }
330                None => Some(MemoView::Opaque { ciphertext }),
331            },
332            None => None,
333        };
334
335        let detection_data =
336            self.transaction_body()
337                .detection_data
338                .as_ref()
339                .map(|detection_data| DetectionData {
340                    fmd_clues: detection_data.fmd_clues.clone(),
341                });
342
343        TransactionView {
344            body_view: TransactionBodyView {
345                action_views,
346                transaction_parameters: self.transaction_parameters(),
347                detection_data,
348                memo_view,
349            },
350            binding_sig: self.binding_sig,
351            anchor: self.anchor,
352        }
353    }
354
355    pub fn actions(&self) -> impl Iterator<Item = &Action> {
356        self.transaction_body.actions.iter()
357    }
358
359    pub fn delegations(&self) -> impl Iterator<Item = &Delegate> {
360        self.actions().filter_map(|action| {
361            if let Action::Delegate(d) = action {
362                Some(d)
363            } else {
364                None
365            }
366        })
367    }
368
369    pub fn undelegations(&self) -> impl Iterator<Item = &Undelegate> {
370        self.actions().filter_map(|action| {
371            if let Action::Undelegate(d) = action {
372                Some(d)
373            } else {
374                None
375            }
376        })
377    }
378
379    pub fn undelegate_claims(&self) -> impl Iterator<Item = &UndelegateClaim> {
380        self.actions().filter_map(|action| {
381            if let Action::UndelegateClaim(d) = action {
382                Some(d)
383            } else {
384                None
385            }
386        })
387    }
388
389    pub fn proposal_submits(&self) -> impl Iterator<Item = &ProposalSubmit> {
390        self.actions().filter_map(|action| {
391            if let Action::ProposalSubmit(s) = action {
392                Some(s)
393            } else {
394                None
395            }
396        })
397    }
398
399    pub fn proposal_withdraws(&self) -> impl Iterator<Item = &ProposalWithdraw> {
400        self.actions().filter_map(|action| {
401            if let Action::ProposalWithdraw(w) = action {
402                Some(w)
403            } else {
404                None
405            }
406        })
407    }
408
409    pub fn validator_votes(&self) -> impl Iterator<Item = &ValidatorVote> {
410        self.actions().filter_map(|action| {
411            if let Action::ValidatorVote(v) = action {
412                Some(v)
413            } else {
414                None
415            }
416        })
417    }
418
419    pub fn delegator_votes(&self) -> impl Iterator<Item = &DelegatorVote> {
420        self.actions().filter_map(|action| {
421            if let Action::DelegatorVote(v) = action {
422                Some(v)
423            } else {
424                None
425            }
426        })
427    }
428
429    pub fn ibc_actions(&self) -> impl Iterator<Item = &IbcRelay> {
430        self.actions().filter_map(|action| {
431            if let Action::IbcRelay(ibc_action) = action {
432                Some(ibc_action)
433            } else {
434                None
435            }
436        })
437    }
438
439    pub fn validator_definitions(
440        &self,
441    ) -> impl Iterator<Item = &penumbra_sdk_stake::validator::Definition> {
442        self.actions().filter_map(|action| {
443            if let Action::ValidatorDefinition(d) = action {
444                Some(d)
445            } else {
446                None
447            }
448        })
449    }
450
451    pub fn outputs(&self) -> impl Iterator<Item = &Output> {
452        self.actions().filter_map(|action| {
453            if let Action::Output(d) = action {
454                Some(d)
455            } else {
456                None
457            }
458        })
459    }
460
461    pub fn swaps(&self) -> impl Iterator<Item = &Swap> {
462        self.actions().filter_map(|action| {
463            if let Action::Swap(s) = action {
464                Some(s)
465            } else {
466                None
467            }
468        })
469    }
470
471    pub fn spent_nullifiers(&self) -> impl Iterator<Item = Nullifier> + '_ {
472        self.actions().filter_map(|action| {
473            // Note: adding future actions that include nullifiers
474            // will need to be matched here as well as Spends
475            match action {
476                Action::Spend(spend) => Some(spend.body.nullifier),
477                Action::SwapClaim(swap_claim) => Some(swap_claim.body.nullifier),
478                _ => None,
479            }
480        })
481    }
482
483    pub fn state_commitments(&self) -> impl Iterator<Item = StateCommitment> + '_ {
484        self.actions()
485            .flat_map(|action| {
486                // Note: adding future actions that include state commitments
487                // will need to be matched here.
488                match action {
489                    Action::Output(output) => {
490                        [Some(output.body.note_payload.note_commitment), None]
491                    }
492                    Action::Swap(swap) => [Some(swap.body.payload.commitment), None],
493                    Action::SwapClaim(claim) => [
494                        Some(claim.body.output_1_commitment),
495                        Some(claim.body.output_2_commitment),
496                    ],
497                    _ => [None, None],
498                }
499            })
500            .filter_map(|x| x)
501    }
502
503    pub fn community_pool_deposits(&self) -> impl Iterator<Item = &CommunityPoolDeposit> {
504        self.actions().filter_map(|action| {
505            if let Action::CommunityPoolDeposit(d) = action {
506                Some(d)
507            } else {
508                None
509            }
510        })
511    }
512
513    pub fn community_pool_spends(&self) -> impl Iterator<Item = &CommunityPoolSpend> {
514        self.actions().filter_map(|action| {
515            if let Action::CommunityPoolSpend(s) = action {
516                Some(s)
517            } else {
518                None
519            }
520        })
521    }
522
523    pub fn spends(&self) -> impl Iterator<Item = &Spend> {
524        self.actions().filter_map(|action| {
525            if let Action::Spend(s) = action {
526                Some(s)
527            } else {
528                None
529            }
530        })
531    }
532
533    pub fn community_pool_outputs(&self) -> impl Iterator<Item = &CommunityPoolOutput> {
534        self.actions().filter_map(|action| {
535            if let Action::CommunityPoolOutput(o) = action {
536                Some(o)
537            } else {
538                None
539            }
540        })
541    }
542
543    pub fn position_openings(&self) -> impl Iterator<Item = &PositionOpen> {
544        self.actions().filter_map(|action| {
545            if let Action::PositionOpen(d) = action {
546                Some(d)
547            } else {
548                None
549            }
550        })
551    }
552
553    pub fn position_closings(&self) -> impl Iterator<Item = &PositionClose> {
554        self.actions().filter_map(|action| {
555            if let Action::PositionClose(d) = action {
556                Some(d)
557            } else {
558                None
559            }
560        })
561    }
562
563    pub fn transaction_body(&self) -> TransactionBody {
564        self.transaction_body.clone()
565    }
566
567    pub fn transaction_parameters(&self) -> TransactionParameters {
568        self.transaction_body.transaction_parameters.clone()
569    }
570
571    pub fn binding_sig(&self) -> &Signature<Binding> {
572        &self.binding_sig
573    }
574
575    pub fn id(&self) -> TransactionId {
576        use sha2::{Digest, Sha256};
577
578        let tx_bytes: Vec<u8> = self.clone().try_into().expect("can serialize transaction");
579        let mut id_bytes = [0; 32];
580        id_bytes[..].copy_from_slice(Sha256::digest(&tx_bytes).as_slice());
581
582        TransactionId(id_bytes)
583    }
584
585    /// Compute the binding verification key from the transaction data.
586    pub fn binding_verification_key(&self) -> VerificationKey<Binding> {
587        let mut balance_commitments = decaf377::Element::default();
588        for action in &self.transaction_body.actions {
589            balance_commitments += action.balance_commitment().0;
590        }
591
592        // Add fee into binding verification key computation.
593        let fee_v_blinding = Fr::zero();
594        let fee_value_commitment = self
595            .transaction_body
596            .transaction_parameters
597            .fee
598            .commit(fee_v_blinding);
599        balance_commitments += fee_value_commitment.0;
600
601        let binding_verification_key_bytes: VerificationKeyBytes<Binding> =
602            balance_commitments.vartime_compress().0.into();
603
604        binding_verification_key_bytes
605            .try_into()
606            .expect("verification key is valid")
607    }
608}
609
610impl DomainType for TransactionSummary {
611    type Proto = pbt::TransactionSummary;
612}
613
614impl From<TransactionSummary> for pbt::TransactionSummary {
615    fn from(summary: TransactionSummary) -> Self {
616        pbt::TransactionSummary {
617            effects: summary
618                .effects
619                .into_iter()
620                .map(|effect| pbt::transaction_summary::Effects {
621                    address: Some(effect.address.into()),
622                    balance: Some(effect.balance.into()),
623                })
624                .collect(),
625        }
626    }
627}
628
629impl TryFrom<pbt::TransactionSummary> for TransactionSummary {
630    type Error = anyhow::Error;
631
632    fn try_from(pbt: pbt::TransactionSummary) -> Result<Self, Self::Error> {
633        let effects = pbt
634            .effects
635            .into_iter()
636            .map(|effect| {
637                Ok(TransactionEffect {
638                    address: effect
639                        .address
640                        .ok_or_else(|| anyhow::anyhow!("missing address field"))?
641                        .try_into()?,
642                    balance: effect
643                        .balance
644                        .ok_or_else(|| anyhow::anyhow!("missing balance field"))?
645                        .try_into()?,
646                })
647            })
648            .collect::<Result<Vec<TransactionEffect>, anyhow::Error>>()?;
649
650        Ok(Self { effects })
651    }
652}
653
654impl DomainType for TransactionBody {
655    type Proto = pbt::TransactionBody;
656}
657
658impl From<TransactionBody> for pbt::TransactionBody {
659    fn from(msg: TransactionBody) -> Self {
660        pbt::TransactionBody {
661            actions: msg.actions.into_iter().map(|x| x.into()).collect(),
662            transaction_parameters: Some(msg.transaction_parameters.into()),
663            detection_data: msg.detection_data.map(|x| x.into()),
664            memo: msg.memo.map(Into::into),
665        }
666    }
667}
668
669impl TryFrom<pbt::TransactionBody> for TransactionBody {
670    type Error = Error;
671
672    fn try_from(proto: pbt::TransactionBody) -> anyhow::Result<Self, Self::Error> {
673        let mut actions = Vec::<Action>::new();
674        for action in proto.actions {
675            actions.push(
676                action
677                    .try_into()
678                    .context("action malformed while parsing transaction body")?,
679            );
680        }
681
682        let memo = proto
683            .memo
684            .map(TryFrom::try_from)
685            .transpose()
686            .context("encrypted memo malformed while parsing transaction body")?;
687
688        let detection_data = proto
689            .detection_data
690            .map(TryFrom::try_from)
691            .transpose()
692            .context("detection data malformed while parsing transaction body")?;
693
694        let transaction_parameters = proto
695            .transaction_parameters
696            .ok_or_else(|| anyhow::anyhow!("transaction body missing transaction parameters"))?
697            .try_into()
698            .context("transaction parameters malformed")?;
699
700        Ok(TransactionBody {
701            actions,
702            transaction_parameters,
703            detection_data,
704            memo,
705        })
706    }
707}
708
709impl DomainType for Transaction {
710    type Proto = pbt::Transaction;
711}
712
713impl From<Transaction> for pbt::Transaction {
714    fn from(msg: Transaction) -> Self {
715        pbt::Transaction {
716            body: Some(msg.transaction_body.into()),
717            anchor: Some(msg.anchor.into()),
718            binding_sig: Some(msg.binding_sig.into()),
719        }
720    }
721}
722
723impl From<&Transaction> for pbt::Transaction {
724    fn from(msg: &Transaction) -> Self {
725        Transaction {
726            transaction_body: msg.transaction_body.clone(),
727            anchor: msg.anchor.clone(),
728            binding_sig: msg.binding_sig.clone(),
729        }
730        .into()
731    }
732}
733
734impl TryFrom<pbt::Transaction> for Transaction {
735    type Error = Error;
736
737    fn try_from(proto: pbt::Transaction) -> anyhow::Result<Self, Self::Error> {
738        let transaction_body = proto
739            .body
740            .ok_or_else(|| anyhow::anyhow!("transaction missing body"))?
741            .try_into()
742            .context("transaction body malformed")?;
743
744        let binding_sig = proto
745            .binding_sig
746            .ok_or_else(|| anyhow::anyhow!("transaction missing binding signature"))?
747            .try_into()
748            .context("transaction binding signature malformed")?;
749
750        let anchor = proto
751            .anchor
752            .ok_or_else(|| anyhow::anyhow!("transaction missing anchor"))?
753            .try_into()
754            .context("transaction anchor malformed")?;
755
756        Ok(Transaction {
757            transaction_body,
758            binding_sig,
759            anchor,
760        })
761    }
762}
763
764impl TryFrom<&[u8]> for Transaction {
765    type Error = Error;
766
767    fn try_from(bytes: &[u8]) -> Result<Transaction, Self::Error> {
768        pbt::Transaction::decode(bytes)?.try_into()
769    }
770}
771
772impl TryFrom<Vec<u8>> for Transaction {
773    type Error = Error;
774
775    fn try_from(bytes: Vec<u8>) -> Result<Transaction, Self::Error> {
776        Self::try_from(&bytes[..])
777    }
778}
779
780impl From<Transaction> for Vec<u8> {
781    fn from(transaction: Transaction) -> Vec<u8> {
782        let protobuf_serialized: pbt::Transaction = transaction.into();
783        protobuf_serialized.encode_to_vec()
784    }
785}
786
787impl From<&Transaction> for Vec<u8> {
788    fn from(transaction: &Transaction) -> Vec<u8> {
789        let protobuf_serialized: pbt::Transaction = transaction.into();
790        protobuf_serialized.encode_to_vec()
791    }
792}