use qp_wormhole_circuit::{
inputs::{CircuitInputs, PrivateCircuitInputs},
nullifier::Nullifier,
};
use qp_wormhole_inputs::PublicCircuitInputs;
use qp_wormhole_prover::WormholeProver;
use qp_zk_circuits_common::{
utils::{digest_to_bytes, BytesDigest},
zk_merkle::SIBLINGS_PER_LEVEL,
};
use std::path::Path;
pub const NATIVE_ASSET_ID: u32 = 0;
pub const SCALE_DOWN_FACTOR: u128 = 10_000_000_000;
pub const VOLUME_FEE_BPS: u32 = 10;
pub type Result<T> = std::result::Result<T, WormholeLibError>;
#[derive(Debug, Clone)]
pub struct WormholeLibError {
pub message: String,
}
impl std::fmt::Display for WormholeLibError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}", self.message)
}
}
impl std::error::Error for WormholeLibError {}
impl From<String> for WormholeLibError {
fn from(message: String) -> Self {
Self { message }
}
}
#[derive(Debug, Clone)]
pub struct ProofGenerationInput {
pub secret: [u8; 32],
pub transfer_count: u64,
pub wormhole_address: [u8; 32],
pub input_amount: u32,
pub block_hash: [u8; 32],
pub block_number: u32,
pub parent_hash: [u8; 32],
pub state_root: [u8; 32],
pub extrinsics_root: [u8; 32],
pub digest: Vec<u8>,
pub zk_tree_root: [u8; 32],
pub zk_merkle_siblings: Vec<[[u8; 32]; SIBLINGS_PER_LEVEL]>,
pub zk_merkle_positions: Vec<u8>,
pub exit_account_1: [u8; 32],
pub exit_account_2: [u8; 32],
pub output_amount_1: u32,
pub output_amount_2: u32,
pub volume_fee_bps: u32,
pub asset_id: u32,
}
#[derive(Debug, Clone)]
pub struct ProofGenerationOutput {
pub proof_bytes: Vec<u8>,
#[allow(dead_code)]
pub nullifier: [u8; 32],
}
pub fn compute_wormhole_address(secret: &[u8; 32]) -> Result<[u8; 32]> {
let secret_digest: BytesDigest = (*secret)
.try_into()
.map_err(|e| WormholeLibError::from(format!("Invalid secret: {:?}", e)))?;
let unspendable =
qp_wormhole_circuit::unspendable_account::UnspendableAccount::from_secret(secret_digest);
Ok(*digest_to_bytes(unspendable.account_id))
}
#[allow(dead_code)]
pub fn compute_nullifier(secret: &[u8; 32], transfer_count: u64) -> Result<[u8; 32]> {
let secret_digest: BytesDigest = (*secret)
.try_into()
.map_err(|e| WormholeLibError::from(format!("Invalid secret: {:?}", e)))?;
let nullifier = Nullifier::from_preimage(secret_digest, transfer_count);
Ok(*digest_to_bytes(nullifier.hash))
}
pub fn quantize_amount(amount: u128) -> Result<u32> {
let quantized = amount / SCALE_DOWN_FACTOR;
if quantized > u32::MAX as u128 {
return Err(WormholeLibError::from(format!(
"Quantized amount {} exceeds u32::MAX",
quantized
)));
}
Ok(quantized as u32)
}
pub fn compute_output_amount(input_amount: u32, fee_bps: u32) -> u32 {
((input_amount as u64) * (10000 - fee_bps as u64) / 10000) as u32
}
pub fn generate_proof(
input: &ProofGenerationInput,
prover_bin_path: &Path,
common_bin_path: &Path,
) -> Result<ProofGenerationOutput> {
let secret_digest: BytesDigest = input
.secret
.try_into()
.map_err(|e| WormholeLibError::from(format!("Invalid secret: {:?}", e)))?;
let nullifier = Nullifier::from_preimage(secret_digest, input.transfer_count);
let nullifier_bytes = digest_to_bytes(nullifier.hash);
let unspendable =
qp_wormhole_circuit::unspendable_account::UnspendableAccount::from_secret(secret_digest);
let unspendable_bytes = digest_to_bytes(unspendable.account_id);
if *unspendable_bytes != input.wormhole_address {
return Err(WormholeLibError::from(
"Wormhole address doesn't match the computed unspendable account from secret"
.to_string(),
));
}
const DIGEST_LOGS_SIZE: usize = 110;
let mut digest_padded = [0u8; DIGEST_LOGS_SIZE];
let copy_len = input.digest.len().min(DIGEST_LOGS_SIZE);
digest_padded[..copy_len].copy_from_slice(&input.digest[..copy_len]);
let private = PrivateCircuitInputs {
secret: secret_digest,
transfer_count: input.transfer_count,
unspendable_account: unspendable_bytes,
parent_hash: input
.parent_hash
.as_slice()
.try_into()
.map_err(|e| WormholeLibError::from(format!("Invalid parent hash: {:?}", e)))?,
state_root: input
.state_root
.as_slice()
.try_into()
.map_err(|e| WormholeLibError::from(format!("Invalid state root: {:?}", e)))?,
extrinsics_root: input
.extrinsics_root
.as_slice()
.try_into()
.map_err(|e| WormholeLibError::from(format!("Invalid extrinsics root: {:?}", e)))?,
digest: digest_padded,
input_amount: input.input_amount,
zk_tree_root: input.zk_tree_root,
zk_merkle_siblings: input.zk_merkle_siblings.clone(),
zk_merkle_positions: input.zk_merkle_positions.clone(),
};
let public = PublicCircuitInputs {
asset_id: input.asset_id,
output_amount_1: input.output_amount_1,
output_amount_2: input.output_amount_2,
volume_fee_bps: input.volume_fee_bps,
nullifier: nullifier_bytes,
exit_account_1: input
.exit_account_1
.as_slice()
.try_into()
.map_err(|e| WormholeLibError::from(format!("Invalid exit account 1: {:?}", e)))?,
exit_account_2: input
.exit_account_2
.as_slice()
.try_into()
.map_err(|e| WormholeLibError::from(format!("Invalid exit account 2: {:?}", e)))?,
block_hash: input
.block_hash
.as_slice()
.try_into()
.map_err(|e| WormholeLibError::from(format!("Invalid block hash: {:?}", e)))?,
block_number: input.block_number,
};
let circuit_inputs = CircuitInputs { public, private };
let prover = WormholeProver::new_from_files(prover_bin_path, common_bin_path)
.map_err(|e| WormholeLibError::from(format!("Failed to load prover: {}", e)))?;
let prover_with_inputs = prover
.commit(&circuit_inputs)
.map_err(|e| WormholeLibError::from(format!("Failed to commit inputs: {}", e)))?;
let proof = prover_with_inputs
.prove()
.map_err(|e| WormholeLibError::from(format!("Proof generation failed: {}", e)))?;
Ok(ProofGenerationOutput { proof_bytes: proof.to_bytes(), nullifier: *nullifier_bytes })
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quantize_amount() {
let result = quantize_amount(1_000_000_000_000).unwrap();
assert_eq!(result, 100);
let result = quantize_amount(10_000_000_000).unwrap();
assert_eq!(result, 1);
}
#[test]
fn test_compute_output_amount() {
let result = compute_output_amount(100, 10);
assert_eq!(result, 99);
let result = compute_output_amount(1000, 10);
assert_eq!(result, 999);
}
#[test]
fn test_compute_wormhole_address() {
let secret = [42u8; 32];
let address = compute_wormhole_address(&secret).unwrap();
assert_eq!(address.len(), 32);
let address2 = compute_wormhole_address(&secret).unwrap();
assert_eq!(address, address2);
}
}