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            }
280        }
281
282        Ok(result)
283    }
284
285    pub fn view_from_perspective(&self, txp: &TransactionPerspective) -> TransactionView {
286        let mut action_views = Vec::new();
287
288        let mut memo_plaintext: Option<MemoPlaintext> = None;
289        let mut memo_ciphertext: Option<MemoCiphertext> = None;
290
291        for action in self.actions() {
292            let action_view = action.view_from_perspective(txp);
293
294            // In the case of Output actions, decrypt the transaction memo if this hasn't already been done.
295            if let ActionView::Output(output) = &action_view {
296                if memo_plaintext.is_none() {
297                    memo_plaintext = match self.transaction_body().memo {
298                        Some(ciphertext) => {
299                            memo_ciphertext = Some(ciphertext.clone());
300                            match output {
301                                OutputView::Visible {
302                                    output: _,
303                                    note: _,
304                                    payload_key: decrypted_memo_key,
305                                } => MemoCiphertext::decrypt(decrypted_memo_key, ciphertext).ok(),
306                                OutputView::Opaque { output: _ } => None,
307                            }
308                        }
309                        None => None,
310                    }
311                }
312            }
313
314            action_views.push(action_view);
315        }
316
317        let memo_view = match memo_ciphertext {
318            Some(ciphertext) => match memo_plaintext {
319                Some(plaintext) => {
320                    let plaintext_view: MemoPlaintextView = MemoPlaintextView {
321                        return_address: txp.view_address(plaintext.return_address()),
322                        text: plaintext.text().to_owned(),
323                    };
324                    Some(MemoView::Visible {
325                        plaintext: plaintext_view,
326                        ciphertext,
327                    })
328                }
329                None => Some(MemoView::Opaque { ciphertext }),
330            },
331            None => None,
332        };
333
334        let detection_data =
335            self.transaction_body()
336                .detection_data
337                .as_ref()
338                .map(|detection_data| DetectionData {
339                    fmd_clues: detection_data.fmd_clues.clone(),
340                });
341
342        TransactionView {
343            body_view: TransactionBodyView {
344                action_views,
345                transaction_parameters: self.transaction_parameters(),
346                detection_data,
347                memo_view,
348            },
349            binding_sig: self.binding_sig,
350            anchor: self.anchor,
351        }
352    }
353
354    pub fn actions(&self) -> impl Iterator<Item = &Action> {
355        self.transaction_body.actions.iter()
356    }
357
358    pub fn delegations(&self) -> impl Iterator<Item = &Delegate> {
359        self.actions().filter_map(|action| {
360            if let Action::Delegate(d) = action {
361                Some(d)
362            } else {
363                None
364            }
365        })
366    }
367
368    pub fn undelegations(&self) -> impl Iterator<Item = &Undelegate> {
369        self.actions().filter_map(|action| {
370            if let Action::Undelegate(d) = action {
371                Some(d)
372            } else {
373                None
374            }
375        })
376    }
377
378    pub fn undelegate_claims(&self) -> impl Iterator<Item = &UndelegateClaim> {
379        self.actions().filter_map(|action| {
380            if let Action::UndelegateClaim(d) = action {
381                Some(d)
382            } else {
383                None
384            }
385        })
386    }
387
388    pub fn proposal_submits(&self) -> impl Iterator<Item = &ProposalSubmit> {
389        self.actions().filter_map(|action| {
390            if let Action::ProposalSubmit(s) = action {
391                Some(s)
392            } else {
393                None
394            }
395        })
396    }
397
398    pub fn proposal_withdraws(&self) -> impl Iterator<Item = &ProposalWithdraw> {
399        self.actions().filter_map(|action| {
400            if let Action::ProposalWithdraw(w) = action {
401                Some(w)
402            } else {
403                None
404            }
405        })
406    }
407
408    pub fn validator_votes(&self) -> impl Iterator<Item = &ValidatorVote> {
409        self.actions().filter_map(|action| {
410            if let Action::ValidatorVote(v) = action {
411                Some(v)
412            } else {
413                None
414            }
415        })
416    }
417
418    pub fn delegator_votes(&self) -> impl Iterator<Item = &DelegatorVote> {
419        self.actions().filter_map(|action| {
420            if let Action::DelegatorVote(v) = action {
421                Some(v)
422            } else {
423                None
424            }
425        })
426    }
427
428    pub fn ibc_actions(&self) -> impl Iterator<Item = &IbcRelay> {
429        self.actions().filter_map(|action| {
430            if let Action::IbcRelay(ibc_action) = action {
431                Some(ibc_action)
432            } else {
433                None
434            }
435        })
436    }
437
438    pub fn validator_definitions(
439        &self,
440    ) -> impl Iterator<Item = &penumbra_sdk_stake::validator::Definition> {
441        self.actions().filter_map(|action| {
442            if let Action::ValidatorDefinition(d) = action {
443                Some(d)
444            } else {
445                None
446            }
447        })
448    }
449
450    pub fn outputs(&self) -> impl Iterator<Item = &Output> {
451        self.actions().filter_map(|action| {
452            if let Action::Output(d) = action {
453                Some(d)
454            } else {
455                None
456            }
457        })
458    }
459
460    pub fn swaps(&self) -> impl Iterator<Item = &Swap> {
461        self.actions().filter_map(|action| {
462            if let Action::Swap(s) = action {
463                Some(s)
464            } else {
465                None
466            }
467        })
468    }
469
470    pub fn spent_nullifiers(&self) -> impl Iterator<Item = Nullifier> + '_ {
471        self.actions().filter_map(|action| {
472            // Note: adding future actions that include nullifiers
473            // will need to be matched here as well as Spends
474            match action {
475                Action::Spend(spend) => Some(spend.body.nullifier),
476                Action::SwapClaim(swap_claim) => Some(swap_claim.body.nullifier),
477                _ => None,
478            }
479        })
480    }
481
482    pub fn state_commitments(&self) -> impl Iterator<Item = StateCommitment> + '_ {
483        self.actions()
484            .flat_map(|action| {
485                // Note: adding future actions that include state commitments
486                // will need to be matched here.
487                match action {
488                    Action::Output(output) => {
489                        [Some(output.body.note_payload.note_commitment), None]
490                    }
491                    Action::Swap(swap) => [Some(swap.body.payload.commitment), None],
492                    Action::SwapClaim(claim) => [
493                        Some(claim.body.output_1_commitment),
494                        Some(claim.body.output_2_commitment),
495                    ],
496                    _ => [None, None],
497                }
498            })
499            .filter_map(|x| x)
500    }
501
502    pub fn community_pool_deposits(&self) -> impl Iterator<Item = &CommunityPoolDeposit> {
503        self.actions().filter_map(|action| {
504            if let Action::CommunityPoolDeposit(d) = action {
505                Some(d)
506            } else {
507                None
508            }
509        })
510    }
511
512    pub fn community_pool_spends(&self) -> impl Iterator<Item = &CommunityPoolSpend> {
513        self.actions().filter_map(|action| {
514            if let Action::CommunityPoolSpend(s) = action {
515                Some(s)
516            } else {
517                None
518            }
519        })
520    }
521
522    pub fn spends(&self) -> impl Iterator<Item = &Spend> {
523        self.actions().filter_map(|action| {
524            if let Action::Spend(s) = action {
525                Some(s)
526            } else {
527                None
528            }
529        })
530    }
531
532    pub fn community_pool_outputs(&self) -> impl Iterator<Item = &CommunityPoolOutput> {
533        self.actions().filter_map(|action| {
534            if let Action::CommunityPoolOutput(o) = action {
535                Some(o)
536            } else {
537                None
538            }
539        })
540    }
541
542    pub fn position_openings(&self) -> impl Iterator<Item = &PositionOpen> {
543        self.actions().filter_map(|action| {
544            if let Action::PositionOpen(d) = action {
545                Some(d)
546            } else {
547                None
548            }
549        })
550    }
551
552    pub fn position_closings(&self) -> impl Iterator<Item = &PositionClose> {
553        self.actions().filter_map(|action| {
554            if let Action::PositionClose(d) = action {
555                Some(d)
556            } else {
557                None
558            }
559        })
560    }
561
562    pub fn transaction_body(&self) -> TransactionBody {
563        self.transaction_body.clone()
564    }
565
566    pub fn transaction_parameters(&self) -> TransactionParameters {
567        self.transaction_body.transaction_parameters.clone()
568    }
569
570    pub fn binding_sig(&self) -> &Signature<Binding> {
571        &self.binding_sig
572    }
573
574    pub fn id(&self) -> TransactionId {
575        use sha2::{Digest, Sha256};
576
577        let tx_bytes: Vec<u8> = self.clone().try_into().expect("can serialize transaction");
578        let mut id_bytes = [0; 32];
579        id_bytes[..].copy_from_slice(Sha256::digest(&tx_bytes).as_slice());
580
581        TransactionId(id_bytes)
582    }
583
584    /// Compute the binding verification key from the transaction data.
585    pub fn binding_verification_key(&self) -> VerificationKey<Binding> {
586        let mut balance_commitments = decaf377::Element::default();
587        for action in &self.transaction_body.actions {
588            balance_commitments += action.balance_commitment().0;
589        }
590
591        // Add fee into binding verification key computation.
592        let fee_v_blinding = Fr::zero();
593        let fee_value_commitment = self
594            .transaction_body
595            .transaction_parameters
596            .fee
597            .commit(fee_v_blinding);
598        balance_commitments += fee_value_commitment.0;
599
600        let binding_verification_key_bytes: VerificationKeyBytes<Binding> =
601            balance_commitments.vartime_compress().0.into();
602
603        binding_verification_key_bytes
604            .try_into()
605            .expect("verification key is valid")
606    }
607}
608
609impl DomainType for TransactionSummary {
610    type Proto = pbt::TransactionSummary;
611}
612
613impl From<TransactionSummary> for pbt::TransactionSummary {
614    fn from(summary: TransactionSummary) -> Self {
615        pbt::TransactionSummary {
616            effects: summary
617                .effects
618                .into_iter()
619                .map(|effect| pbt::transaction_summary::Effects {
620                    address: Some(effect.address.into()),
621                    balance: Some(effect.balance.into()),
622                })
623                .collect(),
624        }
625    }
626}
627
628impl TryFrom<pbt::TransactionSummary> for TransactionSummary {
629    type Error = anyhow::Error;
630
631    fn try_from(pbt: pbt::TransactionSummary) -> Result<Self, Self::Error> {
632        let effects = pbt
633            .effects
634            .into_iter()
635            .map(|effect| {
636                Ok(TransactionEffect {
637                    address: effect
638                        .address
639                        .ok_or_else(|| anyhow::anyhow!("missing address field"))?
640                        .try_into()?,
641                    balance: effect
642                        .balance
643                        .ok_or_else(|| anyhow::anyhow!("missing balance field"))?
644                        .try_into()?,
645                })
646            })
647            .collect::<Result<Vec<TransactionEffect>, anyhow::Error>>()?;
648
649        Ok(Self { effects })
650    }
651}
652
653impl DomainType for TransactionBody {
654    type Proto = pbt::TransactionBody;
655}
656
657impl From<TransactionBody> for pbt::TransactionBody {
658    fn from(msg: TransactionBody) -> Self {
659        pbt::TransactionBody {
660            actions: msg.actions.into_iter().map(|x| x.into()).collect(),
661            transaction_parameters: Some(msg.transaction_parameters.into()),
662            detection_data: msg.detection_data.map(|x| x.into()),
663            memo: msg.memo.map(Into::into),
664        }
665    }
666}
667
668impl TryFrom<pbt::TransactionBody> for TransactionBody {
669    type Error = Error;
670
671    fn try_from(proto: pbt::TransactionBody) -> anyhow::Result<Self, Self::Error> {
672        let mut actions = Vec::<Action>::new();
673        for action in proto.actions {
674            actions.push(
675                action
676                    .try_into()
677                    .context("action malformed while parsing transaction body")?,
678            );
679        }
680
681        let memo = proto
682            .memo
683            .map(TryFrom::try_from)
684            .transpose()
685            .context("encrypted memo malformed while parsing transaction body")?;
686
687        let detection_data = proto
688            .detection_data
689            .map(TryFrom::try_from)
690            .transpose()
691            .context("detection data malformed while parsing transaction body")?;
692
693        let transaction_parameters = proto
694            .transaction_parameters
695            .ok_or_else(|| anyhow::anyhow!("transaction body missing transaction parameters"))?
696            .try_into()
697            .context("transaction parameters malformed")?;
698
699        Ok(TransactionBody {
700            actions,
701            transaction_parameters,
702            detection_data,
703            memo,
704        })
705    }
706}
707
708impl DomainType for Transaction {
709    type Proto = pbt::Transaction;
710}
711
712impl From<Transaction> for pbt::Transaction {
713    fn from(msg: Transaction) -> Self {
714        pbt::Transaction {
715            body: Some(msg.transaction_body.into()),
716            anchor: Some(msg.anchor.into()),
717            binding_sig: Some(msg.binding_sig.into()),
718        }
719    }
720}
721
722impl From<&Transaction> for pbt::Transaction {
723    fn from(msg: &Transaction) -> Self {
724        Transaction {
725            transaction_body: msg.transaction_body.clone(),
726            anchor: msg.anchor.clone(),
727            binding_sig: msg.binding_sig.clone(),
728        }
729        .into()
730    }
731}
732
733impl TryFrom<pbt::Transaction> for Transaction {
734    type Error = Error;
735
736    fn try_from(proto: pbt::Transaction) -> anyhow::Result<Self, Self::Error> {
737        let transaction_body = proto
738            .body
739            .ok_or_else(|| anyhow::anyhow!("transaction missing body"))?
740            .try_into()
741            .context("transaction body malformed")?;
742
743        let binding_sig = proto
744            .binding_sig
745            .ok_or_else(|| anyhow::anyhow!("transaction missing binding signature"))?
746            .try_into()
747            .context("transaction binding signature malformed")?;
748
749        let anchor = proto
750            .anchor
751            .ok_or_else(|| anyhow::anyhow!("transaction missing anchor"))?
752            .try_into()
753            .context("transaction anchor malformed")?;
754
755        Ok(Transaction {
756            transaction_body,
757            binding_sig,
758            anchor,
759        })
760    }
761}
762
763impl TryFrom<&[u8]> for Transaction {
764    type Error = Error;
765
766    fn try_from(bytes: &[u8]) -> Result<Transaction, Self::Error> {
767        pbt::Transaction::decode(bytes)?.try_into()
768    }
769}
770
771impl TryFrom<Vec<u8>> for Transaction {
772    type Error = Error;
773
774    fn try_from(bytes: Vec<u8>) -> Result<Transaction, Self::Error> {
775        Self::try_from(&bytes[..])
776    }
777}
778
779impl From<Transaction> for Vec<u8> {
780    fn from(transaction: Transaction) -> Vec<u8> {
781        let protobuf_serialized: pbt::Transaction = transaction.into();
782        protobuf_serialized.encode_to_vec()
783    }
784}
785
786impl From<&Transaction> for Vec<u8> {
787    fn from(transaction: &Transaction) -> Vec<u8> {
788        let protobuf_serialized: pbt::Transaction = transaction.into();
789        protobuf_serialized.encode_to_vec()
790    }
791}