Skip to main content

snarkvm_ledger_block/transition/output/
bytes.rs

1// Copyright (c) 2019-2026 Provable Inc.
2// This file is part of the snarkVM library.
3
4// Licensed under the Apache License, Version 2.0 (the "License");
5// you may not use this file except in compliance with the License.
6// You may obtain a copy of the License at:
7
8// http://www.apache.org/licenses/LICENSE-2.0
9
10// Unless required by applicable law or agreed to in writing, software
11// distributed under the License is distributed on an "AS IS" BASIS,
12// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13// See the License for the specific language governing permissions and
14// limitations under the License.
15
16use super::*;
17
18/// Reads record fields shared by Record and RecordWithDynamicID variants.
19/// Returns (commitment, checksum, record_ciphertext, sender_ciphertext).
20#[allow(clippy::type_complexity)]
21fn read_record_fields<N: Network, R: Read>(
22    reader: &mut R,
23) -> IoResult<(Field<N>, Field<N>, Option<Record<N, Ciphertext<N>>>, Option<Field<N>>)> {
24    // Read the commitment.
25    let commitment = FromBytes::read_le(&mut *reader)?;
26    // Read the checksum.
27    let checksum = FromBytes::read_le(&mut *reader)?;
28    // Read the record ciphertext.
29    let record_ciphertext_exists: bool = FromBytes::read_le(&mut *reader)?;
30    let record_ciphertext: Option<Record<N, _>> = match record_ciphertext_exists {
31        true => Some(FromBytes::read_le(&mut *reader)?),
32        false => None,
33    };
34    // If the record version is Version 1 or higher, read the sender ciphertext.
35    let sender_ciphertext = match &record_ciphertext {
36        Some(record) => match record.version().is_zero() {
37            true => None,
38            false => {
39                // Read the sender ciphertext version.
40                let sender_ciphertext_version: u8 = FromBytes::read_le(&mut *reader)?;
41                // Ensure the sender ciphertext version is 0.
42                if sender_ciphertext_version != 0 {
43                    return Err(error(format!(
44                        "Failed to decode sender ciphertext version {sender_ciphertext_version}"
45                    )));
46                }
47                // Read the sender ciphertext.
48                Some(FromBytes::read_le(&mut *reader)?)
49            }
50        },
51        None => None,
52    };
53    Ok((commitment, checksum, record_ciphertext, sender_ciphertext))
54}
55
56/// Writes record fields shared by Record and RecordWithDynamicID variants.
57fn write_record_fields<N: Network, W: Write>(
58    writer: &mut W,
59    commitment: &Field<N>,
60    checksum: &Field<N>,
61    record_ciphertext: &Option<Record<N, Ciphertext<N>>>,
62    sender_ciphertext: &Option<Field<N>>,
63) -> IoResult<()> {
64    // Write the commitment.
65    commitment.write_le(&mut *writer)?;
66    // Write the checksum.
67    checksum.write_le(&mut *writer)?;
68    // Write the record ciphertext.
69    match record_ciphertext {
70        Some(record) => {
71            true.write_le(&mut *writer)?;
72            record.write_le(&mut *writer)?;
73            // If the record version is Version 1 or higher, write the sender ciphertext.
74            if !record.version().is_zero() {
75                // Write the sender ciphertext version.
76                0u8.write_le(&mut *writer)?;
77                // Write the sender ciphertext.
78                match sender_ciphertext {
79                    Some(sender) => sender.write_le(&mut *writer)?,
80                    None => {
81                        return Err(error("Failed to encode sender ciphertext for non-zero version record"));
82                    }
83                }
84            }
85        }
86        None => false.write_le(&mut *writer)?,
87    }
88    Ok(())
89}
90
91impl<N: Network> FromBytes for Output<N> {
92    /// Reads the output from a buffer.
93    fn read_le<R: Read>(mut reader: R) -> IoResult<Self> {
94        let index = Variant::read_le(&mut reader)?;
95        let literal = match index {
96            0 => {
97                let plaintext_hash: Field<N> = FromBytes::read_le(&mut reader)?;
98                let plaintext_exists: bool = FromBytes::read_le(&mut reader)?;
99                let plaintext = match plaintext_exists {
100                    true => Some(FromBytes::read_le(&mut reader)?),
101                    false => None,
102                };
103
104                Self::Constant(plaintext_hash, plaintext)
105            }
106            1 => {
107                let plaintext_hash: Field<N> = FromBytes::read_le(&mut reader)?;
108                let plaintext_exists: bool = FromBytes::read_le(&mut reader)?;
109                let plaintext = match plaintext_exists {
110                    true => Some(FromBytes::read_le(&mut reader)?),
111                    false => None,
112                };
113                Self::Public(plaintext_hash, plaintext)
114            }
115            2 => {
116                let ciphertext_hash: Field<N> = FromBytes::read_le(&mut reader)?;
117                let ciphertext_exists: bool = FromBytes::read_le(&mut reader)?;
118                let ciphertext = match ciphertext_exists {
119                    true => Some(FromBytes::read_le(&mut reader)?),
120                    false => None,
121                };
122                Self::Private(ciphertext_hash, ciphertext)
123            }
124            3 => {
125                let (commitment, checksum, record_ciphertext, sender_ciphertext) = read_record_fields(&mut reader)?;
126                Self::Record(commitment, checksum, record_ciphertext, sender_ciphertext)
127            }
128            4 => {
129                let commitment = FromBytes::read_le(&mut reader)?;
130                Self::ExternalRecord(commitment)
131            }
132            5 => {
133                let future_hash: Field<N> = FromBytes::read_le(&mut reader)?;
134                let future_exists: bool = FromBytes::read_le(&mut reader)?;
135                let future = match future_exists {
136                    true => Some(FromBytes::read_le(&mut reader)?),
137                    false => None,
138                };
139                Self::Future(future_hash, future)
140            }
141            6 => {
142                let commitment = FromBytes::read_le(&mut reader)?;
143                Self::DynamicRecord(commitment)
144            }
145            7 => {
146                let (commitment, checksum, record_ciphertext, sender_ciphertext) = read_record_fields(&mut reader)?;
147                // Read the dynamic ID.
148                let dynamic_id: Field<N> = FromBytes::read_le(&mut reader)?;
149                Self::RecordWithDynamicID(commitment, checksum, record_ciphertext, sender_ciphertext, dynamic_id)
150            }
151            8 => {
152                // Read the external record hash.
153                let external_hash: Field<N> = FromBytes::read_le(&mut reader)?;
154                // Read the dynamic ID.
155                let dynamic_id: Field<N> = FromBytes::read_le(&mut reader)?;
156                // Return the external record with dynamic ID.
157                Self::ExternalRecordWithDynamicID(external_hash, dynamic_id)
158            }
159            9.. => return Err(error(format!("Failed to decode output variant {index}"))),
160        };
161        Ok(literal)
162    }
163}
164
165impl<N: Network> ToBytes for Output<N> {
166    /// Writes the output to a buffer.
167    fn write_le<W: Write>(&self, mut writer: W) -> IoResult<()> {
168        match self {
169            Self::Constant(plaintext_hash, plaintext) => {
170                (0 as Variant).write_le(&mut writer)?;
171                plaintext_hash.write_le(&mut writer)?;
172                match plaintext {
173                    Some(plaintext) => {
174                        true.write_le(&mut writer)?;
175                        plaintext.write_le(&mut writer)
176                    }
177                    None => false.write_le(&mut writer),
178                }
179            }
180            Self::Public(plaintext_hash, plaintext) => {
181                (1 as Variant).write_le(&mut writer)?;
182                plaintext_hash.write_le(&mut writer)?;
183                match plaintext {
184                    Some(plaintext) => {
185                        true.write_le(&mut writer)?;
186                        plaintext.write_le(&mut writer)
187                    }
188                    None => false.write_le(&mut writer),
189                }
190            }
191            Self::Private(ciphertext_hash, ciphertext) => {
192                (2 as Variant).write_le(&mut writer)?;
193                ciphertext_hash.write_le(&mut writer)?;
194                match ciphertext {
195                    Some(ciphertext) => {
196                        true.write_le(&mut writer)?;
197                        ciphertext.write_le(&mut writer)
198                    }
199                    None => false.write_le(&mut writer),
200                }
201            }
202            Self::Record(commitment, checksum, record_ciphertext, sender_ciphertext) => {
203                (3 as Variant).write_le(&mut writer)?;
204                write_record_fields(&mut writer, commitment, checksum, record_ciphertext, sender_ciphertext)
205            }
206            Self::ExternalRecord(commitment) => {
207                (4 as Variant).write_le(&mut writer)?;
208                commitment.write_le(&mut writer)
209            }
210            Self::Future(future_hash, future) => {
211                (5 as Variant).write_le(&mut writer)?;
212                future_hash.write_le(&mut writer)?;
213                match future {
214                    Some(future) => {
215                        true.write_le(&mut writer)?;
216                        future.write_le(&mut writer)
217                    }
218                    None => false.write_le(&mut writer),
219                }
220            }
221            Self::DynamicRecord(commitment) => {
222                (6 as Variant).write_le(&mut writer)?;
223                commitment.write_le(&mut writer)
224            }
225            Self::RecordWithDynamicID(commitment, checksum, record_ciphertext, sender_ciphertext, dynamic_id) => {
226                (7 as Variant).write_le(&mut writer)?;
227                write_record_fields(&mut writer, commitment, checksum, record_ciphertext, sender_ciphertext)?;
228                // Write the dynamic ID.
229                dynamic_id.write_le(&mut writer)
230            }
231            Self::ExternalRecordWithDynamicID(external_hash, dynamic_id) => {
232                (8 as Variant).write_le(&mut writer)?;
233                external_hash.write_le(&mut writer)?;
234                dynamic_id.write_le(&mut writer)
235            }
236        }
237    }
238}
239
240#[cfg(test)]
241mod tests {
242    use super::*;
243
244    #[test]
245    fn test_bytes() {
246        for (_, expected) in crate::transition::output::test_helpers::sample_outputs() {
247            // Check the byte representation.
248            let expected_bytes = expected.to_bytes_le().unwrap();
249            assert_eq!(expected, Output::read_le(&expected_bytes[..]).unwrap());
250        }
251    }
252}