snarkvm_console_program/request/
sign.rs

1// Copyright 2024 Aleo Network Foundation
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, and RNG, where:
20    ///     challenge := HashToScalar(r * G, pk_sig, pr_sig, signer, \[tvk, tcm, function ID, input IDs\])
21    ///     response := r - challenge * sk_sig
22    pub fn sign<R: Rng + CryptoRng>(
23        private_key: &PrivateKey<N>,
24        program_id: ProgramID<N>,
25        function_name: Identifier<N>,
26        inputs: impl ExactSizeIterator<Item = impl TryInto<Value<N>>>,
27        input_types: &[ValueType<N>],
28        root_tvk: Option<Field<N>>,
29        is_root: bool,
30        rng: &mut R,
31    ) -> Result<Self> {
32        // Ensure the number of inputs matches the number of input types.
33        if input_types.len() != inputs.len() {
34            bail!(
35                "'{program_id}/{function_name}' expects {} inputs, but {} were provided.",
36                input_types.len(),
37                inputs.len()
38            )
39        }
40
41        // Retrieve `sk_sig`.
42        let sk_sig = private_key.sk_sig();
43
44        // Derive the compute key.
45        let compute_key = ComputeKey::try_from(private_key)?;
46        // Retrieve `pk_sig`.
47        let pk_sig = compute_key.pk_sig();
48        // Retrieve `pr_sig`.
49        let pr_sig = compute_key.pr_sig();
50
51        // Derive the view key.
52        let view_key = ViewKey::try_from((private_key, &compute_key))?;
53        // Derive `sk_tag` from the graph key.
54        let sk_tag = GraphKey::try_from(view_key)?.sk_tag();
55
56        // Sample a random nonce.
57        let nonce = Field::<N>::rand(rng);
58        // Compute a `r` as `HashToScalar(sk_sig || nonce)`. Note: This is the transition secret key `tsk`.
59        let r = N::hash_to_scalar_psd4(&[N::serial_number_domain(), sk_sig.to_field()?, nonce])?;
60        // Compute `g_r` as `r * G`. Note: This is the transition public key `tpk`.
61        let g_r = N::g_scalar_multiply(&r);
62
63        // Derive the signer from the compute key.
64        let signer = Address::try_from(compute_key)?;
65        // Compute the transition view key `tvk` as `r * signer`.
66        let tvk = (*signer * r).to_x_coordinate();
67        // Compute the transition commitment `tcm` as `Hash(tvk)`.
68        let tcm = N::hash_psd2(&[tvk])?;
69        // Compute the signer commitment `scm` as `Hash(signer || root_tvk)`.
70        let root_tvk = root_tvk.unwrap_or(tvk);
71        let scm = N::hash_psd2(&[signer.deref().to_x_coordinate(), root_tvk])?;
72        // Compute 'is_root' as a field element.
73        let is_root = if is_root { Field::<N>::one() } else { Field::<N>::zero() };
74
75        // Retrieve the network ID.
76        let network_id = U16::new(N::ID);
77        // Compute the function ID.
78        let function_id = compute_function_id(&network_id, &program_id, &function_name)?;
79
80        // Construct the hash input as `(r * G, pk_sig, pr_sig, signer, [tvk, tcm, function ID, input IDs])`.
81        let mut message = Vec::with_capacity(9 + 2 * inputs.len());
82        message.extend([g_r, pk_sig, pr_sig, *signer].map(|point| point.to_x_coordinate()));
83        message.extend([tvk, tcm, function_id, is_root]);
84
85        // Initialize a vector to store the prepared inputs.
86        let mut prepared_inputs = Vec::with_capacity(inputs.len());
87        // Initialize a vector to store the input IDs.
88        let mut input_ids = Vec::with_capacity(inputs.len());
89
90        // Prepare the inputs.
91        for (index, (input, input_type)) in inputs.zip_eq(input_types).enumerate() {
92            // Prepare the input.
93            let input = input.try_into().map_err(|_| {
94                anyhow!("Failed to parse input #{index} ('{input_type}') for '{program_id}/{function_name}'")
95            })?;
96            // Store the prepared input.
97            prepared_inputs.push(input.clone());
98
99            match input_type {
100                // A constant input is hashed (using `tcm`) to a field element.
101                ValueType::Constant(..) => {
102                    // Ensure the input is a plaintext.
103                    ensure!(matches!(input, Value::Plaintext(..)), "Expected a plaintext input");
104
105                    // Construct the (console) input index as a field element.
106                    let index = Field::from_u16(u16::try_from(index).or_halt_with::<N>("Input index exceeds u16"));
107                    // Construct the preimage as `(function ID || input || tcm || index)`.
108                    let mut preimage = Vec::new();
109                    preimage.push(function_id);
110                    preimage.extend(input.to_fields()?);
111                    preimage.push(tcm);
112                    preimage.push(index);
113                    // Hash the input to a field element.
114                    let input_hash = N::hash_psd8(&preimage)?;
115
116                    // Add the input hash to the preimage.
117                    message.push(input_hash);
118                    // Add the input ID to the inputs.
119                    input_ids.push(InputID::Constant(input_hash));
120                }
121                // A public input is hashed (using `tcm`) to a field element.
122                ValueType::Public(..) => {
123                    // Ensure the input is a plaintext.
124                    ensure!(matches!(input, Value::Plaintext(..)), "Expected a plaintext input");
125
126                    // Construct the (console) input index as a field element.
127                    let index = Field::from_u16(u16::try_from(index).or_halt_with::<N>("Input index exceeds u16"));
128                    // Construct the preimage as `(function ID || input || tcm || index)`.
129                    let mut preimage = Vec::new();
130                    preimage.push(function_id);
131                    preimage.extend(input.to_fields()?);
132                    preimage.push(tcm);
133                    preimage.push(index);
134                    // Hash the input to a field element.
135                    let input_hash = N::hash_psd8(&preimage)?;
136
137                    // Add the input hash to the preimage.
138                    message.push(input_hash);
139                    // Add the input ID to the inputs.
140                    input_ids.push(InputID::Public(input_hash));
141                }
142                // A private input is encrypted (using `tvk`) and hashed to a field element.
143                ValueType::Private(..) => {
144                    // Ensure the input is a plaintext.
145                    ensure!(matches!(input, Value::Plaintext(..)), "Expected a plaintext input");
146
147                    // Construct the (console) input index as a field element.
148                    let index = Field::from_u16(u16::try_from(index).or_halt_with::<N>("Input index exceeds u16"));
149                    // Compute the input view key as `Hash(function ID || tvk || index)`.
150                    let input_view_key = N::hash_psd4(&[function_id, tvk, index])?;
151                    // Compute the ciphertext.
152                    let ciphertext = match &input {
153                        Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(input_view_key)?,
154                        // Ensure the input is a plaintext.
155                        Value::Record(..) => bail!("Expected a plaintext input, found a record input"),
156                        Value::Future(..) => bail!("Expected a plaintext input, found a future input"),
157                    };
158                    // Hash the ciphertext to a field element.
159                    let input_hash = N::hash_psd8(&ciphertext.to_fields()?)?;
160
161                    // Add the input hash to the preimage.
162                    message.push(input_hash);
163                    // Add the input hash to the inputs.
164                    input_ids.push(InputID::Private(input_hash));
165                }
166                // A record input is computed to its serial number.
167                ValueType::Record(record_name) => {
168                    // Retrieve the record.
169                    let record = match &input {
170                        Value::Record(record) => record,
171                        // Ensure the input is a record.
172                        Value::Plaintext(..) => bail!("Expected a record input, found a plaintext input"),
173                        Value::Future(..) => bail!("Expected a record input, found a future input"),
174                    };
175                    // Ensure the record belongs to the signer.
176                    ensure!(**record.owner() == signer, "Input record for '{program_id}' must belong to the signer");
177
178                    // Compute the record commitment.
179                    let commitment = record.to_commitment(&program_id, record_name)?;
180
181                    // Compute the generator `H` as `HashToGroup(commitment)`.
182                    let h = N::hash_to_group_psd2(&[N::serial_number_domain(), commitment])?;
183                    // Compute `h_r` as `r * H`.
184                    let h_r = h * r;
185                    // Compute `gamma` as `sk_sig * H`.
186                    let gamma = h * sk_sig;
187
188                    // Compute the `serial_number` from `gamma`.
189                    let serial_number = Record::<N, Plaintext<N>>::serial_number_from_gamma(&gamma, commitment)?;
190                    // Compute the tag.
191                    let tag = Record::<N, Plaintext<N>>::tag(sk_tag, commitment)?;
192
193                    // Add (`H`, `r * H`, `gamma`, `tag`) to the preimage.
194                    message.extend([h, h_r, gamma].iter().map(|point| point.to_x_coordinate()));
195                    message.push(tag);
196
197                    // Add the input ID.
198                    input_ids.push(InputID::Record(commitment, gamma, serial_number, tag));
199                }
200                // An external record input is hashed (using `tvk`) to a field element.
201                ValueType::ExternalRecord(..) => {
202                    // Ensure the input is a record.
203                    ensure!(matches!(input, Value::Record(..)), "Expected a record input");
204
205                    // Construct the (console) input index as a field element.
206                    let index = Field::from_u16(u16::try_from(index).or_halt_with::<N>("Input index exceeds u16"));
207                    // Construct the preimage as `(function ID || input || tvk || index)`.
208                    let mut preimage = Vec::new();
209                    preimage.push(function_id);
210                    preimage.extend(input.to_fields()?);
211                    preimage.push(tvk);
212                    preimage.push(index);
213                    // Hash the input to a field element.
214                    let input_hash = N::hash_psd8(&preimage)?;
215
216                    // Add the input hash to the preimage.
217                    message.push(input_hash);
218                    // Add the input hash to the inputs.
219                    input_ids.push(InputID::ExternalRecord(input_hash));
220                }
221                // A future is not a valid input.
222                ValueType::Future(..) => bail!("A future is not a valid input"),
223            }
224        }
225
226        // Compute `challenge` as `HashToScalar(r * G, pk_sig, pr_sig, signer, [tvk, tcm, function ID, input IDs])`.
227        let challenge = N::hash_to_scalar_psd8(&message)?;
228        // Compute `response` as `r - challenge * sk_sig`.
229        let response = r - challenge * sk_sig;
230
231        Ok(Self {
232            signer,
233            network_id,
234            program_id,
235            function_name,
236            input_ids,
237            inputs: prepared_inputs,
238            signature: Signature::from((challenge, response, compute_key)),
239            sk_tag,
240            tvk,
241            tcm,
242            scm,
243        })
244    }
245}