From 81039d659faa946e6cba0a29c59bdda8fd84e168 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 13:14:50 +0300 Subject: [PATCH 01/24] Use anyhow --- crates/light-client-prover/Cargo.toml | 3 +-- guests/risc0/light-client-proof-bitcoin/Cargo.lock | 1 + guests/risc0/light-client-proof-mock/Cargo.lock | 1 + 3 files changed, 3 insertions(+), 2 deletions(-) diff --git a/crates/light-client-prover/Cargo.toml b/crates/light-client-prover/Cargo.toml index e3ff4092c..0ba097ed1 100644 --- a/crates/light-client-prover/Cargo.toml +++ b/crates/light-client-prover/Cargo.toml @@ -21,7 +21,7 @@ sov-rollup-interface = { path = "../sovereign-sdk/rollup-interface" } sov-stf-runner = { path = "../sovereign-sdk/full-node/sov-stf-runner", optional = true } # 3rd-party deps -anyhow = { workspace = true, optional = true } +anyhow = { workspace = true } async-trait = { workspace = true, optional = true } bincode = { workspace = true } borsh = { workspace = true } @@ -49,7 +49,6 @@ native = [ "dep:sov-db", "dep:sov-stf-runner", "dep:sov-ledger-rpc", - "dep:anyhow", "dep:async-trait", "dep:jsonrpsee", "dep:metrics", diff --git a/guests/risc0/light-client-proof-bitcoin/Cargo.lock b/guests/risc0/light-client-proof-bitcoin/Cargo.lock index 21c77740d..507214a91 100644 --- a/guests/risc0/light-client-proof-bitcoin/Cargo.lock +++ b/guests/risc0/light-client-proof-bitcoin/Cargo.lock @@ -804,6 +804,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" name = "citrea-light-client-prover" version = "0.5.0-rc.1" dependencies = [ + "anyhow", "bincode", "borsh", "hex", diff --git a/guests/risc0/light-client-proof-mock/Cargo.lock b/guests/risc0/light-client-proof-mock/Cargo.lock index 71590c375..86d8bd7d4 100644 --- a/guests/risc0/light-client-proof-mock/Cargo.lock +++ b/guests/risc0/light-client-proof-mock/Cargo.lock @@ -713,6 +713,7 @@ checksum = "613afe47fcd5fac7ccf1db93babcb082c5994d996f20b8b159f2ad1658eb5724" name = "citrea-light-client-prover" version = "0.5.0-rc.1" dependencies = [ + "anyhow", "bincode", "borsh", "hex", From 4cec3cc8ea40d14ea5b253989bf70548d648f5e6 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 13:15:25 +0300 Subject: [PATCH 02/24] Add wxtid data to input / output --- .../rollup-interface/src/state_machine/zk/mod.rs | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs index a39c71619..799adfdc9 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs @@ -236,6 +236,8 @@ pub struct LightClientCircuitOutput { pub last_l2_height: u64, /// Genesis state root of Citrea pub l2_genesis_state_root: [u8; 32], + /// A map from tx hash to chunk data + pub wtxid_data: BTreeMap<[u8; 32], Vec>, } /// The input of light client proof @@ -261,4 +263,6 @@ pub struct LightClientCircuitInput { pub previous_light_client_proof_journal: Option>, /// L2 Genesis state root pub l2_genesis_state_root: Option<[u8; 32]>, + /// A map from tx hash to chunk data + pub wtxid_data: BTreeMap<[u8; 32], Vec>, } From 7321775fa4f55aaf769140f4c329cdb3e8480285 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 13:15:37 +0300 Subject: [PATCH 03/24] Concatenate chunked proofs and verify --- crates/light-client-prover/src/circuit.rs | 108 ++++++++++++++++------ 1 file changed, 82 insertions(+), 26 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 3e70890c6..87e544822 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -1,3 +1,6 @@ +use std::collections::{BTreeMap, HashSet}; + +use anyhow::anyhow; use borsh::BorshDeserialize; use sov_modules_api::BlobReaderTrait; use sov_rollup_interface::da::{DaDataLightClient, DaNamespace, DaVerifier}; @@ -91,6 +94,9 @@ pub fn run_circuit( // 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 batch_proof_method_id = input.batch_proof_method_id; + + let mut wtxid_data = BTreeMap::new(); + // Parse the batch proof da data for blob in input.da_data { if blob.sender().as_ref() == input.batch_prover_da_pub_key { @@ -99,35 +105,52 @@ pub fn run_circuit( if let Ok(data) = data { match data { 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 = - match G::verify_and_extract_output( - &journal, - &batch_proof_method_id.into(), - ) { - Ok(output) => output, - Err(_) => continue, - }; - - // 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 process_complete_proof::( + proof, + batch_proof_method_id, + last_l2_height, + &mut initial_to_final, + ) + .is_err() + { 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, - ), - ); } - DaDataLightClient::Aggregate(_) => todo!(), - DaDataLightClient::Chunk(_) => todo!(), + DaDataLightClient::Aggregate(tx_ids) => { + let existing_tx_ids: HashSet<[u8; 32]> = + wtxid_data.keys().cloned().collect(); + let aggregate_tx_ids: HashSet<[u8; 32]> = tx_ids.iter().cloned().collect(); + + // If we have all the chunks, perform verification + if aggregate_tx_ids.is_subset(&existing_tx_ids) { + // Concatenate complete proof + let complete_proof = tx_ids + .iter() + .filter_map(|k| wtxid_data.get(k).cloned()) + .fold(vec![], |mut p, c| { + p.extend(c); + p + }); + + if process_complete_proof::( + complete_proof, + batch_proof_method_id, + last_l2_height, + &mut initial_to_final, + ) + .is_err() + { + continue; + } + + for tx_id in &aggregate_tx_ids { + wtxid_data.remove(tx_id); + } + } + } + DaDataLightClient::Chunk(chunk) => { + wtxid_data.insert(blob.hash(), chunk); + } } } } @@ -160,5 +183,38 @@ pub fn run_circuit( unchained_batch_proofs_info: unchained_outputs, last_l2_height, l2_genesis_state_root, + wtxid_data, }) } + +fn process_complete_proof( + proof: Vec, + batch_proof_method_id: [u32; 8], + last_l2_height: u64, + initial_to_final: &mut std::collections::BTreeMap<[u8; 32], ([u8; 32], u64)>, +) -> anyhow::Result<()> { + 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 = + match G::verify_and_extract_output(&journal, &batch_proof_method_id.into()) { + Ok(output) => output, + Err(_) => return Err(anyhow!("Failed to verify proof")), + }; + + // 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 { + return Err(anyhow!( + "Last L2 height is less than proof's last l2 height" + )); + } + + Ok(recursive_match_state_roots( + initial_to_final, + &BatchProofInfo::new( + batch_proof_output.initial_state_root, + batch_proof_output.final_state_root, + batch_proof_output.last_l2_height, + ), + )) +} From 43c911f388c38a6006801d0ab37446c1c93d5fbb Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 13:47:48 +0300 Subject: [PATCH 04/24] Store and pass wtxid_data --- crates/light-client-prover/src/circuit.rs | 4 ++-- crates/light-client-prover/src/da_block_handler.rs | 10 ++++++---- .../full-node/db/sov-db/src/schema/types.rs | 3 +++ 3 files changed, 11 insertions(+), 6 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 87e544822..72e41b56e 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -1,4 +1,4 @@ -use std::collections::{BTreeMap, HashSet}; +use std::collections::HashSet; use anyhow::anyhow; use borsh::BorshDeserialize; @@ -95,7 +95,7 @@ pub fn run_circuit( // https://github.com/chainwayxyz/citrea/issues/1401 let batch_proof_method_id = input.batch_proof_method_id; - let mut wtxid_data = BTreeMap::new(); + let mut wtxid_data = input.wtxid_data; // Parse the batch proof da data for blob in input.da_data { diff --git a/crates/light-client-prover/src/da_block_handler.rs b/crates/light-client-prover/src/da_block_handler.rs index 0f6cbf0a1..1269e5321 100644 --- a/crates/light-client-prover/src/da_block_handler.rs +++ b/crates/light-client-prover/src/da_block_handler.rs @@ -184,7 +184,7 @@ where let previous_l1_height = l1_height - 1; let mut light_client_proof_journal = None; let mut l2_genesis_state_root = None; - let l2_last_height = match self + let output_data = match self .ledger_db .get_light_client_proof_data_by_l1_height(previous_l1_height)? { @@ -193,7 +193,7 @@ where let output = data.light_client_proof_output; assumptions.push(proof); light_client_proof_journal = Some(borsh::to_vec(&output)?); - Some(output.last_l2_height) + Some((output.last_l2_height, output.wtxid_data)) } None => { let soft_confirmation = self @@ -224,11 +224,11 @@ where previous_l1_height ); } - Some(soft_confirmation.l2_height) + Some((soft_confirmation.l2_height, Default::default())) } }; - let l2_last_height = l2_last_height.ok_or(anyhow!( + let (l2_last_height, wtxid_data) = output_data.ok_or(anyhow!( "Could not determine the last L2 height for batch proof" ))?; let current_fork = fork_from_block_number(FORKS, l2_last_height); @@ -256,6 +256,7 @@ where light_client_proof_method_id: light_client_proof_code_commitment.clone().into(), previous_light_client_proof_journal: light_client_proof_journal, l2_genesis_state_root, + wtxid_data, }; let proof = self @@ -283,6 +284,7 @@ where unchained_batch_proofs_info: circuit_output.unchained_batch_proofs_info, last_l2_height: circuit_output.last_l2_height, l2_genesis_state_root: circuit_output.l2_genesis_state_root, + wtxid_data: circuit_output.wtxid_data, }; self.ledger_db.insert_light_client_proof_data_by_l1_height( diff --git a/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs b/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs index 390163557..b1e5265b2 100644 --- a/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs +++ b/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::fmt::Debug; use std::marker::PhantomData; use std::sync::Arc; @@ -97,6 +98,8 @@ pub struct StoredLightClientProofOutput { pub last_l2_height: u64, /// L2 genesis state root. pub l2_genesis_state_root: [u8; 32], + /// A map from tx hash to chunk data + pub wtxid_data: BTreeMap<[u8; 32], Vec>, } impl From for LightClientProofOutputRpcResponse { From 073e0925bd8a219bf0f68c744a61f8932f81d681 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 13:51:34 +0300 Subject: [PATCH 05/24] Use flatten --- crates/light-client-prover/src/circuit.rs | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 72e41b56e..d657dc829 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -127,10 +127,8 @@ pub fn run_circuit( let complete_proof = tx_ids .iter() .filter_map(|k| wtxid_data.get(k).cloned()) - .fold(vec![], |mut p, c| { - p.extend(c); - p - }); + .flatten() + .collect::>(); if process_complete_proof::( complete_proof, From 79586099bc4806de1b455157bc8b6b7456840634 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 13:57:02 +0300 Subject: [PATCH 06/24] Fix tests --- crates/light-client-prover/src/tests/mod.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/crates/light-client-prover/src/tests/mod.rs b/crates/light-client-prover/src/tests/mod.rs index 59a391505..1476e4a0d 100644 --- a/crates/light-client-prover/src/tests/mod.rs +++ b/crates/light-client-prover/src/tests/mod.rs @@ -28,6 +28,7 @@ fn test_light_client_circuit_valid_da_valid_data() { l2_genesis_state_root: Some([1u8; 32]), batch_proof_method_id, batch_prover_da_pub_key: [9; 32].to_vec(), + wtxid_data: Default::default(), }; let serialized_input = borsh::to_vec(&input).expect("should serialize"); @@ -59,6 +60,7 @@ fn test_light_client_circuit_valid_da_valid_data() { l2_genesis_state_root: None, batch_proof_method_id, batch_prover_da_pub_key: [9; 32].to_vec(), + wtxid_data: Default::default(), }; let serialized_input_2 = borsh::to_vec(&input_2).expect("should serialize"); @@ -93,6 +95,7 @@ fn test_wrong_order_da_blocks_should_still_work() { l2_genesis_state_root: Some([1u8; 32]), batch_proof_method_id: light_client_proof_method_id, batch_prover_da_pub_key: [9; 32].to_vec(), + wtxid_data: Default::default(), }; let serialized_input = borsh::to_vec(&input).expect("should serialize"); @@ -127,6 +130,7 @@ fn create_unchainable_outputs_then_chain_them_on_next_block() { l2_genesis_state_root: Some([1u8; 32]), batch_proof_method_id: light_client_proof_method_id, batch_prover_da_pub_key: [9; 32].to_vec(), + wtxid_data: Default::default(), }; let serialized_input = borsh::to_vec(&input).expect("should serialize"); @@ -198,6 +202,7 @@ fn test_header_chain_proof_height_and_hash() { l2_genesis_state_root: Some([1u8; 32]), batch_proof_method_id: light_client_proof_method_id, batch_prover_da_pub_key: [9; 32].to_vec(), + wtxid_data: Default::default(), }; let serialized_input = borsh::to_vec(&input).expect("should serialize"); @@ -229,6 +234,7 @@ fn test_header_chain_proof_height_and_hash() { l2_genesis_state_root: None, batch_proof_method_id: light_client_proof_method_id, batch_prover_da_pub_key: [9; 32].to_vec(), + wtxid_data: Default::default(), }; let serialized_input_2 = borsh::to_vec(&input_2).expect("should serialize"); @@ -264,6 +270,7 @@ fn test_unverifiable_batch_proofs() { l2_genesis_state_root: Some([1u8; 32]), batch_proof_method_id, batch_prover_da_pub_key: [9; 32].to_vec(), + wtxid_data: Default::default(), }; let serialized_input = borsh::to_vec(&input).expect("should serialize"); @@ -301,6 +308,7 @@ fn test_unverifiable_prev_light_client_proof() { l2_genesis_state_root: Some([1u8; 32]), batch_proof_method_id, batch_prover_da_pub_key: [9; 32].to_vec(), + wtxid_data: Default::default(), }; let serialized_input = borsh::to_vec(&input).expect("should serialize"); @@ -330,6 +338,7 @@ fn test_unverifiable_prev_light_client_proof() { l2_genesis_state_root: None, batch_proof_method_id: light_client_proof_method_id, batch_prover_da_pub_key: [9; 32].to_vec(), + wtxid_data: Default::default(), }; guest.input = borsh::to_vec(&input_2).unwrap(); From fff8c1ef2d15c96ac0dbd33db64d01d3e3df4950 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 18:58:46 +0300 Subject: [PATCH 07/24] Use BTreeSet --- crates/light-client-prover/src/circuit.rs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index d657dc829..87035eb7c 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -1,4 +1,4 @@ -use std::collections::HashSet; +use std::collections::BTreeSet; use anyhow::anyhow; use borsh::BorshDeserialize; @@ -117,9 +117,9 @@ pub fn run_circuit( } } DaDataLightClient::Aggregate(tx_ids) => { - let existing_tx_ids: HashSet<[u8; 32]> = + let existing_tx_ids: BTreeSet<[u8; 32]> = wtxid_data.keys().cloned().collect(); - let aggregate_tx_ids: HashSet<[u8; 32]> = tx_ids.iter().cloned().collect(); + let aggregate_tx_ids: BTreeSet<[u8; 32]> = tx_ids.iter().cloned().collect(); // If we have all the chunks, perform verification if aggregate_tx_ids.is_subset(&existing_tx_ids) { From a0dcb68b59e69808973f09946a78eb5f2b0d7026 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 19:00:52 +0300 Subject: [PATCH 08/24] Remove input field --- crates/light-client-prover/src/circuit.rs | 7 +++---- .../rollup-interface/src/state_machine/zk/mod.rs | 2 -- 2 files changed, 3 insertions(+), 6 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 87035eb7c..842374629 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -60,19 +60,20 @@ pub fn run_circuit( // 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, l2_genesis_state_root) = + let (mut last_state_root, mut last_l2_height, l2_genesis_state_root, wtxid_data) = previous_light_client_proof_output.as_ref().map_or_else( || { let r = input .l2_genesis_state_root .expect("if no preious proof, genesis must exist"); - (r, 0, r) + (r, 0, r, Default::default()) }, |prev_journal| { ( prev_journal.state_root, prev_journal.last_l2_height, prev_journal.l2_genesis_state_root, + prev_journal.wtxid_data.clone(), ) }, ); @@ -95,8 +96,6 @@ pub fn run_circuit( // https://github.com/chainwayxyz/citrea/issues/1401 let batch_proof_method_id = input.batch_proof_method_id; - let mut wtxid_data = input.wtxid_data; - // Parse the batch proof da data for blob in input.da_data { if blob.sender().as_ref() == input.batch_prover_da_pub_key { diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs index 799adfdc9..9508bd1fc 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs @@ -263,6 +263,4 @@ pub struct LightClientCircuitInput { pub previous_light_client_proof_journal: Option>, /// L2 Genesis state root pub l2_genesis_state_root: Option<[u8; 32]>, - /// A map from tx hash to chunk data - pub wtxid_data: BTreeMap<[u8; 32], Vec>, } From d3feecf4b05ca5a2fd29c43285d051d6d657bbf7 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 19:04:02 +0300 Subject: [PATCH 09/24] Remove field from storage --- crates/light-client-prover/src/da_block_handler.rs | 2 -- crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs | 3 --- 2 files changed, 5 deletions(-) diff --git a/crates/light-client-prover/src/da_block_handler.rs b/crates/light-client-prover/src/da_block_handler.rs index 1269e5321..6e118d495 100644 --- a/crates/light-client-prover/src/da_block_handler.rs +++ b/crates/light-client-prover/src/da_block_handler.rs @@ -256,7 +256,6 @@ where light_client_proof_method_id: light_client_proof_code_commitment.clone().into(), previous_light_client_proof_journal: light_client_proof_journal, l2_genesis_state_root, - wtxid_data, }; let proof = self @@ -284,7 +283,6 @@ where unchained_batch_proofs_info: circuit_output.unchained_batch_proofs_info, last_l2_height: circuit_output.last_l2_height, l2_genesis_state_root: circuit_output.l2_genesis_state_root, - wtxid_data: circuit_output.wtxid_data, }; self.ledger_db.insert_light_client_proof_data_by_l1_height( diff --git a/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs b/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs index b1e5265b2..390163557 100644 --- a/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs +++ b/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs @@ -1,4 +1,3 @@ -use std::collections::BTreeMap; use std::fmt::Debug; use std::marker::PhantomData; use std::sync::Arc; @@ -98,8 +97,6 @@ pub struct StoredLightClientProofOutput { pub last_l2_height: u64, /// L2 genesis state root. pub l2_genesis_state_root: [u8; 32], - /// A map from tx hash to chunk data - pub wtxid_data: BTreeMap<[u8; 32], Vec>, } impl From for LightClientProofOutputRpcResponse { From 0f4aac4ac62cd09d6569303280961e7b6f1ad586 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 19:04:22 +0300 Subject: [PATCH 10/24] Revert change in da_block_handler --- crates/light-client-prover/src/da_block_handler.rs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/crates/light-client-prover/src/da_block_handler.rs b/crates/light-client-prover/src/da_block_handler.rs index 6e118d495..f5db9c6b4 100644 --- a/crates/light-client-prover/src/da_block_handler.rs +++ b/crates/light-client-prover/src/da_block_handler.rs @@ -184,7 +184,7 @@ where let previous_l1_height = l1_height - 1; let mut light_client_proof_journal = None; let mut l2_genesis_state_root = None; - let output_data = match self + let last_l2_height = match self .ledger_db .get_light_client_proof_data_by_l1_height(previous_l1_height)? { @@ -193,7 +193,7 @@ where let output = data.light_client_proof_output; assumptions.push(proof); light_client_proof_journal = Some(borsh::to_vec(&output)?); - Some((output.last_l2_height, output.wtxid_data)) + Some(output.last_l2_height) } None => { let soft_confirmation = self @@ -224,11 +224,11 @@ where previous_l1_height ); } - Some((soft_confirmation.l2_height, Default::default())) + Some(soft_confirmation.l2_height) } }; - let (l2_last_height, wtxid_data) = output_data.ok_or(anyhow!( + let l2_last_height = last_l2_height.ok_or(anyhow!( "Could not determine the last L2 height for batch proof" ))?; let current_fork = fork_from_block_number(FORKS, l2_last_height); From 0abc89e4bd8cb18f1cbcce3139d946eaee6421bf Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Tue, 17 Dec 2024 19:04:37 +0300 Subject: [PATCH 11/24] Make wtxid_data mutable --- crates/light-client-prover/src/circuit.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 842374629..6ce097889 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -60,7 +60,7 @@ pub fn run_circuit( // 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, l2_genesis_state_root, wtxid_data) = + let (mut last_state_root, mut last_l2_height, l2_genesis_state_root, mut wtxid_data) = previous_light_client_proof_output.as_ref().map_or_else( || { let r = input From c7a562a370153fc4e00711358b4c3c70199b1eee Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Wed, 18 Dec 2024 13:16:11 +0300 Subject: [PATCH 12/24] Move import --- crates/bitcoin-da/src/service.rs | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/crates/bitcoin-da/src/service.rs b/crates/bitcoin-da/src/service.rs index 79763dc6e..984cdf1a9 100644 --- a/crates/bitcoin-da/src/service.rs +++ b/crates/bitcoin-da/src/service.rs @@ -18,7 +18,7 @@ use bitcoin::hashes::Hash; use bitcoin::secp256k1::SecretKey; use bitcoin::{Amount, BlockHash, CompactTarget, Transaction, Txid, Wtxid}; use bitcoincore_rpc::json::{SignRawTransactionInput, TestMempoolAcceptResult}; -use bitcoincore_rpc::{Auth, Client, Error, RpcApi, RpcError}; +use bitcoincore_rpc::{Auth, Client, Error as BitcoinError, Error, RpcApi, RpcError}; use borsh::BorshDeserialize; use citrea_primitives::compression::{compress_blob, decompress_blob}; use citrea_primitives::MAX_TXBODY_SIZE; @@ -790,12 +790,9 @@ impl DaService for BitcoinService { self.client .get_raw_transaction(&chunk_id, None) .await - .map_err(|e| { - use bitcoincore_rpc::Error; - match e { - Error::Io(_) => backoff::Error::transient(e), - _ => backoff::Error::permanent(e), - } + .map_err(|e| match e { + BitcoinError::Io(_) => backoff::Error::transient(e), + _ => backoff::Error::permanent(e), }) }) .await; From 3f49ea099e768f4db676bc9ff93d47ff6ff136ad Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 11:08:16 +0300 Subject: [PATCH 13/24] Add wtxids to aggregate --- .../helpers/builders/light_client_proof_namespace.rs | 11 ++++++++--- crates/bitcoin-da/src/service.rs | 4 ++-- crates/light-client-prover/src/circuit.rs | 11 ++++++----- .../rollup-interface/src/state_machine/da.rs | 2 +- 4 files changed, 17 insertions(+), 11 deletions(-) diff --git a/crates/bitcoin-da/src/helpers/builders/light_client_proof_namespace.rs b/crates/bitcoin-da/src/helpers/builders/light_client_proof_namespace.rs index 1abd9199e..98d5903e8 100644 --- a/crates/bitcoin-da/src/helpers/builders/light_client_proof_namespace.rs +++ b/crates/bitcoin-da/src/helpers/builders/light_client_proof_namespace.rs @@ -437,12 +437,17 @@ pub fn create_inscription_type_1( } } - let reveal_tx_ids: Vec<_> = reveal_chunks + let (reveal_tx_ids, reveal_wtx_ids): (Vec<_>, Vec<_>) = reveal_chunks .iter() - .map(|tx| tx.compute_txid().to_byte_array()) + .map(|tx| { + ( + tx.compute_txid().to_byte_array(), + tx.compute_wtxid().to_byte_array(), + ) + }) .collect(); - let aggregate = DaDataLightClient::Aggregate(reveal_tx_ids); + let aggregate = DaDataLightClient::Aggregate(reveal_tx_ids, reveal_wtx_ids); // To sign the list of tx ids we assume they form a contigious list of bytes let reveal_body: Vec = diff --git a/crates/bitcoin-da/src/service.rs b/crates/bitcoin-da/src/service.rs index 984cdf1a9..596b97dbd 100644 --- a/crates/bitcoin-da/src/service.rs +++ b/crates/bitcoin-da/src/service.rs @@ -774,7 +774,7 @@ impl DaService for BitcoinService { let mut body = Vec::new(); let data = DaDataLightClient::try_from_slice(&aggregate.body) .map_err(|e| anyhow!("{}: Failed to parse aggregate: {e}", tx_id))?; - let DaDataLightClient::Aggregate(chunk_ids) = data else { + let DaDataLightClient::Aggregate(chunk_ids, _wtx_ids) = data else { error!("{}: Aggregate: unexpected kind", tx_id); continue; }; @@ -816,7 +816,7 @@ impl DaService for BitcoinService { ParsedLightClientTransaction::Chunk(part) => { let data = DaDataLightClient::try_from_slice(&part.body) .map_err(|e| anyhow!("{}: Failed to parse chunk: {e}", tx_id))?; - let DaDataLightClient::Chunk(chunk) = data else { + let DaDataLightClient::Chunk(_wtx_id, chunk) = data else { bail!("{}: Chunk: unexpected kind", tx_id); }; body.extend(chunk); diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 6ce097889..e1ae1fbc4 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -115,15 +115,16 @@ pub fn run_circuit( continue; } } - DaDataLightClient::Aggregate(tx_ids) => { + DaDataLightClient::Aggregate(_, wtx_ids) => { let existing_tx_ids: BTreeSet<[u8; 32]> = wtxid_data.keys().cloned().collect(); - let aggregate_tx_ids: BTreeSet<[u8; 32]> = tx_ids.iter().cloned().collect(); + let aggregate_tx_ids: BTreeSet<[u8; 32]> = + wtx_ids.iter().cloned().collect(); // If we have all the chunks, perform verification if aggregate_tx_ids.is_subset(&existing_tx_ids) { // Concatenate complete proof - let complete_proof = tx_ids + let complete_proof = wtx_ids .iter() .filter_map(|k| wtxid_data.get(k).cloned()) .flatten() @@ -145,8 +146,8 @@ pub fn run_circuit( } } } - DaDataLightClient::Chunk(chunk) => { - wtxid_data.insert(blob.hash(), chunk); + DaDataLightClient::Chunk(wtx_id, chunk) => { + wtxid_data.insert(wtx_id, chunk); } } } diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/da.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/da.rs index d6869071e..67c3d4e14 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/da.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/da.rs @@ -70,7 +70,7 @@ pub enum DaDataLightClient { /// A zk proof and state diff Complete(Proof), /// A list of tx ids - Aggregate(Vec<[u8; 32]>), + Aggregate(Vec<[u8; 32]>, Vec<[u8; 32]>), /// A chunk of an aggregate Chunk(Vec), } From 8e980692dc1cd5daa24b40a0cecc0d0ce2148d6c Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 11:09:38 +0300 Subject: [PATCH 14/24] Pass wtxid to blob init --- crates/bitcoin-da/src/service.rs | 6 ++-- crates/bitcoin-da/src/spec/blob.rs | 29 ++++++++++++------- crates/light-client-prover/src/circuit.rs | 7 +++-- .../src/tests/test_utils.rs | 2 +- .../adapters/mock-da/src/db_connector.rs | 4 +-- .../adapters/mock-da/src/service.rs | 1 + .../adapters/mock-da/src/types/mod.rs | 11 ++++++- .../adapters/mock-da/src/verifier.rs | 4 +++ .../rollup-interface/src/state_machine/da.rs | 3 ++ 9 files changed, 47 insertions(+), 20 deletions(-) diff --git a/crates/bitcoin-da/src/service.rs b/crates/bitcoin-da/src/service.rs index 596b97dbd..a64bca364 100644 --- a/crates/bitcoin-da/src/service.rs +++ b/crates/bitcoin-da/src/service.rs @@ -955,6 +955,7 @@ impl DaService for BitcoinService { seq_comm.body, seq_comm.public_key, hash, + None, ); relevant_txs.push(relevant_tx); @@ -970,7 +971,7 @@ impl DaService for BitcoinService { if let Some(hash) = complete.get_sig_verified_hash() { let blob = decompress_blob(&complete.body); let relevant_tx = - BlobWithSender::new(blob, complete.public_key, hash); + BlobWithSender::new(blob, complete.public_key, hash, None); relevant_txs.push(relevant_tx); } @@ -981,6 +982,7 @@ impl DaService for BitcoinService { aggregate.body, aggregate.public_key, hash, + None, ); relevant_txs.push(relevant_tx); @@ -1125,7 +1127,7 @@ pub fn get_relevant_blobs_from_txs( ParsedBatchProofTransaction::SequencerCommitment(seq_comm) => { if let Some(hash) = seq_comm.get_sig_verified_hash() { let relevant_tx = - BlobWithSender::new(seq_comm.body, seq_comm.public_key, hash); + BlobWithSender::new(seq_comm.body, seq_comm.public_key, hash, None); relevant_txs.push(relevant_tx); } diff --git a/crates/bitcoin-da/src/spec/blob.rs b/crates/bitcoin-da/src/spec/blob.rs index 3f501ddff..4e75ed0c3 100644 --- a/crates/bitcoin-da/src/spec/blob.rs +++ b/crates/bitcoin-da/src/spec/blob.rs @@ -13,8 +13,20 @@ pub struct BlobBuf { pub offset: usize, } +// BlobWithSender is a wrapper around BlobBuf to implement BlobReaderTrait +#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] +pub struct BlobWithSender { + pub hash: [u8; 32], + + pub sender: AddressWrapper, + + pub blob: CountedBufReader, + + pub wtxid: Option<[u8; 32]>, +} + impl BlobWithSender { - pub fn new(blob: Vec, sender: Vec, hash: [u8; 32]) -> Self { + pub fn new(blob: Vec, sender: Vec, hash: [u8; 32], wtxid: Option<[u8; 32]>) -> Self { Self { blob: CountedBufReader::new(BlobBuf { data: blob, @@ -22,6 +34,7 @@ impl BlobWithSender { }), sender: AddressWrapper(sender), hash, + wtxid, } } } @@ -40,16 +53,6 @@ impl Buf for BlobBuf { } } -// BlobWithSender is a wrapper around BlobBuf to implement BlobReaderTrait -#[derive(Clone, Debug, PartialEq, BorshSerialize, BorshDeserialize, Serialize, Deserialize)] -pub struct BlobWithSender { - pub hash: [u8; 32], - - pub sender: AddressWrapper, - - pub blob: CountedBufReader, -} - impl BlobReaderTrait for BlobWithSender { type Address = AddressWrapper; @@ -61,6 +64,10 @@ impl BlobReaderTrait for BlobWithSender { self.hash } + fn wtxid(&self) -> Option<[u8; 32]> { + self.wtxid + } + fn verified_data(&self) -> &[u8] { self.blob.accumulator() } diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index e1ae1fbc4..8dc73907d 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -115,7 +115,7 @@ pub fn run_circuit( continue; } } - DaDataLightClient::Aggregate(_, wtx_ids) => { + DaDataLightClient::Aggregate(_tx_ids, wtx_ids) => { let existing_tx_ids: BTreeSet<[u8; 32]> = wtxid_data.keys().cloned().collect(); let aggregate_tx_ids: BTreeSet<[u8; 32]> = @@ -146,8 +146,9 @@ pub fn run_circuit( } } } - DaDataLightClient::Chunk(wtx_id, chunk) => { - wtxid_data.insert(wtx_id, chunk); + DaDataLightClient::Chunk(chunk) => { + wtxid_data + .insert(blob.wtxid().expect("Wtxid should be set for chunks"), chunk); } } } diff --git a/crates/light-client-prover/src/tests/test_utils.rs b/crates/light-client-prover/src/tests/test_utils.rs index 64a5767f9..4d37ebbe3 100644 --- a/crates/light-client-prover/src/tests/test_utils.rs +++ b/crates/light-client-prover/src/tests/test_utils.rs @@ -45,7 +45,7 @@ pub(crate) fn create_mock_blob( let da_data = DaDataLightClient::Complete(mock_serialized); let da_data_ser = borsh::to_vec(&da_data).expect("should serialize"); - let mut blob = MockBlob::new(da_data_ser, MockAddress::new([9u8; 32]), [0u8; 32]); + let mut blob = MockBlob::new(da_data_ser, MockAddress::new([9u8; 32]), [0u8; 32], None); blob.full_data(); blob diff --git a/crates/sovereign-sdk/adapters/mock-da/src/db_connector.rs b/crates/sovereign-sdk/adapters/mock-da/src/db_connector.rs index c6703d4ce..4df38144f 100644 --- a/crates/sovereign-sdk/adapters/mock-da/src/db_connector.rs +++ b/crates/sovereign-sdk/adapters/mock-da/src/db_connector.rs @@ -150,8 +150,8 @@ mod tests { header: MockBlockHeader::from_height(at_height), is_valid: true, blobs: vec![ - MockBlob::new(vec![2; 44], MockAddress::new([1; 32]), [2; 32]), - MockBlob::new(vec![3; 12], MockAddress::new([2; 32]), [5; 32]), + MockBlob::new(vec![2; 44], MockAddress::new([1; 32]), [2; 32], None), + MockBlob::new(vec![3; 12], MockAddress::new([2; 32]), [5; 32], None), ], } } diff --git a/crates/sovereign-sdk/adapters/mock-da/src/service.rs b/crates/sovereign-sdk/adapters/mock-da/src/service.rs index 9080cfdef..4d40b9213 100644 --- a/crates/sovereign-sdk/adapters/mock-da/src/service.rs +++ b/crates/sovereign-sdk/adapters/mock-da/src/service.rs @@ -223,6 +223,7 @@ impl MockDaService { zkp_proof, self.sequencer_da_address, data_hash, + None, ); let header = MockBlockHeader { prev_hash: previous_block_hash, diff --git a/crates/sovereign-sdk/adapters/mock-da/src/types/mod.rs b/crates/sovereign-sdk/adapters/mock-da/src/types/mod.rs index c6e86ea0f..ce9d7d85b 100644 --- a/crates/sovereign-sdk/adapters/mock-da/src/types/mod.rs +++ b/crates/sovereign-sdk/adapters/mock-da/src/types/mod.rs @@ -177,6 +177,7 @@ pub struct MockDaVerifier {} pub struct MockBlob { pub(crate) address: MockAddress, pub(crate) hash: [u8; 32], + pub(crate) wtxid: Option<[u8; 32]>, /// Actual data from the blob. Public for testing purposes. pub data: CountedBufReader, // Data for the aggregated ZK proof. @@ -185,12 +186,18 @@ pub struct MockBlob { impl MockBlob { /// Creates a new mock blob with the given data, claiming to have been published by the provided address. - pub fn new(data: Vec, address: MockAddress, hash: [u8; 32]) -> Self { + pub fn new( + data: Vec, + address: MockAddress, + hash: [u8; 32], + wtxid: Option<[u8; 32]>, + ) -> Self { Self { address, data: CountedBufReader::new(Bytes::from(data)), zk_proofs_data: Default::default(), hash, + wtxid, } } @@ -200,12 +207,14 @@ impl MockBlob { zk_proofs_data: Vec, address: MockAddress, hash: [u8; 32], + wtxid: Option<[u8; 32]>, ) -> Self { Self { address, hash, data: CountedBufReader::new(Bytes::from(data)), zk_proofs_data, + wtxid, } } } diff --git a/crates/sovereign-sdk/adapters/mock-da/src/verifier.rs b/crates/sovereign-sdk/adapters/mock-da/src/verifier.rs index fb4fddb93..077a4dbf1 100644 --- a/crates/sovereign-sdk/adapters/mock-da/src/verifier.rs +++ b/crates/sovereign-sdk/adapters/mock-da/src/verifier.rs @@ -18,6 +18,10 @@ impl BlobReaderTrait for MockBlob { self.hash } + fn wtxid(&self) -> Option<[u8; 32]> { + self.wtxid + } + fn verified_data(&self) -> &[u8] { self.data.accumulator() } diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/da.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/da.rs index 67c3d4e14..50f9123c1 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/da.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/da.rs @@ -249,6 +249,9 @@ pub trait BlobReaderTrait: /// Returns the hash of the blob as it appears on the DA layer fn hash(&self) -> [u8; 32]; + /// Returns the witness transaction ID of the blob as it appears on the DA layer + fn wtxid(&self) -> Option<[u8; 32]>; + /// Returns a slice containing all the data accessible to the rollup at this point in time. /// When running in native mode, the rollup can extend this slice by calling `advance`. In zk-mode, /// the rollup is limited to only the verified data. From 91c240047535a2db574ba9dfa1aa9f95864b0e0a Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 11:10:00 +0300 Subject: [PATCH 15/24] Verify chunk blob --- crates/bitcoin-da/src/helpers/parsers.rs | 12 ++++++++ crates/bitcoin-da/src/verifier.rs | 39 ++++++++++++++++++++++-- 2 files changed, 49 insertions(+), 2 deletions(-) diff --git a/crates/bitcoin-da/src/helpers/parsers.rs b/crates/bitcoin-da/src/helpers/parsers.rs index 1dd4d756a..3221b00b6 100644 --- a/crates/bitcoin-da/src/helpers/parsers.rs +++ b/crates/bitcoin-da/src/helpers/parsers.rs @@ -118,6 +118,18 @@ impl VerifyParsed for ParsedSequencerCommitment { } } +impl VerifyParsed for ParsedChunk { + fn public_key(&self) -> &[u8] { + &[0] + } + fn signature(&self) -> &[u8] { + &[0] + } + fn body(&self) -> &[u8] { + &self.body + } +} + #[derive(Error, Debug, Clone, PartialEq)] pub enum ParserError { #[error("Invalid header length")] diff --git a/crates/bitcoin-da/src/verifier.rs b/crates/bitcoin-da/src/verifier.rs index afbc1015e..d4ec7345d 100644 --- a/crates/bitcoin-da/src/verifier.rs +++ b/crates/bitcoin-da/src/verifier.rs @@ -143,8 +143,14 @@ impl DaVerifier for BitcoinVerifier { } } } - ParsedLightClientTransaction::Chunk(_chunk) => { - // ignore + ParsedLightClientTransaction::Chunk(chunk) => { + if let Some(blob_content) = + verified_blob_chunk(&chunk, &mut blobs_iter, wtxid)? + { + if blob_content != chunk.body { + return Err(ValidationError::BlobContentWasModified); + } + } } } } @@ -307,6 +313,35 @@ impl DaVerifier for BitcoinVerifier { } } +fn verified_blob_chunk<'a, T, I>( + tx: &T, + blobs_iter: &mut I, + wtxid: &[u8; 32], +) -> Result, ValidationError> +where + T: VerifyParsed, + I: Iterator, +{ + if let Some(blob_hash) = tx.get_sig_verified_hash() { + let blob = blobs_iter.next(); + + let Some(blob) = blob else { + return Err(ValidationError::ValidBlobNotFoundInBlobs); + }; + + if blob.hash != blob_hash { + return Err(ValidationError::BlobWasTamperedWith); + } + + if blob.wtxid != Some(*wtxid) { + return Err(ValidationError::BlobWasTamperedWith); + } + + return Ok(Some(blob.verified_data())); + } + return Ok(None); +} + // Get associated blob content only if signatures, hashes and public keys match fn verified_blob_content<'a, T, I>( tx: &T, From 42b203ab38c6386db8f5f658c776b17b642e30e5 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 11:10:22 +0300 Subject: [PATCH 16/24] Inject wtxid into blob --- crates/bitcoin-da/src/service.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/crates/bitcoin-da/src/service.rs b/crates/bitcoin-da/src/service.rs index a64bca364..c8f36d014 100644 --- a/crates/bitcoin-da/src/service.rs +++ b/crates/bitcoin-da/src/service.rs @@ -816,7 +816,7 @@ impl DaService for BitcoinService { ParsedLightClientTransaction::Chunk(part) => { let data = DaDataLightClient::try_from_slice(&part.body) .map_err(|e| anyhow!("{}: Failed to parse chunk: {e}", tx_id))?; - let DaDataLightClient::Chunk(_wtx_id, chunk) = data else { + let DaDataLightClient::Chunk(chunk) = data else { bail!("{}: Chunk: unexpected kind", tx_id); }; body.extend(chunk); @@ -945,6 +945,7 @@ impl DaService for BitcoinService { let mut relevant_txs = vec![]; for tx in &completeness_proof { + let wtxid = tx.compute_wtxid(); match namespace { DaNamespace::ToBatchProver => { if let Ok(tx) = parse_batch_proof_transaction(tx) { @@ -988,8 +989,11 @@ impl DaService for BitcoinService { relevant_txs.push(relevant_tx); } } - ParsedLightClientTransaction::Chunk(_) => { - // ignore + ParsedLightClientTransaction::Chunk(chunk) => { + let mut relevant_tx = + BlobWithSender::new(chunk.body, vec![0], [0; 32], None); + relevant_tx.wtxid = Some(wtxid.to_byte_array()); + relevant_txs.push(relevant_tx); } } } From e522194290c9302dfa01db2bc467f721f8d01a9f Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 11:15:04 +0300 Subject: [PATCH 17/24] fix test compilation --- crates/bitcoin-da/tests/test_utils.rs | 2 +- crates/bitcoin-da/tests/verifier.rs | 12 +++++++----- crates/light-client-prover/src/tests/mod.rs | 9 --------- 3 files changed, 8 insertions(+), 15 deletions(-) diff --git a/crates/bitcoin-da/tests/test_utils.rs b/crates/bitcoin-da/tests/test_utils.rs index 186d103de..d6776b804 100644 --- a/crates/bitcoin-da/tests/test_utils.rs +++ b/crates/bitcoin-da/tests/test_utils.rs @@ -414,7 +414,7 @@ pub fn get_blob_with_sender(tx: &Transaction, ty: MockData) -> anyhow::Result Date: Fri, 20 Dec 2024 11:51:59 +0300 Subject: [PATCH 18/24] Fix problem with processing proof --- crates/light-client-prover/src/circuit.rs | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 8dc73907d..35d33b8cf 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -208,12 +208,14 @@ fn process_complete_proof( )); } - Ok(recursive_match_state_roots( + recursive_match_state_roots( initial_to_final, &BatchProofInfo::new( batch_proof_output.initial_state_root, batch_proof_output.final_state_root, batch_proof_output.last_l2_height, ), - )) + ); + + Ok(()) } From 9c1e981d9a5260c349e9c106e9bb4dd85b70c442 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 12:00:24 +0300 Subject: [PATCH 19/24] Oh my god, clippy! --- crates/bitcoin-da/src/verifier.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/bitcoin-da/src/verifier.rs b/crates/bitcoin-da/src/verifier.rs index d4ec7345d..f02d7902b 100644 --- a/crates/bitcoin-da/src/verifier.rs +++ b/crates/bitcoin-da/src/verifier.rs @@ -339,7 +339,7 @@ where return Ok(Some(blob.verified_data())); } - return Ok(None); + Ok(None) } // Get associated blob content only if signatures, hashes and public keys match From f0b2c4a70abdbc99c2dcaf8b91faca24598980ed Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 13:26:24 +0300 Subject: [PATCH 20/24] Get hash unverified --- crates/bitcoin-da/src/helpers/parsers.rs | 5 +++++ crates/bitcoin-da/src/verifier.rs | 2 +- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/crates/bitcoin-da/src/helpers/parsers.rs b/crates/bitcoin-da/src/helpers/parsers.rs index 3221b00b6..1ddf5eaff 100644 --- a/crates/bitcoin-da/src/helpers/parsers.rs +++ b/crates/bitcoin-da/src/helpers/parsers.rs @@ -60,6 +60,11 @@ pub trait VerifyParsed { fn signature(&self) -> &[u8]; fn body(&self) -> &[u8]; + /// Returns the hash of the body + fn get_unverified_hash(&self) -> Option<[u8; 32]> { + Some(calculate_sha256(self.body())) + } + /// Verifies the signature of the inscription and returns the hash of the body fn get_sig_verified_hash(&self) -> Option<[u8; 32]> { let public_key = secp256k1::PublicKey::from_slice(self.public_key()); diff --git a/crates/bitcoin-da/src/verifier.rs b/crates/bitcoin-da/src/verifier.rs index f02d7902b..e5a992eca 100644 --- a/crates/bitcoin-da/src/verifier.rs +++ b/crates/bitcoin-da/src/verifier.rs @@ -322,7 +322,7 @@ where T: VerifyParsed, I: Iterator, { - if let Some(blob_hash) = tx.get_sig_verified_hash() { + if let Some(blob_hash) = tx.get_unverified_hash() { let blob = blobs_iter.next(); let Some(blob) = blob else { From 6a073607988a707c687322e2f99a62fc50db9db4 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 13:27:34 +0300 Subject: [PATCH 21/24] Rename wtxid_data to unprocessed_chunks --- crates/light-client-prover/src/circuit.rs | 12 ++++++------ .../rollup-interface/src/state_machine/zk/mod.rs | 2 +- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 35d33b8cf..2b8f98654 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -60,7 +60,7 @@ pub fn run_circuit( // 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, l2_genesis_state_root, mut wtxid_data) = + let (mut last_state_root, mut last_l2_height, l2_genesis_state_root, mut unprocessed_chunks) = previous_light_client_proof_output.as_ref().map_or_else( || { let r = input @@ -73,7 +73,7 @@ pub fn run_circuit( prev_journal.state_root, prev_journal.last_l2_height, prev_journal.l2_genesis_state_root, - prev_journal.wtxid_data.clone(), + prev_journal.unprocessed_chunks.clone(), ) }, ); @@ -117,7 +117,7 @@ pub fn run_circuit( } DaDataLightClient::Aggregate(_tx_ids, wtx_ids) => { let existing_tx_ids: BTreeSet<[u8; 32]> = - wtxid_data.keys().cloned().collect(); + unprocessed_chunks.keys().cloned().collect(); let aggregate_tx_ids: BTreeSet<[u8; 32]> = wtx_ids.iter().cloned().collect(); @@ -126,7 +126,7 @@ pub fn run_circuit( // Concatenate complete proof let complete_proof = wtx_ids .iter() - .filter_map(|k| wtxid_data.get(k).cloned()) + .filter_map(|k| unprocessed_chunks.get(k).cloned()) .flatten() .collect::>(); @@ -142,12 +142,12 @@ pub fn run_circuit( } for tx_id in &aggregate_tx_ids { - wtxid_data.remove(tx_id); + unprocessed_chunks.remove(tx_id); } } } DaDataLightClient::Chunk(chunk) => { - wtxid_data + unprocessed_chunks .insert(blob.wtxid().expect("Wtxid should be set for chunks"), chunk); } } diff --git a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs index 9508bd1fc..b8a52cfc2 100644 --- a/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs +++ b/crates/sovereign-sdk/rollup-interface/src/state_machine/zk/mod.rs @@ -237,7 +237,7 @@ pub struct LightClientCircuitOutput { /// Genesis state root of Citrea pub l2_genesis_state_root: [u8; 32], /// A map from tx hash to chunk data - pub wtxid_data: BTreeMap<[u8; 32], Vec>, + pub unprocessed_chunks: BTreeMap<[u8; 32], Vec>, } /// The input of light client proof From 68d1d145088dcac0b43a3ca84e2291db3838ff48 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 13:29:21 +0300 Subject: [PATCH 22/24] Distinguish between txid and wtxid --- crates/light-client-prover/src/circuit.rs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 2b8f98654..3f07bd2eb 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -116,13 +116,13 @@ pub fn run_circuit( } } DaDataLightClient::Aggregate(_tx_ids, wtx_ids) => { - let existing_tx_ids: BTreeSet<[u8; 32]> = + let existing_wtx_ids: BTreeSet<[u8; 32]> = unprocessed_chunks.keys().cloned().collect(); - let aggregate_tx_ids: BTreeSet<[u8; 32]> = + let aggregate_wtx_ids: BTreeSet<[u8; 32]> = wtx_ids.iter().cloned().collect(); // If we have all the chunks, perform verification - if aggregate_tx_ids.is_subset(&existing_tx_ids) { + if aggregate_wtx_ids.is_subset(&existing_wtx_ids) { // Concatenate complete proof let complete_proof = wtx_ids .iter() @@ -141,8 +141,8 @@ pub fn run_circuit( continue; } - for tx_id in &aggregate_tx_ids { - unprocessed_chunks.remove(tx_id); + for wtx_id in &aggregate_wtx_ids { + unprocessed_chunks.remove(wtx_id); } } } @@ -182,7 +182,7 @@ pub fn run_circuit( unchained_batch_proofs_info: unchained_outputs, last_l2_height, l2_genesis_state_root, - wtxid_data, + unprocessed_chunks, }) } From 065212f36f739a14d5ad1c704a933ca937682f32 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Fri, 20 Dec 2024 13:40:52 +0300 Subject: [PATCH 23/24] Check call result --- crates/light-client-prover/src/circuit.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index 3f07bd2eb..d553007c2 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -104,14 +104,14 @@ pub fn run_circuit( if let Ok(data) = data { match data { DaDataLightClient::Complete(proof) => { - if process_complete_proof::( + let result = process_complete_proof::( proof, batch_proof_method_id, last_l2_height, &mut initial_to_final, - ) - .is_err() - { + ); + + if result.is_err() { continue; } } @@ -130,14 +130,14 @@ pub fn run_circuit( .flatten() .collect::>(); - if process_complete_proof::( + let result = process_complete_proof::( complete_proof, batch_proof_method_id, last_l2_height, &mut initial_to_final, - ) - .is_err() - { + ); + + if result.is_err() { continue; } From bae6c568925e0a0501db5f32789f5fb887707a43 Mon Sep 17 00:00:00 2001 From: Rakan Alhneiti Date: Sat, 21 Dec 2024 20:48:05 +0300 Subject: [PATCH 24/24] Store unprocessed chunks --- crates/light-client-prover/src/circuit.rs | 4 ++-- crates/light-client-prover/src/da_block_handler.rs | 1 + crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs | 3 +++ 3 files changed, 6 insertions(+), 2 deletions(-) diff --git a/crates/light-client-prover/src/circuit.rs b/crates/light-client-prover/src/circuit.rs index d553007c2..20c8dc8f3 100644 --- a/crates/light-client-prover/src/circuit.rs +++ b/crates/light-client-prover/src/circuit.rs @@ -1,4 +1,4 @@ -use std::collections::BTreeSet; +use std::collections::{BTreeMap, BTreeSet}; use anyhow::anyhow; use borsh::BorshDeserialize; @@ -58,7 +58,7 @@ pub fn run_circuit( .map_err(|_| LightClientVerificationError::DaTxsCouldntBeVerified)?; // 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 initial_to_final = BTreeMap::<[u8; 32], ([u8; 32], u64)>::new(); let (mut last_state_root, mut last_l2_height, l2_genesis_state_root, mut unprocessed_chunks) = previous_light_client_proof_output.as_ref().map_or_else( diff --git a/crates/light-client-prover/src/da_block_handler.rs b/crates/light-client-prover/src/da_block_handler.rs index 2bb46bc1e..a4962994d 100644 --- a/crates/light-client-prover/src/da_block_handler.rs +++ b/crates/light-client-prover/src/da_block_handler.rs @@ -283,6 +283,7 @@ where unchained_batch_proofs_info: circuit_output.unchained_batch_proofs_info, last_l2_height: circuit_output.last_l2_height, l2_genesis_state_root: circuit_output.l2_genesis_state_root, + unprocessed_chunks: circuit_output.unprocessed_chunks, }; self.ledger_db.insert_light_client_proof_data_by_l1_height( diff --git a/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs b/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs index 6f90dad4e..af79d2e0a 100644 --- a/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs +++ b/crates/sovereign-sdk/full-node/db/sov-db/src/schema/types.rs @@ -1,3 +1,4 @@ +use std::collections::BTreeMap; use std::fmt::Debug; use std::sync::Arc; @@ -83,6 +84,8 @@ pub struct StoredLightClientProofOutput { pub last_l2_height: u64, /// L2 genesis state root. pub l2_genesis_state_root: [u8; 32], + /// A list of unprocessed chunks + pub unprocessed_chunks: BTreeMap<[u8; 32], Vec>, } impl From for LightClientProofOutputRpcResponse {