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}