Skip to main content

snarkvm_console_program/request/
sign.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
18impl<N: Network> Request<N> {
19    /// Returns the request for a given private key, program ID, function name, inputs, input types, is_dynamic, and RNG, where:
20    ///     challenge := HashToScalar(r * G, pk_sig, pr_sig, signer, \[tvk, tcm, function ID, is_root, program checksum?, input IDs\])
21    ///     response := r - challenge * sk_sig
22    /// The program checksum must be provided if the program has a constructor and should not be provided otherwise.
23    pub fn sign<R: Rng + CryptoRng>(
24        private_key: &PrivateKey<N>,
25        program_id: ProgramID<N>,
26        function_name: Identifier<N>,
27        inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
28        input_types: &[ValueType<N>],
29        root_tvk: Option<Field<N>>,
30        is_root: bool,
31        program_checksum: Option<Field<N>>,
32        is_dynamic: bool,
33        rng: &mut R,
34    ) -> Result<Self> {
35        // Ensure the number of inputs matches the number of input types.
36        if input_types.len() != inputs.len() {
37            bail!(
38                "'{program_id}/{function_name}' expects {} inputs, but {} were provided.",
39                input_types.len(),
40                inputs.len()
41            )
42        }
43
44        // Retrieve `sk_sig`.
45        let sk_sig = private_key.sk_sig();
46
47        // Derive the compute key.
48        let compute_key = ComputeKey::try_from(private_key)?;
49        // Retrieve `pk_sig`.
50        let pk_sig = compute_key.pk_sig();
51        // Retrieve `pr_sig`.
52        let pr_sig = compute_key.pr_sig();
53
54        // Derive the view key.
55        let view_key = ViewKey::try_from((private_key, &compute_key))?;
56        // Derive `sk_tag` from the graph key.
57        let sk_tag = GraphKey::try_from(view_key)?.sk_tag();
58
59        // Sample a random nonce.
60        let nonce = Field::<N>::rand(rng);
61        // Compute a `r` as `HashToScalar(sk_sig || nonce)`. Note: This is the transition secret key `tsk`.
62        let r = N::hash_to_scalar_psd4(&[N::serial_number_domain(), sk_sig.to_field()?, nonce])?;
63        // Compute `g_r` as `r * G`. Note: This is the transition public key `tpk`.
64        let g_r = N::g_scalar_multiply(&r);
65
66        // Derive the signer from the compute key.
67        let signer = Address::try_from(compute_key)?;
68        // Compute the transition view key `tvk` as `r * signer`.
69        let tvk = (*signer * r).to_x_coordinate();
70        // Compute the transition commitment `tcm` as `Hash(tvk)`.
71        let tcm = N::hash_psd2(&[tvk])?;
72        // Compute the signer commitment `scm` as `Hash(signer || root_tvk)`.
73        let root_tvk = root_tvk.unwrap_or(tvk);
74        let scm = N::hash_psd2(&[signer.deref().to_x_coordinate(), root_tvk])?;
75        // Compute 'is_root' as a field element.
76        let is_root = if is_root { Field::<N>::one() } else { Field::<N>::zero() };
77
78        // Retrieve the network ID.
79        let network_id = U16::new(N::ID);
80        // Compute the function ID.
81        let function_id = compute_function_id(&network_id, &program_id, &function_name)?;
82
83        // Construct the hash input as `(r * G, pk_sig, pr_sig, signer, [tvk, tcm, function ID, is_root, program checksum?, input IDs])`.
84        let mut message = Vec::with_capacity(9 + 2 * inputs.len());
85        message.extend([g_r, pk_sig, pr_sig, *signer].map(|point| point.to_x_coordinate()));
86        message.extend([tvk, tcm, function_id, is_root]);
87        // Add the program checksum to the hash input if it was provided.
88        if let Some(program_checksum) = program_checksum {
89            message.push(program_checksum);
90        }
91
92        // Initialize a vector to store the prepared inputs.
93        let mut prepared_inputs = Vec::with_capacity(inputs.len());
94        // Initialize a vector to store the input IDs.
95        let mut input_ids = Vec::with_capacity(inputs.len());
96
97        // Prepare the inputs.
98        for (index, (input, input_type)) in inputs.zip_eq(input_types).enumerate() {
99            // Prepare the input.
100            let input = input.try_into().map_err(|_| {
101                anyhow!("Failed to parse input #{index} ('{input_type}') for '{program_id}/{function_name}'")
102            })?;
103            // If the function expects a dynamic record but a record was provided, convert it.
104            let input = match (&input, input_type) {
105                (Value::Record(record), ValueType::DynamicRecord) => {
106                    Value::DynamicRecord(DynamicRecord::from_record(record)?)
107                }
108                _ => input,
109            };
110            // Store the prepared input.
111            prepared_inputs.push(input.clone());
112
113            // Convert index to u16.
114            let index = u16::try_from(index).map_err(|_| anyhow!("Input index exceeds u16"))?;
115
116            match input_type {
117                // A constant input is hashed (using `tcm`) to a field element.
118                ValueType::Constant(..) => {
119                    let input_id = InputID::constant(function_id, &input, tcm, index)?;
120                    message.push(*input_id.id());
121                    input_ids.push(input_id);
122                }
123                // A public input is hashed (using `tcm`) to a field element.
124                ValueType::Public(..) => {
125                    let input_id = InputID::public(function_id, &input, tcm, index)?;
126                    message.push(*input_id.id());
127                    input_ids.push(input_id);
128                }
129                // A private input is encrypted (using `tvk`) and hashed to a field element.
130                ValueType::Private(..) => {
131                    let input_id = InputID::private(function_id, &input, tvk, index)?;
132                    message.push(*input_id.id());
133                    input_ids.push(input_id);
134                }
135                // A record input is computed to its serial number.
136                ValueType::Record(record_name) => {
137                    // Compute the input ID (commitment, gamma, record view key, serial number, tag).
138                    let input_id =
139                        InputID::record(&program_id, record_name, &input, &signer, &view_key, &sk_sig, sk_tag)?;
140                    // Extract the commitment, gamma, and tag for the message.
141                    let (commitment, gamma, tag) = match &input_id {
142                        InputID::Record(c, g, _, _, t) => (*c, *g, *t),
143                        // InputID::record always returns the Record variant.
144                        _ => unreachable!(),
145                    };
146                    // Compute the generator `H` as `HashToGroup(commitment)`.
147                    let h = N::hash_to_group_psd2(&[N::serial_number_domain(), commitment])?;
148                    // Compute `h_r` as `r * H`.
149                    let h_r = h * r;
150                    // Add (`H`, `r * H`, `gamma`, `tag`) to the preimage.
151                    message.extend([h, h_r, gamma].iter().map(|point| point.to_x_coordinate()));
152                    message.push(tag);
153                    input_ids.push(input_id);
154                }
155                // An external record input is hashed (using `tvk`) to a field element.
156                ValueType::ExternalRecord(..) => {
157                    let input_id = InputID::external_record(function_id, &input, tvk, index)?;
158                    message.push(*input_id.id());
159                    input_ids.push(input_id);
160                }
161                // A future is not a valid input.
162                ValueType::Future(..) => bail!("A future is not a valid input"),
163                // A dynamic record input is hashed (using `tvk`) to a field element.
164                ValueType::DynamicRecord => {
165                    let input_id = InputID::dynamic_record(function_id, &input, tvk, index)?;
166                    message.push(*input_id.id());
167                    input_ids.push(input_id);
168                }
169                // A dynamic future is not a valid input.
170                ValueType::DynamicFuture => bail!("A dynamic future is not a valid input"),
171            }
172        }
173
174        // Compute `challenge` as `HashToScalar(r * G, pk_sig, pr_sig, signer, [tvk, tcm, function ID, is_root, program checksum?, input IDs])`.
175        let challenge = N::hash_to_scalar_psd8(&message)?;
176        // Compute `response` as `r - challenge * sk_sig`.
177        let response = r - challenge * sk_sig;
178
179        Ok(Self {
180            signer,
181            network_id,
182            program_id,
183            function_name,
184            input_ids,
185            inputs: prepared_inputs,
186            signature: Signature::from((challenge, response, compute_key)),
187            sk_tag,
188            tvk,
189            tcm,
190            scm,
191            is_dynamic,
192        })
193    }
194}