use soroban_sdk::{BytesN, Env};
use super::error::ZKError;
use super::traits::{bytes32_to_scalar, i32_to_scalar, u32_to_scalar, GameCircuit};
use super::types::{Groth16Proof, Scalar, VerificationKey};
pub struct MovementCircuit {
pub vk: VerificationKey,
pub max_distance: u32,
}
impl GameCircuit for MovementCircuit {
fn verification_key(&self) -> &VerificationKey {
&self.vk
}
}
impl MovementCircuit {
pub fn new(vk: VerificationKey, max_distance: u32) -> Self {
Self { vk, max_distance }
}
pub fn verify_move(
&self,
env: &Env,
proof: &Groth16Proof,
from_x: i32,
from_y: i32,
to_x: i32,
to_y: i32,
) -> Result<bool, ZKError> {
let public_inputs = alloc::vec![
i32_to_scalar(env, from_x),
i32_to_scalar(env, from_y),
i32_to_scalar(env, to_x),
i32_to_scalar(env, to_y),
u32_to_scalar(env, self.max_distance),
];
self.verify_with_inputs(env, proof, &public_inputs)
}
}
pub struct CombatCircuit {
pub vk: VerificationKey,
}
impl GameCircuit for CombatCircuit {
fn verification_key(&self) -> &VerificationKey {
&self.vk
}
}
impl CombatCircuit {
pub fn new(vk: VerificationKey) -> Self {
Self { vk }
}
pub fn verify_damage(
&self,
env: &Env,
proof: &Groth16Proof,
attacker_commitment: &BytesN<32>,
defender_commitment: &BytesN<32>,
damage_result: u32,
) -> Result<bool, ZKError> {
let public_inputs = alloc::vec![
bytes32_to_scalar(attacker_commitment),
bytes32_to_scalar(defender_commitment),
u32_to_scalar(env, damage_result),
];
self.verify_with_inputs(env, proof, &public_inputs)
}
}
pub struct InventoryCircuit {
pub vk: VerificationKey,
}
impl GameCircuit for InventoryCircuit {
fn verification_key(&self) -> &VerificationKey {
&self.vk
}
}
impl InventoryCircuit {
pub fn new(vk: VerificationKey) -> Self {
Self { vk }
}
pub fn verify_has_item(
&self,
env: &Env,
proof: &Groth16Proof,
inventory_root: &BytesN<32>,
item_id: u32,
) -> Result<bool, ZKError> {
let public_inputs = alloc::vec![
bytes32_to_scalar(inventory_root),
u32_to_scalar(env, item_id),
];
self.verify_with_inputs(env, proof, &public_inputs)
}
}
pub struct TurnSequenceCircuit {
pub vk: VerificationKey,
}
impl GameCircuit for TurnSequenceCircuit {
fn verification_key(&self) -> &VerificationKey {
&self.vk
}
}
impl TurnSequenceCircuit {
pub fn new(vk: VerificationKey) -> Self {
Self { vk }
}
pub fn verify_sequence(
&self,
env: &Env,
proof: &Groth16Proof,
initial_state: &BytesN<32>,
final_state: &BytesN<32>,
action_count: u32,
) -> Result<bool, ZKError> {
let public_inputs = alloc::vec![
bytes32_to_scalar(initial_state),
bytes32_to_scalar(final_state),
u32_to_scalar(env, action_count),
];
self.verify_with_inputs(env, proof, &public_inputs)
}
}
pub struct CustomCircuit {
vk: VerificationKey,
public_inputs: alloc::vec::Vec<Scalar>,
}
impl GameCircuit for CustomCircuit {
fn verification_key(&self) -> &VerificationKey {
&self.vk
}
}
impl CustomCircuit {
pub fn new(vk: VerificationKey, public_inputs: alloc::vec::Vec<Scalar>) -> Self {
Self { vk, public_inputs }
}
pub fn builder(vk: VerificationKey) -> CustomCircuitBuilder {
CustomCircuitBuilder {
vk,
inputs: alloc::vec::Vec::new(),
}
}
pub fn public_inputs(&self) -> &[Scalar] {
&self.public_inputs
}
pub fn verify(&self, env: &Env, proof: &Groth16Proof) -> Result<bool, ZKError> {
self.verify_with_inputs(env, proof, &self.public_inputs)
}
}
pub struct CustomCircuitBuilder {
vk: VerificationKey,
inputs: alloc::vec::Vec<Scalar>,
}
impl CustomCircuitBuilder {
pub fn add_scalar(mut self, scalar: Scalar) -> Self {
self.inputs.push(scalar);
self
}
pub fn add_u32(mut self, env: &Env, val: u32) -> Self {
self.inputs.push(u32_to_scalar(env, val));
self
}
pub fn add_i32(mut self, env: &Env, val: i32) -> Self {
self.inputs.push(i32_to_scalar(env, val));
self
}
pub fn add_bytes32(mut self, val: &BytesN<32>) -> Self {
self.inputs.push(bytes32_to_scalar(val));
self
}
pub fn build(self) -> CustomCircuit {
CustomCircuit {
vk: self.vk,
public_inputs: self.inputs,
}
}
}
#[cfg(test)]
mod tests {
use super::super::traits;
use super::*;
use soroban_sdk::{BytesN, Env, Vec};
use super::super::types::{G1Point, G2Point};
fn make_vk(env: &Env, ic_count: u32) -> VerificationKey {
let g1 = G1Point {
bytes: BytesN::from_array(env, &[0u8; 64]),
};
let g2 = G2Point {
bytes: BytesN::from_array(env, &[0u8; 128]),
};
let mut ic = Vec::new(env);
for _ in 0..ic_count {
ic.push_back(g1.clone());
}
VerificationKey {
alpha: g1,
beta: g2.clone(),
gamma: g2.clone(),
delta: g2,
ic,
}
}
#[test]
fn test_movement_circuit_creation() {
let env = Env::default();
let vk = make_vk(&env, 6); let circuit = MovementCircuit::new(vk, 10);
assert_eq!(circuit.max_distance, 10);
}
#[test]
fn test_movement_circuit_wrong_ic_length() {
let env = Env::default();
let vk = make_vk(&env, 1); let circuit = MovementCircuit::new(vk, 10);
let g1 = G1Point {
bytes: BytesN::from_array(&env, &[0u8; 64]),
};
let g2 = G2Point {
bytes: BytesN::from_array(&env, &[0u8; 128]),
};
let proof = Groth16Proof {
a: g1.clone(),
b: g2,
c: g1,
};
let result = circuit.verify_move(&env, &proof, 0, 0, 3, 4);
assert_eq!(result, Err(ZKError::InvalidVerificationKey));
}
#[test]
fn test_combat_circuit_creation() {
let env = Env::default();
let vk = make_vk(&env, 4);
let circuit = CombatCircuit::new(vk);
assert_eq!(circuit.vk.ic.len(), 4);
}
#[test]
fn test_inventory_circuit_creation() {
let env = Env::default();
let vk = make_vk(&env, 3);
let circuit = InventoryCircuit::new(vk);
assert_eq!(circuit.vk.ic.len(), 3);
}
#[test]
fn test_turn_sequence_circuit_creation() {
let env = Env::default();
let vk = make_vk(&env, 4);
let circuit = TurnSequenceCircuit::new(vk);
assert_eq!(circuit.vk.ic.len(), 4);
}
#[test]
fn test_scalar_encoding_u32() {
let env = Env::default();
let scalar = traits::u32_to_scalar(&env, 42);
assert_eq!(scalar.bytes.len(), 32);
}
#[test]
fn test_scalar_encoding_i32() {
let env = Env::default();
let scalar = traits::i32_to_scalar(&env, -1);
assert_eq!(scalar.bytes.len(), 32);
}
#[test]
fn test_game_circuit_trait_on_movement() {
let env = Env::default();
let vk = make_vk(&env, 1); let circuit = MovementCircuit::new(vk, 10);
assert_eq!(circuit.verification_key().ic.len(), 1);
let g1 = G1Point {
bytes: BytesN::from_array(&env, &[0u8; 64]),
};
let g2 = G2Point {
bytes: BytesN::from_array(&env, &[0u8; 128]),
};
let proof = Groth16Proof {
a: g1.clone(),
b: g2,
c: g1,
};
let inputs = alloc::vec![traits::u32_to_scalar(&env, 1)];
let result = circuit.verify_with_inputs(&env, &proof, &inputs);
assert_eq!(result, Err(ZKError::InvalidVerificationKey));
}
#[test]
fn test_custom_circuit_creation() {
let env = Env::default();
let vk = make_vk(&env, 3); let inputs = alloc::vec![
traits::u32_to_scalar(&env, 10),
traits::u32_to_scalar(&env, 20),
];
let circuit = CustomCircuit::new(vk, inputs);
assert_eq!(circuit.public_inputs().len(), 2);
}
#[test]
fn test_custom_circuit_builder() {
let env = Env::default();
let vk = make_vk(&env, 4); let root = BytesN::from_array(&env, &[0xABu8; 32]);
let circuit = CustomCircuit::builder(vk)
.add_u32(&env, 42)
.add_i32(&env, -5)
.add_bytes32(&root)
.build();
assert_eq!(circuit.public_inputs().len(), 3);
assert_eq!(circuit.verification_key().ic.len(), 4);
}
#[test]
fn test_custom_circuit_verify_wrong_ic() {
let env = Env::default();
let vk = make_vk(&env, 1); let circuit = CustomCircuit::builder(vk)
.add_u32(&env, 42)
.add_u32(&env, 99)
.build();
let g1 = G1Point {
bytes: BytesN::from_array(&env, &[0u8; 64]),
};
let g2 = G2Point {
bytes: BytesN::from_array(&env, &[0u8; 128]),
};
let proof = Groth16Proof {
a: g1.clone(),
b: g2,
c: g1,
};
let result = circuit.verify(&env, &proof);
assert_eq!(result, Err(ZKError::InvalidVerificationKey));
}
}