zebra_rpc/methods/types/
transaction.rs

1//! Verbose transaction-related types.
2
3use std::sync::Arc;
4
5use hex::ToHex;
6
7use zebra_chain::{
8    block,
9    parameters::Network,
10    sapling::NotSmallOrderValueCommitment,
11    transaction::{SerializedTransaction, Transaction},
12    transparent::Script,
13};
14use zebra_consensus::groth16::Description;
15use zebra_state::IntoDisk;
16
17use crate::methods::types;
18
19/// A Transaction object as returned by `getrawtransaction` and `getblock` RPC
20/// requests.
21#[derive(Clone, Debug, PartialEq, serde::Serialize)]
22pub struct TransactionObject {
23    /// The raw transaction, encoded as hex bytes.
24    #[serde(with = "hex")]
25    pub hex: SerializedTransaction,
26    /// The height of the block in the best chain that contains the tx or `None` if the tx is in
27    /// the mempool.
28    #[serde(skip_serializing_if = "Option::is_none")]
29    pub height: Option<u32>,
30    /// The height diff between the block containing the tx and the best chain tip + 1 or `None`
31    /// if the tx is in the mempool.
32    #[serde(skip_serializing_if = "Option::is_none")]
33    pub confirmations: Option<u32>,
34
35    /// Transparent inputs of the transaction.
36    #[serde(rename = "vin", skip_serializing_if = "Option::is_none")]
37    pub inputs: Option<Vec<Input>>,
38
39    /// Transparent outputs of the transaction.
40    #[serde(rename = "vout", skip_serializing_if = "Option::is_none")]
41    pub outputs: Option<Vec<Output>>,
42
43    /// Sapling spends of the transaction.
44    #[serde(rename = "vShieldedSpend", skip_serializing_if = "Option::is_none")]
45    pub shielded_spends: Option<Vec<ShieldedSpend>>,
46
47    /// Sapling outputs of the transaction.
48    #[serde(rename = "vShieldedOutput", skip_serializing_if = "Option::is_none")]
49    pub shielded_outputs: Option<Vec<ShieldedOutput>>,
50
51    /// Orchard actions of the transaction.
52    #[serde(rename = "orchard", skip_serializing_if = "Option::is_none")]
53    pub orchard: Option<Orchard>,
54
55    /// The net value of Sapling Spends minus Outputs in ZEC
56    #[serde(rename = "valueBalance", skip_serializing_if = "Option::is_none")]
57    pub value_balance: Option<f64>,
58
59    /// The net value of Sapling Spends minus Outputs in zatoshis
60    #[serde(rename = "valueBalanceZat", skip_serializing_if = "Option::is_none")]
61    pub value_balance_zat: Option<i64>,
62    // TODO: some fields not yet supported
63}
64
65/// The transparent input of a transaction.
66#[derive(Clone, Debug, PartialEq, serde::Serialize)]
67#[serde(untagged)]
68pub enum Input {
69    /// A coinbase input.
70    Coinbase {
71        /// The coinbase scriptSig as hex.
72        #[serde(with = "hex")]
73        coinbase: Vec<u8>,
74        /// The script sequence number.
75        sequence: u32,
76    },
77    /// A non-coinbase input.
78    NonCoinbase {
79        /// The transaction id.
80        txid: String,
81        /// The vout index.
82        vout: u32,
83        /// The script.
84        script_sig: ScriptSig,
85        /// The script sequence number.
86        sequence: u32,
87        /// The value of the output being spent in ZEC.
88        #[serde(skip_serializing_if = "Option::is_none")]
89        value: Option<f64>,
90        /// The value of the output being spent, in zats.
91        #[serde(rename = "valueZat", skip_serializing_if = "Option::is_none")]
92        value_zat: Option<i64>,
93        /// The address of the output being spent.
94        #[serde(skip_serializing_if = "Option::is_none")]
95        address: Option<String>,
96    },
97}
98
99/// The transparent output of a transaction.
100#[derive(Clone, Debug, PartialEq, serde::Serialize)]
101pub struct Output {
102    /// The value in ZEC.
103    value: f64,
104    /// The value in zats.
105    #[serde(rename = "valueZat")]
106    value_zat: i64,
107    /// index.
108    n: u32,
109    /// The scriptPubKey.
110    #[serde(rename = "scriptPubKey")]
111    script_pub_key: ScriptPubKey,
112}
113
114/// The scriptPubKey of a transaction output.
115#[derive(Clone, Debug, PartialEq, serde::Serialize)]
116pub struct ScriptPubKey {
117    /// the asm.
118    // #9330: The `asm` field is not currently populated.
119    asm: String,
120    /// the hex.
121    #[serde(with = "hex")]
122    hex: Script,
123    /// The required sigs.
124    #[serde(rename = "reqSigs")]
125    req_sigs: u32,
126    /// The type, eg 'pubkeyhash'.
127    // #9330: The `type` field is not currently populated.
128    r#type: String,
129    /// The addresses.
130    addresses: Vec<String>,
131}
132
133/// The scriptSig of a transaction input.
134#[derive(Clone, Debug, PartialEq, serde::Serialize)]
135pub struct ScriptSig {
136    /// The asm.
137    // #9330: The `asm` field is not currently populated.
138    asm: String,
139    /// The hex.
140    #[serde(with = "hex")]
141    hex: Script,
142}
143
144/// A Sapling spend of a transaction.
145#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
146pub struct ShieldedSpend {
147    /// Value commitment to the input note.
148    #[serde(with = "hex")]
149    cv: NotSmallOrderValueCommitment,
150    /// Merkle root of the Sapling note commitment tree.
151    #[serde(with = "hex")]
152    anchor: [u8; 32],
153    /// The nullifier of the input note.
154    #[serde(with = "hex")]
155    nullifier: [u8; 32],
156    /// The randomized public key for spendAuthSig.
157    #[serde(with = "hex")]
158    rk: [u8; 32],
159    /// A zero-knowledge proof using the Sapling Spend circuit.
160    #[serde(with = "hex")]
161    proof: [u8; 192],
162    /// A signature authorizing this Spend.
163    #[serde(rename = "spendAuthSig", with = "hex")]
164    spend_auth_sig: [u8; 64],
165}
166
167/// A Sapling output of a transaction.
168#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
169pub struct ShieldedOutput {
170    /// Value commitment to the input note.
171    #[serde(with = "hex")]
172    cv: NotSmallOrderValueCommitment,
173    /// The u-coordinate of the note commitment for the output note.
174    #[serde(rename = "cmu", with = "hex")]
175    cm_u: [u8; 32],
176    /// A Jubjub public key.
177    #[serde(rename = "ephemeralKey", with = "hex")]
178    ephemeral_key: [u8; 32],
179    /// The output note encrypted to the recipient.
180    #[serde(rename = "encCiphertext", with = "hex")]
181    enc_ciphertext: [u8; 580],
182    /// A ciphertext enabling the sender to recover the output note.
183    #[serde(rename = "outCiphertext", with = "hex")]
184    out_ciphertext: [u8; 80],
185    /// A zero-knowledge proof using the Sapling Output circuit.
186    #[serde(with = "hex")]
187    proof: [u8; 192],
188}
189
190/// Object with Orchard-specific information.
191#[derive(Clone, Debug, PartialEq, serde::Serialize)]
192pub struct Orchard {
193    /// Array of Orchard actions.
194    actions: Vec<OrchardAction>,
195    /// The net value of Orchard Actions in ZEC.
196    #[serde(rename = "valueBalance")]
197    value_balance: f64,
198    /// The net value of Orchard Actions in zatoshis.
199    #[serde(rename = "valueBalanceZat")]
200    value_balance_zat: i64,
201}
202
203/// The Orchard action of a transaction.
204#[derive(Clone, Debug, Eq, PartialEq, serde::Serialize)]
205pub struct OrchardAction {
206    /// A value commitment to the net value of the input note minus the output note.
207    #[serde(with = "hex")]
208    cv: [u8; 32],
209    /// The nullifier of the input note.
210    #[serde(with = "hex")]
211    nullifier: [u8; 32],
212    /// The randomized validating key for spendAuthSig.
213    #[serde(with = "hex")]
214    rk: [u8; 32],
215    /// The x-coordinate of the note commitment for the output note.
216    #[serde(rename = "cmx", with = "hex")]
217    cm_x: [u8; 32],
218    /// An encoding of an ephemeral Pallas public key.
219    #[serde(rename = "ephemeralKey", with = "hex")]
220    ephemeral_key: [u8; 32],
221    /// The output note encrypted to the recipient.
222    #[serde(rename = "encCiphertext", with = "hex")]
223    enc_ciphertext: [u8; 580],
224    /// A ciphertext enabling the sender to recover the output note.
225    #[serde(rename = "spendAuthSig", with = "hex")]
226    spend_auth_sig: [u8; 64],
227    /// A signature authorizing the spend in this Action.
228    #[serde(rename = "outCiphertext", with = "hex")]
229    out_ciphertext: [u8; 80],
230}
231
232impl Default for TransactionObject {
233    fn default() -> Self {
234        Self {
235            hex: SerializedTransaction::from(
236                [0u8; zebra_chain::transaction::MIN_TRANSPARENT_TX_SIZE as usize].to_vec(),
237            ),
238            height: Option::default(),
239            confirmations: Option::default(),
240            inputs: None,
241            outputs: None,
242            shielded_spends: None,
243            shielded_outputs: None,
244            orchard: None,
245            value_balance: None,
246            value_balance_zat: None,
247        }
248    }
249}
250
251impl TransactionObject {
252    /// Converts `tx` and `height` into a new `GetRawTransaction` in the `verbose` format.
253    #[allow(clippy::unwrap_in_result)]
254    pub(crate) fn from_transaction(
255        tx: Arc<Transaction>,
256        height: Option<block::Height>,
257        confirmations: Option<u32>,
258        network: &Network,
259    ) -> Self {
260        Self {
261            hex: tx.clone().into(),
262            height: height.map(|height| height.0),
263            confirmations,
264            inputs: Some(
265                tx.inputs()
266                    .iter()
267                    .map(|input| match input {
268                        zebra_chain::transparent::Input::Coinbase { sequence, .. } => {
269                            Input::Coinbase {
270                                coinbase: input
271                                    .coinbase_script()
272                                    .expect("we know it is a valid coinbase script"),
273                                sequence: *sequence,
274                            }
275                        }
276                        zebra_chain::transparent::Input::PrevOut {
277                            sequence,
278                            unlock_script,
279                            outpoint,
280                        } => Input::NonCoinbase {
281                            txid: outpoint.hash.encode_hex(),
282                            vout: outpoint.index,
283                            script_sig: ScriptSig {
284                                asm: "".to_string(),
285                                hex: unlock_script.clone(),
286                            },
287                            sequence: *sequence,
288                            value: None,
289                            value_zat: None,
290                            address: None,
291                        },
292                    })
293                    .collect(),
294            ),
295            outputs: Some(
296                tx.outputs()
297                    .iter()
298                    .enumerate()
299                    .map(|output| {
300                        let addresses = match output.1.address(network) {
301                            Some(address) => vec![address.to_string()],
302                            None => vec![],
303                        };
304
305                        Output {
306                            value: types::Zec::from(output.1.value).lossy_zec(),
307                            value_zat: output.1.value.zatoshis(),
308                            n: output.0 as u32,
309                            script_pub_key: ScriptPubKey {
310                                asm: "".to_string(),
311                                hex: output.1.lock_script.clone(),
312                                req_sigs: addresses.len() as u32,
313                                r#type: "".to_string(),
314                                addresses,
315                            },
316                        }
317                    })
318                    .collect(),
319            ),
320            shielded_spends: Some(
321                tx.sapling_spends_per_anchor()
322                    .map(|spend| {
323                        let mut anchor = spend.per_spend_anchor.as_bytes();
324                        anchor.reverse();
325
326                        let mut nullifier = spend.nullifier.as_bytes();
327                        nullifier.reverse();
328
329                        let mut rk: [u8; 32] = spend.clone().rk.into();
330                        rk.reverse();
331
332                        let spend_auth_sig: [u8; 64] = spend.spend_auth_sig.into();
333
334                        ShieldedSpend {
335                            cv: spend.cv,
336                            anchor,
337                            nullifier,
338                            rk,
339                            proof: spend.proof().0,
340                            spend_auth_sig,
341                        }
342                    })
343                    .collect(),
344            ),
345            shielded_outputs: Some(
346                tx.sapling_outputs()
347                    .map(|output| {
348                        let mut ephemeral_key: [u8; 32] = output.ephemeral_key.into();
349                        ephemeral_key.reverse();
350                        let enc_ciphertext: [u8; 580] = output.enc_ciphertext.into();
351                        let out_ciphertext: [u8; 80] = output.out_ciphertext.into();
352
353                        ShieldedOutput {
354                            cv: output.cv,
355                            cm_u: output.cm_u.to_bytes(),
356                            ephemeral_key,
357                            enc_ciphertext,
358                            out_ciphertext,
359                            proof: output.proof().0,
360                        }
361                    })
362                    .collect(),
363            ),
364            value_balance: Some(
365                types::Zec::from(tx.sapling_value_balance().sapling_amount()).lossy_zec(),
366            ),
367            value_balance_zat: Some(tx.sapling_value_balance().sapling_amount().zatoshis()),
368
369            orchard: if !tx.has_orchard_shielded_data() {
370                None
371            } else {
372                Some(Orchard {
373                    actions: tx
374                        .orchard_actions()
375                        .collect::<Vec<_>>()
376                        .iter()
377                        .map(|action| {
378                            let spend_auth_sig: [u8; 64] = tx
379                                .orchard_shielded_data()
380                                .and_then(|shielded_data| {
381                                    shielded_data
382                                        .actions
383                                        .iter()
384                                        .find(|authorized_action| {
385                                            authorized_action.action == **action
386                                        })
387                                        .map(|authorized_action| {
388                                            authorized_action.spend_auth_sig.into()
389                                        })
390                                })
391                                .unwrap_or([0; 64]);
392
393                            let cv: [u8; 32] = action.cv.into();
394                            let nullifier: [u8; 32] = action.nullifier.into();
395                            let rk: [u8; 32] = action.rk.into();
396                            let cm_x: [u8; 32] = action.cm_x.into();
397                            let ephemeral_key: [u8; 32] = action.ephemeral_key.into();
398                            let enc_ciphertext: [u8; 580] = action.enc_ciphertext.into();
399                            let out_ciphertext: [u8; 80] = action.out_ciphertext.into();
400
401                            OrchardAction {
402                                cv,
403                                nullifier,
404                                rk,
405                                cm_x,
406                                ephemeral_key,
407                                enc_ciphertext,
408                                spend_auth_sig,
409                                out_ciphertext,
410                            }
411                        })
412                        .collect(),
413                    value_balance: types::Zec::from(tx.orchard_value_balance().orchard_amount())
414                        .lossy_zec(),
415                    value_balance_zat: tx.orchard_value_balance().orchard_amount().zatoshis(),
416                })
417            },
418        }
419    }
420}