From 42bb65e64c590778771c29918e8f553e5d837949 Mon Sep 17 00:00:00 2001 From: Roman Proskuryakoff Date: Thu, 15 Aug 2024 15:13:21 +0400 Subject: [PATCH] Add batchproof/lightclient namespaces. Add SequencerCommitment tx type --- bin/citrea/src/rollup/bitcoin.rs | 8 +- crates/bitcoin-da/src/helpers/builders.rs | 297 +++++- crates/bitcoin-da/src/helpers/mod.rs | 77 +- crates/bitcoin-da/src/helpers/parsers.rs | 468 ++++++--- crates/bitcoin-da/src/helpers/test_utils.rs | 175 ++-- crates/bitcoin-da/src/service.rs | 256 +++-- crates/bitcoin-da/src/spec/mod.rs | 3 +- crates/bitcoin-da/src/verifier.rs | 1035 ++++++++++--------- crates/primitives/src/constants.rs | 7 +- 9 files changed, 1464 insertions(+), 862 deletions(-) diff --git a/bin/citrea/src/rollup/bitcoin.rs b/bin/citrea/src/rollup/bitcoin.rs index 3d29047dc..2999191fd 100644 --- a/bin/citrea/src/rollup/bitcoin.rs +++ b/bin/citrea/src/rollup/bitcoin.rs @@ -4,7 +4,7 @@ use async_trait::async_trait; use bitcoin_da::service::{BitcoinService, DaServiceConfig, TxidWrapper}; use bitcoin_da::spec::{BitcoinSpec, RollupParams}; use bitcoin_da::verifier::BitcoinVerifier; -use citrea_primitives::{DA_TX_ID_LEADING_ZEROS, ROLLUP_NAME}; +use citrea_primitives::{REVEAL_BATCH_PROOF_PREFIX, REVEAL_LIGHT_CLIENT_PREFIX, ROLLUP_NAME}; use citrea_prover::prover_service::ParallelProverService; use citrea_risc0_bonsai_adapter::host::Risc0BonsaiHost; use citrea_risc0_bonsai_adapter::Digest; @@ -117,7 +117,8 @@ impl RollupBlueprint for BitcoinRollup { rollup_config.da.clone(), RollupParams { rollup_name: ROLLUP_NAME.to_string(), - reveal_wtxid_prefix: DA_TX_ID_LEADING_ZEROS.to_vec(), + reveal_light_client_prefix: REVEAL_LIGHT_CLIENT_PREFIX.to_vec(), + reveal_batch_prover_prefix: REVEAL_BATCH_PROOF_PREFIX.to_vec(), }, tx, ) @@ -146,7 +147,8 @@ impl RollupBlueprint for BitcoinRollup { let da_verifier = BitcoinVerifier::new(RollupParams { rollup_name: ROLLUP_NAME.to_string(), - reveal_wtxid_prefix: DA_TX_ID_LEADING_ZEROS.to_vec(), + reveal_light_client_prefix: REVEAL_LIGHT_CLIENT_PREFIX.to_vec(), + reveal_batch_prover_prefix: REVEAL_BATCH_PROOF_PREFIX.to_vec(), }); ParallelProverService::new_with_default_workers( diff --git a/crates/bitcoin-da/src/helpers/builders.rs b/crates/bitcoin-da/src/helpers/builders.rs index 32c807e6e..ec483eb3b 100644 --- a/crates/bitcoin-da/src/helpers/builders.rs +++ b/crates/bitcoin-da/src/helpers/builders.rs @@ -25,7 +25,10 @@ use bitcoin::{ use serde::Serialize; use tracing::{instrument, trace, warn}; -use super::{calculate_double_sha256, TransactionHeader, TransactionKind}; +use super::{ + calculate_double_sha256, TransactionHeaderBatchProof, TransactionHeaderLightClient, + TransactionKindBatchProof, TransactionKindLightClient, +}; use crate::spec::utxo::UTXO; use crate::{MAX_TXBODY_SIZE, REVEAL_OUTPUT_AMOUNT}; @@ -335,10 +338,10 @@ impl fmt::Debug for TxWithId { // TODO: parametrize hardness // so tests are easier -// Creates the inscription transactions (commit and reveal) +// Creates the light client transactions (commit and reveal) #[allow(clippy::too_many_arguments)] #[instrument(level = "trace", skip_all, err)] -pub fn create_inscription_transactions( +pub fn create_zkproof_transactions( rollup_name: &str, body: Vec, signature: Vec, @@ -351,7 +354,7 @@ pub fn create_inscription_transactions( reveal_fee_rate: f64, network: Network, reveal_tx_prefix: &[u8], -) -> Result { +) -> Result { if body.len() < MAX_TXBODY_SIZE { create_inscription_type_0( rollup_name.as_bytes(), @@ -385,9 +388,44 @@ pub fn create_inscription_transactions( } } -/// This is a list of tx we need to send to DA +// TODO: parametrize hardness +// so tests are easier +// Creates the batch proof transactions (commit and reveal) +#[allow(clippy::too_many_arguments)] +#[instrument(level = "trace", skip_all, err)] +pub fn create_seqcommitment_transactions( + rollup_name: &str, + body: Vec, + signature: Vec, + signer_public_key: Vec, + prev_tx: Option, + utxos: Vec, + recipient: Address, + reveal_value: u64, + commit_fee_rate: f64, + reveal_fee_rate: f64, + network: Network, + reveal_tx_prefix: &[u8], +) -> Result { + create_batchproof_type_0( + rollup_name.as_bytes(), + body, + signature, + signer_public_key, + prev_tx, + utxos, + recipient, + reveal_value, + commit_fee_rate, + reveal_fee_rate, + network, + reveal_tx_prefix, + ) +} + +/// This is a list of light client tx we need to send to DA #[derive(Serialize)] -pub(crate) enum InscriptionTxs { +pub(crate) enum LightClientTxs { Complete { commit: Transaction, // unsigned reveal: TxWithId, @@ -400,7 +438,19 @@ pub(crate) enum InscriptionTxs { }, } -impl InscriptionTxs { +/// This is a list of batch proof tx we need to send to DA (only SequencerCommitment for now) +#[derive(Serialize)] +pub(crate) struct BatchProvingTxs { + pub(crate) commit: Transaction, // unsigned + pub(crate) reveal: TxWithId, +} + +// To dump raw da txs into file to recover from a sequencer crash +pub(crate) trait TxListWithReveal: Serialize { + fn reveal_id(&self) -> Txid; +} + +impl TxListWithReveal for LightClientTxs { fn reveal_id(&self) -> Txid { match self { Self::Complete { reveal, .. } => reveal.id, @@ -409,9 +459,15 @@ impl InscriptionTxs { } } +impl TxListWithReveal for BatchProvingTxs { + fn reveal_id(&self) -> Txid { + self.reveal.id + } +} + // TODO: parametrize hardness // so tests are easier -// Creates the inscription transactions Type 0 - InscriptionTxs::Complete +// Creates the inscription transactions Type 0 - LightClientTxs::Complete #[allow(clippy::too_many_arguments)] #[instrument(level = "trace", skip_all, err)] pub fn create_inscription_type_0( @@ -427,15 +483,15 @@ pub fn create_inscription_type_0( reveal_fee_rate: f64, network: Network, reveal_tx_prefix: &[u8], -) -> Result { +) -> Result { // Create commit key let secp256k1 = Secp256k1::new(); let key_pair = UntweakedKeypair::new(&secp256k1, &mut rand::thread_rng()); let (public_key, _parity) = XOnlyPublicKey::from_keypair(&key_pair); - let header = TransactionHeader { + let header = TransactionHeaderLightClient { rollup_name, - kind: TransactionKind::Complete, + kind: TransactionKindLightClient::Complete, }; let header_bytes = header.to_bytes(); @@ -591,7 +647,7 @@ pub fn create_inscription_type_0( commit_tx_address ); - return Ok(InscriptionTxs::Complete { + return Ok(LightClientTxs::Complete { commit: unsigned_commit_tx, reveal: TxWithId { id: reveal_tx_id, @@ -606,7 +662,7 @@ pub fn create_inscription_type_0( // TODO: parametrize hardness // so tests are easier -// Creates the inscription transactions Type 1 - InscriptionTxs::Chunked +// Creates the inscription transactions Type 1 - LightClientTxs::Chunked #[allow(clippy::too_many_arguments)] #[instrument(level = "trace", skip_all, err)] pub fn create_inscription_type_1( @@ -622,7 +678,7 @@ pub fn create_inscription_type_1( reveal_fee_rate: f64, network: Network, reveal_tx_prefix: &[u8], -) -> Result { +) -> Result { // Create commit key let secp256k1 = Secp256k1::new(); let key_pair = UntweakedKeypair::new(&secp256k1, &mut rand::thread_rng()); @@ -632,9 +688,9 @@ pub fn create_inscription_type_1( let mut reveal_chunks: Vec = vec![]; for body in body.chunks(MAX_TXBODY_SIZE) { - let header = TransactionHeader { + let header = TransactionHeaderLightClient { rollup_name, - kind: TransactionKind::ChunkedPart, + kind: TransactionKindLightClient::ChunkedPart, }; let header_bytes = header.to_bytes(); @@ -788,9 +844,9 @@ pub fn create_inscription_type_1( } } - let header = TransactionHeader { + let header = TransactionHeaderLightClient { rollup_name, - kind: TransactionKind::Chunked, + kind: TransactionKindLightClient::Chunked, }; let header_bytes = header.to_bytes(); @@ -944,7 +1000,7 @@ pub fn create_inscription_type_1( commit_tx_address ); - return Ok(InscriptionTxs::Chunked { + return Ok(LightClientTxs::Chunked { commit_chunks, reveal_chunks, commit: unsigned_commit_tx, @@ -959,7 +1015,199 @@ pub fn create_inscription_type_1( } } -pub(crate) fn write_inscription_txs(txs: &InscriptionTxs) { +// TODO: parametrize hardness +// so tests are easier +// Creates the batch proof transactions Type 0 - BatchProvingTxs - SequencerCommitment +#[allow(clippy::too_many_arguments)] +#[instrument(level = "trace", skip_all, err)] +pub fn create_batchproof_type_0( + rollup_name: &[u8], + body: Vec, + signature: Vec, + signer_public_key: Vec, + prev_tx: Option, + utxos: Vec, + recipient: Address, + reveal_value: u64, + commit_fee_rate: f64, + reveal_fee_rate: f64, + network: Network, + reveal_tx_prefix: &[u8], +) -> Result { + debug_assert!( + body.len() < 520, + "The body of a serialized sequencer commitment exceeds 520 bytes" + ); + // Create commit key + let secp256k1 = Secp256k1::new(); + let key_pair = UntweakedKeypair::new(&secp256k1, &mut rand::thread_rng()); + let (public_key, _parity) = XOnlyPublicKey::from_keypair(&key_pair); + + let header = TransactionHeaderBatchProof { + rollup_name, + kind: TransactionKindBatchProof::SequencerCommitment, + }; + let header_bytes = header.to_bytes(); + + // start creating inscription content + let reveal_script_builder = script::Builder::new() + .push_x_only_key(&public_key) + .push_opcode(OP_CHECKSIGVERIFY) + .push_slice(PushBytesBuf::try_from(header_bytes).expect("Cannot push header")) + .push_opcode(OP_FALSE) + .push_opcode(OP_IF) + .push_slice(PushBytesBuf::try_from(signature).expect("Cannot push signature")) + .push_slice( + PushBytesBuf::try_from(signer_public_key).expect("Cannot push sequencer public key"), + ) + .push_slice(PushBytesBuf::try_from(body).expect("Cannot push sequencer commitment")) + .push_opcode(OP_ENDIF); + + // Start loop to find a 'nonce' i.e. random number that makes the reveal tx hash starting with zeros given length + let mut nonce: i64 = 16; // skip the first digits to avoid OP_PUSHNUM_X + loop { + if nonce % 10000 == 0 { + trace!(nonce, "Trying to find commit & reveal nonce"); + if nonce > 65536 { + warn!("Too many iterations finding nonce"); + } + } + let utxos = utxos.clone(); + let recipient = recipient.clone(); + // ownerships are moved to the loop + let mut reveal_script_builder = reveal_script_builder.clone(); + + // push first random number and body tag + reveal_script_builder = reveal_script_builder + .push_slice(nonce.to_le_bytes()) + .push_opcode(OP_DROP); + + // finalize reveal script + let reveal_script = reveal_script_builder.into_script(); + + // create spend info for tapscript + let taproot_spend_info = TaprootBuilder::new() + .add_leaf(0, reveal_script.clone()) + .expect("Cannot add reveal script to taptree") + .finalize(&secp256k1, public_key) + .expect("Cannot finalize taptree"); + + // create control block for tapscript + let control_block = taproot_spend_info + .control_block(&(reveal_script.clone(), LeafVersion::TapScript)) + .expect("Cannot create control block"); + + // create commit tx address + let commit_tx_address = Address::p2tr( + &secp256k1, + public_key, + taproot_spend_info.merkle_root(), + network, + ); + + let commit_value = (get_size( + &[TxIn { + previous_output: OutPoint { + txid: Txid::from_byte_array([0; 32]), + vout: 0, + }, + script_sig: script::Builder::new().into_script(), + witness: Witness::new(), + sequence: Sequence::ENABLE_RBF_NO_LOCKTIME, + }], + &[TxOut { + script_pubkey: recipient.clone().script_pubkey(), + value: Amount::from_sat(reveal_value), + }], + Some(&reveal_script), + Some(&control_block), + ) as f64 + * reveal_fee_rate + + reveal_value as f64) + .ceil() as u64; + + // build commit tx + // we don't need leftover_utxos because they will be requested from bitcoind next call + let (unsigned_commit_tx, _leftover_utxos) = build_commit_transaction( + prev_tx.clone(), + utxos, + commit_tx_address.clone(), + recipient.clone(), + commit_value, + commit_fee_rate, + )?; + + let output_to_reveal = unsigned_commit_tx.output[0].clone(); + + let mut reveal_tx = build_reveal_transaction( + output_to_reveal.clone(), + unsigned_commit_tx.compute_txid(), + 0, + recipient, + reveal_value, + reveal_fee_rate, + &reveal_script, + &control_block, + )?; + + let reveal_tx_id = reveal_tx.compute_txid(); + let reveal_hash = reveal_tx_id.as_raw_hash().to_byte_array(); + + // check if first N bytes equal to the given prefix + if reveal_hash.starts_with(reveal_tx_prefix) { + // start signing reveal tx + let mut sighash_cache = SighashCache::new(&mut reveal_tx); + + // create data to sign + let signature_hash = sighash_cache + .taproot_script_spend_signature_hash( + 0, + &Prevouts::All(&[output_to_reveal]), + TapLeafHash::from_script(&reveal_script, LeafVersion::TapScript), + bitcoin::sighash::TapSighashType::Default, + ) + .expect("Cannot create hash for signature"); + + // sign reveal tx data + let signature = secp256k1.sign_schnorr_with_rng( + &secp256k1::Message::from_digest_slice(signature_hash.as_byte_array()) + .expect("should be cryptographically secure hash"), + &key_pair, + &mut rand::thread_rng(), + ); + + // add signature to witness and finalize reveal tx + let witness = sighash_cache.witness_mut(0).unwrap(); + witness.push(signature.as_ref()); + witness.push(reveal_script); + witness.push(&control_block.serialize()); + + // check if inscription locked to the correct address + let recovery_key_pair = + key_pair.tap_tweak(&secp256k1, taproot_spend_info.merkle_root()); + let (x_only_pub_key, _parity) = recovery_key_pair.to_inner().x_only_public_key(); + assert_eq!( + Address::p2tr_tweaked( + TweakedPublicKey::dangerous_assume_tweaked(x_only_pub_key), + network, + ), + commit_tx_address + ); + + return Ok(BatchProvingTxs { + commit: unsigned_commit_tx, + reveal: TxWithId { + id: reveal_tx_id, + tx: reveal_tx, + }, + }); + } + + nonce += 1; + } +} + +pub(crate) fn write_inscription_txs(txs: &Txs) { let reveal_tx_file = File::create(format!("reveal_{}.tx", txs.reveal_id())).unwrap(); let j = serde_json::to_string(&txs).unwrap(); let mut reveal_tx_writer = BufWriter::new(reveal_tx_file); @@ -976,9 +1224,9 @@ mod tests { use bitcoin::taproot::ControlBlock; use bitcoin::{Address, Amount, ScriptBuf, TxOut, Txid}; - use super::InscriptionTxs; + use super::LightClientTxs; use crate::helpers::compression::{compress_blob, decompress_blob}; - use crate::helpers::parsers::parse_transaction; + use crate::helpers::parsers::{parse_transaction, ParsedLightClientTransaction}; use crate::spec::utxo::UTXO; use crate::REVEAL_OUTPUT_AMOUNT; @@ -1435,7 +1683,7 @@ mod tests { let (rollup_name, body, signature, signer_public_key, address, utxos) = get_mock_data(); let tx_prefix = &[0u8]; - let InscriptionTxs::Complete { commit, reveal } = super::create_inscription_transactions( + let LightClientTxs::Complete { commit, reveal } = super::create_zkproof_transactions( rollup_name, body.clone(), signature.clone(), @@ -1489,6 +1737,9 @@ mod tests { // check inscription let inscription = parse_transaction(&reveal, rollup_name).unwrap(); + let ParsedLightClientTransaction::Complete(inscription) = inscription else { + panic!("Unexpected tx kind"); + }; assert_eq!(inscription.body, body, "body should be correct"); assert_eq!( diff --git a/crates/bitcoin-da/src/helpers/mod.rs b/crates/bitcoin-da/src/helpers/mod.rs index 07381cc76..7411b1f1a 100644 --- a/crates/bitcoin-da/src/helpers/mod.rs +++ b/crates/bitcoin-da/src/helpers/mod.rs @@ -10,26 +10,26 @@ pub mod parsers; #[cfg(test)] pub mod test_utils; -/// Header - first 8 bytes of any rollup transaction -struct TransactionHeader<'a> { +/// Header - represents a header of a LightClient transaction +struct TransactionHeaderLightClient<'a> { pub(crate) rollup_name: &'a [u8], - pub(crate) kind: TransactionKind, + pub(crate) kind: TransactionKindLightClient, } -impl<'a> TransactionHeader<'a> { +impl<'a> TransactionHeaderLightClient<'a> { fn to_bytes(&self) -> Vec { let kind = match self.kind { - TransactionKind::Complete => 0u16.to_le_bytes(), - TransactionKind::Chunked => 1u16.to_le_bytes(), - TransactionKind::ChunkedPart => 2u16.to_le_bytes(), - TransactionKind::Unknown(v) => v.get().to_le_bytes(), + TransactionKindLightClient::Complete => 0u16.to_le_bytes(), + TransactionKindLightClient::Chunked => 1u16.to_le_bytes(), + TransactionKindLightClient::ChunkedPart => 2u16.to_le_bytes(), + TransactionKindLightClient::Unknown(v) => v.get().to_le_bytes(), }; let mut result = vec![]; result.extend_from_slice(&kind); result.extend_from_slice(self.rollup_name); result } - fn from_bytes<'b: 'a>(bytes: &'b [u8]) -> Option> + fn from_bytes<'b: 'a>(bytes: &'b [u8]) -> Option> where 'a: 'b, { @@ -40,18 +40,55 @@ impl<'a> TransactionHeader<'a> { let mut kind_bytes = [0; 2]; kind_bytes.copy_from_slice(kind_slice); let kind = match u16::from_le_bytes(kind_bytes) { - 0 => TransactionKind::Complete, - 1 => TransactionKind::Chunked, - 2 => TransactionKind::ChunkedPart, - n => TransactionKind::Unknown(NonZeroU16::new(n).expect("Is not zero")), + 0 => TransactionKindLightClient::Complete, + 1 => TransactionKindLightClient::Chunked, + 2 => TransactionKindLightClient::ChunkedPart, + n => TransactionKindLightClient::Unknown(NonZeroU16::new(n).expect("Is not zero")), }; Some(Self { rollup_name, kind }) } } -/// Type represents +/// Header - represents a header of a BatchProof transaction +struct TransactionHeaderBatchProof<'a> { + pub(crate) rollup_name: &'a [u8], + pub(crate) kind: TransactionKindBatchProof, +} + +impl<'a> TransactionHeaderBatchProof<'a> { + fn to_bytes(&self) -> Vec { + let kind = match self.kind { + TransactionKindBatchProof::SequencerCommitment => 0u16.to_le_bytes(), + // TransactionKindBatchProof::ForcedTransaction => 1u16.to_le_bytes(), + TransactionKindBatchProof::Unknown(v) => v.get().to_le_bytes(), + }; + let mut result = vec![]; + result.extend_from_slice(&kind); + result.extend_from_slice(self.rollup_name); + result + } + fn from_bytes<'b: 'a>(bytes: &'b [u8]) -> Option> + where + 'a: 'b, + { + let (kind_slice, rollup_name) = bytes.split_at(2); + if kind_slice.len() != 2 { + return None; + } + let mut kind_bytes = [0; 2]; + kind_bytes.copy_from_slice(kind_slice); + let kind = match u16::from_le_bytes(kind_bytes) { + 0 => TransactionKindBatchProof::SequencerCommitment, + // 1 => TransactionKindBatchProof::ForcedTransaction, + n => TransactionKindBatchProof::Unknown(NonZeroU16::new(n).expect("Is not zero")), + }; + Some(Self { rollup_name, kind }) + } +} + +/// Type represents a typed enum for LightClient kind #[repr(u16)] -enum TransactionKind { +enum TransactionKindLightClient { /// This type of transaction includes full body (< 400kb) Complete = 0, /// This type of transaction includes txids of chunks (>= 400kb) @@ -61,6 +98,16 @@ enum TransactionKind { Unknown(NonZeroU16), } +/// Type represents a typed enum for BatchProof kind +#[repr(u16)] +enum TransactionKindBatchProof { + /// SequencerCommitment + SequencerCommitment = 0, + // /// ForcedTransaction + // ForcedTransaction = 1, + Unknown(NonZeroU16), +} + pub fn calculate_double_sha256(input: &[u8]) -> [u8; 32] { let mut hasher = Sha256::default(); hasher.update(input); diff --git a/crates/bitcoin-da/src/helpers/parsers.rs b/crates/bitcoin-da/src/helpers/parsers.rs index a4ca6bdf7..048880a5b 100644 --- a/crates/bitcoin-da/src/helpers/parsers.rs +++ b/crates/bitcoin-da/src/helpers/parsers.rs @@ -1,6 +1,5 @@ use core::num::NonZeroU16; -use bitcoin::blockdata::opcodes::all::{OP_ENDIF, OP_IF}; use bitcoin::blockdata::script::Instruction; use bitcoin::hashes::Hash; use bitcoin::opcodes::all::{OP_CHECKSIGVERIFY, OP_DROP}; @@ -10,28 +9,56 @@ use bitcoin::secp256k1::{ecdsa, Message, Secp256k1}; use bitcoin::{secp256k1, Opcode, Script, Transaction, Txid}; use thiserror::Error; -use super::{calculate_double_sha256, TransactionHeader, TransactionKind}; +use super::{ + calculate_double_sha256, TransactionHeaderBatchProof, TransactionHeaderLightClient, + TransactionKindBatchProof, TransactionKindLightClient, +}; #[derive(Debug, Clone)] -pub struct ParsedInscription { +pub enum ParsedLightClientTransaction { + /// Kind 0 + Complete(ParsedComplete), + /// Kind 1 + Aggregate(ParsedAggregate), + /// Kind 2 + Chunk(ParsedChunk), +} + +#[derive(Debug, Clone)] +pub enum ParsedBatchProofTransaction { + /// Kind 0 + SequencerCommitment(ParsedSequencerCommitment), + // /// Kind 1 + // ForcedTransaction(ForcedTransaction), +} + +#[derive(Debug, Clone)] +pub struct ParsedComplete { pub body: Vec, pub signature: Vec, pub public_key: Vec, } #[derive(Debug, Clone)] -pub struct ParsedChunked { +pub struct ParsedAggregate { pub txids: Vec, pub signature: Vec, pub public_key: Vec, } #[derive(Debug, Clone)] -pub struct ParsedChunkedPart { +pub struct ParsedChunk { pub body: Vec, } -impl ParsedInscription { +#[derive(Debug, Clone)] +pub struct ParsedSequencerCommitment { + pub body: Vec, + pub signature: Vec, + pub public_key: Vec, +} + +impl ParsedComplete { /// Verifies the signature of the inscription and returns the hash of the body pub fn get_sig_verified_hash(&self) -> Option<[u8; 32]> { let public_key = secp256k1::PublicKey::from_slice(&self.public_key); @@ -54,6 +81,29 @@ impl ParsedInscription { } } +impl ParsedSequencerCommitment { + /// Verifies the signature of the sequencer commitment and returns the hash of the body + pub fn get_sig_verified_hash(&self) -> Option<[u8; 32]> { + let public_key = secp256k1::PublicKey::from_slice(&self.public_key); + let signature = ecdsa::Signature::from_compact(&self.signature); + let hash = calculate_double_sha256(&self.body); + let message = Message::from_digest_slice(&hash).unwrap(); // cannot fail + + let secp = Secp256k1::new(); + + if public_key.is_ok() + && signature.is_ok() + && secp + .verify_ecdsa(&message, &signature.unwrap(), &public_key.unwrap()) + .is_ok() + { + Some(hash) + } else { + None + } + } +} + #[derive(Error, Debug, Clone, PartialEq)] pub enum ParserError { #[error("Invalid rollup name")] @@ -81,13 +131,25 @@ impl From for ParserError { pub fn parse_transaction( tx: &Transaction, rollup_name: &str, -) -> Result { +) -> Result { let script = get_script(tx)?; let instructions = script.instructions().peekable(); // Map all Instructions errors into ParserError::ScriptError let mut instructions = instructions.map(|r| r.map_err(ParserError::from)); - parse_relevant_inscriptions(&mut instructions, rollup_name) + parse_relevant_lightclient(&mut instructions, rollup_name) +} + +pub fn parse_batch_proof_transaction( + tx: &Transaction, + rollup_name: &str, +) -> Result { + let script = get_script(tx)?; + let instructions = script.instructions().peekable(); + // Map all Instructions errors into ParserError::ScriptError + let mut instructions = instructions.map(|r| r.map_err(ParserError::from)); + + parse_relevant_batchproof(&mut instructions, rollup_name) } // Returns the script from the first input of the transaction @@ -98,10 +160,10 @@ fn get_script(tx: &Transaction) -> Result<&Script, ParserError> { .ok_or(ParserError::NonTapscriptWitness) } -fn parse_relevant_inscriptions( +fn parse_relevant_lightclient( instructions: &mut dyn Iterator, ParserError>>, rollup_name: &str, -) -> Result { +) -> Result { // PushBytes(XOnlyPublicKey) let _public_key = read_push_bytes(instructions)?; if OP_CHECKSIGVERIFY != read_opcode(instructions)? { @@ -110,7 +172,7 @@ fn parse_relevant_inscriptions( // Parse header let header_slice = read_push_bytes(instructions)?; - let Some(header) = TransactionHeader::from_bytes(header_slice.as_bytes()) else { + let Some(header) = TransactionHeaderLightClient::from_bytes(header_slice.as_bytes()) else { return Err(ParserError::InvalidHeaderLength); }; @@ -121,16 +183,45 @@ fn parse_relevant_inscriptions( // Parse transaction body according to type match header.kind { - TransactionKind::Complete => parse_type_0_body(instructions), - TransactionKind::Chunked => { - let _body = parse_type_1_body(instructions)?; - Err(ParserError::InvalidHeaderType(NonZeroU16::new(1).unwrap())) // FIXME + TransactionKindLightClient::Complete => light_client::parse_type_0_body(instructions) + .map(ParsedLightClientTransaction::Complete), + TransactionKindLightClient::Chunked => light_client::parse_type_1_body(instructions) + .map(ParsedLightClientTransaction::Aggregate), + TransactionKindLightClient::ChunkedPart => { + light_client::parse_type_2_body(instructions).map(ParsedLightClientTransaction::Chunk) } - TransactionKind::ChunkedPart => { - let _body = parse_type_2_body(instructions)?; - Err(ParserError::InvalidHeaderType(NonZeroU16::new(2).unwrap())) // FIXME + TransactionKindLightClient::Unknown(n) => Err(ParserError::InvalidHeaderType(n)), + } +} + +fn parse_relevant_batchproof( + instructions: &mut dyn Iterator, ParserError>>, + rollup_name: &str, +) -> Result { + // PushBytes(XOnlyPublicKey) + let _public_key = read_push_bytes(instructions)?; + if OP_CHECKSIGVERIFY != read_opcode(instructions)? { + return Err(ParserError::UnexpectedOpcode); + } + + // Parse header + let header_slice = read_push_bytes(instructions)?; + let Some(header) = TransactionHeaderBatchProof::from_bytes(header_slice.as_bytes()) else { + return Err(ParserError::InvalidHeaderLength); + }; + + // Check rollup name + if header.rollup_name != rollup_name.as_bytes() { + return Err(ParserError::InvalidRollupName); + } + + // Parse transaction body according to type + match header.kind { + TransactionKindBatchProof::SequencerCommitment => { + batch_proof::parse_type_0_body(instructions) + .map(ParsedBatchProofTransaction::SequencerCommitment) } - TransactionKind::Unknown(n) => Err(ParserError::InvalidHeaderType(n)), + TransactionKindBatchProof::Unknown(n) => Err(ParserError::InvalidHeaderType(n)), } } @@ -163,150 +254,212 @@ fn read_opcode( Ok(op) } -// Parse transaction body of Type0 -fn parse_type_0_body( - instructions: &mut dyn Iterator, ParserError>>, -) -> Result { - let op_false = read_push_bytes(instructions)?; - if !op_false.is_empty() { - return Err(ParserError::UnexpectedOpcode); - } +mod light_client { + use bitcoin::hashes::Hash; + use bitcoin::opcodes::all::{OP_DROP, OP_ENDIF, OP_IF}; + use bitcoin::script::Instruction; + use bitcoin::script::Instruction::{Op, PushBytes}; + use bitcoin::Txid; - if OP_IF != read_opcode(instructions)? { - return Err(ParserError::UnexpectedOpcode); - } + use super::{ + read_instr, read_opcode, read_push_bytes, ParsedAggregate, ParsedChunk, ParsedComplete, + ParserError, + }; + + // Parse transaction body of Type0 + pub(super) fn parse_type_0_body( + instructions: &mut dyn Iterator, ParserError>>, + ) -> Result { + let op_false = read_push_bytes(instructions)?; + if !op_false.is_empty() { + return Err(ParserError::UnexpectedOpcode); + } + + if OP_IF != read_opcode(instructions)? { + return Err(ParserError::UnexpectedOpcode); + } - let signature = read_push_bytes(instructions)?; - let public_key = read_push_bytes(instructions)?; + let signature = read_push_bytes(instructions)?; + let public_key = read_push_bytes(instructions)?; - let mut chunks = vec![]; + let mut chunks = vec![]; - loop { - let instr = read_instr(instructions)?; - match instr { - PushBytes(chunk) => { - if chunk.is_empty() { - return Err(ParserError::UnexpectedOpcode); + loop { + let instr = read_instr(instructions)?; + match instr { + PushBytes(chunk) => { + if chunk.is_empty() { + return Err(ParserError::UnexpectedOpcode); + } + chunks.push(chunk) } - chunks.push(chunk) + Op(OP_ENDIF) => break, + Op(_) => return Err(ParserError::UnexpectedOpcode), } - Op(OP_ENDIF) => break, - Op(_) => return Err(ParserError::UnexpectedOpcode), } - } - - // Nonce - let _nonce = read_push_bytes(instructions)?; - if OP_DROP != read_opcode(instructions)? { - return Err(ParserError::UnexpectedOpcode); - } - // END of transaction - if instructions.next().is_some() { - return Err(ParserError::UnexpectedOpcode); - } - let body_size: usize = chunks.iter().map(|c| c.len()).sum(); - let mut body = Vec::with_capacity(body_size); - for chunk in chunks { - body.extend_from_slice(chunk.as_bytes()); - } + // Nonce + let _nonce = read_push_bytes(instructions)?; + if OP_DROP != read_opcode(instructions)? { + return Err(ParserError::UnexpectedOpcode); + } + // END of transaction + if instructions.next().is_some() { + return Err(ParserError::UnexpectedOpcode); + } - let signature = signature.as_bytes().to_vec(); - let public_key = public_key.as_bytes().to_vec(); + let body_size: usize = chunks.iter().map(|c| c.len()).sum(); + let mut body = Vec::with_capacity(body_size); + for chunk in chunks { + body.extend_from_slice(chunk.as_bytes()); + } - Ok(ParsedInscription { - body, - signature, - public_key, - }) -} + let signature = signature.as_bytes().to_vec(); + let public_key = public_key.as_bytes().to_vec(); -// Parse transaction body of Type1 -fn parse_type_1_body( - instructions: &mut dyn Iterator, ParserError>>, -) -> Result { - let op_false = read_push_bytes(instructions)?; - if !op_false.is_empty() { - return Err(ParserError::UnexpectedOpcode); + Ok(ParsedComplete { + body, + signature, + public_key, + }) } - if OP_IF != read_opcode(instructions)? { - return Err(ParserError::UnexpectedOpcode); - } + // Parse transaction body of Type1 + pub(super) fn parse_type_1_body( + instructions: &mut dyn Iterator, ParserError>>, + ) -> Result { + let op_false = read_push_bytes(instructions)?; + if !op_false.is_empty() { + return Err(ParserError::UnexpectedOpcode); + } - let signature = read_push_bytes(instructions)?; - let public_key = read_push_bytes(instructions)?; + if OP_IF != read_opcode(instructions)? { + return Err(ParserError::UnexpectedOpcode); + } - let mut txids = vec![]; + let signature = read_push_bytes(instructions)?; + let public_key = read_push_bytes(instructions)?; - loop { - let instr = read_instr(instructions)?; - match instr { - PushBytes(chunk) => match Txid::from_slice(chunk.as_bytes()) { - Ok(id) => txids.push(id), - Err(_) => return Err(ParserError::UnexpectedOpcode), - }, - Op(OP_ENDIF) => break, - Op(_) => return Err(ParserError::UnexpectedOpcode), - } - } + let mut txids = vec![]; - // Nonce - let _nonce = read_push_bytes(instructions)?; - if OP_DROP != read_opcode(instructions)? { - return Err(ParserError::UnexpectedOpcode); - } - // END of transaction - if instructions.next().is_some() { - return Err(ParserError::UnexpectedOpcode); - } + loop { + let instr = read_instr(instructions)?; + match instr { + PushBytes(chunk) => match Txid::from_slice(chunk.as_bytes()) { + Ok(id) => txids.push(id), + Err(_) => return Err(ParserError::UnexpectedOpcode), + }, + Op(OP_ENDIF) => break, + Op(_) => return Err(ParserError::UnexpectedOpcode), + } + } - let signature = signature.as_bytes().to_vec(); - let public_key = public_key.as_bytes().to_vec(); + // Nonce + let _nonce = read_push_bytes(instructions)?; + if OP_DROP != read_opcode(instructions)? { + return Err(ParserError::UnexpectedOpcode); + } + // END of transaction + if instructions.next().is_some() { + return Err(ParserError::UnexpectedOpcode); + } - Ok(ParsedChunked { - txids, - signature, - public_key, - }) -} + let signature = signature.as_bytes().to_vec(); + let public_key = public_key.as_bytes().to_vec(); -// Parse transaction body of Type2 -fn parse_type_2_body( - instructions: &mut dyn Iterator, ParserError>>, -) -> Result { - let op_false = read_push_bytes(instructions)?; - if !op_false.is_empty() { - return Err(ParserError::UnexpectedOpcode); + Ok(ParsedAggregate { + txids, + signature, + public_key, + }) } - if OP_IF != read_opcode(instructions)? { - return Err(ParserError::UnexpectedOpcode); - } + // Parse transaction body of Type2 + pub(super) fn parse_type_2_body( + instructions: &mut dyn Iterator, ParserError>>, + ) -> Result { + let op_false = read_push_bytes(instructions)?; + if !op_false.is_empty() { + return Err(ParserError::UnexpectedOpcode); + } + + if OP_IF != read_opcode(instructions)? { + return Err(ParserError::UnexpectedOpcode); + } - let mut chunks = vec![]; + let mut chunks = vec![]; - loop { - let instr = read_instr(instructions)?; - match instr { - PushBytes(chunk) => { - if chunk.is_empty() { - return Err(ParserError::UnexpectedOpcode); + loop { + let instr = read_instr(instructions)?; + match instr { + PushBytes(chunk) => { + if chunk.is_empty() { + return Err(ParserError::UnexpectedOpcode); + } + chunks.push(chunk) } - chunks.push(chunk) + Op(OP_ENDIF) => break, + Op(_) => return Err(ParserError::UnexpectedOpcode), } - Op(OP_ENDIF) => break, - Op(_) => return Err(ParserError::UnexpectedOpcode), } - } - let body_size: usize = chunks.iter().map(|c| c.len()).sum(); - let mut body = Vec::with_capacity(body_size); - for chunk in chunks { - body.extend_from_slice(chunk.as_bytes()); + let body_size: usize = chunks.iter().map(|c| c.len()).sum(); + let mut body = Vec::with_capacity(body_size); + for chunk in chunks { + body.extend_from_slice(chunk.as_bytes()); + } + + Ok(ParsedChunk { body }) } +} + +mod batch_proof { + use bitcoin::opcodes::all::{OP_DROP, OP_ENDIF, OP_IF}; + use bitcoin::script::Instruction; - Ok(ParsedChunkedPart { body }) + use super::{read_opcode, read_push_bytes, ParsedSequencerCommitment, ParserError}; + + // Parse transaction body of Type0 + pub(super) fn parse_type_0_body( + instructions: &mut dyn Iterator, ParserError>>, + ) -> Result { + let op_false = read_push_bytes(instructions)?; + if !op_false.is_empty() { + return Err(ParserError::UnexpectedOpcode); + } + + if OP_IF != read_opcode(instructions)? { + return Err(ParserError::UnexpectedOpcode); + } + + let signature = read_push_bytes(instructions)?; + let public_key = read_push_bytes(instructions)?; + let body = read_push_bytes(instructions)?; + + if OP_ENDIF != read_opcode(instructions)? { + return Err(ParserError::UnexpectedOpcode); + } + + // Nonce + let _nonce = read_push_bytes(instructions)?; + if OP_DROP != read_opcode(instructions)? { + return Err(ParserError::UnexpectedOpcode); + } + // END of transaction + if instructions.next().is_some() { + return Err(ParserError::UnexpectedOpcode); + } + + let signature = signature.as_bytes().to_vec(); + let public_key = public_key.as_bytes().to_vec(); + let body = body.as_bytes().to_vec(); + + Ok(ParsedSequencerCommitment { + body, + signature, + public_key, + }) + } } #[cfg(feature = "native")] @@ -330,13 +483,16 @@ mod tests { use bitcoin::opcodes::{OP_FALSE, OP_TRUE}; use bitcoin::script::{self, PushBytesBuf}; - use super::{parse_relevant_inscriptions, ParserError, TransactionHeader, TransactionKind}; + use super::{ + parse_relevant_lightclient, ParsedLightClientTransaction, ParserError, + TransactionHeaderLightClient, TransactionKindLightClient, + }; #[test] fn correct() { - let header = TransactionHeader { + let header = TransactionHeaderLightClient { rollup_name: b"sov-btc", - kind: TransactionKind::Complete, + kind: TransactionKindLightClient::Complete, }; let reveal_script_builder = script::Builder::new() @@ -358,14 +514,16 @@ mod tests { .instructions() .map(|r| r.map_err(ParserError::from)); - let result = parse_relevant_inscriptions(&mut instructions, "sov-btc"); + let result = parse_relevant_lightclient(&mut instructions, "sov-btc"); let result = result.inspect_err(|e| { dbg!(e); }); assert!(result.is_ok()); - let result = result.unwrap(); + let ParsedLightClientTransaction::Complete(result) = result.unwrap() else { + panic!("Unexpected tx kind"); + }; assert_eq!(result.body, vec![4u8; 128]); assert_eq!(result.signature, vec![2u8; 64]); @@ -374,9 +532,9 @@ mod tests { #[test] fn wrong_rollup_tag() { - let header = TransactionHeader { + let header = TransactionHeaderLightClient { rollup_name: b"not-sov-btc", - kind: TransactionKind::Complete, + kind: TransactionKindLightClient::Complete, }; let reveal_script_builder = script::Builder::new() @@ -389,7 +547,7 @@ mod tests { .instructions() .map(|r| r.map_err(ParserError::from)); - let result = parse_relevant_inscriptions(&mut instructions, "sov-btc"); + let result = parse_relevant_lightclient(&mut instructions, "sov-btc"); assert!(result.is_err()); assert_eq!(result.unwrap_err(), ParserError::InvalidRollupName); @@ -397,9 +555,9 @@ mod tests { #[test] fn only_checksig() { - let header = TransactionHeader { + let header = TransactionHeaderLightClient { rollup_name: b"sov-btc", - kind: TransactionKind::Complete, + kind: TransactionKindLightClient::Complete, }; let reveal_script_builder = script::Builder::new() @@ -413,7 +571,7 @@ mod tests { .instructions() .map(|r| r.map_err(ParserError::from)); - let result = parse_relevant_inscriptions(&mut instructions, "sov-btc"); + let result = parse_relevant_lightclient(&mut instructions, "sov-btc"); assert!(result.is_err()); assert_eq!(result.unwrap_err(), ParserError::UnexpectedEndOfScript); @@ -421,9 +579,9 @@ mod tests { #[test] fn complex_envelope() { - let header = TransactionHeader { + let header = TransactionHeaderLightClient { rollup_name: b"sov-btc", - kind: TransactionKind::Complete, + kind: TransactionKindLightClient::Complete, }; let reveal_script = script::Builder::new() @@ -449,7 +607,7 @@ mod tests { .instructions() .map(|r| r.map_err(ParserError::from)); - let result = parse_relevant_inscriptions(&mut instructions, "sov-btc"); + let result = parse_relevant_lightclient(&mut instructions, "sov-btc"); assert!(result.is_err()); assert_eq!(result.unwrap_err(), ParserError::UnexpectedOpcode); @@ -457,9 +615,9 @@ mod tests { #[test] fn two_envelopes() { - let header = TransactionHeader { + let header = TransactionHeaderLightClient { rollup_name: b"sov-btc", - kind: TransactionKind::Complete, + kind: TransactionKindLightClient::Complete, }; let reveal_script = script::Builder::new() @@ -488,7 +646,7 @@ mod tests { .instructions() .map(|r| r.map_err(ParserError::from)); - let result = parse_relevant_inscriptions(&mut instructions, "sov-btc"); + let result = parse_relevant_lightclient(&mut instructions, "sov-btc"); assert!(result.is_err()); assert_eq!(result.unwrap_err(), ParserError::UnexpectedOpcode); @@ -496,9 +654,9 @@ mod tests { #[test] fn big_push() { - let header = TransactionHeader { + let header = TransactionHeaderLightClient { rollup_name: b"sov-btc", - kind: TransactionKind::Complete, + kind: TransactionKindLightClient::Complete, }; let reveal_script = script::Builder::new() @@ -524,11 +682,13 @@ mod tests { .instructions() .map(|r| r.map_err(ParserError::from)); - let result = parse_relevant_inscriptions(&mut instructions, "sov-btc"); + let result = parse_relevant_lightclient(&mut instructions, "sov-btc"); assert!(result.is_ok()); - let result = result.unwrap(); + let ParsedLightClientTransaction::Complete(result) = result.unwrap() else { + panic!("Unexpected tx kind"); + }; assert_eq!(result.body, vec![1u8; 512 * 6]); assert_eq!(result.signature, vec![2u8; 64]); diff --git a/crates/bitcoin-da/src/helpers/test_utils.rs b/crates/bitcoin-da/src/helpers/test_utils.rs index 2c5a24557..b5a6b0218 100644 --- a/crates/bitcoin-da/src/helpers/test_utils.rs +++ b/crates/bitcoin-da/src/helpers/test_utils.rs @@ -7,6 +7,7 @@ use bitcoin::{BlockHash, CompactTarget, Transaction}; use sov_rollup_interface::da::{DaSpec, DaVerifier}; use super::calculate_double_sha256; +use super::parsers::ParsedLightClientTransaction; use crate::helpers::compression::decompress_blob; use crate::helpers::parsers::{parse_hex_transaction, parse_transaction}; use crate::spec::blob::BlobWithSender; @@ -16,7 +17,7 @@ use crate::verifier::BitcoinVerifier; pub(crate) fn get_mock_txs() -> Vec { // relevant txs are on 6, 8, 10, 12 indices - let txs = std::fs::read_to_string("test_data/mock_txs.txt").unwrap(); + let txs = include_str!("../../test_data/mock_txs.txt"); txs.lines() .map(|tx| parse_hex_transaction(tx).unwrap()) @@ -26,16 +27,24 @@ pub(crate) fn get_mock_txs() -> Vec { pub(crate) fn get_blob_with_sender(tx: &Transaction) -> BlobWithSender { let tx = tx.clone(); - let parsed_inscription = parse_transaction(&tx, "sov-btc").unwrap(); + let parsed_transaction = parse_transaction(&tx, "sov-btc").unwrap(); - let blob = parsed_inscription.body; + let (blob, public_key) = match parsed_transaction { + ParsedLightClientTransaction::Complete(t) => (t.body, t.public_key), + ParsedLightClientTransaction::Aggregate(t) => { + panic!("Unexpected tx kind"); + } + ParsedLightClientTransaction::Chunk(_t) => { + panic!("Unexpected tx kind"); + } + }; // Decompress the blob let decompressed_blob = decompress_blob(&blob); BlobWithSender::new( decompressed_blob, - parsed_inscription.public_key, + public_key, calculate_double_sha256(&blob), ) } @@ -47,69 +56,105 @@ pub(crate) fn get_mock_data() -> ( <::Spec as DaSpec>::CompletenessProof, // completeness proof Vec<<::Spec as DaSpec>::BlobTransaction>, // txs ) { - let header = HeaderWrapper::new( - Header { - version: Version::from_consensus(536870912), - prev_blockhash: BlockHash::from_str( - "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", - ) - .unwrap(), - merkle_root: TxMerkleNode::from_str( - "7750076b3b5498aad3e2e7da55618c66394d1368dc08f19f0b13d1e5b83ae056", - ) - .unwrap(), - time: 1694177029, - bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), - nonce: 0, - }, - 13, - 2, - WitnessMerkleNode::from_str( - "a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71", - ) - .unwrap() - .to_raw_hash() - .to_byte_array(), - ); - - let block_txs = get_mock_txs(); - - // relevant txs are on 6, 8, 10, 12 indices - let completeness_proof = [ - block_txs[6].clone(), - block_txs[8].clone(), - block_txs[10].clone(), - block_txs[12].clone(), - ] - .into_iter() - .map(Into::into) - .collect(); - - let mut inclusion_proof = InclusionMultiProof { - txids: block_txs - .iter() - .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) - .collect(), - wtxids: block_txs - .iter() - .map(|t| t.compute_wtxid().to_byte_array()) - .collect(), - coinbase_tx: block_txs[0].clone().into(), - }; - - // Coinbase tx wtxid should be [0u8;32] - inclusion_proof.wtxids[0] = [0; 32]; - - let txs: Vec = vec![ - get_blob_with_sender(&block_txs[6]), - get_blob_with_sender(&block_txs[8]), - get_blob_with_sender(&block_txs[10]), - get_blob_with_sender(&block_txs[12]), - ]; - - (header, inclusion_proof, completeness_proof, txs) + unimplemented!("mock tx data") } +// #[allow(clippy::type_complexity)] +// pub(crate) fn get_mock_data() -> ( +// <::Spec as DaSpec>::BlockHeader, // block header +// <::Spec as DaSpec>::InclusionMultiProof, // inclusion proof +// <::Spec as DaSpec>::CompletenessProof, // completeness proof +// Vec<<::Spec as DaSpec>::BlobTransaction>, // txs +// ) { +// let header = HeaderWrapper::new( +// Header { +// version: Version::from_consensus(536870912), +// prev_blockhash: BlockHash::from_str( +// "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", +// ) +// .unwrap(), +// merkle_root: TxMerkleNode::from_str( +// "2b84e6a7607e4e383c08af0f3089e460cd52c43ebb7587422064e86402e2f474", +// ) +// .unwrap(), +// time: 1723123913, +// bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), +// nonce: 1, +// }, +// 48, +// 1001, +// WitnessMerkleNode::from_str( +// "bfd78d42d5a8ec8fe480a92521806d7648bb3b42d106bb95878609595efbc232", +// ) +// .unwrap() +// .to_raw_hash() +// .to_byte_array(), +// ); + +// let block_txs = get_mock_txs(); + +// // parse_transaction(&block_txs[3], "sov-btc").unwrap(); + +// // for (id, tx) in block_txs.iter().enumerate() { +// // let r = parse_transaction(tx, "sov-btc"); +// // let err = if let Err(e) = r { +// // e +// // } else { +// // ParserError::ScriptError("OK".to_string()) +// // }; +// // dbg!(id, err); +// // } + +// // relevant txs are on 6, 8, 10, 12 indices +// let completeness_proof = [ +// 4, // complete +// 6, // complete +// 9, // kind 2 +// 13, 15, 17, 19, // chain +// 21, 23, 25, 27, // chain +// 29, 31, 33, 35, 37, // chain +// 41, // complete +// 43, 45, 47, // chain +// ] +// .into_iter() +// .map(|i| block_txs[i].clone()) +// .map(Into::into) +// .collect(); +// // let completeness_proof = [ +// // block_txs[4].clone(), +// // block_txs[6].clone(), +// // block_txs[10].clone(), +// // block_txs[12].clone(), +// // ] +// // .into_iter() +// // .map(Into::into) +// // .collect(); + +// let mut inclusion_proof = InclusionMultiProof { +// txids: block_txs +// .iter() +// .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) +// .collect(), +// wtxids: block_txs +// .iter() +// .map(|t| t.compute_wtxid().to_byte_array()) +// .collect(), +// coinbase_tx: block_txs[0].clone().into(), +// }; + +// // Coinbase tx wtxid should be [0u8;32] +// inclusion_proof.wtxids[0] = [0; 32]; + +// let txs: Vec = vec![ +// get_blob_with_sender(&block_txs[6]), +// get_blob_with_sender(&block_txs[8]), +// get_blob_with_sender(&block_txs[10]), +// get_blob_with_sender(&block_txs[12]), +// ]; + +// (header, inclusion_proof, completeness_proof, txs) +// } + pub(crate) fn get_non_segwit_mock_txs() -> Vec { // There are no relevant txs let txs = std::fs::read_to_string("test_data/mock_non_segwit_txs.txt").unwrap(); diff --git a/crates/bitcoin-da/src/service.rs b/crates/bitcoin-da/src/service.rs index a8fc92a11..2a5fa19d6 100644 --- a/crates/bitcoin-da/src/service.rs +++ b/crates/bitcoin-da/src/service.rs @@ -25,12 +25,12 @@ use tokio::sync::oneshot::channel as oneshot_channel; use tracing::{debug, error, info, instrument, trace}; use crate::helpers::builders::{ - create_inscription_transactions, sign_blob_with_private_key, write_inscription_txs, - InscriptionTxs, TxWithId, + create_seqcommitment_transactions, create_zkproof_transactions, sign_blob_with_private_key, + write_inscription_txs, BatchProvingTxs, LightClientTxs, TxWithId, }; use crate::helpers::compression::{compress_blob, decompress_blob}; use crate::helpers::merkle_tree::BitcoinMerkleTree; -use crate::helpers::parsers::parse_transaction; +use crate::helpers::parsers::{parse_batch_proof_transaction, ParsedBatchProofTransaction}; use crate::helpers::{calculate_double_sha256, merkle_tree}; use crate::spec::blob::BlobWithSender; use crate::spec::block::BitcoinBlock; @@ -50,7 +50,8 @@ pub struct BitcoinService { rollup_name: String, network: bitcoin::Network, da_private_key: Option, - reveal_wtxid_prefix: Vec, + reveal_light_client_prefix: Vec, + reveal_batch_prover_prefix: Vec, inscribes_queue: UnboundedSender>, } @@ -99,7 +100,8 @@ impl BitcoinService { chain_params.rollup_name, config.network, private_key, - chain_params.reveal_wtxid_prefix, + chain_params.reveal_light_client_prefix, + chain_params.reveal_batch_prover_prefix, tx, ) .await) @@ -136,7 +138,6 @@ impl BitcoinService { let prev = prev_tx.take(); loop { // Build and send tx with retries: - let blob = borsh::to_vec(&request.da_data).expect("Should serialize"); let fee_sat_per_vbyte = match self.get_fee_rate().await { Ok(rate) => rate, Err(e) => { @@ -146,7 +147,11 @@ impl BitcoinService { } }; match self - .send_transaction_with_fee_rate(prev.clone(), blob, fee_sat_per_vbyte) + .send_transaction_with_fee_rate( + prev.clone(), + &request.da_data, + fee_sat_per_vbyte, + ) .await { Ok(tx) => { @@ -183,7 +188,7 @@ impl BitcoinService { ) .await?; - let private_key = config + let da_private_key = config .da_private_key .map(|pk| SecretKey::from_str(&pk)) .transpose() @@ -195,8 +200,9 @@ impl BitcoinService { client, rollup_name: chain_params.rollup_name, network: config.network, - da_private_key: private_key, - reveal_wtxid_prefix: chain_params.reveal_wtxid_prefix, + da_private_key, + reveal_light_client_prefix: chain_params.reveal_light_client_prefix, + reveal_batch_prover_prefix: chain_params.reveal_batch_prover_prefix, inscribes_queue: tx, }) } @@ -206,7 +212,8 @@ impl BitcoinService { rollup_name: String, network: bitcoin::Network, da_private_key: Option, - reveal_wtxid_prefix: Vec, + reveal_light_client_prefix: Vec, + reveal_batch_prover_prefix: Vec, inscribes_queue: UnboundedSender>, ) -> Self { let wallets = client @@ -223,7 +230,8 @@ impl BitcoinService { rollup_name, network, da_private_key, - reveal_wtxid_prefix, + reveal_light_client_prefix, + reveal_batch_prover_prefix, inscribes_queue, } } @@ -289,7 +297,7 @@ impl BitcoinService { pub async fn send_transaction_with_fee_rate( &self, prev_tx: Option, - blob: Vec, + da_data: &DaData, fee_sat_per_vbyte: f64, ) -> Result { let client = &self.client; @@ -299,7 +307,15 @@ impl BitcoinService { let da_private_key = self.da_private_key.expect("No private key set"); // Compress the blob - let blob = compress_blob(&blob); + let blob = match da_data { + DaData::ZKProof(proof) => { + let blob = borsh::to_vec(&proof).expect("Should serialize"); + compress_blob(&blob) + } + DaData::SequencerCommitment(commitment) => { + borsh::to_vec(&commitment).expect("Should serialize") + } + }; // get all available utxos let utxos = self.get_utxos().await?; @@ -316,27 +332,117 @@ impl BitcoinService { let (signature, public_key) = sign_blob_with_private_key(&blob, &da_private_key).expect("Sequencer sign the blob"); - // create inscribe transactions - let inscription_txs = create_inscription_transactions( - &rollup_name, - blob, - signature, - public_key, - prev_tx, - utxos, - address, - REVEAL_OUTPUT_AMOUNT, - fee_sat_per_vbyte, - fee_sat_per_vbyte, - network, - self.reveal_wtxid_prefix.as_slice(), - )?; + match da_data { + DaData::ZKProof(_) => { + // create inscribe transactions + let inscription_txs = create_zkproof_transactions( + &rollup_name, + blob, + signature, + public_key, + prev_tx, + utxos, + address, + REVEAL_OUTPUT_AMOUNT, + fee_sat_per_vbyte, + fee_sat_per_vbyte, + network, + &self.reveal_light_client_prefix, + )?; + + // write txs to file, it can be used to continue revealing blob if something goes wrong + write_inscription_txs(&inscription_txs); + + match inscription_txs { + LightClientTxs::Complete { commit, reveal } => { + // sign inscribe transactions + let signed_raw_commit_tx = client + .sign_raw_transaction_with_wallet(&commit, None, None) + .await?; + + // send inscribe transactions + client + .send_raw_transaction(&signed_raw_commit_tx.hex) + .await?; + + // serialize reveal tx + let serialized_reveal_tx = &encode::serialize(&reveal.tx); + + // send reveal tx + let reveal_tx_hash = + client.send_raw_transaction(serialized_reveal_tx).await?; + + info!("Blob inscribe tx sent. Hash: {}", reveal_tx_hash); + Ok(reveal) + } + LightClientTxs::Chunked { + commit_chunks, + reveal_chunks, + commit, + reveal, + } => { + for (commit, reveal) in commit_chunks.into_iter().zip(reveal_chunks) { + // sign inscribe transactions + let signed_raw_commit_tx = client + .sign_raw_transaction_with_wallet(&commit, None, None) + .await?; + + // send inscribe transactions + client + .send_raw_transaction(&signed_raw_commit_tx.hex) + .await?; + + // serialize reveal tx + let serialized_reveal_tx = encode::serialize(&reveal); + + // send reveal tx + let reveal_tx_hash = + client.send_raw_transaction(&serialized_reveal_tx).await?; + info!("Blob chunk inscribe tx sent. Hash: {}", reveal_tx_hash); + } + + // sign inscribe transactions + let signed_raw_commit_tx = client + .sign_raw_transaction_with_wallet(&commit, None, None) + .await?; - // write txs to file, it can be used to continue revealing blob if something goes wrong - write_inscription_txs(&inscription_txs); + // send inscribe transactions + client + .send_raw_transaction(&signed_raw_commit_tx.hex) + .await?; - match inscription_txs { - InscriptionTxs::Complete { commit, reveal } => { + // serialize reveal tx + let serialized_reveal_tx = encode::serialize(&reveal.tx); + + // send reveal tx + let reveal_tx_hash = + client.send_raw_transaction(&serialized_reveal_tx).await?; + info!("Blob chunk aggregate tx sent. Hash: {}", reveal_tx_hash); + Ok(reveal) + } + } + } + DaData::SequencerCommitment(_) => { + // create inscribe transactions + let inscription_txs = create_seqcommitment_transactions( + &rollup_name, + blob, + signature, + public_key, + prev_tx, + utxos, + address, + REVEAL_OUTPUT_AMOUNT, + fee_sat_per_vbyte, + fee_sat_per_vbyte, + network, + &self.reveal_batch_prover_prefix, + )?; + + // write txs to file, it can be used to continue revealing blob if something goes wrong + write_inscription_txs(&inscription_txs); + + let BatchProvingTxs { commit, reveal } = inscription_txs; // sign inscribe transactions let signed_raw_commit_tx = client .sign_raw_transaction_with_wallet(&commit, None, None) @@ -356,49 +462,6 @@ impl BitcoinService { info!("Blob inscribe tx sent. Hash: {}", reveal_tx_hash); Ok(reveal) } - InscriptionTxs::Chunked { - commit_chunks, - reveal_chunks, - commit, - reveal, - } => { - for (commit, reveal) in commit_chunks.into_iter().zip(reveal_chunks) { - // sign inscribe transactions - let signed_raw_commit_tx = client - .sign_raw_transaction_with_wallet(&commit, None, None) - .await?; - - // send inscribe transactions - client - .send_raw_transaction(&signed_raw_commit_tx.hex) - .await?; - - // serialize reveal tx - let serialized_reveal_tx = encode::serialize(&reveal); - - // send reveal tx - let reveal_tx_hash = client.send_raw_transaction(&serialized_reveal_tx).await?; - info!("Blob chunk inscribe tx sent. Hash: {}", reveal_tx_hash); - } - - // sign inscribe transactions - let signed_raw_commit_tx = client - .sign_raw_transaction_with_wallet(&commit, None, None) - .await?; - - // send inscribe transactions - client - .send_raw_transaction(&signed_raw_commit_tx.hex) - .await?; - - // serialize reveal tx - let serialized_reveal_tx = encode::serialize(&reveal.tx); - - // send reveal tx - let reveal_tx_hash = client.send_raw_transaction(&serialized_reveal_tx).await?; - info!("Blob chunk aggregate tx sent. Hash: {}", reveal_tx_hash); - Ok(reveal) - } } } @@ -540,7 +603,7 @@ impl DaService for BitcoinService { ); let txs = block.txdata.iter().map(|tx| tx.inner().clone()).collect(); - get_relevant_blobs_from_txs(txs, &self.rollup_name, self.reveal_wtxid_prefix.as_slice()) + get_relevant_blobs_from_txs(txs, &self.rollup_name, &self.reveal_batch_prover_prefix) } #[instrument(level = "trace", skip_all)] @@ -563,15 +626,15 @@ impl DaService for BitcoinService { wtxids.push([0u8; 32]); // coinbase starts with 0, so we skip it unless the prefix is all 0's - if self.reveal_wtxid_prefix.iter().all(|&x| x == 0) { + if self.reveal_batch_prover_prefix.iter().all(|&x| x == 0) { completeness_proof.push(block.txdata[0].clone()); } block.txdata[1..].iter().for_each(|tx| { let wtxid = tx.compute_wtxid().to_raw_hash().to_byte_array(); - // if tx_hash has two leading zeros, it is in the completeness proof - if wtxid.starts_with(self.reveal_wtxid_prefix.as_slice()) { + // if tx_hash starts with the given prefix, it is in the completeness proof + if wtxid.starts_with(&self.reveal_batch_prover_prefix) { completeness_proof.push(tx.clone()); } @@ -704,7 +767,7 @@ impl DaService for BitcoinService { get_relevant_blobs_from_txs( pending_txs, &self.rollup_name, - self.reveal_wtxid_prefix.as_slice(), + &self.reveal_batch_prover_prefix, ) } } @@ -726,20 +789,16 @@ fn get_relevant_blobs_from_txs( continue; } - let parsed_inscription = parse_transaction(&tx, rollup_name); + if let Ok(tx) = parse_batch_proof_transaction(&tx, rollup_name) { + match tx { + 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); - if let Ok(inscription) = parsed_inscription { - if inscription.get_sig_verified_hash().is_some() { - // Decompress the blob - let decompressed_blob = decompress_blob(&inscription.body); - - let relevant_tx = BlobWithSender::new( - decompressed_blob, - inscription.public_key, - calculate_double_sha256(&inscription.body), - ); - - relevant_txs.push(relevant_tx); + relevant_txs.push(relevant_tx); + } + } } } } @@ -785,7 +844,8 @@ mod tests { runtime_config, RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }, ) .await @@ -877,7 +937,8 @@ mod tests { async fn extract_relevant_blobs_with_proof() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let da_service = get_service().await; @@ -923,7 +984,8 @@ mod tests { runtime_config, RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }, ) .await diff --git a/crates/bitcoin-da/src/spec/mod.rs b/crates/bitcoin-da/src/spec/mod.rs index fa27a470e..7ba4adb1d 100644 --- a/crates/bitcoin-da/src/spec/mod.rs +++ b/crates/bitcoin-da/src/spec/mod.rs @@ -26,7 +26,8 @@ pub struct BitcoinSpec; pub struct RollupParams { pub rollup_name: String, - pub reveal_wtxid_prefix: Vec, + pub reveal_light_client_prefix: Vec, + pub reveal_batch_prover_prefix: Vec, } impl DaSpec for BitcoinSpec { diff --git a/crates/bitcoin-da/src/verifier.rs b/crates/bitcoin-da/src/verifier.rs index 15e1c5eaa..20b679c35 100644 --- a/crates/bitcoin-da/src/verifier.rs +++ b/crates/bitcoin-da/src/verifier.rs @@ -9,7 +9,7 @@ use sov_rollup_interface::zk::ValidityCondition; use thiserror::Error; use crate::helpers::compression::decompress_blob; -use crate::helpers::parsers::parse_transaction; +use crate::helpers::parsers::{parse_batch_proof_transaction, ParsedBatchProofTransaction}; use crate::helpers::{calculate_double_sha256, merkle_tree}; use crate::spec::BitcoinSpec; @@ -17,7 +17,7 @@ pub const WITNESS_COMMITMENT_PREFIX: &[u8] = &[0x6a, 0x24, 0xaa, 0x21, 0xa9, 0xe pub struct BitcoinVerifier { rollup_name: String, - reveal_wtxid_prefix: Vec, + reveal_batch_prover_prefix: Vec, } // TODO: custom errors based on our implementation @@ -79,7 +79,8 @@ impl DaVerifier for BitcoinVerifier { fn new(params: ::ChainParams) -> Self { Self { rollup_name: params.rollup_name, - reveal_wtxid_prefix: params.reveal_wtxid_prefix, + // TODO + reveal_batch_prover_prefix: params.reveal_batch_prover_prefix, } } @@ -96,7 +97,7 @@ impl DaVerifier for BitcoinVerifier { let mut inclusion_iter = inclusion_proof.wtxids.iter(); - let prefix = self.reveal_wtxid_prefix.as_slice(); + let prefix = self.reveal_batch_prover_prefix.as_slice(); // Check starting bytes tx that parsed correctly is in blobs let mut completeness_tx_hashes = BTreeSet::new(); @@ -117,34 +118,38 @@ impl DaVerifier for BitcoinVerifier { } // it must be parsed correctly - if let Ok(parsed_tx) = parse_transaction(tx, &self.rollup_name) { - if let Some(blob_hash) = parsed_tx.get_sig_verified_hash() { - let blob = blobs_iter.next(); - - if blob.is_none() { - return Err(ValidationError::ValidBlobNotFoundInBlobs); - } - - let blob = blob.unwrap(); - if blob.hash != blob_hash { - return Err(ValidationError::BlobWasTamperedWith); - } - - if parsed_tx.public_key != blob.sender.0 { - return Err(ValidationError::IncorrectSenderInBlob); - } - - // decompress the blob - let decompressed_blob = decompress_blob(&parsed_tx.body); - - // read the supplied blob from txs - let mut blob_content = blobs[index_completeness].blob.clone(); - blob_content.advance(blob_content.total_len()); - let blob_content = blob_content.accumulator(); - - // assert tx content is not modified - if blob_content != decompressed_blob { - return Err(ValidationError::BlobContentWasModified); + if let Ok(parsed_tx) = parse_batch_proof_transaction(tx, &self.rollup_name) { + match parsed_tx { + ParsedBatchProofTransaction::SequencerCommitment(seq_comm) => { + if let Some(blob_hash) = seq_comm.get_sig_verified_hash() { + let blob = blobs_iter.next(); + + if blob.is_none() { + return Err(ValidationError::ValidBlobNotFoundInBlobs); + } + + let blob = blob.unwrap(); + if blob.hash != blob_hash { + return Err(ValidationError::BlobWasTamperedWith); + } + + if seq_comm.public_key != blob.sender.0 { + return Err(ValidationError::IncorrectSenderInBlob); + } + + // decompress the blob + let decompressed_blob = decompress_blob(&seq_comm.body); + + // read the supplied blob from txs + let mut blob_content = blobs[index_completeness].blob.clone(); + blob_content.advance(blob_content.total_len()); + let blob_content = blob_content.accumulator(); + + // assert tx content is not modified + if blob_content != decompressed_blob { + return Err(ValidationError::BlobContentWasModified); + } + } } } } @@ -255,7 +260,7 @@ mod tests { use sov_rollup_interface::da::DaVerifier; use super::BitcoinVerifier; - use crate::helpers::parsers::parse_transaction; + use crate::helpers::parsers::{parse_batch_proof_transaction, ParsedBatchProofTransaction}; use crate::helpers::test_utils::{ get_blob_with_sender, get_mock_data, get_mock_txs, get_non_segwit_mock_txs, }; @@ -270,7 +275,8 @@ mod tests { fn correct() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, completeness_proof, txs) = get_mock_data(); @@ -284,379 +290,390 @@ mod tests { ) .is_ok()); } - #[test] - fn test_non_segwit_block() { - let verifier = BitcoinVerifier::new(RollupParams { - rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], - }); - let header = HeaderWrapper::new( - Header { - version: Version::from_consensus(536870912), - prev_blockhash: BlockHash::from_str( - "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", - ) - .unwrap(), - merkle_root: TxMerkleNode::from_slice(&[ - 164, 71, 72, 235, 241, 189, 131, 141, 120, 210, 207, 233, 212, 171, 56, 52, 25, - 40, 83, 62, 135, 211, 81, 44, 3, 109, 10, 127, 210, 213, 124, 221, - ]) - .unwrap(), - time: 1694177029, - bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), - nonce: 0, - }, - 6, - 2, - WitnessMerkleNode::from_str( - "a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71", - ) - .unwrap(), - ); - - let block_txs = get_non_segwit_mock_txs(); - let block_txs: Vec = block_txs.into_iter().map(Into::into).collect(); - - // block does not have any segwit txs - let idx = block_txs[0].output.iter().position(|output| { - output - .script_pubkey - .to_bytes() - .starts_with(WITNESS_COMMITMENT_PREFIX) - }); - assert!(idx.is_none()); - - // tx with txid 00... is not relevant is in this proof - // only used so the completeness proof is not empty - let completeness_proof = vec![]; - - let inclusion_proof = InclusionMultiProof { - txids: block_txs - .iter() - .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) - .collect(), - wtxids: block_txs - .iter() - .map(|t| t.compute_wtxid().to_byte_array()) - .collect(), - coinbase_tx: block_txs[0].clone(), - }; - - // There should not be any blobs - let txs: Vec = vec![]; - - assert!(matches!( - verifier.verify_relevant_tx_list( - &header, - txs.as_slice(), - inclusion_proof, - completeness_proof - ), - Ok(ChainValidityCondition { - prev_hash: _, - block_hash: _ - }) - )); - } - - #[test] - fn false_coinbase_input_witness_should_fail() { - let verifier = BitcoinVerifier::new(RollupParams { - rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], - }); - - let header = HeaderWrapper::new( - Header { - version: Version::from_consensus(536870912), - prev_blockhash: BlockHash::from_str( - "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", - ) - .unwrap(), - merkle_root: TxMerkleNode::from_str( - "7750076b3b5498aad3e2e7da55618c66394d1368dc08f19f0b13d1e5b83ae056", - ) - .unwrap(), - time: 1694177029, - bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), - nonce: 0, - }, - 13, - 2, - WitnessMerkleNode::from_str( - "a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71", - ) - .unwrap(), - ); - - let block_txs = get_mock_txs(); - let mut block_txs: Vec = - block_txs.into_iter().map(Into::into).collect(); - - block_txs[0].input[0].witness = Witness::from_slice(&[vec![1u8; 32]]); - - // relevant txs are on 6, 8, 10, 12 indices - let completeness_proof = [ - block_txs[6].clone(), - block_txs[8].clone(), - block_txs[10].clone(), - block_txs[12].clone(), - ] - .into_iter() - .map(Into::into) - .collect(); - - let mut inclusion_proof = InclusionMultiProof { - txids: block_txs - .iter() - .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) - .collect(), - wtxids: block_txs - .iter() - .map(|t| t.compute_wtxid().to_byte_array()) - .collect(), - coinbase_tx: block_txs[0].clone(), - }; - - // Coinbase tx wtxid should be [0u8;32] - inclusion_proof.wtxids[0] = [0; 32]; - - let txs: Vec = vec![ - get_blob_with_sender(&block_txs[6]), - get_blob_with_sender(&block_txs[8]), - get_blob_with_sender(&block_txs[10]), - get_blob_with_sender(&block_txs[12]), - ]; - - assert_eq!( - verifier.verify_relevant_tx_list( - &header, - txs.as_slice(), - inclusion_proof, - completeness_proof - ), - Err(ValidationError::NonMatchingScript) - ); - } - - #[test] - fn false_coinbase_script_pubkey_should_fail() { - let verifier = BitcoinVerifier::new(RollupParams { - rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], - }); - - let header = HeaderWrapper::new( - Header { - version: Version::from_consensus(536870912), - prev_blockhash: BlockHash::from_str( - "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", - ) - .unwrap(), - merkle_root: TxMerkleNode::from_str( - "7750076b3b5498aad3e2e7da55618c66394d1368dc08f19f0b13d1e5b83ae056", - ) - .unwrap(), - time: 1694177029, - bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), - nonce: 0, - }, - 13, - 2, - WitnessMerkleNode::from_str( - "a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71", - ) - .unwrap(), - ); - - let block_txs = get_mock_txs(); - let mut block_txs: Vec = - block_txs.into_iter().map(Into::into).collect(); - - let idx = block_txs[0] - .output - .iter() - .position(|output| { - output - .script_pubkey - .to_bytes() - .starts_with(WITNESS_COMMITMENT_PREFIX) - }) - .unwrap(); - - // the 7th byte of script pubkey is changed from 104 to 105 - block_txs[0].output[idx].script_pubkey = ScriptBuf::from_bytes(vec![ - 106, 36, 170, 33, 169, 237, 105, 181, 249, 155, 21, 242, 213, 115, 55, 123, 70, 108, - 15, 173, 14, 106, 243, 231, 186, 128, 75, 251, 178, 9, 24, 228, 200, 177, 144, 89, 95, - 182, - ]); - - // relevant txs are on 6, 8, 10, 12 indices - let completeness_proof = [ - block_txs[6].clone(), - block_txs[8].clone(), - block_txs[10].clone(), - block_txs[12].clone(), - ] - .into_iter() - .map(Into::into) - .collect(); - - let mut inclusion_proof = InclusionMultiProof { - txids: block_txs - .iter() - .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) - .collect(), - wtxids: block_txs - .iter() - .map(|t| t.compute_wtxid().to_byte_array()) - .collect(), - coinbase_tx: block_txs[0].clone(), - }; - - // Coinbase tx wtxid should be [0u8;32] - inclusion_proof.wtxids[0] = [0; 32]; - - let txs: Vec = vec![ - get_blob_with_sender(&block_txs[6]), - get_blob_with_sender(&block_txs[8]), - get_blob_with_sender(&block_txs[10]), - get_blob_with_sender(&block_txs[12]), - ]; - - assert_eq!( - verifier.verify_relevant_tx_list( - &header, - txs.as_slice(), - inclusion_proof, - completeness_proof - ), - Err(ValidationError::NonMatchingScript) - ); - } - - #[test] - fn false_witness_script_should_fail() { - let verifier = BitcoinVerifier::new(RollupParams { - rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], - }); - - let header = HeaderWrapper::new( - Header { - version: Version::from_consensus(536870912), - prev_blockhash: BlockHash::from_str( - "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", - ) - .unwrap(), - merkle_root: TxMerkleNode::from_str( - "7750076b3b5498aad3e2e7da55618c66394d1368dc08f19f0b13d1e5b83ae056", - ) - .unwrap(), - time: 1694177029, - bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), - nonce: 0, - }, - 13, - 2, - WitnessMerkleNode::from_str( - "a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71", - ) - .unwrap(), - ); - - let block_txs = get_mock_txs(); - let mut block_txs: Vec = - block_txs.into_iter().map(Into::into).collect(); - - // This is the changed witness of the 6th tx, the first byte of script is changed from 32 to 33 - // This creates a different wtxid, thus the verification should fail - let changed_witness: Vec> = vec![ - vec![ - 81, 88, 52, 28, 35, 77, 19, 30, 98, 146, 2, 231, 141, 193, 70, 58, 24, 252, 94, - 184, 169, 253, 234, 219, 176, 172, 224, 112, 128, 144, 70, 134, 16, 75, 6, 112, - 182, 76, 230, 26, 239, 154, 8, 219, 123, 102, 210, 203, 74, 187, 185, 45, 3, 35, - 94, 95, 64, 209, 195, 34, 66, 246, 47, 239, - ], - vec![ - 33, 113, 162, 71, 125, 67, 165, 112, 30, 91, 79, 0, 158, 242, 217, 32, 194, 150, - 158, 249, 221, 71, 241, 82, 79, 243, 107, 93, 250, 8, 122, 90, 29, 172, 0, 99, 1, - 1, 7, 115, 111, 118, 45, 98, 116, 99, 1, 2, 64, 204, 75, 35, 210, 203, 62, 34, 178, - 197, 122, 89, 242, 64, 136, 118, 79, 57, 247, 183, 137, 132, 126, 152, 59, 158, - 233, 206, 118, 130, 87, 140, 43, 125, 189, 244, 56, 78, 35, 12, 148, 43, 145, 174, - 92, 230, 177, 186, 51, 88, 127, 84, 159, 237, 238, 77, 25, 229, 79, 243, 168, 229, - 70, 1, 232, 1, 3, 33, 2, 88, 141, 32, 42, 252, 193, 238, 74, 181, 37, 76, 120, 71, - 236, 37, 185, 161, 53, 187, 218, 15, 43, 198, 158, 225, 167, 20, 116, 159, 215, - 125, 201, 1, 4, 3, 140, 4, 3, 0, 76, 196, 27, 123, 1, 248, 69, 199, 134, 177, 14, - 144, 99, 139, 92, 216, 128, 35, 8, 24, 35, 176, 108, 32, 185, 0, 64, 64, 16, 82, - 134, 7, 56, 167, 198, 205, 96, 199, 53, 143, 88, 17, 88, 187, 247, 230, 188, 146, - 199, 57, 30, 254, 87, 237, 64, 197, 147, 216, 162, 224, 152, 57, 150, 149, 38, 166, - 136, 221, 108, 223, 62, 19, 150, 90, 236, 168, 89, 44, 83, 183, 232, 187, 206, 143, - 137, 234, 84, 146, 177, 70, 242, 67, 179, 229, 165, 3, 94, 174, 81, 199, 235, 230, - 184, 188, 60, 171, 3, 72, 123, 113, 167, 153, 1, 22, 216, 181, 175, 220, 83, 55, - 14, 149, 187, 22, 167, 192, 173, 189, 132, 137, 116, 155, 150, 173, 21, 174, 68, - 140, 43, 227, 187, 51, 47, 125, 195, 155, 109, 150, 123, 2, 111, 159, 89, 26, 249, - 111, 54, 105, 241, 247, 201, 204, 123, 29, 208, 71, 162, 195, 146, 187, 209, 69, - 218, 241, 17, 66, 119, 98, 83, 228, 32, 245, 236, 204, 22, 154, 251, 85, 105, 61, - 15, 235, 194, 127, 13, 177, 89, 3, 104, - ], - vec![ - 193, 113, 162, 71, 125, 67, 165, 112, 30, 91, 79, 0, 158, 242, 217, 32, 194, 150, - 158, 249, 221, 71, 241, 82, 79, 243, 107, 93, 250, 8, 122, 90, 29, - ], - ]; - - block_txs[6].input[0].witness = Witness::from_slice(&changed_witness); - // relevant txs are on 6, 8, 10, 12 indices - let completeness_proof = [ - block_txs[6].clone(), - block_txs[8].clone(), - block_txs[10].clone(), - block_txs[12].clone(), - ] - .into_iter() - .map(Into::into) - .collect(); - - let mut inclusion_proof = InclusionMultiProof { - txids: block_txs - .iter() - .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) - .collect(), - wtxids: block_txs - .iter() - .map(|t| t.compute_wtxid().to_byte_array()) - .collect(), - coinbase_tx: block_txs[0].clone(), - }; - - // Coinbase tx wtxid should be [0u8;32] - inclusion_proof.wtxids[0] = [0; 32]; - - let txs: Vec = vec![ - get_blob_with_sender(&block_txs[6]), - get_blob_with_sender(&block_txs[8]), - get_blob_with_sender(&block_txs[10]), - get_blob_with_sender(&block_txs[12]), - ]; - - assert_eq!( - verifier.verify_relevant_tx_list( - &header, - txs.as_slice(), - inclusion_proof, - completeness_proof - ), - Err(ValidationError::NonMatchingScript) - ); - } + // #[test] + // fn test_non_segwit_block() { + // let verifier = BitcoinVerifier::new(RollupParams { + // rollup_name: "sov-btc".to_string(), + // reveal_batch_prover_prefix: vec![1, 1], + // reveal_light_client_prefix: vec![2, 2], + // }); + // let header = HeaderWrapper::new( + // Header { + // version: Version::from_consensus(536870912), + // prev_blockhash: BlockHash::from_str( + // "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", + // ) + // .unwrap(), + // merkle_root: TxMerkleNode::from_slice(&[ + // 164, 71, 72, 235, 241, 189, 131, 141, 120, 210, 207, 233, 212, 171, 56, 52, 25, + // 40, 83, 62, 135, 211, 81, 44, 3, 109, 10, 127, 210, 213, 124, 221, + // ]) + // .unwrap(), + // time: 1694177029, + // bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), + // nonce: 0, + // }, + // 6, + // 2, + // WitnessMerkleNode::from_str( + // "a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71", + // ) + // .unwrap(), + // ); + + // let block_txs = get_non_segwit_mock_txs(); + // let block_txs: Vec = block_txs.into_iter().map(Into::into).collect(); + + // // block does not have any segwit txs + // let idx = block_txs[0].output.iter().position(|output| { + // output + // .script_pubkey + // .to_bytes() + // .starts_with(WITNESS_COMMITMENT_PREFIX) + // }); + // assert!(idx.is_none()); + + // // tx with txid 00... is not relevant is in this proof + // // only used so the completeness proof is not empty + // let completeness_proof = vec![]; + + // let inclusion_proof = InclusionMultiProof { + // txids: block_txs + // .iter() + // .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) + // .collect(), + // wtxids: block_txs + // .iter() + // .map(|t| t.compute_wtxid().to_byte_array()) + // .collect(), + // coinbase_tx: block_txs[0].clone(), + // }; + + // // There should not be any blobs + // let txs: Vec = vec![]; + + // assert!(matches!( + // verifier.verify_relevant_tx_list( + // &header, + // txs.as_slice(), + // inclusion_proof, + // completeness_proof + // ), + // Ok(ChainValidityCondition { + // prev_hash: _, + // block_hash: _ + // }) + // )); + // } + + // #[test] + // fn false_coinbase_input_witness_should_fail() { + // let verifier = BitcoinVerifier::new(RollupParams { + // rollup_name: "sov-btc".to_string(), + // reveal_batch_prover_prefix: vec![1, 1], + // reveal_light_client_prefix: vec![2, 2], + // }); + + // let header = HeaderWrapper::new( + // Header { + // version: Version::from_consensus(536870912), + // prev_blockhash: BlockHash::from_str( + // "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", + // ) + // .unwrap(), + // merkle_root: TxMerkleNode::from_str( + // "7750076b3b5498aad3e2e7da55618c66394d1368dc08f19f0b13d1e5b83ae056", + // ) + // .unwrap(), + // time: 1694177029, + // bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), + // nonce: 0, + // }, + // 13, + // 2, + // WitnessMerkleNode::from_str( + // "a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71", + // ) + // .unwrap() + // .to_raw_hash() + // .to_byte_array(), + // ); + + // let block_txs = get_mock_txs(); + // let mut block_txs: Vec = + // block_txs.into_iter().map(Into::into).collect(); + + // block_txs[0].input[0].witness = Witness::from_slice(&[vec![1u8; 32]]); + + // // relevant txs are on 6, 8, 10, 12 indices + // let completeness_proof = [ + // block_txs[6].clone(), + // block_txs[8].clone(), + // block_txs[10].clone(), + // block_txs[12].clone(), + // ] + // .into_iter() + // .map(Into::into) + // .collect(); + + // let mut inclusion_proof = InclusionMultiProof { + // txids: block_txs + // .iter() + // .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) + // .collect(), + // wtxids: block_txs + // .iter() + // .map(|t| t.compute_wtxid().to_byte_array()) + // .collect(), + // coinbase_tx: block_txs[0].clone(), + // }; + + // // Coinbase tx wtxid should be [0u8;32] + // inclusion_proof.wtxids[0] = [0; 32]; + + // let txs: Vec = vec![ + // get_blob_with_sender(&block_txs[6]), + // get_blob_with_sender(&block_txs[8]), + // get_blob_with_sender(&block_txs[10]), + // get_blob_with_sender(&block_txs[12]), + // ]; + + // assert_eq!( + // verifier.verify_relevant_tx_list( + // &header, + // txs.as_slice(), + // inclusion_proof, + // completeness_proof + // ), + // Err(ValidationError::NonMatchingScript) + // ); + // } + + // #[test] + // fn false_coinbase_script_pubkey_should_fail() { + // let verifier = BitcoinVerifier::new(RollupParams { + // rollup_name: "sov-btc".to_string(), + // reveal_batch_prover_prefix: vec![1, 1], + // reveal_light_client_prefix: vec![2, 2], + // }); + + // let header = HeaderWrapper::new( + // Header { + // version: Version::from_consensus(536870912), + // prev_blockhash: BlockHash::from_str( + // "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", + // ) + // .unwrap(), + // merkle_root: TxMerkleNode::from_str( + // "7750076b3b5498aad3e2e7da55618c66394d1368dc08f19f0b13d1e5b83ae056", + // ) + // .unwrap(), + // time: 1694177029, + // bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), + // nonce: 0, + // }, + // 13, + // 2, + // WitnessMerkleNode::from_str( + // "a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71", + // ) + // .unwrap() + // .to_raw_hash() + // .to_byte_array(), + // ); + + // let block_txs = get_mock_txs(); + // let mut block_txs: Vec = + // block_txs.into_iter().map(Into::into).collect(); + + // let idx = block_txs[0] + // .output + // .iter() + // .position(|output| { + // output + // .script_pubkey + // .to_bytes() + // .starts_with(WITNESS_COMMITMENT_PREFIX) + // }) + // .unwrap(); + + // // the 7th byte of script pubkey is changed from 104 to 105 + // block_txs[0].output[idx].script_pubkey = ScriptBuf::from_bytes(vec![ + // 106, 36, 170, 33, 169, 237, 105, 181, 249, 155, 21, 242, 213, 115, 55, 123, 70, 108, + // 15, 173, 14, 106, 243, 231, 186, 128, 75, 251, 178, 9, 24, 228, 200, 177, 144, 89, 95, + // 182, + // ]); + + // // relevant txs are on 6, 8, 10, 12 indices + // let completeness_proof = [ + // block_txs[6].clone(), + // block_txs[8].clone(), + // block_txs[10].clone(), + // block_txs[12].clone(), + // ] + // .into_iter() + // .map(Into::into) + // .collect(); + + // let mut inclusion_proof = InclusionMultiProof { + // txids: block_txs + // .iter() + // .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) + // .collect(), + // wtxids: block_txs + // .iter() + // .map(|t| t.compute_wtxid().to_byte_array()) + // .collect(), + // coinbase_tx: block_txs[0].clone(), + // }; + + // // Coinbase tx wtxid should be [0u8;32] + // inclusion_proof.wtxids[0] = [0; 32]; + + // let txs: Vec = vec![ + // get_blob_with_sender(&block_txs[6]), + // get_blob_with_sender(&block_txs[8]), + // get_blob_with_sender(&block_txs[10]), + // get_blob_with_sender(&block_txs[12]), + // ]; + + // assert_eq!( + // verifier.verify_relevant_tx_list( + // &header, + // txs.as_slice(), + // inclusion_proof, + // completeness_proof + // ), + // Err(ValidationError::NonMatchingScript) + // ); + // } + + // #[test] + // fn false_witness_script_should_fail() { + // let verifier = BitcoinVerifier::new(RollupParams { + // rollup_name: "sov-btc".to_string(), + // reveal_batch_prover_prefix: vec![1, 1], + // reveal_light_client_prefix: vec![2, 2], + // }); + + // let header = HeaderWrapper::new( + // Header { + // version: Version::from_consensus(536870912), + // prev_blockhash: BlockHash::from_str( + // "6b15a2e4b17b0aabbd418634ae9410b46feaabf693eea4c8621ffe71435d24b0", + // ) + // .unwrap(), + // merkle_root: TxMerkleNode::from_str( + // "7750076b3b5498aad3e2e7da55618c66394d1368dc08f19f0b13d1e5b83ae056", + // ) + // .unwrap(), + // time: 1694177029, + // bits: CompactTarget::from_unprefixed_hex("207fffff").unwrap(), + // nonce: 0, + // }, + // 13, + // 2, + // WitnessMerkleNode::from_str( + // "a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71", + // ) + // .unwrap() + // .to_raw_hash() + // .to_byte_array(), + // ); + + // let block_txs = get_mock_txs(); + // let mut block_txs: Vec = + // block_txs.into_iter().map(Into::into).collect(); + + // // This is the changed witness of the 6th tx, the first byte of script is changed from 32 to 33 + // // This creates a different wtxid, thus the verification should fail + // let changed_witness: Vec> = vec![ + // vec![ + // 81, 88, 52, 28, 35, 77, 19, 30, 98, 146, 2, 231, 141, 193, 70, 58, 24, 252, 94, + // 184, 169, 253, 234, 219, 176, 172, 224, 112, 128, 144, 70, 134, 16, 75, 6, 112, + // 182, 76, 230, 26, 239, 154, 8, 219, 123, 102, 210, 203, 74, 187, 185, 45, 3, 35, + // 94, 95, 64, 209, 195, 34, 66, 246, 47, 239, + // ], + // vec![ + // 33, 113, 162, 71, 125, 67, 165, 112, 30, 91, 79, 0, 158, 242, 217, 32, 194, 150, + // 158, 249, 221, 71, 241, 82, 79, 243, 107, 93, 250, 8, 122, 90, 29, 172, 0, 99, 1, + // 1, 7, 115, 111, 118, 45, 98, 116, 99, 1, 2, 64, 204, 75, 35, 210, 203, 62, 34, 178, + // 197, 122, 89, 242, 64, 136, 118, 79, 57, 247, 183, 137, 132, 126, 152, 59, 158, + // 233, 206, 118, 130, 87, 140, 43, 125, 189, 244, 56, 78, 35, 12, 148, 43, 145, 174, + // 92, 230, 177, 186, 51, 88, 127, 84, 159, 237, 238, 77, 25, 229, 79, 243, 168, 229, + // 70, 1, 232, 1, 3, 33, 2, 88, 141, 32, 42, 252, 193, 238, 74, 181, 37, 76, 120, 71, + // 236, 37, 185, 161, 53, 187, 218, 15, 43, 198, 158, 225, 167, 20, 116, 159, 215, + // 125, 201, 1, 4, 3, 140, 4, 3, 0, 76, 196, 27, 123, 1, 248, 69, 199, 134, 177, 14, + // 144, 99, 139, 92, 216, 128, 35, 8, 24, 35, 176, 108, 32, 185, 0, 64, 64, 16, 82, + // 134, 7, 56, 167, 198, 205, 96, 199, 53, 143, 88, 17, 88, 187, 247, 230, 188, 146, + // 199, 57, 30, 254, 87, 237, 64, 197, 147, 216, 162, 224, 152, 57, 150, 149, 38, 166, + // 136, 221, 108, 223, 62, 19, 150, 90, 236, 168, 89, 44, 83, 183, 232, 187, 206, 143, + // 137, 234, 84, 146, 177, 70, 242, 67, 179, 229, 165, 3, 94, 174, 81, 199, 235, 230, + // 184, 188, 60, 171, 3, 72, 123, 113, 167, 153, 1, 22, 216, 181, 175, 220, 83, 55, + // 14, 149, 187, 22, 167, 192, 173, 189, 132, 137, 116, 155, 150, 173, 21, 174, 68, + // 140, 43, 227, 187, 51, 47, 125, 195, 155, 109, 150, 123, 2, 111, 159, 89, 26, 249, + // 111, 54, 105, 241, 247, 201, 204, 123, 29, 208, 71, 162, 195, 146, 187, 209, 69, + // 218, 241, 17, 66, 119, 98, 83, 228, 32, 245, 236, 204, 22, 154, 251, 85, 105, 61, + // 15, 235, 194, 127, 13, 177, 89, 3, 104, + // ], + // vec![ + // 193, 113, 162, 71, 125, 67, 165, 112, 30, 91, 79, 0, 158, 242, 217, 32, 194, 150, + // 158, 249, 221, 71, 241, 82, 79, 243, 107, 93, 250, 8, 122, 90, 29, + // ], + // ]; + + // block_txs[6].input[0].witness = Witness::from_slice(&changed_witness); + // // relevant txs are on 6, 8, 10, 12 indices + // let completeness_proof = [ + // block_txs[6].clone(), + // block_txs[8].clone(), + // block_txs[10].clone(), + // block_txs[12].clone(), + // ] + // .into_iter() + // .map(Into::into) + // .collect(); + + // let mut inclusion_proof = InclusionMultiProof { + // txids: block_txs + // .iter() + // .map(|t| t.compute_txid().to_raw_hash().to_byte_array()) + // .collect(), + // wtxids: block_txs + // .iter() + // .map(|t| t.compute_wtxid().to_byte_array()) + // .collect(), + // coinbase_tx: block_txs[0].clone(), + // }; + + // // Coinbase tx wtxid should be [0u8;32] + // inclusion_proof.wtxids[0] = [0; 32]; + + // let txs: Vec = vec![ + // get_blob_with_sender(&block_txs[6]), + // get_blob_with_sender(&block_txs[8]), + // get_blob_with_sender(&block_txs[10]), + // get_blob_with_sender(&block_txs[12]), + // ]; + + // assert_eq!( + // verifier.verify_relevant_tx_list( + // &header, + // txs.as_slice(), + // inclusion_proof, + // completeness_proof + // ), + // Err(ValidationError::NonMatchingScript) + // ); + // } // verifies it, and then changes the witness and sees that it cannot be verified #[test] fn different_wtxid_fails_verification() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, mut inclusion_proof, completeness_proof, txs) = get_mock_data(); @@ -696,99 +713,104 @@ mod tests { .is_err()); } - #[test] - fn extra_tx_in_inclusion() { - let verifier = BitcoinVerifier::new(RollupParams { - rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], - }); - - let (block_header, mut inclusion_proof, completeness_proof, txs) = get_mock_data(); - - inclusion_proof.txids.push([1; 32]); - - assert_eq!( - verifier.verify_relevant_tx_list( - &block_header, - txs.as_slice(), - inclusion_proof, - completeness_proof, - ), - Err(ValidationError::IncorrectInclusionProof) - ); - } - - #[test] - fn missing_tx_in_inclusion() { - let verifier = BitcoinVerifier::new(RollupParams { - rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], - }); - - let (block_header, mut inclusion_proof, completeness_proof, txs) = get_mock_data(); - - inclusion_proof.txids.pop(); - - assert_eq!( - verifier.verify_relevant_tx_list( - &block_header, - txs.as_slice(), - inclusion_proof, - completeness_proof, - ), - Err(ValidationError::RelevantTxNotFoundInBlock) - ); - } - - #[test] - fn empty_inclusion() { - let verifier = BitcoinVerifier::new(RollupParams { - rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], - }); - - let (block_header, mut inclusion_proof, completeness_proof, txs) = get_mock_data(); - - inclusion_proof.txids.clear(); - - assert_eq!( - verifier.verify_relevant_tx_list( - &block_header, - txs.as_slice(), - inclusion_proof, - completeness_proof, - ), - Err(ValidationError::RelevantTxNotFoundInBlock) - ); - } - - #[test] - fn break_order_of_inclusion() { - let verifier = BitcoinVerifier::new(RollupParams { - rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], - }); - - let (block_header, mut inclusion_proof, completeness_proof, txs) = get_mock_data(); - - inclusion_proof.txids.swap(0, 1); - - assert_eq!( - verifier.verify_relevant_tx_list( - &block_header, - txs.as_slice(), - inclusion_proof, - completeness_proof, - ), - Err(ValidationError::IncorrectInclusionProof) - ); - } + // #[test] + // fn extra_tx_in_inclusion() { + // let verifier = BitcoinVerifier::new(RollupParams { + // rollup_name: "sov-btc".to_string(), + // reveal_batch_prover_prefix: vec![1, 1], + // reveal_light_client_prefix: vec![2, 2], + // }); + + // let (block_header, mut inclusion_proof, completeness_proof, txs) = get_mock_data(); + + // inclusion_proof.txids.push([1; 32]); + + // assert_eq!( + // verifier.verify_relevant_tx_list( + // &block_header, + // txs.as_slice(), + // inclusion_proof, + // completeness_proof, + // ), + // Err(ValidationError::IncorrectInclusionProof) + // ); + // } + + // #[test] + // fn missing_tx_in_inclusion() { + // let verifier = BitcoinVerifier::new(RollupParams { + // rollup_name: "sov-btc".to_string(), + // reveal_batch_prover_prefix: vec![1, 1], + // reveal_light_client_prefix: vec![2, 2], + // }); + + // let (block_header, mut inclusion_proof, completeness_proof, txs) = get_mock_data(); + + // inclusion_proof.txids.pop(); + + // assert_eq!( + // verifier.verify_relevant_tx_list( + // &block_header, + // txs.as_slice(), + // inclusion_proof, + // completeness_proof, + // ), + // Err(ValidationError::RelevantTxNotFoundInBlock) + // ); + // } + + // #[test] + // fn empty_inclusion() { + // let verifier = BitcoinVerifier::new(RollupParams { + // rollup_name: "sov-btc".to_string(), + // reveal_batch_prover_prefix: vec![1, 1], + // reveal_light_client_prefix: vec![2, 2], + // }); + + // let (block_header, mut inclusion_proof, completeness_proof, txs) = get_mock_data(); + + // inclusion_proof.txids.clear(); + + // assert_eq!( + // verifier.verify_relevant_tx_list( + // &block_header, + // txs.as_slice(), + // inclusion_proof, + // completeness_proof, + // ), + // Err(ValidationError::RelevantTxNotFoundInBlock) + // ); + // } + + // #[test] + // fn break_order_of_inclusion() { + // let verifier = BitcoinVerifier::new(RollupParams { + // rollup_name: "sov-btc".to_string(), + // reveal_batch_prover_prefix: vec![1, 1], + // reveal_light_client_prefix: vec![2, 2], + // }); + + // let (block_header, mut inclusion_proof, completeness_proof, txs) = get_mock_data(); + + // inclusion_proof.txids.swap(0, 1); + + // assert_eq!( + // verifier.verify_relevant_tx_list( + // &block_header, + // txs.as_slice(), + // inclusion_proof, + // completeness_proof, + // ), + // Err(ValidationError::IncorrectInclusionProof) + // ); + // } #[test] fn missing_tx_in_completeness_proof() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, mut completeness_proof, txs) = get_mock_data(); @@ -810,7 +832,8 @@ mod tests { fn empty_completeness_proof() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, mut completeness_proof, txs) = get_mock_data(); @@ -832,7 +855,8 @@ mod tests { fn non_relevant_tx_in_completeness_proof() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, mut completeness_proof, txs) = get_mock_data(); @@ -854,7 +878,8 @@ mod tests { fn break_completeness_proof_order() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, mut completeness_proof, mut txs) = get_mock_data(); @@ -877,7 +902,8 @@ mod tests { fn break_rel_tx_order() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, completeness_proof, mut txs) = get_mock_data(); @@ -899,7 +925,8 @@ mod tests { fn break_rel_tx_and_completeness_proof_order() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, mut completeness_proof, mut txs) = get_mock_data(); @@ -922,7 +949,8 @@ mod tests { fn tamper_rel_tx_content() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, completeness_proof, mut txs) = get_mock_data(); @@ -945,16 +973,18 @@ mod tests { fn tamper_senders() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, completeness_proof, mut txs) = get_mock_data(); let tx1 = &completeness_proof[1]; - txs[1] = BlobWithSender::new( - parse_transaction(tx1, "sov-btc").unwrap().body, - vec![2; 33], - txs[1].hash, - ); + let body = { + let parsed = parse_batch_proof_transaction(tx1, "sov-btc").unwrap(); + let ParsedBatchProofTransaction::SequencerCommitment(seq) = parsed; + seq.body + }; + txs[1] = BlobWithSender::new(body, vec![2; 33], txs[1].hash); assert_eq!( verifier.verify_relevant_tx_list( @@ -971,7 +1001,8 @@ mod tests { fn missing_rel_tx() { let verifier = BitcoinVerifier::new(RollupParams { rollup_name: "sov-btc".to_string(), - reveal_wtxid_prefix: vec![0, 0], + reveal_batch_prover_prefix: vec![1, 1], + reveal_light_client_prefix: vec![2, 2], }); let (block_header, inclusion_proof, completeness_proof, mut txs) = get_mock_data(); diff --git a/crates/primitives/src/constants.rs b/crates/primitives/src/constants.rs index f17bb704d..46bd2ecff 100644 --- a/crates/primitives/src/constants.rs +++ b/crates/primitives/src/constants.rs @@ -1,7 +1,10 @@ pub const ROLLUP_NAME: &str = "citrea-devnet"; -/// Leading zeros prefix for the reveal transaction id. -pub const DA_TX_ID_LEADING_ZEROS: &[u8] = [0, 0].as_slice(); +/// Prefix for the reveal transaction ids - batch proof namespace. +pub const REVEAL_BATCH_PROOF_PREFIX: &[u8] = [1, 1].as_slice(); + +/// Prefix for the reveal transaction ids - light client namespace. +pub const REVEAL_LIGHT_CLIENT_PREFIX: &[u8] = [2, 2].as_slice(); pub const TEST_PRIVATE_KEY: &str = "1212121212121212121212121212121212121212121212121212121212121212";