/ circuit / program / src / response / from_outputs.rs
from_outputs.rs
  1  // Copyright (c) 2019-2025 Alpha-Delta Network Inc.
  2  // This file is part of the deltavm 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<A: Alpha> Response<A> {
 19      /// Initializes a response, given the number of inputs, tvk, tcm, outputs, output types, and output registers.
 20      pub fn from_outputs(
 21          signer: &Address<A>,
 22          network_id: &U16<A>,
 23          program_id: &ProgramID<A>,
 24          function_name: &Identifier<A>,
 25          num_inputs: usize,
 26          tvk: &Field<A>,
 27          tcm: &Field<A>,
 28          outputs: Vec<Value<A>>,
 29          output_types: &[console::ValueType<A::Network>], // Note: Console type
 30          output_registers: &[Option<console::Register<A::Network>>], // Note: Console type
 31      ) -> Self {
 32          // Compute the function ID.
 33          let function_id = compute_function_id(network_id, program_id, function_name);
 34  
 35          // Compute the output IDs.
 36          let output_ids = outputs
 37              .iter()
 38              .zip_eq(output_types)
 39              .zip_eq(output_registers)
 40              .enumerate()
 41              .map(|(index, ((output, output_type), output_register))| {
 42                  match output_type {
 43                      // For a constant output, compute the hash (using `tcm`) of the output.
 44                      console::ValueType::Constant(..) => {
 45                          // Prepare the index as a constant field element.
 46                          let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
 47                          // Construct the preimage as `(function ID || output || tcm || index)`.
 48                          let mut preimage = Vec::new();
 49                          preimage.push(function_id.clone());
 50                          preimage.extend(output.to_fields());
 51                          preimage.push(tcm.clone());
 52                          preimage.push(output_index);
 53  
 54                          // Hash the output to a field element.
 55                          match &output {
 56                              // Return the output ID.
 57                              Value::Plaintext(..) => OutputID::constant(A::hash_psd8(&preimage)),
 58                              // Ensure the output is a plaintext.
 59                              Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
 60                              Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
 61                          }
 62                      }
 63                      // For a public output, compute the hash (using `tcm`) of the output.
 64                      console::ValueType::Public(..) => {
 65                          // Prepare the index as a constant field element.
 66                          let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
 67                          // Construct the preimage as `(function ID || output || tcm || index)`.
 68                          let mut preimage = Vec::new();
 69                          preimage.push(function_id.clone());
 70                          preimage.extend(output.to_fields());
 71                          preimage.push(tcm.clone());
 72                          preimage.push(output_index);
 73  
 74                          // Hash the output to a field element.
 75                          match &output {
 76                              // Return the output ID.
 77                              Value::Plaintext(..) => OutputID::public(A::hash_psd8(&preimage)),
 78                              // Ensure the output is a plaintext.
 79                              Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
 80                              Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
 81                          }
 82                      }
 83                      // For a private output, compute the ciphertext (using `tvk`) and hash the ciphertext.
 84                      console::ValueType::Private(..) => {
 85                          // Prepare the index as a constant field element.
 86                          let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
 87                          // Compute the output view key as `Hash(function ID || tvk || index)`.
 88                          let output_view_key = A::hash_psd4(&[function_id.clone(), tvk.clone(), output_index]);
 89                          // Compute the ciphertext.
 90                          let ciphertext = match &output {
 91                              Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(output_view_key),
 92                              // Ensure the output is a plaintext.
 93                              Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
 94                              Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
 95                          };
 96                          // Return the output ID.
 97                          OutputID::private(A::hash_psd8(&ciphertext.to_fields()))
 98                      }
 99                      // For a record output, compute the record commitment, and encrypt the record (using `tvk`).
100                      console::ValueType::Record(record_name) => {
101                          // Retrieve the record.
102                          let record = match &output {
103                              Value::Record(record) => record,
104                              // Ensure the output is a record.
105                              Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
106                              Value::Future(..) => A::halt("Expected a record output, found a future output"),
107                          };
108  
109                          // Retrieve the output register.
110                          let output_register = match output_register {
111                              Some(output_register) => output_register,
112                              None => A::halt("Expected a register to be paired with a record output"),
113                          };
114  
115                          // Prepare the index as a constant field element.
116                          let output_index = Field::constant(console::Field::from_u64(output_register.locator()));
117                          // Compute the encryption randomizer as `HashToScalar(tvk || index)`.
118                          let randomizer = A::hash_to_scalar_psd2(&[tvk.clone(), output_index]);
119  
120                          // Encrypt the record, using the randomizer.
121                          let (encrypted_record, record_view_key) = record.encrypt_symmetric(&randomizer);
122  
123                          // Compute the record commitment.
124                          let commitment =
125                              record.to_commitment(program_id, &Identifier::constant(*record_name), &record_view_key);
126  
127                          // Compute the record checksum, as the hash of the encrypted record.
128                          let checksum = A::hash_bhp1024(&encrypted_record.to_bits_le());
129  
130                          // Prepare a randomizer for the sender ciphertext.
131                          let randomizer = A::hash_psd4(&[A::encryption_domain(), record_view_key, Field::one()]);
132                          // Encrypt the signer address using the randomizer.
133                          let sender_ciphertext = signer.to_group().to_x_coordinate() + randomizer;
134  
135                          // Return the output ID.
136                          OutputID::record(commitment, checksum, sender_ciphertext)
137                      }
138                      // For an external record output, compute the hash (using `tvk`) of the output.
139                      console::ValueType::ExternalRecord(..) => {
140                          // Prepare the index as a constant field element.
141                          let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
142                          // Construct the preimage as `(function ID || output || tvk || index)`.
143                          let mut preimage = Vec::new();
144                          preimage.push(function_id.clone());
145                          preimage.extend(output.to_fields());
146                          preimage.push(tvk.clone());
147                          preimage.push(output_index);
148  
149                          // Return the output ID.
150                          match &output {
151                              Value::Record(..) => OutputID::external_record(A::hash_psd8(&preimage)),
152                              // Ensure the output is a record.
153                              Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
154                              Value::Future(..) => A::halt("Expected a record output, found a future output"),
155                          }
156                      }
157                      // For a future output, compute the hash (using `tcm`) of the output.
158                      console::ValueType::Future(..) => {
159                          // Prepare the index as a constant field element.
160                          let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
161                          // Construct the preimage as `(function ID || output || tcm || index)`.
162                          let mut preimage = Vec::new();
163                          preimage.push(function_id.clone());
164                          preimage.extend(output.to_fields());
165                          preimage.push(tcm.clone());
166                          preimage.push(output_index);
167  
168                          // Hash the output to a field element.
169                          match &output {
170                              // Return the output ID.
171                              Value::Future(..) => OutputID::future(A::hash_psd8(&preimage)),
172                              // Ensure the output is a future.
173                              Value::Plaintext(..) => A::halt("Expected a future output, found a plaintext output"),
174                              Value::Record(..) => A::halt("Expected a future output, found a record output"),
175                          }
176                      }
177                  }
178              })
179              .collect();
180  
181          // Return the response.
182          Self { output_ids, outputs }
183      }
184  }
185  
186  #[cfg(test)]
187  mod tests {
188      use super::*;
189      use crate::Circuit;
190      use deltavm_circuit_types::U16;
191      use deltavm_utilities::{TestRng, Uniform};
192  
193      use anyhow::Result;
194  
195      pub(crate) const ITERATIONS: usize = 20;
196  
197      fn check_from_outputs(
198          mode: Mode,
199          num_constants: u64,
200          num_public: u64,
201          num_private: u64,
202          num_constraints: u64,
203      ) -> Result<()> {
204          use console::Network;
205  
206          let rng = &mut TestRng::default();
207  
208          for i in 0..ITERATIONS {
209              // Sample a `tvk`.
210              let tvk = console::Field::rand(rng);
211              // Compute the transition commitment as `Hash(tvk)`.
212              let tcm = <Circuit as Environment>::Network::hash_psd2(&[tvk])?;
213  
214              // Compute the nonce.
215              let index = console::Field::from_u64(8);
216              let randomizer = <Circuit as Environment>::Network::hash_to_scalar_psd2(&[tvk, index]).unwrap();
217              let nonce = <Circuit as Environment>::Network::g_scalar_multiply(&randomizer);
218  
219              // Construct the outputs.
220              let output_constant = console::Value::<<Circuit as Environment>::Network>::Plaintext(
221                  console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
222              );
223              let output_public = console::Value::<<Circuit as Environment>::Network>::Plaintext(
224                  console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
225              );
226              let output_private = console::Value::<<Circuit as Environment>::Network>::Plaintext(
227                  console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
228              );
229              let output_record = console::Value::<<Circuit as Environment>::Network>::Record(console::Record::from_str(&format!("{{ owner: dx150w2lvhdzychwvzu54ys5zas7tm5s0ycdyw563pms83g9u0vucgqe5fs5w.private, token_amount: 100u64.private, _nonce: {nonce}.public }}")).unwrap());
230              let output_external_record = console::Value::<<Circuit as Environment>::Network>::Record(console::Record::from_str("{ owner: dx150w2lvhdzychwvzu54ys5zas7tm5s0ycdyw563pms83g9u0vucgqe5fs5w.private, token_amount: 100u64.private, _nonce: 0group.public }").unwrap());
231              let outputs = vec![output_constant, output_public, output_private, output_record, output_external_record];
232  
233              // Construct the output types.
234              let output_types = vec![
235                  console::ValueType::from_str("amount.constant").unwrap(),
236                  console::ValueType::from_str("amount.public").unwrap(),
237                  console::ValueType::from_str("amount.private").unwrap(),
238                  console::ValueType::from_str("token.record").unwrap(),
239                  console::ValueType::from_str("token.delta/token.record").unwrap(),
240              ];
241  
242              // Construct the output registers.
243              let output_registers = vec![
244                  Some(console::Register::Locator(5)),
245                  Some(console::Register::Locator(6)),
246                  Some(console::Register::Locator(7)),
247                  Some(console::Register::Locator(8)),
248                  Some(console::Register::Locator(9)),
249              ];
250  
251              // Construct a signer.
252              let signer = console::Address::rand(rng);
253              // Construct a network ID.
254              let network_id = console::U16::new(<Circuit as Environment>::Network::ID);
255              // Construct a program ID.
256              let program_id = console::ProgramID::from_str("test.delta")?;
257              // Construct a function name.
258              let function_name = console::Identifier::from_str("check")?;
259  
260              // Construct the response.
261              let response = console::Response::new(
262                  &signer,
263                  &network_id,
264                  &program_id,
265                  &function_name,
266                  4,
267                  &tvk,
268                  &tcm,
269                  outputs.clone(),
270                  &output_types,
271                  &output_registers,
272              )?;
273  
274              // Inject the signer, network ID, program ID, function name, `tvk`, `tcm`, and outputs.
275              let signer = Address::<Circuit>::new(mode, signer);
276              let network_id = U16::<Circuit>::constant(network_id);
277              let program_id = ProgramID::<Circuit>::new(mode, program_id);
278              let function_name = Identifier::<Circuit>::new(mode, function_name);
279              let tvk = Field::<Circuit>::new(mode, tvk);
280              let tcm = Field::<Circuit>::new(mode, tcm);
281              let outputs = Inject::new(mode, outputs);
282  
283              Circuit::scope(format!("Response {i}"), || {
284                  // Compute the response using outputs (circuit).
285                  let candidate = Response::from_outputs(
286                      &signer,
287                      &network_id,
288                      &program_id,
289                      &function_name,
290                      4,
291                      &tvk,
292                      &tcm,
293                      outputs,
294                      &output_types,
295                      &output_registers,
296                  );
297                  assert_eq!(response, candidate.eject_value());
298                  match mode.is_constant() {
299                      true => assert_scope!(<=num_constants, <=num_public, <=num_private, <=num_constraints),
300                      false => assert_scope!(<=num_constants, num_public, num_private, num_constraints),
301                  }
302              });
303              Circuit::reset();
304          }
305          Ok(())
306      }
307  
308      // Note: These counts are correct. At this (high) level of a program, we override the default mode in many cases,
309      // based on the user-defined visibility in the types. Thus, we have nonzero public, private, and constraint values.
310  
311      #[test]
312      fn test_from_outputs_constant() -> Result<()> {
313          check_from_outputs(Mode::Constant, 38500, 7, 13500, 13500)
314      }
315  
316      #[test]
317      fn test_from_outputs_public() -> Result<()> {
318          check_from_outputs(Mode::Public, 37257, 7, 18057, 18085)
319      }
320  
321      #[test]
322      fn test_from_outputs_private() -> Result<()> {
323          check_from_outputs(Mode::Private, 37257, 7, 18057, 18085)
324      }
325  }