use super::*;
impl<A: Aleo> Response<A> {
pub fn from_outputs(
signer: &Address<A>,
network_id: &U16<A>,
program_id: &ProgramID<A>,
function_name: &Identifier<A>,
num_inputs: usize,
tvk: &Field<A>,
tcm: &Field<A>,
outputs: Vec<Value<A>>,
output_types: &[console::ValueType<A::Network>], output_registers: &[Option<console::Register<A::Network>>], ) -> Self {
let function_id = compute_function_id(network_id, program_id, function_name);
let output_ids = outputs
.iter()
.zip_eq(output_types)
.zip_eq(output_registers)
.enumerate()
.map(|(index, ((output, output_type), output_register))| {
match output_type {
console::ValueType::Constant(..) => {
let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
let mut preimage = Vec::new();
preimage.push(function_id.clone());
preimage.extend(output.to_fields());
preimage.push(tcm.clone());
preimage.push(output_index);
match &output {
Value::Plaintext(..) => OutputID::constant(A::hash_psd8(&preimage)),
Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
Value::DynamicRecord(..) => {
A::halt("Expected a plaintext output, found a dynamic record output")
}
Value::DynamicFuture(..) => {
A::halt("Expected a plaintext output, found a dynamic future output")
}
}
}
console::ValueType::Public(..) => {
let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
let mut preimage = Vec::new();
preimage.push(function_id.clone());
preimage.extend(output.to_fields());
preimage.push(tcm.clone());
preimage.push(output_index);
match &output {
Value::Plaintext(..) => OutputID::public(A::hash_psd8(&preimage)),
Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
Value::DynamicRecord(..) => {
A::halt("Expected a plaintext output, found a dynamic record output")
}
Value::DynamicFuture(..) => {
A::halt("Expected a plaintext output, found a dynamic future output")
}
}
}
console::ValueType::Private(..) => {
let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
let output_view_key = A::hash_psd4(&[function_id.clone(), tvk.clone(), output_index]);
let ciphertext = match &output {
Value::Plaintext(plaintext) => plaintext.encrypt_symmetric(output_view_key),
Value::Record(..) => A::halt("Expected a plaintext output, found a record output"),
Value::Future(..) => A::halt("Expected a plaintext output, found a future output"),
Value::DynamicRecord(..) => {
A::halt("Expected a plaintext output, found a dynamic record output")
}
Value::DynamicFuture(..) => {
A::halt("Expected a plaintext output, found a dynamic future output")
}
};
OutputID::private(A::hash_psd8(&ciphertext.to_fields()))
}
console::ValueType::Record(record_name) => {
let record = match &output {
Value::Record(record) => record,
Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
Value::Future(..) => A::halt("Expected a record output, found a future output"),
Value::DynamicRecord(..) => {
A::halt("Expected a record output, found a dynamic record output")
}
Value::DynamicFuture(..) => {
A::halt("Expected a record output, found a dynamic future output")
}
};
let output_register = match output_register {
Some(output_register) => output_register,
None => A::halt("Expected a register to be paired with a record output"),
};
let output_index = Field::constant(console::Field::from_u64(output_register.locator()));
let randomizer = A::hash_to_scalar_psd2(&[tvk.clone(), output_index]);
let (encrypted_record, record_view_key) = record.encrypt_symmetric(&randomizer);
let commitment =
record.to_commitment(program_id, &Identifier::constant(*record_name), &record_view_key);
let checksum = A::hash_bhp1024(&encrypted_record.to_bits_le());
let randomizer = A::hash_psd4(&[A::encryption_domain(), record_view_key, Field::one()]);
let sender_ciphertext = signer.to_group().to_x_coordinate() + randomizer;
OutputID::record(commitment, checksum, sender_ciphertext)
}
console::ValueType::ExternalRecord(..) => {
let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
let mut preimage = Vec::new();
preimage.push(function_id.clone());
preimage.extend(output.to_fields());
preimage.push(tvk.clone());
preimage.push(output_index);
match &output {
Value::Record(..) => OutputID::external_record(A::hash_psd8(&preimage)),
Value::Plaintext(..) => A::halt("Expected a record output, found a plaintext output"),
Value::Future(..) => A::halt("Expected a record output, found a future output"),
Value::DynamicRecord(..) => {
A::halt("Expected a record output, found a dynamic record output")
}
Value::DynamicFuture(..) => {
A::halt("Expected a record output, found a dynamic future output")
}
}
}
console::ValueType::Future(..) => {
let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
let mut preimage = Vec::new();
preimage.push(function_id.clone());
preimage.extend(output.to_fields());
preimage.push(tcm.clone());
preimage.push(output_index);
match &output {
Value::Future(..) => OutputID::future(A::hash_psd8(&preimage)),
Value::Plaintext(..) => A::halt("Expected a future output, found a plaintext output"),
Value::Record(..) => A::halt("Expected a future output, found a record output"),
Value::DynamicRecord(..) => {
A::halt("Expected a future output, found a dynamic record output")
}
Value::DynamicFuture(..) => {
A::halt("Expected a future output, found a dynamic future output")
}
}
}
console::ValueType::DynamicRecord => {
let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
let mut preimage = Vec::new();
preimage.push(function_id.clone());
preimage.extend(output.to_fields());
preimage.push(tvk.clone());
preimage.push(output_index);
match &output {
Value::DynamicRecord(..) => OutputID::dynamic_record(A::hash_psd8(&preimage)),
Value::Plaintext(..) => {
A::halt("Expected a dynamic record output, found a plaintext output")
}
Value::Future(..) => A::halt("Expected a dynamic record output, found a future output"),
Value::Record(..) => A::halt("Expected a dynamic record output, found a record output"),
Value::DynamicFuture(..) => {
A::halt("Expected a dynamic record output, found a dynamic future output")
}
}
}
console::ValueType::DynamicFuture => {
let output_index = Field::constant(console::Field::from_u16((num_inputs + index) as u16));
let mut preimage = Vec::new();
preimage.push(function_id.clone());
preimage.extend(output.to_fields());
preimage.push(tcm.clone());
preimage.push(output_index);
match &output {
Value::DynamicFuture(..) => OutputID::dynamic_future(A::hash_psd8(&preimage)),
Value::Plaintext(..) => {
A::halt("Expected a dynamic future output, found a plaintext output")
}
Value::Record(..) => A::halt("Expected a dynamic future output, found a record output"),
Value::DynamicRecord(..) => {
A::halt("Expected a dynamic future output, found a dynamic record output")
}
Value::Future(..) => A::halt("Expected a dynamic future output, found a future output"),
}
}
}
})
.collect();
Self { output_ids, outputs }
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::Circuit;
use snarkvm_circuit_types::{U16, environment::UpdatableCount};
use snarkvm_utilities::{TestRng, Uniform};
use anyhow::Result;
pub(crate) const ITERATIONS: usize = 10;
fn check_from_outputs(
mode: Mode,
program_id: &str,
function_name: &str,
is_dynamic: bool,
use_record: bool,
expected_count: UpdatableCount,
) -> Result<()> {
use console::Network;
let rng = &mut TestRng::default();
for i in 0..ITERATIONS {
let tvk = console::Field::rand(rng);
let tcm = <Circuit as Environment>::Network::hash_psd2(&[tvk])?;
let index = console::Field::from_u64(8);
let randomizer = <Circuit as Environment>::Network::hash_to_scalar_psd2(&[tvk, index]).unwrap();
let nonce = <Circuit as Environment>::Network::g_scalar_multiply(&randomizer);
let output_constant = console::Value::<<Circuit as Environment>::Network>::Plaintext(
console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
);
let output_public = console::Value::<<Circuit as Environment>::Network>::Plaintext(
console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
);
let output_private = console::Value::<<Circuit as Environment>::Network>::Plaintext(
console::Plaintext::from_str("{ token_amount: 9876543210u128 }").unwrap(),
);
let output_record = console::Value::<<Circuit as Environment>::Network>::Record(console::Record::from_str(&format!("{{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, token_amount: 100u64.private, _nonce: {nonce}.public }}")).unwrap());
let output_external_record = console::Value::<<Circuit as Environment>::Network>::Record(console::Record::from_str("{ owner: aleo1d5hg2z3ma00382pngntdp68e74zv54jdxy249qhaujhks9c72yrs33ddah.private, token_amount: 100u64.private, _nonce: 0group.public }").unwrap());
let outputs = if use_record {
vec![output_constant, output_public, output_private, output_record, output_external_record]
} else {
vec![output_constant, output_public, output_private, output_external_record]
};
let output_types = if use_record {
vec![
console::ValueType::from_str("amount.constant").unwrap(),
console::ValueType::from_str("amount.public").unwrap(),
console::ValueType::from_str("amount.private").unwrap(),
console::ValueType::from_str("token.record").unwrap(),
console::ValueType::from_str("token.aleo/token.record").unwrap(),
]
} else {
vec![
console::ValueType::from_str("amount.constant").unwrap(),
console::ValueType::from_str("amount.public").unwrap(),
console::ValueType::from_str("amount.private").unwrap(),
console::ValueType::from_str("token.aleo/token.record").unwrap(),
]
};
let mut output_registers = vec![
Some(console::Register::Locator(5)),
Some(console::Register::Locator(6)),
Some(console::Register::Locator(7)),
Some(console::Register::Locator(8)),
];
if use_record {
output_registers.push(Some(console::Register::Locator(9)));
}
let signer = console::Address::rand(rng);
let network_id = console::U16::new(<Circuit as Environment>::Network::ID);
let program_id = console::ProgramID::from_str(program_id)?;
let function_name = console::Identifier::from_str(function_name)?;
let response = console::Response::new(
&signer,
&network_id,
&program_id,
&function_name,
4,
&tvk,
&tcm,
outputs.clone(),
&output_types,
&output_registers,
)?;
let signer = Address::<Circuit>::new(mode, signer);
let network_id = U16::<Circuit>::constant(network_id);
let program_id = match is_dynamic {
false => ProgramID::<Circuit>::constant(program_id),
true => ProgramID::<Circuit>::public(program_id),
};
let function_name = match is_dynamic {
false => Identifier::<Circuit>::constant(function_name),
true => Identifier::<Circuit>::public(function_name),
};
let tvk = Field::<Circuit>::new(mode, tvk);
let tcm = Field::<Circuit>::new(mode, tcm);
let outputs = Inject::new(mode, outputs);
Circuit::scope(format!("Response {i}"), || {
let candidate = Response::from_outputs(
&signer,
&network_id,
&program_id,
&function_name,
4,
&tvk,
&tcm,
outputs,
&output_types,
&output_registers,
);
assert_eq!(response, candidate.eject_value());
expected_count.assert_matches(
Circuit::num_constants_in_scope(),
Circuit::num_public_in_scope(),
Circuit::num_private_in_scope(),
Circuit::num_constraints_in_scope(),
);
});
Circuit::reset();
}
Ok(())
}
#[test]
#[rustfmt::skip]
fn test_from_outputs_constant() -> Result<()> {
check_from_outputs(Mode::Constant, "test.aleo", "foo", false, false, count_less_than!(19397, 4, 1497, 1505))?;
check_from_outputs(Mode::Constant, "credits.aleo", "transfer_public", false, false, count_less_than!(1011, 4, 1497, 1505))?;
check_from_outputs(Mode::Constant, "test.aleo", "foo", false, true, count_less_than!(19445, 7, 13274, 13300))?;
check_from_outputs(Mode::Constant, "credits.aleo", "transfer_public", false, true, count_less_than!(5176, 7, 13376, 13402))?;
check_from_outputs(Mode::Constant, "test.aleo", "foo", true, false, count_less_than!(713, 4, 6571, 6585))?;
check_from_outputs(Mode::Constant, "credits.aleo", "transfer_public", true, false, count_less_than!(713, 4, 6571, 6585))?;
check_from_outputs(Mode::Constant, "test.aleo", "foo", true, true, count_less_than!(4848, 7, 19481, 19517))?;
check_from_outputs(Mode::Constant, "credits.aleo", "transfer_public", true, true, count_less_than!(4716, 7, 19653, 19689))?;
Ok(())
}
#[test]
#[rustfmt::skip]
fn test_from_outputs_public() -> Result<()> {
check_from_outputs(Mode::Public, "test.aleo", "foo", false, false, count_is!(<=19397, 4, 3762, 3770))?;
check_from_outputs(Mode::Public, "credits.aleo", "transfer_public", false, false, count_is!(1009, 4, 3762, 3770))?;
check_from_outputs(Mode::Public, "test.aleo", "foo", false, true, count_is!(<=18692, 7, 18057, 18085))?;
check_from_outputs(Mode::Public, "credits.aleo", "transfer_public", false, true, count_is!(4419, 7, 18159, 18187))?;
check_from_outputs(Mode::Public, "test.aleo", "foo", true, false, count_is!(705, 4, 5472, 5486))?;
check_from_outputs(Mode::Public, "credits.aleo", "transfer_public", true, false, count_is!(707, 4, 5677, 5691))?;
check_from_outputs(Mode::Public, "test.aleo", "foo", true, true, count_is!(4087, 7, 19890, 19924))?;
check_from_outputs(Mode::Public, "credits.aleo", "transfer_public", true, true, count_is!(3957, 7, 20267, 20301))?;
Ok(())
}
#[test]
#[rustfmt::skip]
fn test_from_outputs_private() -> Result<()> {
check_from_outputs(Mode::Private, "test.aleo", "foo", false, false, count_is!(<=19397, 4, 3762, 3770))?;
check_from_outputs(Mode::Private, "credits.aleo", "transfer_public", false, false, count_is!(1009, 4, 3762, 3770))?;
check_from_outputs(Mode::Private, "test.aleo", "foo", false, true, count_is!(<=18692, 7, 18057, 18085))?;
check_from_outputs(Mode::Private, "credits.aleo", "transfer_public", false, true, count_is!(4419, 7, 18159, 18187))?;
check_from_outputs(Mode::Private, "test.aleo", "foo", true, false, count_is!(705, 4, 5472, 5486))?;
check_from_outputs(Mode::Private, "credits.aleo", "transfer_public", true, false, count_is!(707, 4, 5677, 5691))?;
check_from_outputs(Mode::Private, "test.aleo", "foo", true, true, count_is!(4087, 7, 19890, 19924))?;
check_from_outputs(Mode::Private, "credits.aleo", "transfer_public", true, true, count_is!(3957, 7, 20267, 20301))?;
Ok(())
}
}