Skip to main content

snarkvm_ledger_block/transition/input/
mod.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
16mod bytes;
17mod serialize;
18mod string;
19
20use console::{
21    network::prelude::*,
22    program::{Ciphertext, Plaintext, TransitionLeaf, ValueType},
23    types::Field,
24};
25
26type Variant = u8;
27
28/// The transition input.
29#[derive(Clone, PartialEq, Eq)]
30pub enum Input<N: Network> {
31    /// The plaintext hash and (optional) plaintext.
32    Constant(Field<N>, Option<Plaintext<N>>),
33    /// The plaintext hash and (optional) plaintext.
34    Public(Field<N>, Option<Plaintext<N>>),
35    /// The ciphertext hash and (optional) ciphertext.
36    Private(Field<N>, Option<Ciphertext<N>>),
37    /// The serial number and tag of the record.
38    Record(Field<N>, Field<N>),
39    /// The hash of the external record's (function_id, record, tvk, input index).
40    ExternalRecord(Field<N>),
41    /// The hash of the dynamic record's (function_id, record, tvk, input index).
42    DynamicRecord(Field<N>),
43    /// The serial number, tag, and dynamic ID of a record input in a dynamic call transition.
44    /// The `dynamic_id` is computed from `hash(function_id, record, tvk, index)`.
45    /// From the caller's perspective, this appears as `DynamicRecord(dynamic_id)`.
46    RecordWithDynamicID(Field<N>, Field<N>, Field<N>),
47    /// The external record hash and dynamic ID of an external record input in a dynamic call transition.
48    /// The `dynamic_id` is computed from `hash(function_id, record, tvk, index)`.
49    /// From the caller's perspective, this appears as `DynamicRecord(dynamic_id)`.
50    ExternalRecordWithDynamicID(Field<N>, Field<N>),
51}
52
53impl<N: Network> Input<N> {
54    /// Returns the variant of the input.
55    pub const fn variant(&self) -> Variant {
56        match self {
57            Input::Constant(..) => 0,
58            Input::Public(..) => 1,
59            Input::Private(..) => 2,
60            Input::Record(..) => 3, // <- Changing this will invalidate 'console::StatePath' and 'circuit::StatePath'.
61            Input::ExternalRecord(..) => 4,
62            Input::DynamicRecord(..) => 5,
63            Input::RecordWithDynamicID(..) => 6,
64            Input::ExternalRecordWithDynamicID(..) => 7,
65        }
66    }
67
68    /// Returns the ID of the input.
69    pub const fn id(&self) -> &Field<N> {
70        match self {
71            Input::Constant(id, ..) => id,
72            Input::Public(id, ..) => id,
73            Input::Private(id, ..) => id,
74            Input::Record(serial_number, ..) => serial_number,
75            Input::ExternalRecord(id) => id,
76            Input::DynamicRecord(id) => id,
77            Input::RecordWithDynamicID(serial_number, ..) => serial_number,
78            Input::ExternalRecordWithDynamicID(id, ..) => id,
79        }
80    }
81
82    /// Returns the input as a transition leaf.
83    /// Note: RecordWithDynamicID uses leaf variant 3 (same as Record) with version 2.
84    /// Note: ExternalRecordWithDynamicID uses leaf variant 4 (same as ExternalRecord) with version 2.
85    pub fn to_transition_leaf(&self, index: u8) -> TransitionLeaf<N> {
86        match self {
87            // RecordWithDynamicID produces leaf with version 2, variant 3.
88            Input::RecordWithDynamicID(..) => TransitionLeaf::new_record_with_dynamic_id(index, *self.id()),
89            // ExternalRecordWithDynamicID produces leaf with version 2, variant 4.
90            Input::ExternalRecordWithDynamicID(..) => {
91                TransitionLeaf::new_external_record_with_dynamic_id(index, *self.id())
92            }
93            // All other variants use their serialization variant byte.
94            _ => TransitionLeaf::new(index, self.variant(), *self.id()),
95        }
96    }
97
98    /// Returns the tag, if the input is a record.
99    pub const fn tag(&self) -> Option<&Field<N>> {
100        match self {
101            Input::Record(_, tag) | Input::RecordWithDynamicID(_, tag, _) => Some(tag),
102            _ => None,
103        }
104    }
105
106    /// Returns the tag, if the input is a record, and consumes `self`.
107    pub fn into_tag(self) -> Option<Field<N>> {
108        match self {
109            Input::Record(_, tag) | Input::RecordWithDynamicID(_, tag, _) => Some(tag),
110            _ => None,
111        }
112    }
113
114    /// Returns the serial number, if the input is a record.
115    pub const fn serial_number(&self) -> Option<&Field<N>> {
116        match self {
117            Input::Record(serial_number, ..) | Input::RecordWithDynamicID(serial_number, ..) => Some(serial_number),
118            _ => None,
119        }
120    }
121
122    /// Returns the serial number, if the input is a record, and consumes `self`.
123    pub fn into_serial_number(self) -> Option<Field<N>> {
124        match self {
125            Input::Record(serial_number, ..) | Input::RecordWithDynamicID(serial_number, ..) => Some(serial_number),
126            _ => None,
127        }
128    }
129
130    /// Returns the public verifier inputs for the proof.
131    pub fn verifier_inputs(&self) -> impl '_ + Iterator<Item = N::Field> {
132        [Some(self.id()), self.tag()].into_iter().flatten().map(|id| **id)
133    }
134
135    /// Returns the dynamic ID, if the input carries one.
136    pub const fn dynamic_id(&self) -> Option<&Field<N>> {
137        match self {
138            Input::RecordWithDynamicID(_, _, dynamic_id) | Input::ExternalRecordWithDynamicID(_, dynamic_id) => {
139                Some(dynamic_id)
140            }
141            _ => None,
142        }
143    }
144
145    /// Returns the input from the caller's perspective.
146    /// This converts internal variants (like RecordWithDynamicID) to what
147    /// the caller would see (like DynamicRecord).
148    pub fn to_caller_input(&self) -> Self {
149        match self {
150            // RecordWithDynamicID becomes DynamicRecord from caller's view.
151            Self::RecordWithDynamicID(_, _, dynamic_id) => Self::DynamicRecord(*dynamic_id),
152            // ExternalRecordWithDynamicID becomes DynamicRecord from caller's view.
153            Self::ExternalRecordWithDynamicID(_, dynamic_id) => Self::DynamicRecord(*dynamic_id),
154            // All other variants are unchanged.
155            other => other.clone(),
156        }
157    }
158
159    /// Returns `true` if the input is well-formed.
160    /// If the optional value exists, this method checks that it hashes to the input ID.
161    pub fn verify(&self, function_id: Field<N>, tcm: &Field<N>, index: usize) -> bool {
162        // Ensure the hash of the value (if the value exists) is correct.
163        let result = || match self {
164            Input::Constant(hash, Some(input)) => {
165                match input.to_fields() {
166                    Ok(fields) => {
167                        // Construct the (console) input index as a field element.
168                        let index = Field::from_u16(index as u16);
169                        // Construct the preimage as `(function ID || input || tcm || index)`.
170                        let mut preimage = Vec::new();
171                        preimage.push(function_id);
172                        preimage.extend(fields);
173                        preimage.push(*tcm);
174                        preimage.push(index);
175                        // Ensure the hash matches.
176                        match N::hash_psd8(&preimage) {
177                            Ok(candidate_hash) => Ok(hash == &candidate_hash),
178                            Err(error) => Err(error),
179                        }
180                    }
181                    Err(error) => Err(error),
182                }
183            }
184            Input::Public(hash, Some(input)) => {
185                match input.to_fields() {
186                    Ok(fields) => {
187                        // Construct the (console) input index as a field element.
188                        let index = Field::from_u16(index as u16);
189                        // Construct the preimage as `(function ID || input || tcm || index)`.
190                        let mut preimage = Vec::new();
191                        preimage.push(function_id);
192                        preimage.extend(fields);
193                        preimage.push(*tcm);
194                        preimage.push(index);
195                        // Ensure the hash matches.
196                        match N::hash_psd8(&preimage) {
197                            Ok(candidate_hash) => Ok(hash == &candidate_hash),
198                            Err(error) => Err(error),
199                        }
200                    }
201                    Err(error) => Err(error),
202                }
203            }
204            Input::Private(hash, Some(value)) => {
205                match value.to_fields() {
206                    // Ensure the hash matches.
207                    Ok(fields) => match N::hash_psd8(&fields) {
208                        Ok(candidate_hash) => Ok(hash == &candidate_hash),
209                        Err(error) => Err(error),
210                    },
211                    Err(error) => Err(error),
212                }
213            }
214            Input::Constant(_, None) | Input::Public(_, None) | Input::Private(_, None) => {
215                // This enforces that the transition *must* contain the value for this transition input.
216                // A similar rule is enforced for the transition output.
217                bail!("A transition input value is missing")
218            }
219            Input::Record(_, _)
220            | Input::ExternalRecord(_)
221            | Input::DynamicRecord(_)
222            | Input::RecordWithDynamicID(_, _, _)
223            | Input::ExternalRecordWithDynamicID(_, _) => Ok(true),
224        };
225
226        match result() {
227            Ok(is_hash_valid) => is_hash_valid,
228            Err(error) => {
229                eprintln!("{error}");
230                false
231            }
232        }
233    }
234
235    /// Returns `true` if the input matches the expected value type.
236    pub fn is_type(&self, expected_value_type: &ValueType<N>) -> bool {
237        matches!(
238            (self, expected_value_type),
239            (Self::Constant(..), ValueType::Constant(..))
240                | (Self::Public(..), ValueType::Public(..))
241                | (Self::Private(..), ValueType::Private(..))
242                | (Self::Record(..), ValueType::Record(..))
243                | (Self::RecordWithDynamicID(..), ValueType::Record(..))
244                | (Self::ExternalRecord(..), ValueType::ExternalRecord(..))
245                | (Self::ExternalRecordWithDynamicID(..), ValueType::ExternalRecord(..))
246                | (Self::DynamicRecord(..), ValueType::DynamicRecord)
247        )
248    }
249}
250
251#[cfg(test)]
252pub(crate) mod test_helpers {
253    use super::*;
254    use console::{network::MainnetV0, program::Literal};
255
256    type CurrentNetwork = MainnetV0;
257
258    /// Sample the transition inputs.
259    pub(crate) fn sample_inputs() -> Vec<(<CurrentNetwork as Network>::TransitionID, Input<CurrentNetwork>)> {
260        let rng = &mut TestRng::default();
261
262        // Sample a transition.
263        let transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0);
264        let transition = transaction.transitions().next().unwrap();
265
266        // Retrieve the transition ID and input.
267        let transition_id = *transition.id();
268        let input = transition.inputs().iter().next().unwrap().clone();
269
270        // Sample a random plaintext.
271        let plaintext = Plaintext::Literal(Literal::Field(Uniform::rand(rng)), Default::default());
272        let plaintext_hash = CurrentNetwork::hash_bhp1024(&plaintext.to_bits_le()).unwrap();
273        // Sample a random ciphertext.
274        let fields: Vec<_> = (0..10).map(|_| Uniform::rand(rng)).collect();
275        let ciphertext = Ciphertext::from_fields(&fields).unwrap();
276        let ciphertext_hash = CurrentNetwork::hash_bhp1024(&ciphertext.to_bits_le()).unwrap();
277
278        vec![
279            (transition_id, input),
280            (Uniform::rand(rng), Input::Constant(Uniform::rand(rng), None)),
281            (Uniform::rand(rng), Input::Constant(plaintext_hash, Some(plaintext.clone()))),
282            (Uniform::rand(rng), Input::Public(Uniform::rand(rng), None)),
283            (Uniform::rand(rng), Input::Public(plaintext_hash, Some(plaintext))),
284            (Uniform::rand(rng), Input::Private(Uniform::rand(rng), None)),
285            (Uniform::rand(rng), Input::Private(ciphertext_hash, Some(ciphertext))),
286            (Uniform::rand(rng), Input::Record(Uniform::rand(rng), Uniform::rand(rng))),
287            (Uniform::rand(rng), Input::ExternalRecord(Uniform::rand(rng))),
288            (
289                Uniform::rand(rng),
290                Input::RecordWithDynamicID(Uniform::rand(rng), Uniform::rand(rng), Uniform::rand(rng)),
291            ),
292            (Uniform::rand(rng), Input::ExternalRecordWithDynamicID(Uniform::rand(rng), Uniform::rand(rng))),
293            (Uniform::rand(rng), Input::DynamicRecord(Uniform::rand(rng))),
294        ]
295    }
296}
297
298#[cfg(test)]
299mod tests {
300    use super::*;
301    use console::network::MainnetV0;
302
303    type CurrentNetwork = MainnetV0;
304
305    #[test]
306    fn test_to_caller_input_record_with_dynamic_id() {
307        // RecordWithDynamicID should become DynamicRecord(dynamic_id) from caller's view.
308        let serial_number = Field::<CurrentNetwork>::from_u64(1);
309        let tag = Field::<CurrentNetwork>::from_u64(2);
310        let dynamic_id = Field::<CurrentNetwork>::from_u64(3);
311
312        let input = Input::<CurrentNetwork>::RecordWithDynamicID(serial_number, tag, dynamic_id);
313        let caller_input = input.to_caller_input();
314
315        assert_eq!(caller_input, Input::<CurrentNetwork>::DynamicRecord(dynamic_id));
316    }
317
318    #[test]
319    fn test_to_caller_input_external_record_with_dynamic_id() {
320        // ExternalRecordWithDynamicID should become DynamicRecord(dynamic_id) from caller's view.
321        let ext_id = Field::<CurrentNetwork>::from_u64(10);
322        let dynamic_id = Field::<CurrentNetwork>::from_u64(20);
323
324        let input = Input::<CurrentNetwork>::ExternalRecordWithDynamicID(ext_id, dynamic_id);
325        let caller_input = input.to_caller_input();
326
327        assert_eq!(caller_input, Input::<CurrentNetwork>::DynamicRecord(dynamic_id));
328    }
329
330    #[test]
331    fn test_to_caller_input_non_dynamic_variants_unchanged() {
332        // Non-dynamic variants must be returned unchanged.
333        let id = Field::<CurrentNetwork>::from_u64(42);
334
335        let constant = Input::<CurrentNetwork>::Constant(id, None);
336        assert_eq!(constant.to_caller_input(), constant);
337
338        let public = Input::<CurrentNetwork>::Public(id, None);
339        assert_eq!(public.to_caller_input(), public);
340
341        let dynamic_record = Input::<CurrentNetwork>::DynamicRecord(id);
342        assert_eq!(dynamic_record.to_caller_input(), dynamic_record);
343
344        let external = Input::<CurrentNetwork>::ExternalRecord(id);
345        assert_eq!(external.to_caller_input(), external);
346    }
347}