mod prepare;
#[cfg(debug_assertions)]
use crate::Stack;
use console::{
network::prelude::*,
program::{InputID, StatePath, TRANSACTION_DEPTH, TransactionLeaf, TransitionLeaf, TransitionPath},
types::{Field, Group},
};
use ledger_block::{Input, Output, Transaction, Transition};
use ledger_query::QueryTrait;
use std::collections::HashMap;
#[derive(Clone, Debug)]
struct InputTask<N: Network> {
commitment: Field<N>,
gamma: Group<N>,
serial_number: Field<N>,
local: Option<(TransactionLeaf<N>, Field<N>, Field<N>, TransitionPath<N>, TransitionLeaf<N>)>,
}
#[derive(Clone, Debug, Default)]
pub(super) struct Inclusion<N: Network> {
input_tasks: HashMap<N::TransitionID, Vec<InputTask<N>>>,
output_commitments:
HashMap<Field<N>, (TransactionLeaf<N>, Field<N>, Field<N>, TransitionPath<N>, TransitionLeaf<N>)>,
}
impl<N: Network> Inclusion<N> {
pub fn new() -> Self {
Self { input_tasks: HashMap::new(), output_commitments: HashMap::new() }
}
pub fn insert_transition(&mut self, input_ids: &[InputID<N>], transition: &Transition<N>) -> Result<()> {
if input_ids.len() != transition.inputs().len() {
bail!("Inclusion expected the same number of input IDs as transition inputs")
}
let transition_index = u16::try_from(self.input_tasks.len())?;
let input_tasks = self.input_tasks.entry(*transition.id()).or_default();
for input_id in input_ids {
if let InputID::Record(commitment, gamma, serial_number, ..) = input_id {
input_tasks.push(InputTask {
commitment: *commitment,
gamma: *gamma,
serial_number: *serial_number,
local: self.output_commitments.get(commitment).cloned(),
});
}
}
if !transition.outputs().is_empty() {
let transaction_leaf = TransactionLeaf::new_execution(transition_index, **transition.id());
let transition_root = transition.to_root()?;
let tcm = *transition.tcm();
for (index, output) in transition.outputs().iter().enumerate() {
if let Output::Record(commitment, ..) = output {
let output_index = u8::try_from(input_ids.len().saturating_add(index))?;
let transition_leaf = output.to_transition_leaf(output_index);
let transition_path = transition.to_path(&transition_leaf)?;
self.output_commitments.insert(
*commitment,
(transaction_leaf, transition_root, tcm, transition_path, transition_leaf),
);
}
}
}
Ok(())
}
}
impl<N: Network> Inclusion<N> {
pub fn prepare_verifier_inputs<'a>(
global_state_root: N::StateRoot,
transitions: impl ExactSizeIterator<Item = &'a Transition<N>>,
) -> Result<Vec<Vec<N::Field>>> {
let num_transitions = transitions.len();
let mut transaction_tree = N::merkle_tree_bhp::<TRANSACTION_DEPTH>(&[])?;
let mut batch_verifier_inputs = vec![];
for (transition_index, transition) in transitions.enumerate() {
let local_state_root = *transaction_tree.root();
for input in transition.inputs() {
if let Input::Record(serial_number, _) = input {
let verifier_inputs =
vec![N::Field::one(), **global_state_root, *local_state_root, **serial_number];
batch_verifier_inputs.push(verifier_inputs);
}
}
if transition_index + 1 != num_transitions {
let leaf = TransactionLeaf::new_execution(u16::try_from(transition_index)?, **transition.id());
transaction_tree.append(&[leaf.to_bits_le()])?;
}
}
if batch_verifier_inputs.is_empty() && *global_state_root == Field::zero() {
bail!("Inclusion expected the global state root in the execution to *not* be zero")
}
Ok(batch_verifier_inputs)
}
}
#[derive(Clone, Debug)]
pub struct InclusionAssignment<N: Network> {
pub(crate) state_path: StatePath<N>,
commitment: Field<N>,
gamma: Group<N>,
serial_number: Field<N>,
local_state_root: N::TransactionID,
is_global: bool,
}
impl<N: Network> InclusionAssignment<N> {
pub fn new(
state_path: StatePath<N>,
commitment: Field<N>,
gamma: Group<N>,
serial_number: Field<N>,
local_state_root: N::TransactionID,
is_global: bool,
) -> Self {
Self { state_path, commitment, gamma, serial_number, local_state_root, is_global }
}
pub fn to_circuit_assignment<A: circuit::Aleo<Network = N>>(&self) -> Result<circuit::Assignment<N::Field>> {
use circuit::Inject;
assert_eq!(A::count(), (0, 1, 0, 0, (0, 0, 0)));
A::reset();
let state_path = circuit::StatePath::<A>::new(circuit::Mode::Private, self.state_path.clone());
let commitment = circuit::Field::<A>::new(circuit::Mode::Private, self.commitment);
let gamma = circuit::Group::<A>::new(circuit::Mode::Private, self.gamma);
let local_state_root = circuit::Field::<A>::new(circuit::Mode::Public, *self.local_state_root);
let is_global = circuit::Boolean::<A>::new(circuit::Mode::Private, self.is_global);
let serial_number = circuit::Field::<A>::new(circuit::Mode::Public, self.serial_number);
let candidate_serial_number =
circuit::Record::<A, circuit::Plaintext<A>>::serial_number_from_gamma(&gamma, commitment.clone());
A::assert_eq(candidate_serial_number, serial_number);
A::assert_eq(state_path.transition_leaf().id(), commitment);
A::assert(state_path.verify(&is_global, &local_state_root));
#[cfg(debug_assertions)]
Stack::log_circuit::<A, _>(&format!("State Path for {}", self.serial_number));
Ok(A::eject_assignment_and_reset())
}
}