Skip to content

Commit

Permalink
batch proof output compatibility (#1639)
Browse files Browse the repository at this point in the history
Co-authored-by: eyusufatik <[email protected]>
Co-authored-by: yaziciahmet <[email protected]>
  • Loading branch information
3 people authored Dec 24, 2024
1 parent 1dde2ac commit 865d9b9
Show file tree
Hide file tree
Showing 14 changed files with 364 additions and 130 deletions.
14 changes: 7 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

40 changes: 35 additions & 5 deletions crates/batch-prover/src/proving.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,9 @@ use sov_modules_api::{BatchProofCircuitOutput, BlobReaderTrait, SlotData, SpecId
use sov_rollup_interface::da::{BlockHeaderTrait, DaNamespace, DaSpec, SequencerCommitment};
use sov_rollup_interface::rpc::SoftConfirmationStatus;
use sov_rollup_interface::services::da::DaService;
use sov_rollup_interface::zk::{BatchProofCircuitInput, Proof, ZkvmHost};
use sov_rollup_interface::zk::{
BatchProofCircuitInput, OldBatchProofCircuitOutput, Proof, ZkvmHost,
};
use sov_stf_runner::ProverService;
use tokio::sync::Mutex;
use tracing::{debug, info};
Expand Down Expand Up @@ -321,13 +323,41 @@ where
// l1_height => (tx_id, proof, circuit_output)
// save proof along with tx id to db, should be queryable by slot number or slot hash
// TODO: select output version based on spec
let circuit_output = Vm::extract_output::<
let (last_active_spec_id, circuit_output) = match Vm::extract_output::<
<Da as DaService>::Spec,
BatchProofCircuitOutput<<Da as DaService>::Spec, StateRoot>,
>(&proof)
.expect("Proof should be deserializable");

let last_active_spec_id = fork_from_block_number(circuit_output.last_l2_height).spec_id;
{
Ok(output) => (
fork_from_block_number(output.last_l2_height).spec_id,
output,
),
Err(e) => {
info!("Failed to extract post fork 1 output from proof: {:?}. Trying to extract pre fork 1 output", e);
let output = Vm::extract_output::<
<Da as DaService>::Spec,
OldBatchProofCircuitOutput<<Da as DaService>::Spec, StateRoot>,
>(&proof)
.expect("Should be able to extract either pre or post fork 1 output");
let batch_proof_output = BatchProofCircuitOutput::<Da::Spec, StateRoot> {
initial_state_root: output.initial_state_root,
final_state_root: output.final_state_root,
state_diff: output.state_diff,
da_slot_hash: output.da_slot_hash,
sequencer_commitments_range: output.sequencer_commitments_range,
sequencer_public_key: output.sequencer_public_key,
sequencer_da_public_key: output.sequencer_da_public_key,
preproven_commitments: output.preproven_commitments,
// We don't have these fields in pre fork 1
// That's why we serve them as 0
prev_soft_confirmation_hash: [0; 32],
final_soft_confirmation_hash: [0; 32],
last_l2_height: 0,
};
// If we got output of pre fork 1 that means we are in genesis
(SpecId::Genesis, batch_proof_output)
}
};

let code_commitment = code_commitments_by_spec
.get(&last_active_spec_id)
Expand Down
40 changes: 36 additions & 4 deletions crates/fullnode/src/da_block_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,9 @@ use sov_rollup_interface::da::{BlockHeaderTrait, SequencerCommitment};
use sov_rollup_interface::rpc::SoftConfirmationStatus;
use sov_rollup_interface::services::da::{DaService, SlotData};
use sov_rollup_interface::spec::SpecId;
use sov_rollup_interface::zk::{BatchProofCircuitOutput, Proof, ZkvmHost};
use sov_rollup_interface::zk::{
BatchProofCircuitOutput, OldBatchProofCircuitOutput, Proof, ZkvmHost,
};
use tokio::select;
use tokio::sync::{mpsc, Mutex};
use tokio::time::{sleep, Duration};
Expand Down Expand Up @@ -301,11 +303,42 @@ where
tracing::trace!("ZK proof: {:?}", proof);

// TODO: select output version based on spec
let batch_proof_output = Vm::extract_output::<
let (last_active_spec_id, batch_proof_output) = match Vm::extract_output::<
<Da as DaService>::Spec,
BatchProofCircuitOutput<<Da as DaService>::Spec, StateRoot>,
>(&proof)
.expect("Proof should be deserializable");
{
Ok(output) => (
fork_from_block_number(output.last_l2_height).spec_id,
output,
),
Err(e) => {
info!("Failed to extract post fork 1 output from proof: {:?}. Trying to extract pre fork 1 output", e);
let output = Vm::extract_output::<
<Da as DaService>::Spec,
OldBatchProofCircuitOutput<<Da as DaService>::Spec, StateRoot>,
>(&proof)
.expect("Should be able to extract either pre or post fork 1 output");
let batch_proof_output = BatchProofCircuitOutput::<Da::Spec, StateRoot> {
initial_state_root: output.initial_state_root,
final_state_root: output.final_state_root,
state_diff: output.state_diff,
da_slot_hash: output.da_slot_hash,
sequencer_commitments_range: output.sequencer_commitments_range,
sequencer_public_key: output.sequencer_public_key,
sequencer_da_public_key: output.sequencer_da_public_key,
preproven_commitments: output.preproven_commitments,
// We don't have these fields in pre fork 1
// That's why we serve them as 0
prev_soft_confirmation_hash: [0; 32],
final_soft_confirmation_hash: [0; 32],
last_l2_height: 0,
};
// If we got output of pre fork 1 that means we are in genesis
(SpecId::Genesis, batch_proof_output)
}
};

if batch_proof_output.sequencer_da_public_key != self.sequencer_da_pub_key
|| batch_proof_output.sequencer_public_key != self.sequencer_pub_key
{
Expand All @@ -314,7 +347,6 @@ where
).into());
}

let last_active_spec_id = fork_from_block_number(batch_proof_output.last_l2_height).spec_id;
let code_commitment = self
.code_commitments_by_spec
.get(&last_active_spec_id)
Expand Down
114 changes: 88 additions & 26 deletions crates/light-client-prover/src/circuit.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use sov_modules_api::BlobReaderTrait;
use sov_rollup_interface::da::{DaDataLightClient, DaNamespace, DaVerifier};
use sov_rollup_interface::zk::{
BatchProofCircuitOutput, BatchProofInfo, LightClientCircuitInput, LightClientCircuitOutput,
ZkvmGuest,
OldBatchProofCircuitOutput, ZkvmGuest,
};
use sov_rollup_interface::Network;

Expand All @@ -16,11 +16,14 @@ pub enum LightClientVerificationError {
InvalidPreviousLightClientProof,
}

// L2 activation height of the fork, and the batch proof method ID
type InitialBatchProofMethodIds = Vec<(u64, [u32; 8])>;

pub fn run_circuit<DaV: DaVerifier, G: ZkvmGuest>(
da_verifier: DaV,
input: LightClientCircuitInput<DaV::Spec>,
l2_genesis_root: [u8; 32],
batch_proof_method_id: [u32; 8],
initial_batch_proof_method_ids: InitialBatchProofMethodIds,
batch_prover_da_public_key: &[u8],
network: Network,
) -> Result<LightClientCircuitOutput, LightClientVerificationError> {
Expand All @@ -42,6 +45,12 @@ pub fn run_circuit<DaV: DaVerifier, G: ZkvmGuest>(
None
};

let batch_proof_method_ids = previous_light_client_proof_output
.as_ref()
.map_or(initial_batch_proof_method_ids, |o| {
o.batch_proof_method_ids.clone()
});

let new_da_state = da_verifier
.verify_header_chain(
previous_light_client_proof_output
Expand All @@ -66,15 +75,6 @@ pub fn run_circuit<DaV: DaVerifier, G: ZkvmGuest>(
// Mapping from initial state root to final state root and last L2 height
let mut initial_to_final = std::collections::BTreeMap::<[u8; 32], ([u8; 32], u64)>::new();

let (mut last_state_root, mut last_l2_height) =
previous_light_client_proof_output.as_ref().map_or_else(
|| {
// if no previous proof, we start from genesis state root
(l2_genesis_root, 0)
},
|prev_journal| (prev_journal.state_root, prev_journal.last_l2_height),
);

// If we have a previous light client proof, check they can be chained
// If not, skip for now
if let Some(previous_output) = &previous_light_client_proof_output {
Expand All @@ -89,8 +89,16 @@ pub fn run_circuit<DaV: DaVerifier, G: ZkvmGuest>(
);
}
}
// TODO: Test for multiple assumptions to see if the env::verify function does automatic matching between the journal and the assumption or do we need to verify them in order?
// https://github.com/chainwayxyz/citrea/issues/1401

let (mut last_state_root, mut last_l2_height) =
previous_light_client_proof_output.as_ref().map_or_else(
|| {
// if no previous proof, we start from genesis state root
(l2_genesis_root, 0)
},
|prev_journal| (prev_journal.state_root, prev_journal.last_l2_height),
);

// Parse the batch proof da data
for blob in input.da_data {
if blob.sender().as_ref() == batch_prover_da_public_key {
Expand All @@ -101,28 +109,68 @@ pub fn run_circuit<DaV: DaVerifier, G: ZkvmGuest>(
DaDataLightClient::Complete(proof) => {
let journal =
G::extract_raw_output(&proof).expect("DaData proofs must be valid");
// TODO: select output version based on the spec
let batch_proof_output: BatchProofCircuitOutput<DaV::Spec, [u8; 32]> =
match G::verify_and_extract_output(
&journal,
&batch_proof_method_id.into(),
) {
Ok(output) => output,
Err(_) => continue,
};

let (
batch_proof_output_initial_state_root,
batch_proof_output_final_state_root,
batch_proof_output_last_l2_height,
) = if let Ok(output) = G::deserialize_output::<
BatchProofCircuitOutput<DaV::Spec, [u8; 32]>,
>(&journal)
{
(
output.initial_state_root,
output.final_state_root,
output.last_l2_height,
)
} else if let Ok(output) = G::deserialize_output::<
OldBatchProofCircuitOutput<DaV::Spec, [u8; 32]>,
>(&journal)
{
(output.initial_state_root, output.final_state_root, 0)
} else {
continue; // cannot parse the output, skip
};

// Do not add if last l2 height is smaller or equal to previous output
// This is to defend against replay attacks, for example if somehow there is the script of batch proof 1 we do not need to go through it again
if batch_proof_output.last_l2_height <= last_l2_height {
if batch_proof_output_last_l2_height <= last_l2_height {
continue;
}

let batch_proof_method_id = if batch_proof_method_ids.len() == 1 {
// Check if last l2 height is greater than or equal to the only batch proof method id activation height
if batch_proof_output_last_l2_height >= batch_proof_method_ids[0].0 {
batch_proof_method_ids[0].1
} else {
// If not continue to the next blob
continue;
}
} else {
let idx = match batch_proof_method_ids
// Returns err and the index to be inserted, which is the index of the first element greater than the key
// That is why we need to subtract 1 to get the last element smaller than the key
.binary_search_by_key(
&batch_proof_output_last_l2_height,
|(height, _)| *height,
) {
Ok(idx) => idx,
Err(idx) => idx.saturating_sub(1),
};
batch_proof_method_ids[idx].1
};

if G::verify(&journal, &batch_proof_method_id.into()).is_err() {
// if the batch proof is invalid, continue to the next blob
continue;
}

recursive_match_state_roots(
&mut initial_to_final,
&BatchProofInfo::new(
batch_proof_output.initial_state_root,
batch_proof_output.final_state_root,
batch_proof_output.last_l2_height,
batch_proof_output_initial_state_root,
batch_proof_output_final_state_root,
batch_proof_output_last_l2_height,
),
);
}
Expand Down Expand Up @@ -154,5 +202,19 @@ pub fn run_circuit<DaV: DaVerifier, G: ZkvmGuest>(
latest_da_state: new_da_state,
unchained_batch_proofs_info: unchained_outputs,
last_l2_height,
batch_proof_method_ids,
})
}

#[test]
fn test_binary_search() {
let ve = [1, 4, 7, 9, 14];
let idx = ve.binary_search(&4); // 1
assert_eq!(idx, Ok(1));
let idx = ve.binary_search(&100); // 5 - 1
assert_eq!(idx, Err(5));
let idx = ve.binary_search(&7); // 2
assert_eq!(idx, Ok(2));
let idx = ve.binary_search(&8); // 3-1
assert_eq!(idx, Err(3));
}
Loading

0 comments on commit 865d9b9

Please sign in to comment.