diff --git a/Cargo.lock b/Cargo.lock index 1e2e452..f862d42 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -531,6 +531,7 @@ dependencies = [ "hex", "log", "primitive-types", + "qp-poseidon-core 1.0.1", "rand 0.8.5", "thiserror 1.0.69", "wgpu", @@ -1798,7 +1799,7 @@ dependencies = [ "num-bigint", "num-traits", "primitive-types", - "qp-poseidon-core", + "qp-poseidon-core 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "qpow-math", "thiserror 1.0.69", ] @@ -1867,6 +1868,17 @@ dependencies = [ "thiserror 1.0.69", ] +[[package]] +name = "qp-poseidon-constants" +version = "1.0.1" +dependencies = [ + "p3-field", + "p3-goldilocks", + "p3-poseidon2", + "rand 0.9.2", + "rand_chacha 0.9.0", +] + [[package]] name = "qp-poseidon-constants" version = "1.0.1" @@ -1880,6 +1892,18 @@ dependencies = [ "rand_chacha 0.9.0", ] +[[package]] +name = "qp-poseidon-core" +version = "1.0.1" +dependencies = [ + "p3-field", + "p3-goldilocks", + "p3-poseidon2", + "p3-symmetric", + "qp-poseidon-constants 1.0.1", + "rand_chacha 0.9.0", +] + [[package]] name = "qp-poseidon-core" version = "1.0.1" @@ -1890,7 +1914,7 @@ dependencies = [ "p3-goldilocks", "p3-poseidon2", "p3-symmetric", - "qp-poseidon-constants", + "qp-poseidon-constants 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "rand_chacha 0.9.0", ] @@ -1904,7 +1928,7 @@ dependencies = [ "num-bigint", "num-traits", "primitive-types", - "qp-poseidon-core", + "qp-poseidon-core 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", ] [[package]] diff --git a/crates/engine-gpu/Cargo.toml b/crates/engine-gpu/Cargo.toml index fd00592..e17154d 100644 --- a/crates/engine-gpu/Cargo.toml +++ b/crates/engine-gpu/Cargo.toml @@ -22,6 +22,7 @@ primitive-types = { workspace = true } log = { workspace = true } thiserror = { workspace = true } anyhow = { workspace = true } +qp-poseidon-core = { path = "../../../qp-poseidon/core" } wgpu = { version = "27.0.1" } # GPU compute library futures = "0.3" # For async executor diff --git a/crates/engine-gpu/src/main.rs b/crates/engine-gpu/src/main.rs index 3c20f90..ebc3e95 100644 --- a/crates/engine-gpu/src/main.rs +++ b/crates/engine-gpu/src/main.rs @@ -2,6 +2,245 @@ use bytemuck; use futures::executor::block_on; use wgpu::{self, util::DeviceExt}; +// Test vector generation using the CPU implementation +fn generate_test_vectors() -> Vec<([u8; 96], [u8; 64])> { + println!("Generating test vectors using CPU Poseidon2..."); + + let mut test_vectors = Vec::new(); + + // Test vector 1: All zeros + let input1 = [0u8; 96]; + let expected1 = qp_poseidon_core::hash_squeeze_twice(&input1); + test_vectors.push((input1, expected1)); + println!( + "Test vector 1 (zeros): input={:?}, expected={:?}", + &input1[..8], + &expected1[..8] + ); + + // Test vector 2: Incremental pattern + let mut input2 = [0u8; 96]; + for i in 0..96 { + input2[i] = (i % 256) as u8; + } + let expected2 = qp_poseidon_core::hash_squeeze_twice(&input2); + test_vectors.push((input2, expected2)); + println!( + "Test vector 2 (incremental): input={:?}, expected={:?}", + &input2[..8], + &expected2[..8] + ); + + // Test vector 3: Example mining input (header + nonce) + let mut input3 = [0u8; 96]; + // Header (32 bytes) + input3[..32].copy_from_slice(&[ + 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, + 26, 27, 28, 29, 30, 31, 32, + ]); + // Nonce (64 bytes) - start with simple pattern + for i in 32..96 { + input3[i] = ((i - 32) % 256) as u8; + } + let expected3 = qp_poseidon_core::hash_squeeze_twice(&input3); + test_vectors.push((input3, expected3)); + println!( + "Test vector 3 (mining): input={:?}, expected={:?}", + &input3[..8], + &expected3[..8] + ); + + test_vectors +} + +fn u8_array_to_u32_array(bytes: &[u8; 96]) -> [u32; 24] { + let mut result = [0u32; 24]; + for i in 0..24 { + let idx = i * 4; + result[i] = + u32::from_le_bytes([bytes[idx], bytes[idx + 1], bytes[idx + 2], bytes[idx + 3]]); + } + result +} + +fn u8_array_to_u32_array_64(bytes: &[u8; 64]) -> [u32; 16] { + let mut result = [0u32; 16]; + for i in 0..16 { + let idx = i * 4; + result[i] = + u32::from_le_bytes([bytes[idx], bytes[idx + 1], bytes[idx + 2], bytes[idx + 3]]); + } + result +} + +async fn test_gpu_with_vectors( + device: &wgpu::Device, + queue: &wgpu::Queue, + bind_group_layout: &wgpu::BindGroupLayout, + pipeline: &wgpu::ComputePipeline, + test_vectors: &[([u8; 96], [u8; 64])], +) -> Result<(), Box> { + println!("\nTesting GPU implementation with known vectors..."); + + for (i, (input_bytes, expected_bytes)) in test_vectors.iter().enumerate() { + println!("\n--- Testing Vector {} ---", i + 1); + + let input_u32 = u8_array_to_u32_array(input_bytes); + let expected_u32 = u8_array_to_u32_array_64(expected_bytes); + + // Create test buffers + let test_results_data = vec![0u32; 33]; + let test_results_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Test Results Buffer"), + contents: bytemuck::cast_slice(&test_results_data), + usage: wgpu::BufferUsages::STORAGE | wgpu::BufferUsages::COPY_SRC, + }); + + // Split input into header and nonce parts + let header_data = [ + input_u32[0], + input_u32[1], + input_u32[2], + input_u32[3], + input_u32[4], + input_u32[5], + input_u32[6], + input_u32[7], + ]; + let nonce_data = [ + input_u32[8], + input_u32[9], + input_u32[10], + input_u32[11], + input_u32[12], + input_u32[13], + input_u32[14], + input_u32[15], + input_u32[16], + input_u32[17], + input_u32[18], + input_u32[19], + input_u32[20], + input_u32[21], + input_u32[22], + input_u32[23], + ]; + + let test_header_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Test Header Buffer"), + contents: bytemuck::cast_slice(&header_data), + usage: wgpu::BufferUsages::STORAGE, + }); + + let test_nonce_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Test Nonce Buffer"), + contents: bytemuck::cast_slice(&nonce_data), + usage: wgpu::BufferUsages::STORAGE, + }); + + // Use max target so any hash will be "valid" + let max_target_data = [0xFFFFFFFFu32; 16]; + let test_target_buffer = device.create_buffer_init(&wgpu::util::BufferInitDescriptor { + label: Some("Test Target Buffer"), + contents: bytemuck::cast_slice(&max_target_data), + usage: wgpu::BufferUsages::STORAGE, + }); + + let test_bind_group = device.create_bind_group(&wgpu::BindGroupDescriptor { + label: Some("Test Bind Group"), + layout: bind_group_layout, + entries: &[ + wgpu::BindGroupEntry { + binding: 0, + resource: test_results_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 1, + resource: test_header_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 2, + resource: test_nonce_buffer.as_entire_binding(), + }, + wgpu::BindGroupEntry { + binding: 3, + resource: test_target_buffer.as_entire_binding(), + }, + ], + }); + + // Run single thread (thread 0 will use exact nonce) + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Test Command Encoder"), + }); + { + let mut pass = encoder.begin_compute_pass(&wgpu::ComputePassDescriptor { + label: Some("Test Compute Pass"), + timestamp_writes: None, + }); + pass.set_pipeline(pipeline); + pass.set_bind_group(0, &test_bind_group, &[]); + pass.dispatch_workgroups(1, 1, 1); // Just 1 workgroup = 64 threads, thread 0 uses exact nonce + } + queue.submit(Some(encoder.finish())); + + // Read results + let staging = device.create_buffer(&wgpu::BufferDescriptor { + label: Some("Test Staging Buffer"), + size: test_results_buffer.size(), + usage: wgpu::BufferUsages::MAP_READ | wgpu::BufferUsages::COPY_DST, + mapped_at_creation: false, + }); + + let mut encoder = device.create_command_encoder(&wgpu::CommandEncoderDescriptor { + label: Some("Test Copy Command Encoder"), + }); + encoder.copy_buffer_to_buffer( + &test_results_buffer, + 0, + &staging, + 0, + test_results_buffer.size(), + ); + queue.submit(Some(encoder.finish())); + + let slice = staging.slice(..); + slice.map_async(wgpu::MapMode::Read, |_| ()); + device + .poll(wgpu::PollType::Wait { + submission_index: None, + timeout: None, + }) + .unwrap(); + let mapped = slice.get_mapped_range(); + let result: Vec = bytemuck::cast_slice(&mapped).to_vec(); + drop(mapped); + staging.unmap(); + + if result[0] == 1 { + let gpu_hash = &result[17..33]; + println!("GPU result: {:?}", &gpu_hash[..4]); + println!("Expected: {:?}", &expected_u32[..4]); + + let matches = gpu_hash + .iter() + .zip(expected_u32.iter()) + .all(|(a, b)| a == b); + if matches { + println!("✅ Test vector {} PASSED", i + 1); + } else { + println!("❌ Test vector {} FAILED", i + 1); + println!("GPU hash: {:?}", gpu_hash); + println!("Expected: {:?}", expected_u32); + } + } else { + println!("❌ Test vector {} FAILED - no result computed", i + 1); + } + } + + Ok(()) +} + fn main() { block_on(run()).unwrap(); } @@ -20,6 +259,9 @@ async fn run() -> Result<(), Box> { .request_device(&wgpu::DeviceDescriptor::default()) .await?; + // Generate test vectors first + let test_vectors = generate_test_vectors(); + // Load WGSL shader from external file let shader_source = include_str!("mining.wgsl"); @@ -204,5 +446,15 @@ async fn run() -> Result<(), Box> { println!("First few results: {:?}", &result[0..10]); } + // Test with known vectors + test_gpu_with_vectors( + &device, + &queue, + &bind_group_layout, + &pipeline, + &test_vectors, + ) + .await?; + Ok(()) }