whisky_pallas/tx_parser/
mod.rs

1mod certificates;
2mod collaterals;
3mod context;
4mod inputs;
5mod metadata;
6mod mints;
7pub mod outputs;
8mod parsable;
9mod reference_inputs;
10mod required_signers;
11mod validity_range;
12mod votes;
13mod withdrawals;
14
15use crate::{
16    tx_parser::{
17        certificates::extract_certificates, collaterals::extract_collaterals,
18        context::ParserContext, inputs::extract_inputs, metadata::extract_metadata,
19        mints::extract_mints, outputs::extract_outputs, reference_inputs::extract_reference_inputs,
20        required_signers::extract_required_signers, validity_range::extract_validity_range,
21        votes::extract_votes, withdrawals::extract_withdrawals,
22    },
23    wrapper::transaction_body::{ScriptRef, ScriptRefKind, Transaction},
24};
25
26use pallas::ledger::traverse::ComputeHash;
27use pallas_crypto::key::ed25519::{PublicKey, Signature};
28use whisky_common::{TxBuilderBody, UTxO, UtxoInput, UtxoOutput, WError};
29
30pub fn parse(tx_hex: &str, resolved_utxos: &[UTxO]) -> Result<TxBuilderBody, WError> {
31    let bytes = hex::decode(tx_hex).map_err(|e| {
32        WError::new(
33            "WhiskyPallas - parse tx hex:",
34            &format!("Hex decode error: {}", e),
35        )
36    })?;
37    let pallas_tx = Transaction::decode_bytes(&bytes)?;
38    let mut parser_context = ParserContext::new();
39    parser_context.fill_resolved_utxos(&pallas_tx.inner.transaction_body, resolved_utxos)?;
40    parser_context
41        .collect_script_witnesses_from_tx_witnesses_set(&pallas_tx.inner.transaction_witness_set)?;
42    parser_context.collect_script_witnesses_from_tx_body(&pallas_tx.inner.transaction_body)?;
43
44    let inputs = extract_inputs(&pallas_tx.inner, &parser_context)?;
45    let outputs = extract_outputs(&pallas_tx.inner)?;
46    let collaterals = extract_collaterals(&pallas_tx.inner, &parser_context)?;
47    let required_signers = extract_required_signers(&pallas_tx.inner)?;
48    let reference_inputs = extract_reference_inputs(&pallas_tx.inner, &parser_context)?;
49    let withdrawals = extract_withdrawals(&pallas_tx.inner, &parser_context)?;
50    let mints = extract_mints(&pallas_tx.inner, &parser_context)?;
51    let certificates = extract_certificates(&pallas_tx.inner, &parser_context)?;
52    let validity_range = extract_validity_range(&pallas_tx.inner)?;
53    let metadata = extract_metadata(&pallas_tx.inner)?;
54    let votes = extract_votes(&pallas_tx.inner, &parser_context)?;
55
56    let change_output = outputs.last().unwrap();
57    Ok(TxBuilderBody {
58        inputs,
59        outputs: outputs.clone(),
60        collaterals,
61        required_signatures: required_signers,
62        reference_inputs,
63        withdrawals,
64        mints,
65        change_address: change_output.address.clone(),
66        change_datum: change_output.datum.clone(),
67        metadata,
68        validity_range,
69        certificates,
70        votes,
71        signing_key: vec![],
72        fee: None, // These fields are expected to be recalculated by the TxBuilder
73        network: None,
74        total_collateral: None, // These fields are expected to be recalculated by the TxBuilder
75        collateral_return_address: None, // These fields are expected to be recalculated by the TxBuilder
76    })
77}
78
79pub fn check_tx_required_signers(tx_hex: &str) -> Result<bool, WError> {
80    let bytes = hex::decode(tx_hex).map_err(|e| {
81        WError::new(
82            "WhiskyPallas - check tx required signers:",
83            &format!("Hex decode error: {}", e),
84        )
85    })?;
86    let pallas_tx = Transaction::decode_bytes(&bytes)?;
87    let required_signers = extract_required_signers(&pallas_tx.inner)?;
88
89    if let Some(signatures) = &pallas_tx.inner.transaction_witness_set.vkeywitness {
90        Ok(required_signers.iter().all(|signer| {
91            signatures.iter().any(|vkey_witness| {
92                let vkey_hex = vkey_witness.vkey.to_string();
93                let public_key =
94                    PublicKey::from(<[u8; 32]>::try_from(vkey_witness.vkey.to_vec()).unwrap());
95                public_key.verify(
96                    pallas_tx.inner.transaction_body.compute_hash(),
97                    &Signature::from(
98                        <[u8; 64]>::try_from(vkey_witness.signature.to_vec()).unwrap(),
99                    ),
100                ) && (vkey_hex == *signer)
101            })
102        }))
103    } else {
104        Ok(required_signers.is_empty())
105    }
106}
107
108pub fn extract_tx_utxos(tx_hex: &str) -> Result<Vec<whisky_common::UTxO>, WError> {
109    let bytes = hex::decode(tx_hex).map_err(|e| {
110        WError::new(
111            "WhiskyPallas - extract tx outputs:",
112            &format!("Hex decode error: {}", e),
113        )
114    })?;
115    let pallas_tx = Transaction::decode_bytes(&bytes)?;
116    extract_outputs(&pallas_tx.inner).and_then(|outputs| {
117        outputs
118            .into_iter()
119            .enumerate()
120            .map(|(index, output)| -> Result<UTxO, WError> {
121                let datum_cbor = match output.datum.clone() {
122                    Some(datum) => match datum {
123                        whisky_common::Datum::Inline(s) => Some(s),
124                        whisky_common::Datum::Hash(_) => None,
125                        whisky_common::Datum::Embedded(_) => None,
126                    },
127                    None => None,
128                };
129                let datum_hash = match output.datum {
130                    Some(datum) => match datum {
131                        whisky_common::Datum::Inline(_) => None,
132                        whisky_common::Datum::Hash(s) => Some(s),
133                        whisky_common::Datum::Embedded(s) => Some(s),
134                    },
135                    None => None,
136                };
137                let script_cbor: Option<String> = match output.reference_script {
138                    Some(script) => match script {
139                        whisky_common::OutputScriptSource::ProvidedSimpleScriptSource(
140                            provided_simple_script_source,
141                        ) => Some(
142                            ScriptRef::new(ScriptRefKind::NativeScript {
143                                native_script_hex: provided_simple_script_source.script_cbor,
144                            })
145                            .unwrap()
146                            .encode()?,
147                        ),
148
149                        whisky_common::OutputScriptSource::ProvidedScriptSource(
150                            provided_script_source,
151                        ) => match provided_script_source.language_version {
152                            whisky_common::LanguageVersion::V1 => Some(
153                                ScriptRef::new(ScriptRefKind::PlutusV1Script {
154                                    plutus_v1_script_hex: provided_script_source.script_cbor,
155                                })
156                                .unwrap()
157                                .encode()?,
158                            ),
159                            whisky_common::LanguageVersion::V2 => Some(
160                                ScriptRef::new(ScriptRefKind::PlutusV2Script {
161                                    plutus_v2_script_hex: provided_script_source.script_cbor,
162                                })
163                                .unwrap()
164                                .encode()?,
165                            ),
166                            whisky_common::LanguageVersion::V3 => Some(
167                                ScriptRef::new(ScriptRefKind::PlutusV3Script {
168                                    plutus_v3_script_hex: provided_script_source.script_cbor,
169                                })
170                                .unwrap()
171                                .encode()?,
172                            ),
173                        },
174                    },
175                    None => None,
176                };
177                Ok(UTxO {
178                    input: UtxoInput {
179                        tx_hash: pallas_tx.inner.transaction_body.compute_hash().to_string(),
180                        output_index: index as u32,
181                    },
182                    output: UtxoOutput {
183                        address: output.address,
184                        amount: output.amount,
185                        plutus_data: datum_cbor,
186                        data_hash: datum_hash,
187                        script_ref: script_cbor,
188                        script_hash: None,
189                    },
190                })
191            })
192            .collect()
193    })
194}