use crate::{Network, OutputPrivateVariables, OutputPublicVariables, Payload};
use snarkvm_gadgets::{
bits::{Boolean, ToBytesGadget},
integers::uint::UInt8,
traits::{
algorithms::{CRHGadget, CommitmentGadget, EncryptionGadget},
alloc::AllocGadget,
eq::{ConditionalEqGadget, EqGadget},
},
FpGadget,
ToConstraintFieldGadget,
};
use snarkvm_r1cs::{errors::SynthesisError, ConstraintSynthesizer, ConstraintSystem};
use snarkvm_utilities::{FromBytes, ToBytes};
#[derive(Clone)]
pub struct OutputCircuit<N: Network> {
public: OutputPublicVariables<N>,
private: OutputPrivateVariables<N>,
}
impl<N: Network> OutputCircuit<N> {
pub fn blank() -> Self {
Self { public: OutputPublicVariables::blank(), private: OutputPrivateVariables::blank() }
}
pub fn new(public: OutputPublicVariables<N>, private: OutputPrivateVariables<N>) -> Self {
Self { public, private }
}
}
impl<N: Network> ConstraintSynthesizer<N::InnerScalarField> for OutputCircuit<N> {
fn generate_constraints<CS: ConstraintSystem<N::InnerScalarField>>(
&self,
cs: &mut CS,
) -> Result<(), SynthesisError> {
let public = &self.public;
let private = &self.private;
let commitment = public.commitment();
let value_commitment = public.output_value_commitment();
let record = &private.output_record;
let encryption_randomness = &private.encryption_randomness;
let value_commitment_randomness = &private.output_value_commitment_randomness;
let zero_value = UInt8::constant_vec(&(0u64).to_bytes_le()?);
let empty_payload = UInt8::constant_vec(&Payload::<N>::default().to_bytes_le()?);
let empty_program_id_bytes = UInt8::constant_vec(&vec![0u8; N::PROGRAM_ID_SIZE_IN_BYTES]);
let zero_value_field_elements =
zero_value.to_constraint_field(&mut cs.ns(|| "convert zero value to field elements"))?;
let empty_payload_field_elements =
empty_payload.to_constraint_field(&mut cs.ns(|| "convert empty payload to field elements"))?;
let empty_program_id_field_elements =
empty_program_id_bytes.to_constraint_field(&mut cs.ns(|| "convert empty program ID to field elements"))?;
let program_id_fe = {
let program_id_bytes = public
.program_id
.map_or(Ok(vec![0u8; N::PROGRAM_ID_SIZE_IN_BYTES]), |program_id| program_id.to_bytes_le())?;
let executable_program_id_bytes =
UInt8::alloc_input_vec_le(&mut cs.ns(|| "Allocate executable_program_id"), &program_id_bytes)?;
executable_program_id_bytes
.to_constraint_field(&mut cs.ns(|| "convert executable program ID to field elements"))?
};
let (account_encryption_parameters, record_commitment_parameters, value_commitment_parameters) = {
let cs = &mut cs.ns(|| "Declare parameters");
let account_encryption_parameters = N::AccountEncryptionGadget::alloc_constant(
&mut cs.ns(|| "Declare account encryption parameters"),
|| Ok(N::account_encryption_scheme().clone()),
)?;
let record_commitment_parameters =
N::CommitmentGadget::alloc_constant(&mut cs.ns(|| "Declare record commitment parameters"), || {
Ok(N::commitment_scheme().clone())
})?;
let value_commitment_parameters = N::ValueCommitmentGadget::alloc_constant(
&mut cs.ns(|| "Declare record value commitment parameters"),
|| Ok(N::value_commitment_scheme().clone()),
)?;
(account_encryption_parameters, record_commitment_parameters, value_commitment_parameters)
};
let cs = &mut cs.ns(|| "Process output record");
let (given_owner, given_is_dummy, given_value_bytes, given_payload, given_program_id, given_randomizer) = {
let declare_cs = &mut cs.ns(|| "Declare output record");
let given_owner = <N::AccountEncryptionGadget as EncryptionGadget<
N::AccountEncryptionScheme,
N::InnerScalarField,
>>::PublicKeyGadget::alloc(
&mut declare_cs.ns(|| "given_record_owner"), || Ok(*record.owner())
)?;
let given_is_dummy = Boolean::alloc(&mut declare_cs.ns(|| "given_is_dummy"), || Ok(record.is_dummy()))?;
let given_value_bytes =
UInt8::alloc_vec(&mut declare_cs.ns(|| "given_value"), &record.value().to_bytes_le()?)?;
let payload = record.payload().clone().unwrap_or_default();
let given_payload = UInt8::alloc_vec(&mut declare_cs.ns(|| "given_payload"), &payload.to_bytes_le()?)?;
let program_id_bytes = record
.program_id()
.map_or(Ok(vec![0u8; N::PROGRAM_ID_SIZE_IN_BYTES]), |program_id| program_id.to_bytes_le())?;
let given_program_id = UInt8::alloc_vec(&mut declare_cs.ns(|| "given_program_id"), &program_id_bytes)?;
let given_randomizer = <N::AccountEncryptionGadget as EncryptionGadget<
N::AccountEncryptionScheme,
N::InnerScalarField,
>>::CiphertextRandomizer::alloc(
&mut declare_cs.ns(|| "given_randomizer"), || Ok(record.randomizer())
)?;
(given_owner, given_is_dummy, given_value_bytes, given_payload, given_program_id, given_randomizer)
};
let value_bytes = {
let commitment_cs = &mut cs.ns(|| "Check that record is well-formed");
let given_is_dummy_bytes =
given_is_dummy.to_bytes(&mut commitment_cs.ns(|| "Convert given_is_dummy to bytes"))?;
{
let given_value_field_elements = given_value_bytes
.to_constraint_field(&mut commitment_cs.ns(|| "convert given value to field elements"))?;
let given_payload_field_elements = given_payload
.to_constraint_field(&mut commitment_cs.ns(|| "convert given payload to field elements"))?;
let given_program_id_field_elements = given_program_id
.to_constraint_field(&mut commitment_cs.ns(|| "convert given program ID to field elements"))?;
given_value_field_elements.conditional_enforce_equal(
&mut commitment_cs.ns(|| "If the output record is empty, enforce it has a value of 0"),
&zero_value_field_elements,
&given_is_dummy,
)?;
given_payload_field_elements.conditional_enforce_equal(
&mut commitment_cs.ns(|| "If the output record is empty, enforce it has an empty payload"),
&empty_payload_field_elements,
&given_is_dummy,
)?;
given_program_id_field_elements.conditional_enforce_equal(
&mut commitment_cs.ns(|| "If the output record is empty, enforce it has an empty program ID"),
&empty_program_id_field_elements,
&given_is_dummy,
)?;
given_program_id_field_elements.conditional_enforce_equal(
&mut commitment_cs.ns(|| "Check the-th output program ID matches"),
&program_id_fe,
&given_is_dummy.not(),
)?;
}
let owner_fe = FromBytes::read_le(&record.owner().to_bytes_le()?[..])?;
let given_owner_gadget = FpGadget::alloc(&mut commitment_cs.ns(|| "Field element"), || Ok(&owner_fe))?;
let encoded_given_value = <N::AccountEncryptionGadget as EncryptionGadget<
N::AccountEncryptionScheme,
N::InnerScalarField,
>>::encode_message(
&mut commitment_cs.ns(|| "encode input value"), &given_value_bytes
)?;
let encoded_given_payload = <N::AccountEncryptionGadget as EncryptionGadget<
N::AccountEncryptionScheme,
N::InnerScalarField,
>>::encode_message(
&mut commitment_cs.ns(|| "encode input payload"), &given_payload
)?;
let mut plaintext = Vec::with_capacity(1 + encoded_given_value.len() + encoded_given_payload.len());
plaintext.push(given_owner_gadget);
plaintext.extend_from_slice(&encoded_given_value);
plaintext.extend_from_slice(&encoded_given_payload);
let encryption_randomness = <N::AccountEncryptionGadget as EncryptionGadget<
N::AccountEncryptionScheme,
N::InnerScalarField,
>>::ScalarRandomnessGadget::alloc(
&mut commitment_cs.ns(|| "output record encryption_randomness"),
|| Ok(encryption_randomness),
)?;
let (candidate_ciphertext_randomizer, ciphertext, record_view_key) = account_encryption_parameters
.check_encryption_from_scalar_randomness(
&mut commitment_cs.ns(|| "output record check_encryption_gadget"),
&encryption_randomness,
&given_owner,
&plaintext,
)?;
candidate_ciphertext_randomizer.enforce_equal(
&mut commitment_cs.ns(|| "Check that the given randomizer matches public input"),
&given_randomizer,
)?;
let record_view_key_commitment = account_encryption_parameters.check_symmetric_key_commitment(
&mut commitment_cs.ns(|| "output record check_symmetric_key_commitment"),
&record_view_key,
)?;
let given_randomizer_bytes =
given_randomizer.to_bytes(&mut commitment_cs.ns(|| "Convert given_randomizer to bytes"))?;
let record_view_key_commitment_bytes = record_view_key_commitment
.to_bytes(&mut commitment_cs.ns(|| "Convert record_view_key_commitment to bytes"))?;
let ciphertext_bytes = ciphertext
.iter()
.enumerate()
.flat_map(|(i, element)| {
element
.to_bytes(&mut commitment_cs.ns(|| format!("Convert output ciphertext {} to bytes", i)))
.unwrap()
})
.collect::<Vec<_>>();
let mut commitment_input = Vec::with_capacity(
given_randomizer_bytes.len()
+ record_view_key_commitment_bytes.len()
+ ciphertext_bytes.len()
+ given_program_id.len()
+ given_is_dummy_bytes.len(),
);
commitment_input.extend_from_slice(&given_randomizer_bytes);
commitment_input.extend_from_slice(&record_view_key_commitment_bytes);
commitment_input.extend_from_slice(&ciphertext_bytes);
commitment_input.extend_from_slice(&given_program_id);
commitment_input.extend_from_slice(&given_is_dummy_bytes);
let candidate_commitment = record_commitment_parameters
.check_evaluation_gadget(&mut commitment_cs.ns(|| "Compute record commitment"), commitment_input)?;
let given_commitment = <N::CommitmentGadget as CRHGadget<
N::CommitmentScheme,
N::InnerScalarField,
>>::OutputGadget::alloc_input(
&mut commitment_cs.ns(|| "Allocate given output commitment"),
|| Ok(*commitment),
)?;
candidate_commitment.enforce_equal(
&mut commitment_cs.ns(|| "Check that the-th output commitment is valid"),
&given_commitment,
)?;
given_value_bytes
};
{
let vc_cs = &mut cs.ns(|| "Check that value commitment is derived correctly");
let value_commitment_randomness_gadget = <N::ValueCommitmentGadget as CommitmentGadget<
N::ValueCommitmentScheme,
N::InnerScalarField,
>>::RandomnessGadget::alloc(
&mut vc_cs.ns(|| "Output value commitment randomness"),
|| Ok(value_commitment_randomness),
)?;
let given_value_commitment_gadget = <N::ValueCommitmentGadget as CommitmentGadget<
N::ValueCommitmentScheme,
N::InnerScalarField,
>>::OutputGadget::alloc_input(
&mut vc_cs.ns(|| "Output value commitment"),
|| Ok(**value_commitment),
)?;
let candidate_value_commitment_gadget = value_commitment_parameters.check_commitment_gadget(
&mut vc_cs.ns(|| "Compute output value commitment"),
&value_bytes,
&value_commitment_randomness_gadget,
)?;
candidate_value_commitment_gadget.enforce_equal(
&mut vc_cs.ns(|| "Check that the-th output value commitment is valid"),
&given_value_commitment_gadget,
)?;
}
Ok(())
}
}