Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

😡 Add Disprove Script #9

Merged
merged 30 commits into from
Oct 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
42470b3
WIP
Velnbur Oct 1, 2024
700a4a7
:interrobang: preparing for merge
ZamDimon Oct 4, 2024
5764145
🧑‍💻 SHA256 Splitting Demo (rebased) (#8)
ZamDimon Oct 4, 2024
bcff390
:bulb: make fuzzy script splitter
ZamDimon Oct 9, 2024
b4e04cd
:bulb: add u32 split functionality
ZamDimon Oct 9, 2024
c3ef167
:construction: wip, adding u32 stack split
ZamDimon Oct 9, 2024
46d0f7d
:twisted_rightwards_arrows: Merge `bitcoin-scriptexec` from `BitVM` (…
Velnbur Oct 9, 2024
fc1e257
WIP
Velnbur Oct 1, 2024
0ea730d
:construction: wip, adding u32 stack split
ZamDimon Oct 9, 2024
3791c02
:tada: it works
ZamDimon Oct 9, 2024
515ca3d
:twisted_rightwards_arrows: Merge `bitcoin-scriptexec` from `BitVM` (…
Velnbur Oct 9, 2024
3ef1e1a
Implement basic winternitz
Velnbur Oct 9, 2024
957f331
Add checksum calculations
Velnbur Sep 30, 2024
6556c39
Optimize memory allocations using lazy iterators
Velnbur Sep 30, 2024
be69602
Add comments
Velnbur Sep 30, 2024
5c214a5
Return Chunked pubkey
Velnbur Sep 30, 2024
04a1996
Push initial version of Winternitz verification script
Velnbur Sep 30, 2024
5b75b45
Store n0 and n1 value to message's parts
Velnbur Oct 3, 2024
c048bca
Add u32 implementation of winternitz
Velnbur Oct 7, 2024
204a36a
Add comments
Velnbur Oct 8, 2024
7dd6be2
Fix tests and doc comments
Velnbur Oct 8, 2024
aebb7e6
:construction: wip, finalizing disprove script
ZamDimon Oct 14, 2024
7246059
:test_tube: add first tests in the `core` package
ZamDimon Oct 15, 2024
8a7fe96
:art: finalize disprove script
ZamDimon Oct 15, 2024
29815e2
:adhesive_bandage: finalize disprove script
ZamDimon Oct 16, 2024
fc146db
Merge branch 'main' into feature/assert-tx
ZamDimon Oct 16, 2024
9f7a9f5
:bug: fix bug after merge
ZamDimon Oct 16, 2024
128069c
:art: polishing repo
ZamDimon Oct 16, 2024
f82339d
:art: tiny polishes
ZamDimon Oct 16, 2024
698be8a
:truck: restructure the `core` package
ZamDimon Oct 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 9 additions & 4 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
[workspace]
members = ["bitcoin-splitter", "integration-tests", "bitcoin-scriptexec"]
members = [
"bitcoin-splitter",
"integration-tests",
"bitcoin-winternitz",
"bitcoin-scriptexec",
"core"
]
resolver = "2"

[workspace.dependencies]
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", branch = "bitvm" }
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", branch = "bitvm", features = ["rand-std"]}
tracing = "0.1.40"
tracing-subscriber = "0.3.18"


[profile.dev]
opt-level = 3

Expand Down Expand Up @@ -36,4 +41,4 @@ branch = "bitvm"

[patch.crates-io.bitcoin-units]
git = "https://github.com/rust-bitcoin/rust-bitcoin"
branch = "bitvm"
branch = "bitvm"
2 changes: 2 additions & 0 deletions bitcoin-scriptexec/src/data_structures.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@ impl StackEntry {
num <= u32::MAX.into(),
"There should not be entries with more than 32 bits on the stack at this point"
);
// Since num is for sure <= u32::MAX, we can safely convert it to u32
let num = num as u32;
num.to_le_bytes().to_vec()
}
StackEntry::StrRef(v) => {
Expand Down
17 changes: 11 additions & 6 deletions bitcoin-scriptexec/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -488,12 +488,14 @@ impl Exec {
// Some things we do even when we're not executing.

// Note how OP_RESERVED does not count towards the opcode limit.
if (self.ctx == ExecCtx::Legacy || self.ctx == ExecCtx::SegwitV0) && op.to_u8() > OP_PUSHNUM_16.to_u8() {
self.opcode_count += 1;
if self.opcode_count > MAX_OPS_PER_SCRIPT {
return self.fail(ExecError::OpCount);
}
if (self.ctx == ExecCtx::Legacy || self.ctx == ExecCtx::SegwitV0)
&& op.to_u8() > OP_PUSHNUM_16.to_u8()
{
self.opcode_count += 1;
if self.opcode_count > MAX_OPS_PER_SCRIPT {
return self.fail(ExecError::OpCount);
}
}

match op {
OP_CAT if !self.opt.experimental.op_cat || self.ctx != ExecCtx::Tapscript => {
Expand Down Expand Up @@ -607,7 +609,10 @@ impl Exec {
}
}
// Under segwit v0 only enabled as policy.
if self.opt.verify_minimal_if && self.ctx == ExecCtx::SegwitV0 && (top.len() > 1 || (top.len() == 1 && top[0] != 1)) {
if self.opt.verify_minimal_if
&& self.ctx == ExecCtx::SegwitV0
&& (top.len() > 1 || (top.len() == 1 && top[0] != 1))
{
return Err(ExecError::TapscriptMinimalIf);
}
let b = if op == OP_NOTIF {
Expand Down
5 changes: 3 additions & 2 deletions bitcoin-scriptexec/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -81,7 +81,8 @@ fn inner_main() -> Result<(), String> {
stats: Some(exec.stats()),
};
serde_json::to_writer(&out, &step).expect("I/O error");
out.write_all('\n'.to_string().as_bytes()).expect("I/O error");
out.write_all('\n'.to_string().as_bytes())
.expect("I/O error");
} else {
println!(
"Remaining script: {}",
Expand Down Expand Up @@ -119,7 +120,7 @@ fn inner_main() -> Result<(), String> {
println!("Stats:\n{:#?}", exec.stats());
println!("Time elapsed: {}ms", start.elapsed().as_millis());
}

Ok(())
}

Expand Down
5 changes: 3 additions & 2 deletions bitcoin-splitter/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ edition = "2021"

[dependencies]
# Bitcoin Libraries
bitcoin = { git = "https://github.com/rust-bitcoin/rust-bitcoin", branch = "bitvm", features = ["rand-std"]}
bitcoin = { workspace = true, features = ["rand-std"]}
bitcoin-script = { git = "https://github.com/BitVM/rust-bitcoin-script" }
bitcoin-scriptexec = { git = "https://github.com/BitVM/rust-bitcoin-scriptexec/"}
bitcoin-scriptexec = { path = "../bitcoin-scriptexec" }
bitcoin-script-stack = { git = "https://github.com/FairgateLabs/rust-bitcoin-script-stack"}

# BitVM scripts
Expand All @@ -19,6 +19,7 @@ strum_macros = "0.26"
serde = { version = "1.0.197", features = ["derive"] }
serde_json = "1.0.116"
tokio = { version = "1.37.0", features = ["full"] }
indicatif = "0.17.8" # Progress bar

# Crypto libraries
hex = "0.4.3"
Expand Down
22 changes: 22 additions & 0 deletions bitcoin-splitter/src/debug.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,17 @@ impl fmt::Display for ExecuteInfo {
}

writeln!(f, "Stats: {:?}", self.stats)?;

writeln!(f, "Stack:")?;
for element in self.main_stack.iter_str() {
writeln!(f, "> {}", hex::encode(element))?;
}

writeln!(f, "\nAltStack:")?;
for element in self.alt_stack.iter_str() {
writeln!(f, "> {}", hex::encode(element))?;
}

Ok(())
}
}
Expand Down Expand Up @@ -76,6 +87,17 @@ pub fn execute_script(script: ScriptBuf) -> ExecuteInfo {
}
}

pub fn run(script: bitcoin::ScriptBuf) {
let exec_result = execute_script(script);
if !exec_result.success {
println!(
"ERROR: {:?} <--- \n STACK: {:#?} \n ALTSTACK {:#?}",
exec_result.error, exec_result.main_stack, exec_result.alt_stack
);
}
assert!(exec_result.success);
}

/// Execute a script on stack without `MAX_STACK_SIZE` limit.
/// This function is only used for script test, not for production.
///
Expand Down
6 changes: 3 additions & 3 deletions bitcoin-splitter/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
#[allow(dead_code)]
// Re-export what is needed to write treepp scripts
pub mod treepp {
pub use crate::debug::execute_script;
pub use crate::debug::{execute_script, run};
pub use bitcoin_script::{define_pushable, script};

define_pushable!();
pub use bitcoin::ScriptBuf as Script;
}

pub mod split;
pub mod test_scripts;
pub mod utils;

pub(crate) mod bitvm;
pub(crate) mod debug;
pub(crate) mod pseudo;
pub(crate) mod test_scripts;
pub(crate) mod utils;

#[cfg(test)]
mod tests {
Expand Down
82 changes: 76 additions & 6 deletions bitcoin-splitter/src/split/core.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
//! Module containing the logic of splitting the script into smaller parts

use std::panic;

use bitcoin::{
opcodes::all::{OP_ENDIF, OP_IF},
opcodes::all::{OP_ENDIF, OP_IF, OP_NOTIF},
script::Instruction,
};
use indicatif::ProgressBar;

use super::script::SplitResult;
use crate::{split::intermediate_state::IntermediateState, treepp::*};
Expand All @@ -15,22 +18,36 @@ use crate::{split::intermediate_state::IntermediateState, treepp::*};
/// that will be used to split the script and the algorithm
/// will try to keep the size of the script as close to the optimal
/// as possible.
pub(super) const OPTIMAL_SCRIPT_SIZE: usize = 20000;
pub(super) const DEFAULT_SCRIPT_SIZE: usize = 7000;

/// Maximum scriptsize in bytes that is allowed by the Bitcoin network
pub(super) const MAX_SCRIPT_SIZE: usize = 50000;

/// When dealing with BitVM2 Disprove transaction,
/// there are two factors that we need to consider:
/// 1. The chunk (shard) script size.
/// 2. The intermediate state size.
///
/// The total size of the disprove script is (approximately):
/// `shard_size_i + (z_i_size + z_(i-1)_size) * STACK_SIZE_INDEX`
///
/// The `STACK_SIZE_INDEX` is the factor of how much intermediate states
/// are contributing to the total size of the script.
pub(super) const STACK_SIZE_INDEX: usize = 1000;

/// Type of the split that we are going to use
///
/// - [`SplitType::ByInstructions`]- splits the script by the number of instructions
/// - [`SplitType::ByBytes`] - splits the script by the number of bytes
#[derive(Debug, Clone, Copy, Default)]
pub enum SplitType {
#[default]
ByInstructions,
ByBytes,
}

/// Splits the given script into smaller parts. Tries to keep each chunk size
/// to the optimal size ([`OPTIMAL_SCRIPT_SIZE`]) as close as possible.
/// to the optimal size `chunk_size` as close as possible.
pub(super) fn split_into_shards(
script: &Script,
chunk_size: usize,
Expand Down Expand Up @@ -63,7 +80,7 @@ pub(super) fn split_into_shards(
// Checking if the current instruction is OP_IF or OP_ENDIF
if let Instruction::Op(op) = instruction {
match op {
OP_IF => {
OP_IF | OP_NOTIF => {
if_count += 1;
}
OP_ENDIF => {
Expand All @@ -77,6 +94,8 @@ pub(super) fn split_into_shards(
// is the same, we need to create a new one
if current_shard_size >= chunk_size && if_count == endif_count {
shards.push(Script::new());
if_count = 0;
endif_count = 0;
}

// Checking that the total size has not exceeded the maximum size
Expand All @@ -89,14 +108,65 @@ pub(super) fn split_into_shards(
shards
}

/// Fuzzy split of the script into smaller parts by searching for the optimal size
/// by checking various script sizes
pub(super) fn fuzzy_split(input: Script, script: Script, split_type: SplitType) -> SplitResult {
// Define the limits
const MIN_CHUNK_SIZE: usize = 100;
const MAX_CHUNK_SIZE: usize = MAX_SCRIPT_SIZE;
const STEP_SIZE: usize = 20;

// Defining the final result
let mut resultant_split = SplitResult::new(vec![], vec![]);
let mut resultant_complexity = usize::MAX;

// Now, displaying the progress bar
let total_progress = (MAX_CHUNK_SIZE - MIN_CHUNK_SIZE) / STEP_SIZE;
let bar = ProgressBar::new(total_progress as u64);

// Trying to find the optimal size of the chunk by checking
// each size from MIN_CHUNK_SIZE to MAX_CHUNK_SIZE
for chunk_size in (MIN_CHUNK_SIZE..MAX_CHUNK_SIZE).step_by(STEP_SIZE) {
// Incrementing the progress bar
bar.inc(1);
// We are using panic::catch_unwind to catch any panics that might occur
// during the splitting process. If a panic occurs, we just skip the current
// chunk size and continue with the next one.
let current_split_result = panic::catch_unwind(|| {
naive_split(input.clone(), script.clone(), split_type, chunk_size)
});
if let Ok(split_result) = current_split_result {
let current_complexity = split_result.complexity_index();

if current_complexity < resultant_complexity {
resultant_complexity = current_complexity;
resultant_split = split_result;
}
}
}

bar.finish();
resultant_split
}

/// Default split of the script into smaller parts with the hard-coded optimal size
pub(super) fn default_split(input: Script, script: Script, split_type: SplitType) -> SplitResult {
naive_split(input, script, split_type, DEFAULT_SCRIPT_SIZE)
}

/// Naive split of the script into smaller parts. It works as follows:
/// 1. We split the script into smaller parts
/// 2. We execute each shard with the input
/// 3. Save intermediate results
/// 4. Return all the shards and intermediate results in the form of [`SplitResult`]
pub(super) fn naive_split(input: Script, script: Script, split_type: SplitType) -> SplitResult {
pub(super) fn naive_split(
input: Script,
script: Script,
split_type: SplitType,
chunk_size: usize,
) -> SplitResult {
// First, we split the script into smaller parts
let shards = split_into_shards(&script, OPTIMAL_SCRIPT_SIZE, split_type);
let shards = split_into_shards(&script, chunk_size, split_type);
let mut intermediate_states: Vec<IntermediateState> = vec![];

// Then, we do the following steps:
Expand Down
Loading