Skip to main content

phoenix_core/
transaction.rs

1// This Source Code Form is subject to the terms of the Mozilla Public
2// License, v. 2.0. If a copy of the MPL was not distributed with this
3// file, You can obtain one at http://mozilla.org/MPL/2.0/.
4//
5// Copyright (c) DUSK NETWORK. All rights reserved.
6
7//! Transaction skeleton defining the minimum amount of data needed for a
8//! phoenix transaction.
9
10extern crate alloc;
11use alloc::vec::Vec;
12
13use dusk_bls12_381::BlsScalar;
14use dusk_bytes::{DeserializableSlice, Error as BytesError, Serializable};
15#[cfg(feature = "rkyv-impl")]
16use rkyv::{Archive, Deserialize, Serialize};
17
18use crate::{Note, OUTPUT_NOTES};
19
20/// A phoenix transaction, referred to as tx-skeleton in the specs.
21#[derive(Debug, Clone, PartialEq, Eq)]
22#[cfg_attr(
23    feature = "rkyv-impl",
24    derive(Archive, Serialize, Deserialize),
25    archive_attr(derive(bytecheck::CheckBytes))
26)]
27#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
28pub struct TxSkeleton {
29    /// The root of the transfer tree on top of which this transaction is
30    /// based.
31    pub root: BlsScalar,
32    /// The nullifiers of the old notes this transaction spends.
33    pub nullifiers: Vec<BlsScalar>,
34    /// The new output notes of this transaction.
35    pub outputs: [Note; OUTPUT_NOTES],
36    /// Describes the maximum fee to be paid for this transaction.
37    #[cfg_attr(
38        feature = "serde",
39        serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
40    )]
41    pub max_fee: u64,
42    /// A deposit is used to transferring funds to a contract
43    #[cfg_attr(
44        feature = "serde",
45        serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
46    )]
47    pub deposit: u64,
48}
49
50impl TxSkeleton {
51    fn from_slice_with(
52        buf: &[u8],
53        read_note: fn(&mut &[u8]) -> Result<Note, BytesError>,
54    ) -> Result<Self, BytesError> {
55        let mut buffer = buf;
56        let root = BlsScalar::from_reader(&mut buffer)?;
57
58        let num_nullifiers_u64 = u64::from_reader(&mut buffer)?;
59        let min_tail_len = OUTPUT_NOTES
60            .checked_mul(Note::SIZE)
61            .and_then(|len| len.checked_add(u64::SIZE + u64::SIZE))
62            .ok_or(BytesError::InvalidData)?;
63        if buffer.len() < min_tail_len {
64            return Err(BytesError::InvalidData);
65        }
66
67        let max_nullifiers_by_len =
68            (buffer.len() - min_tail_len) / BlsScalar::SIZE;
69        let num_nullifiers = usize::try_from(num_nullifiers_u64)
70            .map_err(|_| BytesError::InvalidData)?;
71        if num_nullifiers > max_nullifiers_by_len {
72            return Err(BytesError::InvalidData);
73        }
74
75        let mut nullifiers = Vec::with_capacity(num_nullifiers);
76        for _ in 0..num_nullifiers {
77            nullifiers.push(BlsScalar::from_reader(&mut buffer)?);
78        }
79
80        let mut outputs = Vec::with_capacity(OUTPUT_NOTES);
81        for _ in 0..OUTPUT_NOTES {
82            outputs.push(read_note(&mut buffer)?);
83        }
84        let outputs: [Note; OUTPUT_NOTES] =
85            outputs.try_into().map_err(|_| BytesError::InvalidData)?;
86
87        let max_fee = u64::from_reader(&mut buffer)?;
88        let deposit = u64::from_reader(&mut buffer)?;
89
90        Ok(Self {
91            root,
92            nullifiers,
93            outputs,
94            max_fee,
95            deposit,
96        })
97    }
98
99    /// Return input bytes to a hash function for the transaction.
100    #[must_use]
101    pub fn to_hash_input_bytes(&self) -> Vec<u8> {
102        let mut bytes = Vec::new();
103
104        bytes.extend(self.root.to_bytes());
105
106        for nullifier in &self.nullifiers {
107            bytes.extend(nullifier.to_bytes());
108        }
109        for note in &self.outputs {
110            bytes.extend(note.to_bytes());
111        }
112
113        bytes.extend(self.max_fee.to_bytes());
114        bytes.extend(self.deposit.to_bytes());
115
116        bytes
117    }
118
119    /// Serialize the transaction to a variable length byte buffer.
120    #[allow(unused_must_use)]
121    pub fn to_var_bytes(&self) -> Vec<u8> {
122        let mut bytes = Vec::new();
123
124        bytes.extend(self.root.to_bytes());
125
126        let num_nullifiers = self.nullifiers.len() as u64;
127        bytes.extend(num_nullifiers.to_bytes());
128        self.nullifiers.iter().for_each(|nullifier| {
129            bytes.extend(nullifier.to_bytes());
130        });
131
132        self.outputs.iter().for_each(|note| {
133            bytes.extend(note.to_bytes());
134        });
135
136        bytes.extend(self.max_fee.to_bytes());
137        bytes.extend(self.deposit.to_bytes());
138
139        bytes
140    }
141
142    /// Deserialize the transaction from a bytes buffer.
143    pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
144        Self::from_slice_with(buf, Note::read_strict)
145    }
146
147    /// Deserialize the transaction from a bytes buffer, accepting historical
148    /// on-curve `JubJub` points inside output notes.
149    pub fn from_slice_legacy_compat(buf: &[u8]) -> Result<Self, BytesError> {
150        Self::from_slice_with(buf, Note::read_legacy_compat)
151    }
152
153    /// Returns the inputs to the transaction.
154    pub fn nullifiers(&self) -> &[BlsScalar] {
155        &self.nullifiers
156    }
157
158    /// Returns the outputs of the transaction.
159    pub fn outputs(&self) -> &[Note] {
160        &self.outputs
161    }
162
163    /// Returns the maximum fee of the transaction.
164    pub fn max_fee(&self) -> u64 {
165        self.max_fee
166    }
167
168    /// Returns the deposit of the transaction.
169    pub fn deposit(&self) -> u64 {
170        self.deposit
171    }
172}
173
174#[cfg(test)]
175mod tests {
176    use super::*;
177
178    #[test]
179    fn from_slice_rejects_unbounded_nullifier_count() {
180        let mut bytes = Vec::new();
181        bytes.extend(BlsScalar::from(0u64).to_bytes());
182        bytes.extend(u64::MAX.to_bytes());
183
184        // Fill enough bytes to pass fixed-tail length checks while keeping the
185        // nullifier count obviously inconsistent with the available payload.
186        let min_tail_len = OUTPUT_NOTES * Note::SIZE + u64::SIZE + u64::SIZE;
187        bytes.resize(BlsScalar::SIZE + u64::SIZE + min_tail_len, 0u8);
188
189        let err = TxSkeleton::from_slice(&bytes).unwrap_err();
190        assert_eq!(err, BytesError::InvalidData);
191    }
192}