Skip to main content

snarkvm_console_program/request/
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 input_id;
17pub use input_id::InputID;
18
19mod bytes;
20mod serialize;
21mod sign;
22mod string;
23mod verify;
24
25use crate::{DynamicRecord, Identifier, Plaintext, ProgramID, Record, Value, ValueType, compute_function_id};
26use snarkvm_console_account::{Address, ComputeKey, GraphKey, PrivateKey, Signature, ViewKey};
27use snarkvm_console_network::Network;
28use snarkvm_console_types::prelude::*;
29
30#[derive(Clone, PartialEq, Eq)]
31pub struct Request<N: Network> {
32    /// The request signer.
33    signer: Address<N>,
34    /// The network ID.
35    network_id: U16<N>,
36    /// The program ID.
37    program_id: ProgramID<N>,
38    /// The function name.
39    function_name: Identifier<N>,
40    /// The input ID for the transition.
41    input_ids: Vec<InputID<N>>,
42    /// The function inputs.
43    inputs: Vec<Value<N>>,
44    /// The signature for the transition.
45    signature: Signature<N>,
46    /// The tag secret key.
47    sk_tag: Field<N>,
48    /// The transition view key.
49    tvk: Field<N>,
50    /// The transition commitment.
51    tcm: Field<N>,
52    /// The signer commitment.
53    scm: Field<N>,
54    /// A flag indicating whether or not the request is dynamic.
55    is_dynamic: bool,
56}
57
58impl<N: Network>
59    From<(
60        Address<N>,
61        U16<N>,
62        ProgramID<N>,
63        Identifier<N>,
64        Vec<InputID<N>>,
65        Vec<Value<N>>,
66        Signature<N>,
67        Field<N>,
68        Field<N>,
69        Field<N>,
70        Field<N>,
71        bool,
72    )> for Request<N>
73{
74    /// Note: See `Request::sign` to create the request. This method is used to eject from a circuit.
75    fn from(
76        (
77            signer,
78            network_id,
79            program_id,
80            function_name,
81            input_ids,
82            inputs,
83            signature,
84            sk_tag,
85            tvk,
86            tcm,
87            scm,
88            is_dynamic,
89        ): (
90            Address<N>,
91            U16<N>,
92            ProgramID<N>,
93            Identifier<N>,
94            Vec<InputID<N>>,
95            Vec<Value<N>>,
96            Signature<N>,
97            Field<N>,
98            Field<N>,
99            Field<N>,
100            Field<N>,
101            bool,
102        ),
103    ) -> Self {
104        // Ensure that the number of inputs matches the number of input IDs.
105        if inputs.len() != input_ids.len() {
106            N::halt(format!(
107                "Invalid request: mismatching number of input IDs ({}) and inputs ({})",
108                input_ids.len(),
109                inputs.len()
110            ))
111        }
112
113        // Ensure the network ID is correct.
114        if *network_id != N::ID {
115            N::halt(format!("Invalid network ID. Expected {}, found {}", N::ID, *network_id))
116        } else {
117            Self {
118                signer,
119                network_id,
120                program_id,
121                function_name,
122                input_ids,
123                inputs,
124                signature,
125                sk_tag,
126                tvk,
127                tcm,
128                scm,
129                is_dynamic,
130            }
131        }
132    }
133}
134
135impl<N: Network> Request<N> {
136    /// Returns the request signer.
137    pub const fn signer(&self) -> &Address<N> {
138        &self.signer
139    }
140
141    /// Returns the network ID.
142    pub const fn network_id(&self) -> &U16<N> {
143        &self.network_id
144    }
145
146    /// Returns the program ID.
147    pub const fn program_id(&self) -> &ProgramID<N> {
148        &self.program_id
149    }
150
151    /// Returns the function name.
152    pub const fn function_name(&self) -> &Identifier<N> {
153        &self.function_name
154    }
155
156    /// Returns the input ID for the transition.
157    pub fn input_ids(&self) -> &[InputID<N>] {
158        &self.input_ids
159    }
160
161    /// Returns the function inputs.
162    pub fn inputs(&self) -> &[Value<N>] {
163        &self.inputs
164    }
165
166    /// Returns the signature for the transition.
167    pub const fn signature(&self) -> &Signature<N> {
168        &self.signature
169    }
170
171    /// Returns the tag secret key `sk_tag`.
172    pub const fn sk_tag(&self) -> &Field<N> {
173        &self.sk_tag
174    }
175
176    /// Returns the transition view key `tvk`.
177    pub const fn tvk(&self) -> &Field<N> {
178        &self.tvk
179    }
180
181    /// Returns the transition public key `tpk`.
182    pub fn to_tpk(&self) -> Group<N> {
183        // Retrieve the challenge from the signature.
184        let challenge = self.signature.challenge();
185        // Retrieve the response from the signature.
186        let response = self.signature.response();
187        // Retrieve `pk_sig` from the signature.
188        let pk_sig = self.signature.compute_key().pk_sig();
189        // Compute `tpk` as `(challenge * pk_sig) + (response * G)`, equivalent to `r * G`.
190        (pk_sig * challenge) + N::g_scalar_multiply(&response)
191    }
192
193    /// Returns the transition commitment `tcm`.
194    pub const fn tcm(&self) -> &Field<N> {
195        &self.tcm
196    }
197
198    /// Returns the signer commitment `scm`.
199    pub const fn scm(&self) -> &Field<N> {
200        &self.scm
201    }
202
203    /// Returns whether or not the request is dynamic.
204    pub const fn is_dynamic(&self) -> bool {
205        self.is_dynamic
206    }
207
208    /// Returns the expected caller input IDs for a dynamic call by:
209    ///
210    /// - converting all record inputs to dynamic record inputs
211    /// - leaving all other inputs unchanged.
212    ///
213    /// and then computing their corresponding input IDs.
214    pub fn to_dynamic_input_ids(&self) -> Result<Vec<InputID<N>>> {
215        // Compute the function ID.
216        let function_id = compute_function_id(&self.network_id, &self.program_id, &self.function_name)?;
217
218        ensure!(
219            self.input_ids().len() == self.inputs.len(),
220            "Mismatched number of input IDs and inputs: {} vs. {}",
221            self.input_ids().len(),
222            self.inputs.len(),
223        );
224
225        // Compute and return the caller input IDs.
226        self.input_ids()
227            .iter()
228            .zip(self.inputs.iter())
229            .enumerate()
230            .map(|(index, (input_id, input))| match (input_id, input) {
231                (InputID::Constant(..), Value::Plaintext(..))
232                | (InputID::Public(..), Value::Plaintext(..))
233                | (InputID::Private(..), Value::Plaintext(..))
234                | (InputID::DynamicRecord(..), Value::DynamicRecord(..)) => Ok(*input_id),
235                (InputID::Record(..), Value::Record(record)) | (InputID::ExternalRecord(..), Value::Record(record)) => {
236                    // Convert index to u16.
237                    let index = u16::try_from(index).map_err(|_| anyhow!("Input index exceeds u16"))?;
238                    // Convert the record to a dynamic record.
239                    let caller_input = Value::DynamicRecord(DynamicRecord::from_record(record)?);
240                    // Compute the input ID for the dynamic record.
241                    InputID::dynamic_record(function_id, &caller_input, self.tvk, index)
242                }
243                _ => bail!("Mismatching input ID and input value at index {index}"),
244            })
245            .collect()
246    }
247
248    /// Returns the expected caller inputs for a dynamic call by:
249    /// - converting all record inputs to dynamic record inputs
250    /// - leaving all other inputs unchanged.
251    pub fn to_dynamic_inputs(&self) -> Result<Vec<Value<N>>> {
252        self.inputs
253            .iter()
254            .map(|input| match input {
255                Value::Record(record) => {
256                    // This covers both the non-external and external record cases.
257                    Ok(Value::DynamicRecord(DynamicRecord::from_record(record)?))
258                }
259                Value::Future(_) => bail!("A future cannot be an input to a request."),
260                Value::DynamicFuture(_) => bail!("A dynamic future cannot be an input to a request."),
261                _ => Ok(input.clone()),
262            })
263            .collect::<Result<Vec<_>>>()
264    }
265}
266
267#[cfg(test)]
268mod test_helpers {
269    use super::*;
270    use snarkvm_console_network::MainnetV0;
271
272    type CurrentNetwork = MainnetV0;
273
274    const ITERATIONS: u64 = 1000;
275
276    pub(super) fn sample_requests(rng: &mut TestRng) -> Vec<Request<CurrentNetwork>> {
277        (0..ITERATIONS)
278            .map(|i| {
279                // Sample a random private key and address.
280                let private_key = PrivateKey::<CurrentNetwork>::new(rng).unwrap();
281                let address = Address::try_from(&private_key).unwrap();
282
283                // Construct a program ID and function name.
284                let program_id = ProgramID::from_str("token.aleo").unwrap();
285                let function_name = Identifier::from_str("transfer").unwrap();
286
287                // Prepare a record belonging to the address.
288                let record_string =
289                    format!("{{ owner: {address}.private, token_amount: {i}u64.private, _nonce: 2293253577170800572742339369209137467208538700597121244293392265726446806023group.public }}");
290
291                // Construct four inputs.
292                let input_constant = Value::from_str(&format!("{{ token_amount: {i}u128 }}")).unwrap();
293                let input_public = Value::from_str(&format!("{{ token_amount: {i}u128 }}")).unwrap();
294                let input_private = Value::from_str(&format!("{{ token_amount: {i}u128 }}")).unwrap();
295                let input_record = Value::from_str(&record_string).unwrap();
296                let input_external_record = Value::from_str(&record_string).unwrap();
297                let inputs = vec![input_constant, input_public, input_private, input_record, input_external_record];
298
299                // Construct the input types.
300                let input_types = [
301                    ValueType::from_str("amount.constant").unwrap(),
302                    ValueType::from_str("amount.public").unwrap(),
303                    ValueType::from_str("amount.private").unwrap(),
304                    ValueType::from_str("token.record").unwrap(),
305                    ValueType::from_str("token.aleo/token.record").unwrap(),
306                ];
307
308                // Sample root_tvk.
309                let root_tvk = None;
310                // Construct 'is_root'.
311                let is_root = Uniform::rand(rng);
312                // Sample the program checksum.
313                let program_checksum = match bool::rand(rng) {
314                    true => Some(Field::rand(rng)),
315                    false => None,
316                };
317
318                // Randomly choose whether to sign as static or dynamic.
319                let is_dynamic = bool::rand(rng);
320                // Compute the signed request.
321                let request = Request::sign(
322                    &private_key,
323                    program_id,
324                    function_name,
325                    inputs.into_iter(),
326                    &input_types,
327                    root_tvk,
328                    is_root,
329                    program_checksum,
330                    is_dynamic,
331                    rng,
332                )
333                .unwrap();
334                assert!(request.verify(&input_types, is_root, program_checksum));
335                request
336            })
337            .collect()
338    }
339}