Overview
Core Lane uses Bitcoin as a data availability (DA) layer, embedding transaction data directly into Bitcoin blocks using Taproot. This provides:
Security - Inherits Bitcoin’s proof-of-work security
Availability - Data stored on Bitcoin’s global network
Verifiability - Anyone can verify Core Lane state from Bitcoin
Taproot Data Availability
Core Lane leverages Bitcoin’s Taproot upgrade to embed data efficiently using the commit-reveal pattern .
Commit-Reveal Pattern
Data is published to Bitcoin in two transactions submitted as an atomic package:
Commit Transaction
Creates a Taproot output with data committed in the script tree
Reveal Transaction
Immediately spends the Taproot output, exposing the data on-chain
Both transactions are submitted together using Bitcoin Core’s submitpackage RPC (src/taproot_da.rs:548-603), ensuring atomic inclusion in the same block.
Core Lane supports two data format prefixes:
CORE_LANE - Single Transaction
For individual transactions (src/bitcoin_block.rs:304-306):
CORE_LANE || <RLP-encoded Ethereum transaction>
Extracted from Taproot envelopes during block scanning.
CORE_BNDL - Transaction Bundles
For bundled transactions with compression (src/taproot_da.rs:618-645):
CORE_BNDL || <CBOR-encoded bundle>
Bundle CBOR Schema (src/block.rs:165-171):
[
type , // u8: 0=no compression, 1=brotli
decompressed_length , // u32: size of decompressed data
valid_for_block , // u64: bundle validity block
flash_loan_amount , // 32 bytes: U256
flash_loaner_address , // 20 bytes: Address
sequencer_payment_recipient , // 20 bytes: Address
compressed_transactions , // bytes: brotli-compressed CBOR array
signature , // optional 65 bytes: secp256k1 recoverable sig
marker // optional u8: bundle position marker
]
Brotli compression typically achieves 60-80% size reduction on transaction bundles, reducing Bitcoin fees significantly.
Taproot Envelope Construction
The envelope script embeds data using Bitcoin script opcodes (src/taproot_da.rs:922-936):
fn create_taproot_envelope_script ( & self , data : & [ u8 ]) -> Result < ScriptBuf > {
let mut script = Builder :: new ();
script = script . push_opcode ( OP_FALSE ) . push_opcode ( OP_IF );
// Add data in chunks of 520 bytes (Bitcoin script push limit)
for chunk in data . chunks ( 520 ) {
script = script . push_slice ( chunk );
}
script = script . push_opcode ( OP_ENDIF ) . push_opcode ( OP_TRUE );
Ok ( script . into_script ())
}
Script Pattern : OP_FALSE OP_IF <data_chunks> OP_ENDIF OP_TRUE
OP_FALSE OP_IF - Creates unexecutable branch (data never executed)
Data chunks - Payload split into ≤520 byte pushes
OP_ENDIF OP_TRUE - Script evaluates to true (spendable)
Transaction Submission Process
Step 1: Wallet Sync
BDK wallet is loaded and synced (src/taproot_da.rs:249-351):
Regtest : Syncs via Bitcoin Core RPC using bdk_bitcoind_rpc::Emitter
Other networks : Syncs via Electrum using bdk_electrum::BdkElectrumClient
Step 2: Fee Calculation
Optimal fee rate is calculated (src/taproot_da.rs:155-228):
async fn calculate_optimal_fee_rate ( & self ) -> Result < u64 > {
// Get min relay fee from Bitcoin node
let min_relay_fee_sat_vb = /* from getnetworkinfo */ ;
// Get market fee estimate
let sat_per_vb = /* from estimatesmartfee */ ;
// Cap between min relay and 50 sat/vB
let capped_sat_per_vb = sat_per_vb . max ( min_relay_fee_sat_vb ) . min ( 50 );
Ok ( capped_sat_per_vb . clamp ( 1 , 10 )) // Force to 1-10 range for testing
}
The reveal transaction fee is calculated exactly based on payload size (src/taproot_da.rs:36-94) to avoid overpaying.
Step 3: Build Commit Transaction
BDK constructs and signs the commit transaction (src/taproot_da.rs:413-457):
let mut tx_builder = wallet . build_tx ();
tx_builder . fee_rate ( fee_rate );
tx_builder . add_recipient (
taproot_address . script_pubkey (),
Amount :: from_sat ( min_taproot_output ),
);
let mut psbt = tx_builder . finish () ? ;
wallet . sign ( & mut psbt , SignOptions :: default ()) ? ;
let commit_tx = psbt . extract_tx () ? ;
Step 4: Build Reveal Transaction
Reveal transaction immediately spends the Taproot output (src/taproot_da.rs:522-544):
let mut reveal_tx = Transaction {
version : bitcoin :: transaction :: Version :: TWO ,
lock_time : bitcoin :: absolute :: LockTime :: ZERO ,
input : vec! [ bitcoin :: TxIn {
previous_output : bitcoin :: OutPoint {
txid : commit_txid ,
vout : taproot_vout_index as u32 ,
},
script_sig : ScriptBuf :: new (),
sequence : bitcoin :: Sequence :: MAX ,
witness : Witness :: new (),
}],
output : vec! [ bitcoin :: TxOut {
value : bitcoin :: Amount :: from_sat ( 0 ),
script_pubkey : op_return_script , // OP_RETURN "CORELANE"
}],
};
// Add witness to reveal the envelope
let mut witness = Witness :: new ();
witness . push ( envelope_script . as_bytes ());
witness . push ( & control_block );
reveal_tx . input[ 0 ] . witness = witness ;
Step 5: Submit Package
Both transactions submitted atomically (src/taproot_da.rs:548-603):
let package_txs = vec! [
serde_json :: json! ( commit_tx_hex ),
serde_json :: json! ( reveal_final_hex ),
];
let package_result = self . rpc_client
. call ( "submitpackage" , & [ serde_json :: json! ( package_txs )]) ? ;
Package submission requires Bitcoin Core v22.0+ with submitpackage RPC support.
Core Lane nodes scan Bitcoin blocks to extract embedded data.
Block Processing Flow
See src/bitcoin_block.rs:19-138:
pub fn process_bitcoin_block (
bitcoin_client : Arc < dyn BitcoinRpcReadClient >,
height : u64 ,
) -> Result < CoreLaneBlockParsed > {
let block = bitcoin_client . get_block_by_hash_hex ( & hash_hex ) ? ;
let mut core_lane_block = CoreLaneBlockParsed :: new (
bitcoin_block_hash_bytes ,
bitcoin_block_timestamp ,
height ,
parent_hash ,
);
// Pass 1: Extract burns (P2WSH + OP_RETURN pattern)
for tx in block . txdata . iter () {
if let Some (( payload , burn_value )) = extract_burn_payload_from_tx ( tx ) {
core_lane_block . add_burn ( process_bitcoin_burn ( payload , burn_value ) ? );
}
}
// Pass 2: Extract CORE_LANE transactions
for tx in block . txdata . iter () {
if let Some ( lane_tx ) = extract_core_lane_transaction ( tx ) {
core_lane_block . add_bundle_from_single_tx ( tx , sender , lane_tx );
}
}
// Pass 3: Extract CORE_BNDL bundles
for tx in block . txdata . iter () {
if let Some ( bundle_data ) = extract_core_bndl_transaction ( tx ) {
let cbor_bundle = CoreLaneBundleCbor :: from_cbor ( & bundle_data ) ? ;
core_lane_block . add_bundle_from_cbor ( cbor_bundle ) ? ;
}
}
Ok ( core_lane_block )
}
Generic envelope extraction (src/bitcoin_block.rs:310-449):
fn extract_envelope_data_with_prefix ( script : & Script , prefix : & [ u8 ]) -> Option < Vec < u8 >> {
// 1. Verify script starts with OP_FALSE/OP_IF
// 2. Collect all push operations between OP_IF and OP_ENDIF
// 3. Verify script ends with OP_ENDIF/OP_TRUE
// 4. Concatenate all push data
// 5. Check for prefix match
// 6. Remove padding and return payload
}
Bundle Signatures
Bundles can be signed for authenticity verification (src/block.rs:492-635):
Signing Process
pub fn sign ( & mut self , secret_key : & SecretKey ) -> anyhow :: Result <[ u8 ; 65 ]> {
let secp = Secp256k1 :: new ();
// Get hash with signature zeroed
let hash = self . get_signing_hash () ? ;
let message = Message :: from_digest ( hash );
// Sign with recovery
let signature = secp . sign_ecdsa_recoverable ( & message , secret_key );
// Serialize to 65 bytes [r || s || recovery_id]
let ( recovery_id , signature_bytes ) = signature . serialize_compact ();
let mut sig_array = [ 0 u8 ; 65 ];
sig_array [ .. 64 ] . copy_from_slice ( & signature_bytes );
sig_array [ 64 ] = recovery_id . to_i32 () as u8 ;
self . signature = Some ( sig_array );
Ok ( sig_array )
}
Address Recovery
Recover signer’s Ethereum address from bundle signature (src/block.rs:596-634):
pub fn recover_signer_address ( & self ) -> anyhow :: Result < Address > {
let signature = self . signature . ok_or ( anyhow! ( "No signature" )) ? ;
let hash = self . get_signing_hash () ? ;
let message = Message :: from_digest ( hash );
// Recover public key
let recovery_id = RecoveryId :: from_i32 ( signature [ 64 ] as i32 ) ? ;
let sig = RecoverableSignature :: from_compact ( & signature [ .. 64 ], recovery_id ) ? ;
let public_key = secp . recover_ecdsa ( & message , & sig ) ? ;
// Convert to Ethereum address
let public_key_uncompressed = public_key . serialize_uncompressed ();
let public_key_bytes = & public_key_uncompressed [ 1 .. ];
let hash = Keccak256 :: new () . chain_update ( public_key_bytes ) . finalize ();
Ok ( Address :: from_slice ( & hash [ 12 .. ]))
}
Compression
Brotli compression reduces bundle size significantly (src/block.rs:428-439):
let params = brotli :: enc :: BrotliEncoderParams {
quality : 6 , // Balanced compression/speed
lgwin : 22 , // 4MB window size
.. Default :: default ()
};
brotli :: BrotliCompress (
& mut uncompressed_transactions . as_slice (),
& mut compressed_transactions ,
& params ,
) ? ;
Decompression Protection (src/block.rs:12-14, 219-225):
const MAX_DECOMPRESSED_SIZE : u32 = 128 * 1024 * 1024 ; // 128 MB
if decompressed_length > MAX_DECOMPRESSED_SIZE {
return Err ( anyhow! (
"Decompressed length {} exceeds maximum of {} bytes (128MB)" ,
decompressed_length , MAX_DECOMPRESSED_SIZE
));
}
Decompression bombs are prevented by enforcing a 128 MB limit on decompressed data.
Bitcoin Burn Mechanism
Cross-chain burns for minting Core Lane tokens (src/bitcoin_block.rs:183-234):
Hybrid P2WSH + OP_RETURN Pattern
P2WSH output - Unspendable witness script hash (burns BTC)
OP_RETURN output - Contains BRN1 || chain_id || eth_address
Burn Processing
fn process_bitcoin_burn (
payload : Vec < u8 >,
burn_value : u64 ,
txid : String ,
) -> Result < CoreLaneBurn > {
// Parse BRN1 payload
let chain_id = u32 :: from_be_bytes ([ payload [ 4 .. 8 ]]);
let eth_address = Address :: from_slice ( & payload [ 8 .. 28 ]);
// Verify chain ID (1281453634 for Core Lane, 1 for Ethereum)
if chain_id == 1281453634 || chain_id == 1 {
// Convert sats to wei: 1 sat = 10^10 wei
let conversion_factor = U256 :: from ( 10_000_000_000 u64 );
let mint_amount = U256 :: from ( burn_value ) * conversion_factor ;
Ok ( CoreLaneBurn :: new ( mint_amount , eth_address ))
} else {
Err ( anyhow! ( "Invalid chain ID" ))
}
}
Burned Bitcoin is permanently unrecoverable. Ensure the correct chain ID and address before burning.
Next Steps
Taproot DA Details Deep dive into Taproot DA mechanism
Transaction Processing See how transactions flow through the system