use std::ffi::c_void;
use openvm_cuda_common::{
copy::cuda_memcpy_on, d_buffer::DeviceBuffer, error::MemCopyError, stream::GpuDeviceCtx,
};
use openvm_stark_backend::FiatShamirTranscript;
use openvm_stark_sdk::config::{
baby_bear_bn254_poseidon2::{BabyBearBn254Poseidon2Config, Bn254Scalar, Transcript},
bn254_poseidon2::default_bn254_poseidon2_width3,
};
use p3_baby_bear::BabyBear;
use p3_field::{PrimeCharacteristicRing, PrimeField32};
use crate::sponge::{validate_gpu_grind_bits, GpuFiatShamirTranscript, GrindError};
type Digest = [Bn254Scalar; 1];
#[repr(C)]
#[derive(Clone, Debug, Default)]
pub struct DeviceBn254SpongeState {
pub sponge_state: [[u64; 4]; 3], pub absorb_idx: u32, pub sample_idx: u32, pub observe_buf: [u32; 8], pub observe_buf_len: u32, }
const _: () = assert!(
std::mem::size_of::<Bn254Scalar>() == std::mem::size_of::<[u64; 4]>(),
"Bn254Scalar must be 32 bytes (same as [u64; 4])"
);
const _: () = assert!(
std::mem::align_of::<Bn254Scalar>() == std::mem::align_of::<[u64; 4]>(),
"Bn254Scalar alignment must match [u64; 4]"
);
const _: () = assert!(
std::mem::size_of::<DeviceBn254SpongeState>() == 144,
"DeviceBn254SpongeState must be 144 bytes to match CUDA struct"
);
fn bn254_scalar_to_raw(s: Bn254Scalar) -> [u64; 4] {
unsafe { std::ptr::read((&s as *const Bn254Scalar).cast::<[u64; 4]>()) }
}
#[derive(Debug)]
pub struct MultiFieldTranscriptGpu {
inner: Transcript,
device: DeviceBuffer<DeviceBn254SpongeState>,
}
impl Default for MultiFieldTranscriptGpu {
fn default() -> Self {
Self {
inner: Transcript::from(default_bn254_poseidon2_width3()),
device: DeviceBuffer::new(),
}
}
}
impl Clone for MultiFieldTranscriptGpu {
fn clone(&self) -> Self {
Self {
inner: self.inner.clone(),
device: DeviceBuffer::new(),
}
}
}
impl MultiFieldTranscriptGpu {
pub fn new() -> Self {
Self::default()
}
fn ensure_device_allocated(&mut self, device_ctx: &GpuDeviceCtx) {
if self.device.is_empty() {
self.device = DeviceBuffer::with_capacity_on(1, device_ctx);
}
}
pub fn sync_h2d(&mut self, device_ctx: &GpuDeviceCtx) -> Result<(), MemCopyError> {
self.ensure_device_allocated(device_ctx);
let mut ds = DeviceBn254SpongeState::default();
for (i, &s) in self.inner.sponge_state().iter().enumerate() {
ds.sponge_state[i] = bn254_scalar_to_raw(s);
}
ds.absorb_idx = self.inner.absorb_idx() as u32;
ds.sample_idx = self.inner.sample_idx() as u32;
for (i, &bb) in self.inner.observe_buf().iter().enumerate() {
ds.observe_buf[i] = bb.as_canonical_u32();
}
ds.observe_buf_len = self.inner.observe_buf().len() as u32;
unsafe {
cuda_memcpy_on::<false, true>(
self.device.as_mut_ptr() as *mut c_void,
&ds as *const DeviceBn254SpongeState as *const c_void,
std::mem::size_of::<DeviceBn254SpongeState>(),
device_ctx,
)
}
}
}
impl FiatShamirTranscript<BabyBearBn254Poseidon2Config> for MultiFieldTranscriptGpu {
fn observe(&mut self, value: BabyBear) {
FiatShamirTranscript::<BabyBearBn254Poseidon2Config>::observe(&mut self.inner, value);
}
fn sample(&mut self) -> BabyBear {
FiatShamirTranscript::<BabyBearBn254Poseidon2Config>::sample(&mut self.inner)
}
fn observe_commit(&mut self, digest: Digest) {
FiatShamirTranscript::<BabyBearBn254Poseidon2Config>::observe_commit(
&mut self.inner,
digest,
);
}
}
impl GpuFiatShamirTranscript<BabyBearBn254Poseidon2Config> for MultiFieldTranscriptGpu {
fn grind_gpu(
&mut self,
bits: usize,
device_ctx: &GpuDeviceCtx,
) -> Result<BabyBear, GrindError> {
validate_gpu_grind_bits(bits)?;
if bits == 0 {
return Ok(BabyBear::ZERO);
}
self.sync_h2d(device_ctx)?;
let witness_u32 = unsafe {
crate::cuda::bn254_merkle_tree::bn254_sponge_grind(
self.device.as_ptr(),
bits as u32,
BabyBear::ORDER_U32 - 1,
device_ctx,
)?
};
let witness = BabyBear::from_u32(witness_u32);
FiatShamirTranscript::<BabyBearBn254Poseidon2Config>::observe(&mut self.inner, witness);
let _ = FiatShamirTranscript::<BabyBearBn254Poseidon2Config>::sample(&mut self.inner);
Ok(witness)
}
}
#[cfg(test)]
mod tests {
use openvm_cuda_common::{common::get_device, stream::GpuDeviceCtx};
use openvm_stark_backend::FiatShamirTranscript;
use openvm_stark_sdk::config::baby_bear_bn254_poseidon2::default_transcript;
use p3_field::PrimeCharacteristicRing;
use super::*;
#[test]
fn test_grind_gpu_witness_valid_on_cpu() {
let bits = 8;
let ctx = GpuDeviceCtx::for_device(get_device().unwrap() as u32).unwrap();
for num_observed in [0, 1, 3, 7, 8, 9, 15, 16, 17] {
let mut gpu = MultiFieldTranscriptGpu::new();
let mut cpu = default_transcript();
for i in 0..num_observed {
let val = BabyBear::from_u32((i as u32).wrapping_mul(41).wrapping_add(7));
FiatShamirTranscript::<BabyBearBn254Poseidon2Config>::observe(&mut gpu, val);
FiatShamirTranscript::<BabyBearBn254Poseidon2Config>::observe(&mut cpu, val);
}
let witness = gpu
.grind_gpu(bits, &ctx)
.unwrap_or_else(|e| panic!("grind_gpu failed with {num_observed} observed: {e:?}"));
FiatShamirTranscript::<BabyBearBn254Poseidon2Config>::observe(&mut cpu, witness);
let witness_bits =
FiatShamirTranscript::<BabyBearBn254Poseidon2Config>::sample_bits(&mut cpu, bits);
assert_eq!(
witness_bits, 0,
"CUDA witness {witness:?} invalid on CPU (observed {num_observed} values, witness_bits {witness_bits})"
);
}
}
}