Use Cases¶
Privacy-Preserving Smart Contracts¶
A traditional smart contract operates entirely on public on-chain data and publicly visible contract code. In contrast, a privacy-preserving smart contract can handle both public and private data — and if desired, even the contract code can remain hidden.
This privacy capability is enabled by zkMove's on-chain / off-chain hybrid computation model. Developers extract privacy-sensitive state and logic from the main smart contract and implement them as off-chain functions. These functions execute on the user's client and generate zero-knowledge proofs; only the proofs are submitted on-chain. The on-chain contract then verifies the submitted proofs and executes the remaining public logic.
Example: Confidential Assets¶
Confidential Assets (CA) allow digital assets to be stored and transferred on-chain in encrypted form, visible only to authorized parties. This preserves financial privacy without sacrificing verifiability.
The following example demonstrates a portion of a CA smart contract. It allows a user to prove that their asset balance falls within a given range [min, max], without revealing the actual amount.
On-Chain Contract¶
The on-chain contract receives a proof from the user and verifies it against the encrypted asset value:
module confidential_asset::on_chain {
use aptos_std::bn254_algebra::Fr;
use halo2_common::public_inputs;
use verifier_api::verifier;
// Error codes
const EINVALID_PROOF: u64 = 100;
const EINVALID_INPUT: u64 = 101;
// KZG variants
const KZG_GWC: u8 = 1;
const KZG_SHPLONK: u8 = 0;
public entry fun range_check(
encrypted_value: u256,
min: u128,
max: u128,
proof: vector<u8>
) {
assert!(min <= max, EINVALID_INPUT);
// Verify: "encrypted_value is an encryption of a value in range [min, max]"
let pi = public_inputs::empty<Fr>(public_inputs::get_vm_public_inputs_column_count());
public_inputs::push_u128(&mut pi, min);
public_inputs::push_u128(&mut pi, max);
public_inputs::push_u256(&mut pi, encrypted_value);
assert!(
verifier::verify_proof(
@param_address,
@circuit_range_check_address,
pi,
proof,
KZG_GWC
),
EINVALID_PROOF
);
}
}
Off-Chain Client Function¶
The actual asset value is stored on the user's client. The user executes the following function off-chain to generate a proof that the value lies within [min, max]. Only the encrypted value (a hash) and the proof are sent on-chain — the raw asset amount is never exposed.
module confidential_asset::off_chain {
use std::zkhash;
const E_INVALID_ENCRYPTION: u64 = 0;
const E_INVALID_INPUT: u64 = 1;
// Public inputs: min, max, encrypted_value
public entry fun check_range(
value: u128,
min: u128,
max: u128,
encrypted_value: u256,
nonce: u128
) {
assert!(value >= min && value <= max, E_INVALID_INPUT);
assert!(zkhash::hash(value, nonce) == encrypted_value, E_INVALID_ENCRYPTION);
}
}
The complete Confidential Assets example is available in the zkMove repository.
Example: Incomplete-Information Games¶
zkMove can also power incomplete-information games — on-chain games where players hold private state that is never revealed to opponents. A well-known example of this pattern is Dark Forest, where zero-knowledge proofs allow players to hide planet locations while still enforcing game rules on a public blockchain.
The on-chain contract verifies proofs of valid moves without ever learning the players' private coordinates:
module dark_forest::on_chain {
use aptos_std::bn254_algebra::Fr;
use halo2_common::public_inputs;
use verifier_api::verifier;
const E_INVALID_COORDINATES: u64 = 0;
/// Moves a player from position (x1, y1) to (x2, y2), verifying the Euclidean distance
/// using a zero-knowledge proof.
///
/// # Arguments
/// * `hash_1` - Poseidon hash of the player's current position (x1, y1)
/// * `hash_2` - Poseidon hash of the target position (x2, y2)
/// * `distance_squared` - Squared Euclidean distance between the two positions
/// * `proof` - Zero-knowledge proof generated by the Euclidean distance circuit
/// * `kzg_variant` - KZG commitment variant used for proof verification
public entry fun move_to(
hash_1: u256,
hash_2: u256,
distance_squared: u128,
proof: vector<u8>,
kzg_variant: u8
) acquires GameManager {
let pi = public_inputs::empty<Fr>(public_inputs::get_vm_public_inputs_column_count());
public_inputs::push_u256(&mut pi, hash_1);
public_inputs::push_u256(&mut pi, hash_2);
public_inputs::push_u128(&mut pi, distance_squared);
assert!(
verifier::verify_proof(
@param_address,
@circuit_euclid_distance_address,
pi,
proof,
kzg_variant
),
E_INVALID_COORDINATES
);
}
}
The proof is generated by the player’s client using the following off-chain function, which checks that the distance between the current and target positions is correct without revealing any coordinates:
module dark_forest::euclid_distance {
use std::zkhash;
const E_INVALID_COORDINATES: u64 = 0;
/// Euclidean distance squared (no sqrt needed).
/// Public inputs: hash_1, hash_2, distance_squared
public entry fun check_euclid_distance(
x1: u128, y1: u128,
x2: u128, y2: u128,
hash_1: u256, hash_2: u256,
distance_squared: u128
) {
assert!(zkhash::hash(x1, y1) == hash_1, E_INVALID_COORDINATES);
assert!(zkhash::hash(x2, y2) == hash_2, E_INVALID_COORDINATES);
let dx = if (x1 > x2) { x1 - x2 } else { x2 - x1 };
let dy = if (y1 > y2) { y1 - y2 } else { y2 - y1 };
let expected_distance_squared = dx * dx + dy * dy;
assert!(distance_squared == expected_distance_squared, E_INVALID_COORDINATES);
}
}
The complete Dark Forest example is available in the zkMove repository.
Current Limitations¶
zkMove's support for programmable privacy is still in its early stages. The current model covers scenarios where off-chain computation depends solely on the user's own private data combined with public on-chain state.
Scenarios requiring interaction between the private data of multiple users — for example, comparing two private values from different parties — are not yet supported. This is an active area of development.