phoenix_core/
transaction.rs1extern 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#[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 pub root: BlsScalar,
32 pub nullifiers: Vec<BlsScalar>,
34 pub outputs: [Note; OUTPUT_NOTES],
36 #[cfg_attr(
38 feature = "serde",
39 serde(with = "serde_with::As::<serde_with::DisplayFromStr>")
40 )]
41 pub max_fee: u64,
42 #[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 #[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 #[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 pub fn from_slice(buf: &[u8]) -> Result<Self, BytesError> {
144 Self::from_slice_with(buf, Note::read_strict)
145 }
146
147 pub fn from_slice_legacy_compat(buf: &[u8]) -> Result<Self, BytesError> {
150 Self::from_slice_with(buf, Note::read_legacy_compat)
151 }
152
153 pub fn nullifiers(&self) -> &[BlsScalar] {
155 &self.nullifiers
156 }
157
158 pub fn outputs(&self) -> &[Note] {
160 &self.outputs
161 }
162
163 pub fn max_fee(&self) -> u64 {
165 self.max_fee
166 }
167
168 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 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}