1use wasm_bindgen::prelude::*;
6use zk402_prover::{build_payload as prover_build_payload, ClientWitness, ProverConfig};
7
8#[wasm_bindgen(start)]
10pub fn init() {
11 console_error_panic_hook::set_once();
12}
13
14#[derive(serde::Deserialize)]
16#[serde(rename_all = "camelCase")]
17struct WasmWitnessInput {
18 secret_key: String, from: String, payment_nonce: String, proving_key: String, valid_after: u64,
23 valid_before: u64,
24}
25
26#[wasm_bindgen]
67pub fn build_payload(requirements_json: &str, witness_json: &str) -> Result<String, JsValue> {
68 let requirements: zk402_core::PaymentRequirements = serde_json::from_str(requirements_json)
70 .map_err(|e| JsValue::from_str(&format!("failed to parse requirements: {}", e)))?;
71
72 let witness_input: WasmWitnessInput = serde_json::from_str(witness_json)
74 .map_err(|e| JsValue::from_str(&format!("failed to parse witness: {}", e)))?;
75
76 let secret_key = decode_hex(&witness_input.secret_key)?;
78 let from = decode_address(&witness_input.from)?;
79 let payment_nonce = decode_nonce(&witness_input.payment_nonce)?;
80 let proving_key = decode_hex(&witness_input.proving_key)?;
81
82 let witness = ClientWitness {
84 secret_key,
85 from,
86 payment_nonce,
87 };
88
89 let config = ProverConfig {
91 proving_key,
92 valid_after: witness_input.valid_after,
93 valid_before: witness_input.valid_before,
94 };
95
96 let payload = prover_build_payload(&requirements, &witness, &config)
98 .map_err(|e| JsValue::from_str(&format!("proof generation failed: {}", e)))?;
99
100 serde_json::to_string(&payload)
102 .map_err(|e| JsValue::from_str(&format!("failed to serialize payload: {}", e)))
103}
104
105fn decode_hex(s: &str) -> Result<Vec<u8>, JsValue> {
107 let s = s.strip_prefix("0x").unwrap_or(s);
108 hex::decode(s).map_err(|e| JsValue::from_str(&format!("invalid hex: {}", e)))
109}
110
111fn decode_address(s: &str) -> Result<[u8; 20], JsValue> {
113 let bytes = decode_hex(s)?;
114 bytes
115 .try_into()
116 .map_err(|_| JsValue::from_str("address must be 20 bytes"))
117}
118
119fn decode_nonce(s: &str) -> Result<[u8; 32], JsValue> {
121 let bytes = decode_hex(s)?;
122 bytes
123 .try_into()
124 .map_err(|_| JsValue::from_str("nonce must be 32 bytes"))
125}
126
127#[cfg(test)]
128mod tests {
129 use super::*;
130
131 #[test]
132 fn decodes_hex_with_prefix() {
133 let result = decode_hex("0x1234");
134 assert!(result.is_ok());
135 assert_eq!(result.unwrap(), vec![0x12, 0x34]);
136 }
137
138 #[test]
139 fn decodes_hex_without_prefix() {
140 let result = decode_hex("1234");
141 assert!(result.is_ok());
142 assert_eq!(result.unwrap(), vec![0x12, 0x34]);
143 }
144
145 #[test]
146 fn decodes_address() {
147 let addr = "0x0000000000000000000000000000000000000001";
148 let result = decode_address(addr);
149 assert!(result.is_ok());
150 assert_eq!(result.unwrap()[19], 1);
151 }
152
153 #[test]
154 fn rejects_invalid_address_length() {
155 let addr = "0x1234"; let result = decode_address(addr);
157 assert!(result.is_err());
158 }
159}