diff --git a/src/bin/mine-pow-alephium.rs b/src/bin/mine-pow-alephium.rs index 30180cc..31553a2 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..63086ce 100644 --- a/src/core.rs +++ b/src/core.rs @@ -1,4 +1,4 @@ -use crate::utils::NonceSearchProgress; +use crate::utils::{NonceSearchProgress, encode_scriptnum}; 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,45 @@ 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 { +// 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 = 32u8.div_ceil(limb_len); + let mut b = Builder::new().push_int(0); // acc = 0 - for i in 0..8 { - // acc *= 16 - for _ in 0..4 { + 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); } + // 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) - .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); + .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: 4, 8, 16 +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( signer_pubkey: &PublicKey, @@ -207,7 +225,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 +234,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 +245,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 +277,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 +305,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 +335,19 @@ 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..0cd6118 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(()) }