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