1use codec::Encode;
13use qp_wormhole_circuit::{
14 inputs::{CircuitInputs, PrivateCircuitInputs},
15 nullifier::Nullifier,
16};
17use qp_wormhole_inputs::PublicCircuitInputs;
18use qp_wormhole_prover::WormholeProver;
19use qp_zk_circuits_common::{
20 storage_proof::prepare_proof_for_circuit,
21 utils::{digest_to_bytes, BytesDigest},
22};
23use sp_core::crypto::AccountId32;
24use std::path::Path;
25
26pub const NATIVE_ASSET_ID: u32 = 0;
28
29pub const SCALE_DOWN_FACTOR: u128 = 10_000_000_000;
31
32pub const VOLUME_FEE_BPS: u32 = 10;
34
35pub type TransferProofData = (u32, u64, AccountId32, AccountId32, u128);
38
39pub type TransferProofKey = (AccountId32, u64);
42
43pub type Result<T> = std::result::Result<T, WormholeLibError>;
45
46#[derive(Debug, Clone)]
48pub struct WormholeLibError {
49 pub message: String,
50}
51
52impl std::fmt::Display for WormholeLibError {
53 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
54 write!(f, "{}", self.message)
55 }
56}
57
58impl std::error::Error for WormholeLibError {}
59
60impl From<String> for WormholeLibError {
61 fn from(message: String) -> Self {
62 Self { message }
63 }
64}
65
66#[derive(Debug, Clone)]
69pub struct ProofGenerationInput {
70 pub secret: [u8; 32],
72 pub transfer_count: u64,
74 pub funding_account: [u8; 32],
76 pub wormhole_address: [u8; 32],
78 pub funding_amount: u128,
80 pub block_hash: [u8; 32],
82 pub block_number: u32,
84 pub parent_hash: [u8; 32],
86 pub state_root: [u8; 32],
88 pub extrinsics_root: [u8; 32],
90 pub digest: Vec<u8>,
92 pub proof_nodes: Vec<Vec<u8>>,
94 pub exit_account_1: [u8; 32],
96 pub exit_account_2: [u8; 32],
98 pub output_amount_1: u32,
100 pub output_amount_2: u32,
102 pub volume_fee_bps: u32,
104 pub asset_id: u32,
106}
107
108#[derive(Debug, Clone)]
110pub struct ProofGenerationOutput {
111 pub proof_bytes: Vec<u8>,
113 #[allow(dead_code)]
115 pub nullifier: [u8; 32],
116}
117
118pub fn compute_leaf_hash(
133 asset_id: u32,
134 transfer_count: u64,
135 funding_account: &[u8; 32],
136 wormhole_address: &[u8; 32],
137 amount: u128,
138) -> [u8; 32] {
139 let from_account = AccountId32::new(*funding_account);
141 let to_account = AccountId32::new(*wormhole_address);
142
143 let transfer_data: TransferProofData =
144 (asset_id, transfer_count, from_account, to_account, amount);
145 let encoded_data = transfer_data.encode();
146
147 qp_poseidon::PoseidonHasher::hash_storage::<TransferProofData>(&encoded_data)
148}
149
150pub fn compute_storage_key(wormhole_address: &[u8; 32], transfer_count: u64) -> Vec<u8> {
162 let pallet_hash = sp_core::twox_128(b"Wormhole");
163 let storage_hash = sp_core::twox_128(b"TransferProof");
164
165 let mut final_key = Vec::with_capacity(32 + 32);
166 final_key.extend_from_slice(&pallet_hash);
167 final_key.extend_from_slice(&storage_hash);
168
169 let to_account = AccountId32::new(*wormhole_address);
171 let key_tuple: TransferProofKey = (to_account, transfer_count);
172 let encoded_key = key_tuple.encode();
173 let key_hash = sp_core::blake2_256(&encoded_key);
174 final_key.extend_from_slice(&key_hash);
175
176 final_key
177}
178
179pub fn compute_wormhole_address(secret: &[u8; 32]) -> Result<[u8; 32]> {
187 let secret_digest: BytesDigest = (*secret)
188 .try_into()
189 .map_err(|e| WormholeLibError::from(format!("Invalid secret: {:?}", e)))?;
190
191 let unspendable =
192 qp_wormhole_circuit::unspendable_account::UnspendableAccount::from_secret(secret_digest);
193
194 Ok(*digest_to_bytes(unspendable.account_id))
195}
196
197#[allow(dead_code)]
206pub fn compute_nullifier(secret: &[u8; 32], transfer_count: u64) -> Result<[u8; 32]> {
207 let secret_digest: BytesDigest = (*secret)
208 .try_into()
209 .map_err(|e| WormholeLibError::from(format!("Invalid secret: {:?}", e)))?;
210
211 let nullifier = Nullifier::from_preimage(secret_digest, transfer_count);
212 Ok(*digest_to_bytes(nullifier.hash))
213}
214
215pub fn quantize_amount(amount: u128) -> Result<u32> {
223 let quantized = amount / SCALE_DOWN_FACTOR;
224 if quantized > u32::MAX as u128 {
225 return Err(WormholeLibError::from(format!(
226 "Quantized amount {} exceeds u32::MAX",
227 quantized
228 )));
229 }
230 Ok(quantized as u32)
231}
232
233pub fn compute_output_amount(input_amount: u32, fee_bps: u32) -> u32 {
237 ((input_amount as u64) * (10000 - fee_bps as u64) / 10000) as u32
238}
239
240pub fn generate_proof(
253 input: &ProofGenerationInput,
254 prover_bin_path: &Path,
255 common_bin_path: &Path,
256) -> Result<ProofGenerationOutput> {
257 let secret_digest: BytesDigest = input
259 .secret
260 .try_into()
261 .map_err(|e| WormholeLibError::from(format!("Invalid secret: {:?}", e)))?;
262
263 let leaf_hash = compute_leaf_hash(
265 input.asset_id,
266 input.transfer_count,
267 &input.funding_account,
268 &input.wormhole_address,
269 input.funding_amount,
270 );
271
272 let processed_proof = prepare_proof_for_circuit(
274 input.proof_nodes.clone(),
275 hex::encode(input.state_root),
276 leaf_hash,
277 )
278 .map_err(|e| WormholeLibError::from(format!("Storage proof preparation failed: {}", e)))?;
279
280 let input_amount_quantized = quantize_amount(input.funding_amount)?;
282
283 let nullifier = Nullifier::from_preimage(secret_digest, input.transfer_count);
285 let nullifier_bytes = digest_to_bytes(nullifier.hash);
286
287 let unspendable =
289 qp_wormhole_circuit::unspendable_account::UnspendableAccount::from_secret(secret_digest);
290 let unspendable_bytes = digest_to_bytes(unspendable.account_id);
291
292 const DIGEST_LOGS_SIZE: usize = 110;
294 let mut digest_padded = [0u8; DIGEST_LOGS_SIZE];
295 let copy_len = input.digest.len().min(DIGEST_LOGS_SIZE);
296 digest_padded[..copy_len].copy_from_slice(&input.digest[..copy_len]);
297
298 let private = PrivateCircuitInputs {
300 secret: secret_digest,
301 transfer_count: input.transfer_count,
302 funding_account: input
303 .funding_account
304 .as_slice()
305 .try_into()
306 .map_err(|e| WormholeLibError::from(format!("Invalid funding account: {:?}", e)))?,
307 storage_proof: processed_proof,
308 unspendable_account: unspendable_bytes,
309 parent_hash: input
310 .parent_hash
311 .as_slice()
312 .try_into()
313 .map_err(|e| WormholeLibError::from(format!("Invalid parent hash: {:?}", e)))?,
314 state_root: input
315 .state_root
316 .as_slice()
317 .try_into()
318 .map_err(|e| WormholeLibError::from(format!("Invalid state root: {:?}", e)))?,
319 extrinsics_root: input
320 .extrinsics_root
321 .as_slice()
322 .try_into()
323 .map_err(|e| WormholeLibError::from(format!("Invalid extrinsics root: {:?}", e)))?,
324 digest: digest_padded,
325 input_amount: input_amount_quantized,
326 };
327
328 let public = PublicCircuitInputs {
329 asset_id: input.asset_id,
330 output_amount_1: input.output_amount_1,
331 output_amount_2: input.output_amount_2,
332 volume_fee_bps: input.volume_fee_bps,
333 nullifier: nullifier_bytes,
334 exit_account_1: input
335 .exit_account_1
336 .as_slice()
337 .try_into()
338 .map_err(|e| WormholeLibError::from(format!("Invalid exit account 1: {:?}", e)))?,
339 exit_account_2: input
340 .exit_account_2
341 .as_slice()
342 .try_into()
343 .map_err(|e| WormholeLibError::from(format!("Invalid exit account 2: {:?}", e)))?,
344 block_hash: input
345 .block_hash
346 .as_slice()
347 .try_into()
348 .map_err(|e| WormholeLibError::from(format!("Invalid block hash: {:?}", e)))?,
349 block_number: input.block_number,
350 };
351
352 let circuit_inputs = CircuitInputs { public, private };
353
354 let prover = WormholeProver::new_from_files(prover_bin_path, common_bin_path)
356 .map_err(|e| WormholeLibError::from(format!("Failed to load prover: {}", e)))?;
357
358 let prover_with_inputs = prover
359 .commit(&circuit_inputs)
360 .map_err(|e| WormholeLibError::from(format!("Failed to commit inputs: {}", e)))?;
361
362 let proof = prover_with_inputs
363 .prove()
364 .map_err(|e| WormholeLibError::from(format!("Proof generation failed: {}", e)))?;
365
366 Ok(ProofGenerationOutput { proof_bytes: proof.to_bytes(), nullifier: *nullifier_bytes })
367}
368
369#[cfg(test)]
370mod tests {
371 use super::*;
372
373 #[test]
374 fn test_quantize_amount() {
375 let result = quantize_amount(1_000_000_000_000).unwrap();
377 assert_eq!(result, 100);
378
379 let result = quantize_amount(10_000_000_000).unwrap();
381 assert_eq!(result, 1);
382 }
383
384 #[test]
385 fn test_compute_output_amount() {
386 let result = compute_output_amount(100, 10);
388 assert_eq!(result, 99);
389
390 let result = compute_output_amount(1000, 10);
392 assert_eq!(result, 999);
393 }
394
395 #[test]
396 fn test_storage_key_computation() {
397 let wormhole_address = [0u8; 32];
399 let key = compute_storage_key(&wormhole_address, 1);
400 assert_eq!(key.len(), 64);
402 }
403}