prover/
serialization.rs

1//! Serialization utilities for witness and proof data
2//!
3//! Handles conversion between JavaScript types and Arkworks field elements.
4//! All byte arrays use Little-Endian format (as expected by Arkworks).
5
6use alloc::{format, string::String, vec::Vec};
7use ark_bn254::Fr;
8use ark_ff::{Field, PrimeField};
9use core::ops::{Add, Mul};
10use wasm_bindgen::prelude::*;
11use zkhash::fields::bn256::FpBN256 as Scalar;
12
13use crate::types::FIELD_SIZE;
14
15/// Convert Little-Endian bytes to Arkworks Fr field element
16pub fn bytes_to_fr(bytes: &[u8]) -> Result<Fr, JsValue> {
17    if bytes.len() != FIELD_SIZE {
18        return Err(JsValue::from_str(&format!(
19            "Expected {} bytes, got {}",
20            FIELD_SIZE,
21            bytes.len()
22        )));
23    }
24    Ok(Fr::from_le_bytes_mod_order(bytes))
25}
26
27/// Convert Arkworks Fr field element to Little-Endian bytes
28pub fn fr_to_bytes(fr: &Fr) -> Vec<u8> {
29    let mut bytes = Vec::with_capacity(FIELD_SIZE);
30    // Arkworks BigInt is stored as limbs, we need to convert to LE bytes
31    let bigint = fr.into_bigint();
32    for limb in bigint.0.iter() {
33        bytes.extend_from_slice(&limb.to_le_bytes());
34    }
35    bytes.truncate(FIELD_SIZE);
36    bytes
37}
38
39/// Convert Little-Endian bytes to zkhash Scalar
40pub fn bytes_to_scalar(bytes: &[u8]) -> Result<Scalar, JsValue> {
41    if bytes.len() != FIELD_SIZE {
42        return Err(JsValue::from_str(&format!(
43            "Expected {} bytes, got {}",
44            FIELD_SIZE,
45            bytes.len()
46        )));
47    }
48    Ok(Scalar::from_le_bytes_mod_order(bytes))
49}
50
51/// Convert zkhash Scalar to Little-Endian bytes
52pub fn scalar_to_bytes(scalar: &Scalar) -> Vec<u8> {
53    let mut bytes = Vec::with_capacity(FIELD_SIZE);
54    let bigint = scalar.into_bigint();
55    for limb in bigint.0.iter() {
56        bytes.extend_from_slice(&limb.to_le_bytes());
57    }
58    bytes.truncate(FIELD_SIZE);
59    bytes
60}
61
62/// Convert zkhash Scalar to hex string (for JS BigInt)
63pub fn scalar_to_hex(scalar: &Scalar) -> String {
64    let bytes = scalar_to_bytes(scalar);
65    // Convert to big-endian hex for human readability
66    let mut hex = String::from("0x");
67    for byte in bytes.iter().rev() {
68        hex.push_str(&format!("{:02x}", byte));
69    }
70    hex
71}
72
73/// Convert hex string to zkhash Scalar
74pub fn hex_to_scalar(hex: &str) -> Result<Scalar, JsValue> {
75    let hex = hex.strip_prefix("0x").unwrap_or(hex);
76
77    if hex.len() > 64 {
78        return Err(JsValue::from_str("Hex string too long"));
79    }
80
81    // Pad to 64 characters
82    let padded = format!("{:0>64}", hex);
83
84    // Parse hex to bytes (big-endian)
85    let mut bytes = [0u8; FIELD_SIZE];
86    for (i, chunk) in padded.as_bytes().chunks(2).enumerate() {
87        let byte_str =
88            core::str::from_utf8(chunk).map_err(|_| JsValue::from_str("Invalid hex character"))?;
89        let idx = FIELD_SIZE
90            .checked_sub(1)
91            .and_then(|v| v.checked_sub(i))
92            .ok_or_else(|| JsValue::from_str("Index overflow"))?;
93        bytes[idx] = u8::from_str_radix(byte_str, 16)
94            .map_err(|_| JsValue::from_str("Invalid hex character"))?;
95    }
96
97    Ok(Scalar::from_le_bytes_mod_order(&bytes))
98}
99
100/// Parse witness bytes into vector of Fr elements
101///
102/// Witness bytes are Little-Endian, 32 bytes per element
103#[wasm_bindgen]
104pub fn parse_witness(witness_bytes: &[u8]) -> Result<Vec<u8>, JsValue> {
105    if !witness_bytes.len().is_multiple_of(FIELD_SIZE) {
106        return Err(JsValue::from_str(&format!(
107            "Witness bytes length {} is not a multiple of {}",
108            witness_bytes.len(),
109            FIELD_SIZE
110        )));
111    }
112
113    // For now, just validate and return as-is
114    // The actual parsing happens in the prover
115    Ok(witness_bytes.to_vec())
116}
117
118/// Get the number of witness elements
119#[wasm_bindgen]
120pub fn witness_element_count(witness_bytes: &[u8]) -> Result<u32, JsValue> {
121    if !witness_bytes.len().is_multiple_of(FIELD_SIZE) {
122        return Err(JsValue::from_str("Invalid witness bytes length"));
123    }
124    let count = witness_bytes.len() / FIELD_SIZE;
125    u32::try_from(count).map_err(|_| JsValue::from_str("Witness count exceeds u32"))
126}
127
128/// Convert a u64 to Little-Endian field element bytes
129#[wasm_bindgen]
130pub fn u64_to_field_bytes(value: u64) -> Vec<u8> {
131    let scalar = Scalar::from(value);
132    scalar_to_bytes(&scalar)
133}
134
135/// Convert a decimal string to Little-Endian field element bytes
136#[wasm_bindgen]
137pub fn decimal_to_field_bytes(decimal: &str) -> Result<Vec<u8>, JsValue> {
138    // Parse decimal string to BigInt-like representation
139    // For simplicity, handle up to u128 range
140    let value: u128 = decimal
141        .parse()
142        .map_err(|_| JsValue::from_str("Invalid decimal string"))?;
143
144    // Convert to field element using safe field arithmetic
145    let low = (value & 0xFFFFFFFFFFFFFFFF) as u64;
146    let high = (value >> 64) as u64;
147
148    let scalar = Scalar::from(low).add(Scalar::from(high).mul(Scalar::from(1u64 << 32).square()));
149    Ok(scalar_to_bytes(&scalar))
150}
151
152/// Convert Little-Endian field bytes to hex string
153#[wasm_bindgen]
154pub fn field_bytes_to_hex(bytes: &[u8]) -> Result<String, JsValue> {
155    let scalar = bytes_to_scalar(bytes)?;
156    Ok(scalar_to_hex(&scalar))
157}
158
159/// Convert hex string to Little-Endian field bytes
160#[wasm_bindgen]
161pub fn hex_to_field_bytes(hex: &str) -> Result<Vec<u8>, JsValue> {
162    let scalar = hex_to_scalar(hex)?;
163    Ok(scalar_to_bytes(&scalar))
164}