mod bytes;
mod serialize;
mod string;
use console::{
network::prelude::*,
program::{Ciphertext, Plaintext, TransitionLeaf, ValueType},
types::Field,
};
type Variant = u8;
#[derive(Clone, PartialEq, Eq)]
pub enum Input<N: Network> {
Constant(Field<N>, Option<Plaintext<N>>),
Public(Field<N>, Option<Plaintext<N>>),
Private(Field<N>, Option<Ciphertext<N>>),
Record(Field<N>, Field<N>),
ExternalRecord(Field<N>),
DynamicRecord(Field<N>),
RecordWithDynamicID(Field<N>, Field<N>, Field<N>),
ExternalRecordWithDynamicID(Field<N>, Field<N>),
}
impl<N: Network> Input<N> {
pub const fn variant(&self) -> Variant {
match self {
Input::Constant(..) => 0,
Input::Public(..) => 1,
Input::Private(..) => 2,
Input::Record(..) => 3, Input::ExternalRecord(..) => 4,
Input::DynamicRecord(..) => 5,
Input::RecordWithDynamicID(..) => 6,
Input::ExternalRecordWithDynamicID(..) => 7,
}
}
pub const fn id(&self) -> &Field<N> {
match self {
Input::Constant(id, ..) => id,
Input::Public(id, ..) => id,
Input::Private(id, ..) => id,
Input::Record(serial_number, ..) => serial_number,
Input::ExternalRecord(id) => id,
Input::DynamicRecord(id) => id,
Input::RecordWithDynamicID(serial_number, ..) => serial_number,
Input::ExternalRecordWithDynamicID(id, ..) => id,
}
}
pub fn to_transition_leaf(&self, index: u8) -> TransitionLeaf<N> {
match self {
Input::RecordWithDynamicID(..) => TransitionLeaf::new_record_with_dynamic_id(index, *self.id()),
Input::ExternalRecordWithDynamicID(..) => {
TransitionLeaf::new_external_record_with_dynamic_id(index, *self.id())
}
_ => TransitionLeaf::new(index, self.variant(), *self.id()),
}
}
pub const fn tag(&self) -> Option<&Field<N>> {
match self {
Input::Record(_, tag) | Input::RecordWithDynamicID(_, tag, _) => Some(tag),
_ => None,
}
}
pub fn into_tag(self) -> Option<Field<N>> {
match self {
Input::Record(_, tag) | Input::RecordWithDynamicID(_, tag, _) => Some(tag),
_ => None,
}
}
pub const fn serial_number(&self) -> Option<&Field<N>> {
match self {
Input::Record(serial_number, ..) | Input::RecordWithDynamicID(serial_number, ..) => Some(serial_number),
_ => None,
}
}
pub fn into_serial_number(self) -> Option<Field<N>> {
match self {
Input::Record(serial_number, ..) | Input::RecordWithDynamicID(serial_number, ..) => Some(serial_number),
_ => None,
}
}
pub fn verifier_inputs(&self) -> impl '_ + Iterator<Item = N::Field> {
[Some(self.id()), self.tag()].into_iter().flatten().map(|id| **id)
}
pub const fn dynamic_id(&self) -> Option<&Field<N>> {
match self {
Input::RecordWithDynamicID(_, _, dynamic_id) | Input::ExternalRecordWithDynamicID(_, dynamic_id) => {
Some(dynamic_id)
}
_ => None,
}
}
pub fn to_caller_input(&self) -> Self {
match self {
Self::RecordWithDynamicID(_, _, dynamic_id) => Self::DynamicRecord(*dynamic_id),
Self::ExternalRecordWithDynamicID(_, dynamic_id) => Self::DynamicRecord(*dynamic_id),
other => other.clone(),
}
}
pub fn verify(&self, function_id: Field<N>, tcm: &Field<N>, index: usize) -> bool {
let result = || match self {
Input::Constant(hash, Some(input)) => {
match input.to_fields() {
Ok(fields) => {
let index = Field::from_u16(index as u16);
let mut preimage = Vec::new();
preimage.push(function_id);
preimage.extend(fields);
preimage.push(*tcm);
preimage.push(index);
match N::hash_psd8(&preimage) {
Ok(candidate_hash) => Ok(hash == &candidate_hash),
Err(error) => Err(error),
}
}
Err(error) => Err(error),
}
}
Input::Public(hash, Some(input)) => {
match input.to_fields() {
Ok(fields) => {
let index = Field::from_u16(index as u16);
let mut preimage = Vec::new();
preimage.push(function_id);
preimage.extend(fields);
preimage.push(*tcm);
preimage.push(index);
match N::hash_psd8(&preimage) {
Ok(candidate_hash) => Ok(hash == &candidate_hash),
Err(error) => Err(error),
}
}
Err(error) => Err(error),
}
}
Input::Private(hash, Some(value)) => {
match value.to_fields() {
Ok(fields) => match N::hash_psd8(&fields) {
Ok(candidate_hash) => Ok(hash == &candidate_hash),
Err(error) => Err(error),
},
Err(error) => Err(error),
}
}
Input::Constant(_, None) | Input::Public(_, None) | Input::Private(_, None) => {
bail!("A transition input value is missing")
}
Input::Record(_, _)
| Input::ExternalRecord(_)
| Input::DynamicRecord(_)
| Input::RecordWithDynamicID(_, _, _)
| Input::ExternalRecordWithDynamicID(_, _) => Ok(true),
};
match result() {
Ok(is_hash_valid) => is_hash_valid,
Err(error) => {
eprintln!("{error}");
false
}
}
}
pub fn is_type(&self, expected_value_type: &ValueType<N>) -> bool {
matches!(
(self, expected_value_type),
(Self::Constant(..), ValueType::Constant(..))
| (Self::Public(..), ValueType::Public(..))
| (Self::Private(..), ValueType::Private(..))
| (Self::Record(..), ValueType::Record(..))
| (Self::RecordWithDynamicID(..), ValueType::Record(..))
| (Self::ExternalRecord(..), ValueType::ExternalRecord(..))
| (Self::ExternalRecordWithDynamicID(..), ValueType::ExternalRecord(..))
| (Self::DynamicRecord(..), ValueType::DynamicRecord)
)
}
}
#[cfg(test)]
pub(crate) mod test_helpers {
use super::*;
use console::{network::MainnetV0, program::Literal};
type CurrentNetwork = MainnetV0;
pub(crate) fn sample_inputs() -> Vec<(<CurrentNetwork as Network>::TransitionID, Input<CurrentNetwork>)> {
let rng = &mut TestRng::default();
let transaction = crate::transaction::test_helpers::sample_execution_transaction_with_fee(true, rng, 0);
let transition = transaction.transitions().next().unwrap();
let transition_id = *transition.id();
let input = transition.inputs().iter().next().unwrap().clone();
let plaintext = Plaintext::Literal(Literal::Field(Uniform::rand(rng)), Default::default());
let plaintext_hash = CurrentNetwork::hash_bhp1024(&plaintext.to_bits_le()).unwrap();
let fields: Vec<_> = (0..10).map(|_| Uniform::rand(rng)).collect();
let ciphertext = Ciphertext::from_fields(&fields).unwrap();
let ciphertext_hash = CurrentNetwork::hash_bhp1024(&ciphertext.to_bits_le()).unwrap();
vec![
(transition_id, input),
(Uniform::rand(rng), Input::Constant(Uniform::rand(rng), None)),
(Uniform::rand(rng), Input::Constant(plaintext_hash, Some(plaintext.clone()))),
(Uniform::rand(rng), Input::Public(Uniform::rand(rng), None)),
(Uniform::rand(rng), Input::Public(plaintext_hash, Some(plaintext))),
(Uniform::rand(rng), Input::Private(Uniform::rand(rng), None)),
(Uniform::rand(rng), Input::Private(ciphertext_hash, Some(ciphertext))),
(Uniform::rand(rng), Input::Record(Uniform::rand(rng), Uniform::rand(rng))),
(Uniform::rand(rng), Input::ExternalRecord(Uniform::rand(rng))),
(
Uniform::rand(rng),
Input::RecordWithDynamicID(Uniform::rand(rng), Uniform::rand(rng), Uniform::rand(rng)),
),
(Uniform::rand(rng), Input::ExternalRecordWithDynamicID(Uniform::rand(rng), Uniform::rand(rng))),
(Uniform::rand(rng), Input::DynamicRecord(Uniform::rand(rng))),
]
}
}
#[cfg(test)]
mod tests {
use super::*;
use console::network::MainnetV0;
type CurrentNetwork = MainnetV0;
#[test]
fn test_to_caller_input_record_with_dynamic_id() {
let serial_number = Field::<CurrentNetwork>::from_u64(1);
let tag = Field::<CurrentNetwork>::from_u64(2);
let dynamic_id = Field::<CurrentNetwork>::from_u64(3);
let input = Input::<CurrentNetwork>::RecordWithDynamicID(serial_number, tag, dynamic_id);
let caller_input = input.to_caller_input();
assert_eq!(caller_input, Input::<CurrentNetwork>::DynamicRecord(dynamic_id));
}
#[test]
fn test_to_caller_input_external_record_with_dynamic_id() {
let ext_id = Field::<CurrentNetwork>::from_u64(10);
let dynamic_id = Field::<CurrentNetwork>::from_u64(20);
let input = Input::<CurrentNetwork>::ExternalRecordWithDynamicID(ext_id, dynamic_id);
let caller_input = input.to_caller_input();
assert_eq!(caller_input, Input::<CurrentNetwork>::DynamicRecord(dynamic_id));
}
#[test]
fn test_to_caller_input_non_dynamic_variants_unchanged() {
let id = Field::<CurrentNetwork>::from_u64(42);
let constant = Input::<CurrentNetwork>::Constant(id, None);
assert_eq!(constant.to_caller_input(), constant);
let public = Input::<CurrentNetwork>::Public(id, None);
assert_eq!(public.to_caller_input(), public);
let dynamic_record = Input::<CurrentNetwork>::DynamicRecord(id);
assert_eq!(dynamic_record.to_caller_input(), dynamic_record);
let external = Input::<CurrentNetwork>::ExternalRecord(id);
assert_eq!(external.to_caller_input(), external);
}
}