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