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 }