Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Fix] Add additional input/output checks to Execution verification #2511

Merged
merged 2 commits into from
Jul 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions synthesizer/process/src/verify_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@ impl<N: Network> Process<N> {

// Ensure each output is valid.
let num_inputs = transition.inputs().len();
let num_outputs = transition.outputs().len();
if transition
.outputs()
.iter()
Expand All @@ -106,6 +107,10 @@ impl<N: Network> Process<N> {
// Retrieve the function from the stack.
let function = stack.get_function(transition.function_name())?;

// Ensure the number of inputs and outputs match the expected number in the function.
ensure!(function.inputs().len() == num_inputs, "The number of transition inputs is incorrect");
ensure!(function.outputs().len() == num_outputs, "The number of transition outputs is incorrect");

// Retrieve the parent program ID.
// Note: The last transition in the execution does not have a parent, by definition.
let parent = reverse_call_graph.get(transition.id()).and_then(|tid| execution.get_program_id(tid));
Expand Down
71 changes: 70 additions & 1 deletion synthesizer/src/vm/verify.rs
Original file line number Diff line number Diff line change
Expand Up @@ -383,7 +383,7 @@ mod tests {
account::{Address, ViewKey},
types::Field,
};
use ledger_block::{Block, Header, Metadata, Transaction};
use ledger_block::{Block, Header, Metadata, Transaction, Transition};

type CurrentNetwork = test_helpers::CurrentNetwork;

Expand Down Expand Up @@ -641,4 +641,73 @@ function compute:
// Ensure that the program can't be deployed.
assert!(vm.deploy_raw(&program, rng).is_err());
}

#[test]
fn test_check_mutated_execution() {
let rng = &mut TestRng::default();

// Initialize the VM.
let vm = crate::vm::test_helpers::sample_vm();
// Fetch the caller's private key.
let caller_private_key = crate::vm::test_helpers::sample_genesis_private_key(rng);
// Initialize the genesis block.
let genesis = crate::vm::test_helpers::sample_genesis_block(rng);
// Update the VM.
vm.add_next_block(&genesis).unwrap();

// Fetch a valid execution transaction with a public fee.
let valid_transaction = crate::vm::test_helpers::sample_execution_transaction_with_public_fee(rng);
vm.check_transaction(&valid_transaction, None, rng).unwrap();

// Mutate the execution transaction by inserting a Field::Zero as an output.
let execution = valid_transaction.execution().unwrap();

// Extract the first transition from the execution.
let transitions: Vec<_> = execution.transitions().collect();
assert_eq!(transitions.len(), 1);
let transition = transitions[0].clone();

// Mutate the transition by adding an additional `Field::zero` output. This is significant because the Varuna
// verifier pads the inputs with `Field::zero`s, which means that the same proof is valid for both the
// original and the mutated executions.
let added_output = Output::ExternalRecord(Field::zero());
let mutated_outputs = [transition.outputs(), &[added_output]].concat();
let mutated_transition = Transition::new(
*transition.program_id(),
*transition.function_name(),
transition.inputs().to_vec(),
mutated_outputs,
*transition.tpk(),
*transition.tcm(),
*transition.scm(),
)
.unwrap();

// Construct the mutated execution.
let mutated_execution = Execution::from(
[mutated_transition].into_iter(),
execution.global_state_root(),
execution.proof().cloned(),
)
.unwrap();

// Authorize the fee.
let authorization = vm
.authorize_fee_public(
&caller_private_key,
10_000_000,
100,
mutated_execution.to_execution_id().unwrap(),
rng,
)
.unwrap();
// Compute the fee.
let fee = vm.execute_fee_authorization(authorization, None, rng).unwrap();

// Construct the transaction.
let mutated_transaction = Transaction::from_execution(mutated_execution, Some(fee)).unwrap();

// Ensure that the mutated transaction fails verification due to an extra output.
assert!(vm.check_transaction(&mutated_transaction, None, rng).is_err());
}
}