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 }