feat(lee): construct chained-call pre-states from protocol state (incremental update PR4)

ChainedCall.pre_states: Vec<AccountWithMetadata> becomes
pre_state_refs: Vec<AccountId>. A calling program no longer supplies
the concrete account value (or an is_authorized claim) for an account
it hands to a chained call — it only names which account, and the
protocol (sequencer for public transactions, host driver for
privacy-preserving ones) resolves the real, currently-tracked value
itself before invoking the callee.

This removes two things simultaneously: the burden on guest programs
to predict another call's outcome before it happens (the calling
program previously had no reliable way to know it), and the ability
to supply a stale or entirely fabricated account value at all. The
malicious_injector/malicious_launderer guest programs and
malicious_authorization_changer, which existed specifically to
demonstrate that forgery, are deleted — the attack they showed is now
a compile-time impossibility rather than something caught after the
fact by a runtime check.

The privacy circuit itself (execution_state.rs) needed no logic
changes: it never trusted a caller-supplied pre-state in the first
place, only the callee's own proven echo against its internally
tracked state. The public path already had the tracking map it
needed (state_diff); the privacy host driver gained one
(materialized_state), plus first-sighting position tracking so a
chained call's private-PDA authorization can be verified the same
way the circuit derives it internally.

Regenerates all lee_core/lez guest artifacts and the prebuilt
sequencer db fixture via `just build-artifacts`, required since
ChainedCall's shape is part of the proven wire format.
This commit is contained in:
Marvin Jones
2026-08-22 15:19:55 -04:00
parent 4c65fd0dd3
commit 172c58324e
67 changed files with 738 additions and 1380 deletions
@@ -46,7 +46,6 @@ fn main() {
// Unpack the input account pre state
let [pre_state] = pre_states
.clone()
.try_into()
.unwrap_or_else(|_| panic!("Input pre states should consist of a single account"));
@@ -63,7 +62,7 @@ fn main() {
let chained_call = ChainedCall {
program_id: hello_world_program_id(),
instruction_data: chained_call_instruction_data,
pre_states,
pre_state_refs: vec![pre_state.account_id],
pda_seeds: vec![],
};
@@ -17,7 +17,9 @@ use lee_core::{
//
// Emits this account unchanged, then performs a tail call to the
// Hello-World-with-Authorization program with a fixed greeting. The same
// account is passed along but marked with `is_authorized = true`.
// account is passed along by id; the callee resolves its `is_authorized`
// itself, from the PDA seed below, rather than trusting a value this
// program supplies.
const HELLO_WORLD_WITH_AUTHORIZATION_PROGRAM_ID_HEX: &str =
"1d95c761168a7fa62eb15a3cc74d3f075e6ec98e6c1ac25bd5bcc7e0a9426398";
@@ -66,16 +68,10 @@ fn main() {
b"Hello from tail call with Program Derived Account ID".to_vec();
let chained_call_instruction_data = risc0_zkvm::serde::to_vec(&chained_call_greeting).unwrap();
// Flip the `is_authorized` flag to true
let pre_state_for_chained_call = {
let mut this = pre_state.clone();
this.is_authorized = true;
this
};
let chained_call = ChainedCall {
program_id: hello_world_program_id(),
instruction_data: chained_call_instruction_data,
pre_states: vec![pre_state_for_chained_call],
pre_state_refs: vec![pre_state.account_id],
pda_seeds: vec![PDA_SEED],
};