Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
22 changes: 9 additions & 13 deletions src/bin/mine-pow-alephium.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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
}
Expand Down
182 changes: 70 additions & 112 deletions src/core.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
use crate::utils::NonceSearchProgress;
use crate::utils::{NonceSearchProgress, encode_scriptnum};
use bitcoin::{
Amount, PublicKey, XOnlyPublicKey,
blockdata::script::{Builder, ScriptBuf},
Expand All @@ -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};

Expand Down Expand Up @@ -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,
Expand All @@ -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 = {
Expand All @@ -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()
Expand All @@ -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());
Expand Down Expand Up @@ -260,30 +277,23 @@ pub fn build_script_f1_blake3_locked(
}

/// Build an F2 script with onchain BLAKE3, checking x<F2_THRESHOLD and prefix
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,
test_mode: bool,
) -> 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()
Expand All @@ -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())
Expand Down Expand Up @@ -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<Vec<u8>> {
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...");
Expand Down Expand Up @@ -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::<Vec<u8>>().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<u32> {
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<u32> {
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 {
Expand Down Expand Up @@ -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(),
Expand Down Expand Up @@ -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));
Expand Down Expand Up @@ -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);
Expand Down
Loading