From b79509c6b418433caa1ae47f1c65fd32f4998591 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kami=C5=84ski?= Date: Wed, 4 Jun 2025 16:05:53 +0200 Subject: [PATCH 1/5] fix lim len management --- src/bin/mine-pow-alephium.rs | 22 ++-- src/core.rs | 196 ++++++++++++++--------------------- src/transactions.rs | 22 ++-- 3 files changed, 93 insertions(+), 147 deletions(-) diff --git a/src/bin/mine-pow-alephium.rs b/src/bin/mine-pow-alephium.rs index 30180cc..94b16f2 100644 --- a/src/bin/mine-pow-alephium.rs +++ b/src/bin/mine-pow-alephium.rs @@ -7,7 +7,6 @@ use bitcoin::opcodes::OP_TRUE; use bitcoin::script::Builder; use bitcoin_script_stack::optimizer; -use bitvm::hash::blake3::blake3_push_message_script_with_limb; use bitvm::{ ExecuteInfo, execute_script_buf, hash::blake3::blake3_compute_script_with_limb, @@ -16,8 +15,8 @@ use bitvm::{ use clap::Parser; use collidervm_toy::core::{ - build_drop, build_prefix_equalverify, build_script_hash_to_limbs, - combine_scripts, + blake3_message_to_limbs, build_drop, build_prefix_equalverify, + build_script_hash_to_limbs, combine_scripts, }; use num_bigint::BigUint; @@ -220,14 +219,11 @@ fn build_check_alephium_block_hash( ) -> ScriptBuf { let limb_len = 16; - // let message_limbs = blake3_message_to_limbs(&block, limb_len) - // .iter() - // .map(|&limb| limb as i64) - // .fold(Builder::new(), |b, limb| b.push_int(limb)) - // .into_script(); - - let message_limbs = - blake3_push_message_script_with_limb(block, limb_len).compile(); + let message_limbs = blake3_message_to_limbs(&block, limb_len) + .iter() + .map(|&limb| limb as i64) + .fold(Builder::new(), |b, limb| b.push_int(limb)) + .into_script(); let h1 = optimizer::optimize( blake3_compute_script_with_limb(BLAKE3_BUF_LEN, limb_len).compile(), @@ -280,8 +276,8 @@ pub fn verify_alephium_block_hash_with_script( let res = execute_script_buf(script); - println!("Script executed with success: {}", res.success); - println!("stack: {:?}", res.final_stack); + // println!("Script executed with success: {}", res.success); + // println!("stack: {:?}", res.final_stack); res.success } diff --git a/src/core.rs b/src/core.rs index 68e0bf2..1f9b412 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,4 +1,4 @@ -use crate::utils::NonceSearchProgress; +use crate::utils::{encode_scriptnum, NonceSearchProgress}; use bitcoin::{ Amount, PublicKey, XOnlyPublicKey, blockdata::script::{Builder, ScriptBuf}, @@ -8,10 +8,14 @@ use bitcoin_hashes::{HashEngine, sha256}; pub use bitcoin_script::builder::StructuredScript as Script; pub use bitcoin_script::script; use bitcoin_script_stack::optimizer; -use bitvm::hash::blake3::blake3_compute_script_with_limb; +use bitvm::{ + execute_script_buf, + hash::blake3::{ + blake3_compute_script_with_limb, blake3_push_message_script_with_limb, + }, +}; use blake3::Hasher; use indicatif::{ProgressBar, ProgressStyle}; -use itertools::Itertools; use secp256k1::Message; use std::time::{Duration, Instant}; @@ -174,31 +178,43 @@ pub fn build_drop(items: usize) -> ScriptBuf { b.into_script() } -/// duplicates (keeps) the first 8 nibbles, accumulates them into `x`, -/// leaves `x` on the stack, original 24 nibbles untouched. -fn build_script_reconstruct_x() -> ScriptBuf { - let mut b = Builder::new().push_int(0); // acc = 0 +// Reconstructs a 32-bit value `x` from limbs of `limb_len` size. +// Only supports power-of-2 limb lengths for Bitcoin Script efficiency. +fn build_script_reconstruct_x(limb_len: u8) -> ScriptBuf { + // Validate that limb_len_bits is a power of 2 + assert!(limb_len > 0 && (limb_len & (limb_len - 1)) == 0, + "limb_len_bits must be a power of 2"); + + let limbs_needed = (32 + limb_len - 1) / limb_len; // Ceiling division + + println!("limb_len: {limb_len}, limbs_needed: {limbs_needed}"); - for i in 0..8 { - // acc *= 16 - for _ in 0..4 { - b = b - .push_opcode(opcodes::all::OP_DUP) - .push_opcode(opcodes::all::OP_ADD); + let mut b = Builder::new().push_int(0); // acc = 0 + + for i in 0..limbs_needed { + for _ in 0..limb_len { + b = b.push_opcode(opcodes::all::OP_DUP) + .push_opcode(opcodes::all::OP_ADD); } - - b = b - .push_opcode(opcodes::all::OP_DEPTH) - .push_opcode(opcodes::all::OP_1SUB) - .push_int(i as i64) - .push_opcode(opcodes::all::OP_SUB) - .push_opcode(opcodes::all::OP_PICK); - // acc += nib - b = b.push_opcode(opcodes::all::OP_ADD); + + // Pick the i-th limb from bottom of stack and add to accumulator + b = b.push_opcode(opcodes::all::OP_DEPTH) + .push_opcode(opcodes::all::OP_1SUB) // depth - 1 (bottom index) + .push_int(i as i64) // limb index + .push_opcode(opcodes::all::OP_SUB) // (depth-1) - i + .push_opcode(opcodes::all::OP_PICK) // copy limb to top + .push_opcode(opcodes::all::OP_ADD); // acc += limb } + b.into_script() } +// limb length for blake3 in bits, +// blake3 accepts any limb length [4, 32) but due to the way how build_script_reconstruct_x +// it must be a power of 2 between 1 and 16 +// Valid values: 1, 2, 4, 8, 16 +const LIMB_LEN: u8 = 16; + /// Build an F1 script with onchain BLAKE3, checking x>F1_THRESHOLD and the top (b_bits/8) bytes match flow_id_prefix. pub fn build_script_f1_blake3_locked( signer_pubkey: &PublicKey, @@ -207,7 +223,6 @@ pub fn build_script_f1_blake3_locked( ) -> ScriptBuf { let prefix_len = flow_id_prefix.len(); let total_msg_len = 12; // x_4b + r_4b0 + r_4b1 - let limb_len = 4; // 1) Script to check signature let verify_signature_script = { @@ -217,7 +232,7 @@ pub fn build_script_f1_blake3_locked( }; // 2) Reconstruct x from first 8 nibbles - let reconstruct_x_script = build_script_reconstruct_x(); + let reconstruct_x_script = build_script_reconstruct_x(LIMB_LEN); // 3) Check x_num > 100 let x_greater_check_script = Builder::new() @@ -228,7 +243,7 @@ pub fn build_script_f1_blake3_locked( // 4) BLAKE3 compute snippet - OPTIMIZED let compute_compiled = - blake3_compute_script_with_limb(total_msg_len, limb_len).compile(); + blake3_compute_script_with_limb(total_msg_len, LIMB_LEN).compile(); let compute_optimized = optimizer::optimize(compute_compiled); let compute_blake3_script = ScriptBuf::from_bytes(compute_optimized.to_bytes()); @@ -260,30 +275,23 @@ pub fn build_script_f1_blake3_locked( } /// Build an F2 script with onchain BLAKE3, checking x ScriptBuf { let prefix_len = flow_id_prefix.len(); let total_msg_len = 12; - let limb_len = 4; // 1) Script to check signature let verify_signature_script = { let mut b = Builder::new(); - if test_mode { - // workaround an issue with sig verification implementation in script executor - b = b.push_key(signer_pubkey); - } else { - b = b.push_x_only_key(&XOnlyPublicKey::from(signer_pubkey.inner)); - } + b = b.push_x_only_key(&XOnlyPublicKey::from(signer_pubkey.inner)); b.push_opcode(opcodes::all::OP_CHECKSIGVERIFY).into_script() }; // 2) Reconstruct x from first 8 nibbles - let reconstruct_x_script = build_script_reconstruct_x(); + let reconstruct_x_script = build_script_reconstruct_x(LIMB_LEN); // 3) Check x_num < 200 let x_less_check_script = Builder::new() @@ -295,7 +303,7 @@ fn build_script_f2_blake3_locked_with_mode( // 4) BLAKE3 compute snippet - OPTIMIZED let compute_blake3_script = { let compiled = - blake3_compute_script_with_limb(total_msg_len, limb_len).compile(); + blake3_compute_script_with_limb(total_msg_len, LIMB_LEN).compile(); // Important: Optimize the compute script let optimized = optimizer::optimize(compiled); ScriptBuf::from_bytes(optimized.to_bytes()) @@ -325,19 +333,21 @@ fn build_script_f2_blake3_locked_with_mode( ]) } -pub fn build_script_f2_blake3_locked( - signer_pubkey: &PublicKey, - flow_id_prefix: &[u8], - _b_bits: usize, -) -> ScriptBuf { - build_script_f2_blake3_locked_with_mode( - signer_pubkey, - flow_id_prefix, - _b_bits, - false, - ) -} +pub fn message_to_witness_limbs(x: u32, nonce: u64) -> Vec> { + + let message = [ + x.to_le_bytes(), + nonce.to_le_bytes()[0..4].try_into().unwrap(), + nonce.to_le_bytes()[4..8].try_into().unwrap(), + ] + .concat(); + + blake3_message_to_limbs(&message, LIMB_LEN) + .into_iter() + .map(|limb| encode_scriptnum(limb.into())) + .collect() +} /// A basic "hash rate" calibration pub fn benchmark_hash_rate(duration_secs: u64) -> u64 { println!("Calibrating for {duration_secs} seconds..."); @@ -371,74 +381,21 @@ pub fn benchmark_hash_rate(duration_secs: u64) -> u64 { rate as u64 } -pub fn chunk_message(message_bytes: &[u8]) -> Vec<[u8; 64]> { - let len = message_bytes.len(); - let needed_padding_bytes = if len % 64 == 0 { 0 } else { 64 - (len % 64) }; - - message_bytes - .iter() - .copied() - .chain(std::iter::repeat_n(0u8, needed_padding_bytes)) - .chunks(4) // reverse 4-byte chunks - .into_iter() - .flat_map(|chunk| chunk.collect::>().into_iter().rev()) - .chunks(64) // collect 64-byte chunks - .into_iter() - .map(|mut chunk| std::array::from_fn(|_| chunk.next().unwrap())) - .collect() -} - -fn pack_32_bytes_to_limbs(bytes: &[u8; 32], limb_len: u8) -> Vec { - let mut acc = 0u64; - let mut bits = 0usize; - let mask = (1u64 << limb_len) - 1; - let mut limbs = Vec::with_capacity( - (256 + limb_len as usize - 1).div_ceil(limb_len as usize), - ); - - for &byte in bytes { - // big‑endian: shift current accumulator left - acc = (acc << 8) | byte as u64; - bits += 8; - - while bits >= limb_len as usize { - let shift = bits - limb_len as usize; - limbs.push(((acc >> shift) & mask) as u32); - bits -= limb_len as usize; - acc &= (1u64 << bits) - 1; // clear the bits we just used - } - } - if bits > 0 { - limbs.push((acc << (limb_len as usize - bits)) as u32); - } - limbs -} - +// for off-chain use pub fn blake3_message_to_limbs(message_bytes: &[u8], limb_len: u8) -> Vec { - assert!( - message_bytes.len() <= 1024, - "This BLAKE3 implementation doesn't support messages longer than 1024 bytes" - ); - assert!( - (4..32).contains(&limb_len), - "limb length must be in the range [4, 32)" - ); - - let chunks = chunk_message(message_bytes); - let mut limbs = Vec::new(); - - for chunk in chunks.into_iter() { - limbs.extend(pack_32_bytes_to_limbs( - &chunk[..32].try_into().unwrap(), - limb_len, - )); - limbs.extend(pack_32_bytes_to_limbs( - &chunk[32..].try_into().unwrap(), - limb_len, - )); - } - - limbs + let script = + blake3_push_message_script_with_limb(message_bytes, limb_len).compile(); + let res = execute_script_buf(script); + + res.final_stack + .0 + .iter_str() + .map(|v| { + let mut arr = [0u8; 4]; + arr[..v.len()].copy_from_slice(&v); + u32::from_le_bytes(arr) + }) + .collect() } pub fn build_script_hash_to_limbs() -> ScriptBuf { @@ -488,7 +445,7 @@ mod tests { let nonce = u64::from_be_bytes([ 0x56, 0x78, 0x9a, 0xbc, 0xde, 0xf0, 0x21, 0x43, ]); - let limb_len: u8 = 4; + let limb_len: u8 = 16; let message = [ input_value.to_le_bytes(), @@ -517,7 +474,7 @@ mod tests { #[test] fn test_blake3_script_generation() { let message = [0u8; 32]; - let limb_len: u8 = 4; + let limb_len: u8 = 16; let expected_hash = *blake3::hash(message.as_ref()).as_bytes(); println!("Expected hash: {}", hex::encode(expected_hash)); @@ -618,16 +575,17 @@ mod tests { #[test] fn test_blake3_input_from_witness() { + let limb_len = 16; let message = [ 0x7b, 0x00, 0x00, 0x00, 0xd9, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, ]; let msg_push_script = - blake3_push_message_script_with_limb(&message, 4).compile(); + blake3_push_message_script_with_limb(&message, limb_len).compile(); let push_script = ScriptBuf::from_bytes(msg_push_script.to_bytes()); let total_msg_len = 12; - let limb_len = 4; + let compute_compiled = blake3_compute_script_with_limb(total_msg_len, limb_len).compile(); let compute_optimized = optimizer::optimize(compute_compiled); diff --git a/src/transactions.rs b/src/transactions.rs index 8088ebd..b99c7ea 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -1,8 +1,8 @@ use crate::core::{ - blake3_message_to_limbs, build_script_f1_blake3_locked, - build_script_f2_blake3_locked, + build_script_f1_blake3_locked, + build_script_f2_blake3_locked, message_to_witness_limbs, }; -use crate::utils::{encode_scriptnum, estimate_fee_vbytes}; +use crate::utils::{estimate_fee_vbytes}; use anyhow; use bitcoin::sighash::Prevouts; use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo}; @@ -79,7 +79,7 @@ pub fn create_f1_tx( }; let xonly_pk = XOnlyPublicKey::from(*pk_signer); - let funding_script = get_funding_script(&x_only_pk); + let funding_script = get_funding_script(&xonly_pk); let leaf_hash = TapLeafHash::from_script(&funding_script, LeafVersion::TapScript); @@ -220,17 +220,9 @@ pub fn finalize_lock_tx( .control_block(&(lock.clone(), LeafVersion::TapScript)) .unwrap(); - // Encode input_value || nonce - let message = [ - x.to_le_bytes(), - nonce.to_le_bytes()[0..4].try_into()?, - nonce.to_le_bytes()[4..8].try_into()?, - ] - .concat(); - let mut witness = Witness::new(); - for limb in blake3_message_to_limbs(&message, 4) { - witness.push(encode_scriptnum(limb.into())); + for limb in message_to_witness_limbs(*x, *nonce) { + witness.push(limb); } witness.push(sig.serialize()); @@ -539,7 +531,7 @@ mod tests { assert!( exec_info_spending.success, "Spending script dry run failed: {:?}", - exec_info_f2.last_opcode + exec_info_spending.last_opcode ); Ok(()) } From 36f6dd7f5caef8990a5c2aca01b1f3ed0a51b7d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kami=C5=84ski?= Date: Wed, 4 Jun 2025 16:07:19 +0200 Subject: [PATCH 2/5] fmt --- src/core.rs | 42 ++++++++++++++++++++++-------------------- src/transactions.rs | 6 +++--- 2 files changed, 25 insertions(+), 23 deletions(-) diff --git a/src/core.rs b/src/core.rs index 1f9b412..8e343a4 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,4 +1,4 @@ -use crate::utils::{encode_scriptnum, NonceSearchProgress}; +use crate::utils::{NonceSearchProgress, encode_scriptnum}; use bitcoin::{ Amount, PublicKey, XOnlyPublicKey, blockdata::script::{Builder, ScriptBuf}, @@ -182,34 +182,38 @@ pub fn build_drop(items: usize) -> ScriptBuf { // Only supports power-of-2 limb lengths for Bitcoin Script efficiency. fn build_script_reconstruct_x(limb_len: u8) -> ScriptBuf { // Validate that limb_len_bits is a power of 2 - assert!(limb_len > 0 && (limb_len & (limb_len - 1)) == 0, - "limb_len_bits must be a power of 2"); - + assert!( + limb_len > 0 && (limb_len & (limb_len - 1)) == 0, + "limb_len_bits must be a power of 2" + ); + let limbs_needed = (32 + limb_len - 1) / limb_len; // Ceiling division - + println!("limb_len: {limb_len}, limbs_needed: {limbs_needed}"); let mut b = Builder::new().push_int(0); // acc = 0 - + for i in 0..limbs_needed { for _ in 0..limb_len { - b = b.push_opcode(opcodes::all::OP_DUP) - .push_opcode(opcodes::all::OP_ADD); + b = b + .push_opcode(opcodes::all::OP_DUP) + .push_opcode(opcodes::all::OP_ADD); } - + // Pick the i-th limb from bottom of stack and add to accumulator - b = b.push_opcode(opcodes::all::OP_DEPTH) - .push_opcode(opcodes::all::OP_1SUB) // depth - 1 (bottom index) - .push_int(i as i64) // limb index - .push_opcode(opcodes::all::OP_SUB) // (depth-1) - i - .push_opcode(opcodes::all::OP_PICK) // copy limb to top - .push_opcode(opcodes::all::OP_ADD); // acc += limb + b = b + .push_opcode(opcodes::all::OP_DEPTH) + .push_opcode(opcodes::all::OP_1SUB) // depth - 1 (bottom index) + .push_int(i as i64) // limb index + .push_opcode(opcodes::all::OP_SUB) // (depth-1) - i + .push_opcode(opcodes::all::OP_PICK) // copy limb to top + .push_opcode(opcodes::all::OP_ADD); // acc += limb } - + b.into_script() } -// limb length for blake3 in bits, +// limb length for blake3 in bits, // blake3 accepts any limb length [4, 32) but due to the way how build_script_reconstruct_x // it must be a power of 2 between 1 and 16 // Valid values: 1, 2, 4, 8, 16 @@ -333,9 +337,7 @@ pub fn build_script_f2_blake3_locked( ]) } - pub fn message_to_witness_limbs(x: u32, nonce: u64) -> Vec> { - let message = [ x.to_le_bytes(), nonce.to_le_bytes()[0..4].try_into().unwrap(), @@ -585,7 +587,7 @@ mod tests { let push_script = ScriptBuf::from_bytes(msg_push_script.to_bytes()); let total_msg_len = 12; - + let compute_compiled = blake3_compute_script_with_limb(total_msg_len, limb_len).compile(); let compute_optimized = optimizer::optimize(compute_compiled); diff --git a/src/transactions.rs b/src/transactions.rs index b99c7ea..0cd6118 100644 --- a/src/transactions.rs +++ b/src/transactions.rs @@ -1,8 +1,8 @@ use crate::core::{ - build_script_f1_blake3_locked, - build_script_f2_blake3_locked, message_to_witness_limbs, + build_script_f1_blake3_locked, build_script_f2_blake3_locked, + message_to_witness_limbs, }; -use crate::utils::{estimate_fee_vbytes}; +use crate::utils::estimate_fee_vbytes; use anyhow; use bitcoin::sighash::Prevouts; use bitcoin::taproot::{LeafVersion, TaprootBuilder, TaprootSpendInfo}; From b5163558c1e03444a66d721420c86adafd55da95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kami=C5=84ski?= Date: Wed, 4 Jun 2025 16:50:53 +0200 Subject: [PATCH 3/5] clippy --- src/bin/mine-pow-alephium.rs | 2 +- src/core.rs | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/src/bin/mine-pow-alephium.rs b/src/bin/mine-pow-alephium.rs index 94b16f2..31553a2 100644 --- a/src/bin/mine-pow-alephium.rs +++ b/src/bin/mine-pow-alephium.rs @@ -219,7 +219,7 @@ fn build_check_alephium_block_hash( ) -> ScriptBuf { let limb_len = 16; - let message_limbs = blake3_message_to_limbs(&block, limb_len) + let message_limbs = blake3_message_to_limbs(block, limb_len) .iter() .map(|&limb| limb as i64) .fold(Builder::new(), |b, limb| b.push_int(limb)) diff --git a/src/core.rs b/src/core.rs index 8e343a4..9c6271b 100644 --- a/src/core.rs +++ b/src/core.rs @@ -187,9 +187,7 @@ fn build_script_reconstruct_x(limb_len: u8) -> ScriptBuf { "limb_len_bits must be a power of 2" ); - let limbs_needed = (32 + limb_len - 1) / limb_len; // Ceiling division - - println!("limb_len: {limb_len}, limbs_needed: {limbs_needed}"); + let limbs_needed = 32u32.div_ceil(limb_len as u32) as usize; let mut b = Builder::new().push_int(0); // acc = 0 @@ -216,8 +214,8 @@ fn build_script_reconstruct_x(limb_len: u8) -> ScriptBuf { // limb length for blake3 in bits, // blake3 accepts any limb length [4, 32) but due to the way how build_script_reconstruct_x // it must be a power of 2 between 1 and 16 -// Valid values: 1, 2, 4, 8, 16 -const LIMB_LEN: u8 = 16; +// Valid values: 4, 8, 16 +const LIMB_LEN: u8 = 8; /// Build an F1 script with onchain BLAKE3, checking x>F1_THRESHOLD and the top (b_bits/8) bytes match flow_id_prefix. pub fn build_script_f1_blake3_locked( From 11d1191c9e0e4f4e97c864e6257c9745537c8dda Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kami=C5=84ski?= Date: Wed, 4 Jun 2025 16:52:13 +0200 Subject: [PATCH 4/5] clippy 2 --- src/core.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core.rs b/src/core.rs index 9c6271b..e4c4f38 100644 --- a/src/core.rs +++ b/src/core.rs @@ -187,7 +187,7 @@ fn build_script_reconstruct_x(limb_len: u8) -> ScriptBuf { "limb_len_bits must be a power of 2" ); - let limbs_needed = 32u32.div_ceil(limb_len as u32) as usize; + let limbs_needed = 32u8.div_ceil(limb_len); let mut b = Builder::new().push_int(0); // acc = 0 From a8ceecba40fe75ac761922b7dd34df0ad2e875ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maciej=20Kami=C5=84ski?= Date: Wed, 4 Jun 2025 17:02:00 +0200 Subject: [PATCH 5/5] limb len back to 4 for now --- src/core.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/core.rs b/src/core.rs index e4c4f38..63086ce 100644 --- a/src/core.rs +++ b/src/core.rs @@ -215,7 +215,7 @@ fn build_script_reconstruct_x(limb_len: u8) -> ScriptBuf { // blake3 accepts any limb length [4, 32) but due to the way how build_script_reconstruct_x // it must be a power of 2 between 1 and 16 // Valid values: 4, 8, 16 -const LIMB_LEN: u8 = 8; +const LIMB_LEN: u8 = 4; /// Build an F1 script with onchain BLAKE3, checking x>F1_THRESHOLD and the top (b_bits/8) bytes match flow_id_prefix. pub fn build_script_f1_blake3_locked(