Skip to content

Commit

Permalink
Esad/better inclusion proof (#991)
Browse files Browse the repository at this point in the history
* depend only on wtxid

* verify merkle root of coinbase tx, use zkvm efficient sh256 and bitcoin merkle tree

* remove unused asserts

* pr fixes

* pr fixes
  • Loading branch information
eyusufatik authored Aug 15, 2024
1 parent 4bd460f commit 33c8342
Show file tree
Hide file tree
Showing 15 changed files with 292 additions and 127 deletions.
1 change: 1 addition & 0 deletions Cargo.lock

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

1 change: 1 addition & 0 deletions bin/citrea/provers/risc0/guest-bitcoin/Cargo.lock

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

Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ pub fn main() {
stf,
BitcoinVerifier::new(RollupParams {
rollup_name: ROLLUP_NAME.to_string(),
reveal_tx_id_prefix: DA_TX_ID_LEADING_ZEROS.to_vec(),
reveal_wtxid_prefix: DA_TX_ID_LEADING_ZEROS.to_vec(),
}),
);

Expand Down
4 changes: 2 additions & 2 deletions bin/citrea/src/rollup/bitcoin.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,7 +117,7 @@ impl RollupBlueprint for BitcoinRollup {
rollup_config.da.clone(),
RollupParams {
rollup_name: ROLLUP_NAME.to_string(),
reveal_tx_id_prefix: DA_TX_ID_LEADING_ZEROS.to_vec(),
reveal_wtxid_prefix: DA_TX_ID_LEADING_ZEROS.to_vec(),
},
tx,
)
Expand Down Expand Up @@ -146,7 +146,7 @@ impl RollupBlueprint for BitcoinRollup {

let da_verifier = BitcoinVerifier::new(RollupParams {
rollup_name: ROLLUP_NAME.to_string(),
reveal_tx_id_prefix: DA_TX_ID_LEADING_ZEROS.to_vec(),
reveal_wtxid_prefix: DA_TX_ID_LEADING_ZEROS.to_vec(),
});

ParallelProverService::new_with_default_workers(
Expand Down
1 change: 1 addition & 0 deletions crates/bitcoin-da/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ tracing = { workspace = true, optional = true }
bitcoin = { workspace = true }
brotli = { workspace = true }
futures.workspace = true
sha2 = { workspace = true }

bitcoincore-rpc = { workspace = true, optional = true }

Expand Down
6 changes: 3 additions & 3 deletions crates/bitcoin-da/src/helpers/builders.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use bitcoin::absolute::LockTime;
use bitcoin::blockdata::opcodes::all::{OP_DROP, OP_ENDIF, OP_IF};
use bitcoin::blockdata::opcodes::OP_FALSE;
use bitcoin::blockdata::script;
use bitcoin::hashes::{sha256d, Hash};
use bitcoin::hashes::Hash;
use bitcoin::key::{TapTweak, TweakedPublicKey, UntweakedKeypair};
use bitcoin::opcodes::all::OP_CHECKSIGVERIFY;
use bitcoin::script::PushBytesBuf;
Expand All @@ -25,7 +25,7 @@ use bitcoin::{
use serde::Serialize;
use tracing::{instrument, trace, warn};

use super::{TransactionHeader, TransactionKind};
use super::{calculate_double_sha256, TransactionHeader, TransactionKind};
use crate::spec::utxo::UTXO;
use crate::{MAX_TXBODY_SIZE, REVEAL_OUTPUT_AMOUNT};

Expand All @@ -34,7 +34,7 @@ pub fn sign_blob_with_private_key(
blob: &[u8],
private_key: &SecretKey,
) -> Result<(Vec<u8>, Vec<u8>), ()> {
let message = sha256d::Hash::hash(blob).to_byte_array();
let message = calculate_double_sha256(blob);
let secp = Secp256k1::new();
let public_key = secp256k1::PublicKey::from_secret_key(&secp, private_key);
let msg = secp256k1::Message::from_digest_slice(&message).unwrap();
Expand Down
142 changes: 142 additions & 0 deletions crates/bitcoin-da/src/helpers/merkle_tree.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,142 @@
/// Code is taken from Clementine
/// https://github.com/chainwayxyz/clementine/blob/b600ea18df72bdc60015ded01b78131b4c9121d7/operator/src/bitcoin_merkle.rs
///
use super::calculate_double_sha256;

#[derive(Debug, Clone)]
pub struct BitcoinMerkleTree {
depth: u32,
nodes: Vec<Vec<[u8; 32]>>,
}

impl BitcoinMerkleTree {
pub fn new(transactions: Vec<[u8; 32]>) -> Self {
if transactions.len() == 1 {
// root is the coinbase txid
return BitcoinMerkleTree {
depth: 1,
nodes: vec![transactions],
};
}

let depth = (transactions.len() - 1).ilog(2) + 1;
let mut tree = BitcoinMerkleTree {
depth,
nodes: vec![transactions],
};

// Construct the tree
let mut curr_level_offset: usize = 1;
let mut prev_level_size = tree.nodes[0].len();
let mut prev_level_index_offset = 0;
let mut preimage: [u8; 64] = [0; 64];
while prev_level_size > 1 {
tree.nodes.push(vec![]);
for i in 0..(prev_level_size / 2) {
preimage[..32].copy_from_slice(
&tree.nodes[curr_level_offset - 1 as usize][prev_level_index_offset + i * 2],
);
preimage[32..].copy_from_slice(
&tree.nodes[curr_level_offset - 1][prev_level_index_offset + i * 2 + 1],
);
let combined_hash = calculate_double_sha256(&preimage);
tree.nodes[curr_level_offset].push(combined_hash);
}
if prev_level_size % 2 == 1 {
let mut preimage: [u8; 64] = [0; 64];
preimage[..32].copy_from_slice(
&tree.nodes[curr_level_offset - 1]
[prev_level_index_offset + prev_level_size - 1],
);
preimage[32..].copy_from_slice(
&tree.nodes[curr_level_offset - 1]
[prev_level_index_offset + prev_level_size - 1],
);
let combined_hash = calculate_double_sha256(&preimage);
tree.nodes[curr_level_offset].push(combined_hash);
}
curr_level_offset += 1;
prev_level_size = (prev_level_size + 1) / 2;
prev_level_index_offset = 0;
}
tree
}

// Returns the Merkle root
pub fn root(&self) -> [u8; 32] {
return self.nodes[self.nodes.len() - 1][0];
}

pub fn get_element(&self, level: u32, index: u32) -> [u8; 32] {
return self.nodes[level as usize][index as usize];
}

pub fn get_idx_path(&self, index: u32) -> Vec<[u8; 32]> {
assert!(
index <= self.nodes[0].len() as u32 - 1,
"Index out of bounds"
);
let mut path = vec![];
let mut level = 0;
let mut i = index;
while level < self.nodes.len() as u32 - 1 {
if i % 2 == 1 {
path.push(self.nodes[level as usize][i as usize - 1]);
} else {
if (self.nodes[level as usize].len() - 1) as u32 == i {
path.push(self.nodes[level as usize][i as usize]);
} else {
path.push(self.nodes[level as usize][(i + 1) as usize]);
}
}
level += 1;
i = i / 2;
}
return path;
}

pub fn calculate_root_with_merkle_proof(
txid: [u8; 32],
idx: u32,
merkle_proof: Vec<[u8; 32]>,
) -> [u8; 32] {
let mut preimage: [u8; 64] = [0; 64];
let mut combined_hash: [u8; 32] = txid.clone();
let mut index = idx;
let mut level: u32 = 0;
while level < merkle_proof.len() as u32 {
if index % 2 == 0 {
preimage[..32].copy_from_slice(&combined_hash);
preimage[32..].copy_from_slice(&merkle_proof[level as usize]);
combined_hash = calculate_double_sha256(&preimage);
} else {
preimage[..32].copy_from_slice(&merkle_proof[level as usize]);
preimage[32..].copy_from_slice(&combined_hash);
combined_hash = calculate_double_sha256(&preimage);
}
level += 1;
index = index / 2;
}
combined_hash
}
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
fn test_merkle_tree() {
let mut transactions: Vec<[u8; 32]> = vec![];
for i in 0u8..100u8 {
let tx = [i; 32];
transactions.push(tx);
}
let tree = BitcoinMerkleTree::new(transactions.clone());
let root = tree.root();
let idx_path = tree.get_idx_path(0);
let calculated_root =
BitcoinMerkleTree::calculate_root_with_merkle_proof(transactions[0], 0, idx_path);
assert_eq!(root, calculated_root);
}
}
11 changes: 11 additions & 0 deletions crates/bitcoin-da/src/helpers/mod.rs
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
use core::num::NonZeroU16;

use sha2::{Digest, Sha256};

#[cfg(feature = "native")]
pub mod builders;
pub mod compression;
pub mod merkle_tree;
pub mod parsers;
#[cfg(test)]
pub mod test_utils;
Expand Down Expand Up @@ -57,3 +60,11 @@ enum TransactionKind {
ChunkedPart = 2,
Unknown(NonZeroU16),
}

pub fn calculate_double_sha256(input: &[u8]) -> [u8; 32] {
let mut hasher = Sha256::default();
hasher.update(input);
let result = hasher.finalize_reset();
hasher.update(result);
hasher.finalize().try_into().unwrap()
}
6 changes: 3 additions & 3 deletions crates/bitcoin-da/src/helpers/parsers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@ use core::num::NonZeroU16;

use bitcoin::blockdata::opcodes::all::{OP_ENDIF, OP_IF};
use bitcoin::blockdata::script::Instruction;
use bitcoin::hashes::{sha256d, Hash};
use bitcoin::hashes::Hash;
use bitcoin::opcodes::all::{OP_CHECKSIGVERIFY, OP_DROP};
use bitcoin::script::Instruction::{Op, PushBytes};
use bitcoin::script::{Error as ScriptError, PushBytes as StructPushBytes};
use bitcoin::secp256k1::{ecdsa, Message, Secp256k1};
use bitcoin::{secp256k1, Opcode, Script, Transaction, Txid};
use thiserror::Error;

use super::{TransactionHeader, TransactionKind};
use super::{calculate_double_sha256, TransactionHeader, TransactionKind};

#[derive(Debug, Clone)]
pub struct ParsedInscription {
Expand All @@ -36,7 +36,7 @@ impl ParsedInscription {
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 = sha256d::Hash::hash(&self.body).to_byte_array();
let hash = calculate_double_sha256(&self.body);
let message = Message::from_digest_slice(&hash).unwrap(); // cannot fail

let secp = Secp256k1::new();
Expand Down
9 changes: 6 additions & 3 deletions crates/bitcoin-da/src/helpers/test_utils.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ use core::str::FromStr;

use bitcoin::block::{Header, Version};
use bitcoin::hash_types::{TxMerkleNode, WitnessMerkleNode};
use bitcoin::hashes::{sha256d, Hash};
use bitcoin::hashes::Hash;
use bitcoin::{BlockHash, CompactTarget, Transaction};
use sov_rollup_interface::da::{DaSpec, DaVerifier};

use super::calculate_double_sha256;
use crate::helpers::compression::decompress_blob;
use crate::helpers::parsers::{parse_hex_transaction, parse_transaction};
use crate::spec::blob::BlobWithSender;
Expand Down Expand Up @@ -35,7 +36,7 @@ pub(crate) fn get_blob_with_sender(tx: &Transaction) -> BlobWithSender {
BlobWithSender::new(
decompressed_blob,
parsed_inscription.public_key,
sha256d::Hash::hash(&blob).to_byte_array(),
calculate_double_sha256(&blob),
)
}

Expand Down Expand Up @@ -66,7 +67,9 @@ pub(crate) fn get_mock_data() -> (
WitnessMerkleNode::from_str(
"a8b25755ed6e2f1df665b07e751f6acc1ff4e1ec765caa93084176e34fa5ad71",
)
.unwrap(),
.unwrap()
.to_raw_hash()
.to_byte_array(),
);

let block_txs = get_mock_txs();
Expand Down
Loading

0 comments on commit 33c8342

Please sign in to comment.