Zero-Knowledge Circuit Deep Dive
Circuit Overview
The Zyfai Rebalancer Validation Circuit (rebalancer-validation.circom) is a Circom 2.x circuit that validates DeFi rebalancing opportunities against backend constraints using zero-knowledge proofs. The circuit implements the core validation logic from Zyfai's backend, ensuring opportunities meet all safety and performance criteria.
Circuit Properties
- Language: Circom 2.0.0+
- Proof System: Groth16 (efficient on-chain verification)
- Curve: bn128 (bn254)
- Constraints: ~100-200 (includes comparison circuits from circomlib)
- Public Signals: 15 (all inputs are public)
- Private Signals: None (transparency-focused implementation)
Public Inputs
The circuit has 15 public input signals organized into three categories:
New Opportunity Data (7 signals)
These signals describe the rebalancing opportunity being evaluated:
| Signal | Description | Scaling | Example |
|---|---|---|---|
liquidity | Available liquidity in the pool | ×100 (2 decimals) | 10000000 = $100,000.00 |
zyfiTvl | Current Zyfai TVL in the pool | ×100 (2 decimals) | 500000 = $5,000.00 |
amount | Rebalancer deposit amount | Token smallest unit | 1000000 = 1 USDC (6 decimals) |
poolTvl | Total pool TVL | ×100 (2 decimals) | 10000000 = $100,000.00 |
newApy | New opportunity APY | ×10000 (4 decimals) | 54352 = 5.4352% |
apyStable7Days | APY stable over 7 days | Boolean (0 or 1) | 1 = stable |
tvlStable | TVL is stable | Boolean (0 or 1) | 1 = stable |
Old Opportunity Data (7 signals)
These signals describe the previous opportunity (if any) for comparison and edge case handling:
| Signal | Description | Scaling | Default |
|---|---|---|---|
oldApy | Previous opportunity APY | ×10000 (4 decimals) | 0 = no old opportunity |
oldLiquidity | Old opportunity liquidity | ×100 (2 decimals) | 0 = no old opportunity |
oldZyfiTvl | Old opportunity Zyfai TVL | ×100 (2 decimals) | 0 = no old opportunity |
oldTvlStable | Old opportunity TVL stable | Boolean (0 or 1) | 1 = defaults to stable |
oldUtilizationStable | Old opportunity utilization stable | Boolean (0 or 1) | 1 = defaults to stable |
oldCollateralHealth | Old opportunity collateral healthy | Boolean (0 or 1) | 1 = defaults to healthy |
oldZyfiTVLCheck | Old opportunity passes Zyfai TVL check | Boolean (0 or 1) | 1 = defaults to pass |
User Preferences (1 signal)
| Signal | Description | Scaling | Example |
|---|---|---|---|
supportsCurrentPool | Current pool is in user's preferences | Boolean (0 or 1) | 1 = supported |
Validation Constraints
The circuit enforces 5 core validation rules that all must pass for a valid proof:
1. Available Liquidity Check
Rule: liquidity * 0.85 > zyfiTvl
Purpose: Ensures sufficient liquidity to cover the rebalancing amount.
Implementation:
// Avoid floating point: liquidity * 85 > zyfiTvl * 100
signal liquidityLeft <== liquidity * 85;
signal liquidityRight <== zyfiTvl * 100;
component liquidityCheck = GreaterThan(252);
liquidityCheck.in[0] <== liquidityLeft;
liquidityCheck.in[1] <== liquidityRight;
Example:
liquidity = 10,000,000 (scaled: $100,000.00)
zyfiTvl = 500,000 (scaled: $5,000.00)
Check: 10,000,000 × 85 = 850,000,000 > 500,000 × 100 = 50,000,000 ✓
2. TVL Constraint
Rule: poolTvl * 1e6 > amount * 400
Purpose: Prevents excessive allocation. Ensures rebalancer is at most 25% of pool TVL.
Math: amount * (100 / 25) = amount * 4, scaled by 100: poolTvl * 1e6 > amount * 400
Implementation:
signal tvlLeft <== poolTvl * 1000000;
signal tvlRight <== amount * 400;
component tvlCheck = GreaterThan(252);
tvlCheck.in[0] <== tvlLeft;
tvlCheck.in[1] <== tvlRight;
3. APY Performance Check (with Edge Cases)
Rule: newApy > oldApy + 10 OR edge cases
Purpose: Requires meaningful yield improvement (0.1%+) unless bypassed by edge cases.
Edge Cases (APY check bypassed if ANY are true):
- No Old Opportunity:
oldApy == 0(first deposit or no previous opportunity) - Rebalancing from Problematic Pool:
shouldRebalanceFromOld == 1(computed internally) - Pool No Longer Supported:
supportsCurrentPool == 0(user preferences changed)
shouldRebalanceFromOld Computation:
shouldRebalanceFromOld = enoughLiquidity && (
!oldZyfiTVLCheck ||
!oldTvlStable ||
oldLiquidityIsLow ||
!oldUtilizationStable ||
!oldCollateralHealth
)
Where:
- enoughLiquidity = oldLiquidity * 1e6 >= amount
- oldLiquidityIsLow = oldLiquidity * 0.85 < oldZyfiTvl
Implementation:
// Base APY check: newApy > oldApy + 10 (0.1% improvement)
signal apyThreshold <== oldApy + 10;
component apyCheck = GreaterThan(252);
apyCheck.in[0] <== newApy;
apyCheck.in[1] <== apyThreshold;
// Edge case 1: No old opportunity
component oldApyIsZero = IsEqual();
oldApyIsZero.in[0] <== oldApy;
oldApyIsZero.in[1] <== 0;
// Edge case 2: shouldRebalanceFromOld (computed internally)
// ... (see circuit code for full implementation)
// Edge case 3: Pool no longer supported
signal notSupportsCurrentPool <== 1 - supportsCurrentPool;
// APY check passes if ANY are true (OR logic)
signal apyCheckOrSkip <== apyCheckValue + oldApyIsZeroValue +
shouldRebalanceFromOldValue + notSupportsCurrentPool;
component apyCheckFinal = GreaterThan(252);
apyCheckFinal.in[0] <== apyCheckOrSkip;
apyCheckFinal.in[1] <== 0;
Example:
newApy = 120,000 (12.0000%)
oldApy = 100,000 (10.0000%)
supportsCurrentPool = 1
Check: 120,000 > 100,000 + 10 = 100,010 ✓
Result: 2% improvement exceeds 0.1% threshold
4. APY Stability Check
Rule: apyStable7Days == 1
Purpose: Ensures APY has been stable over the past 7 days (prevents volatile pools).
Implementation:
component apyStabilityCheck = IsEqual();
apyStabilityCheck.in[0] <== apyStable7Days;
apyStabilityCheck.in[1] <== 1;
Example:
apyStable7Days = 1
Check: 1 == 1 ✓
Result: APY is stable
5. TVL Stability Check
Rule: tvlStable == 1
Purpose: Verifies pool TVL is stable (prevents pools with unstable liquidity).
Implementation:
component tvlStabilityCheck = IsEqual();
tvlStabilityCheck.in[0] <== tvlStable;
tvlStabilityCheck.in[1] <== 1;
Example:
tvlStable = 1
Check: 1 == 1 ✓
Result: TVL is stable
Constraint Composition
All 5 checks are combined using AND logic (all must pass):
signal check12 <== liquidityCheckValue * tvlCheckValue;
signal check34 <== apyCheckFinalValue * apyStabilityCheckValue;
signal check5 <== tvlStabilityCheckValue;
signal check1234 <== check12 * check34;
signal finalCheck <== check1234 * check5;
// Enforce all checks must pass
finalCheck === 1;
If any constraint fails, witness generation fails and no proof can be generated.
Example Input
{
"liquidity": 100000000,
"zyfiTvl": 500000,
"amount": 1000000,
"poolTvl": 100000000,
"newApy": 120000,
"apyStable7Days": 1,
"tvlStable": 1,
"oldApy": 100000,
"oldLiquidity": 8000000,
"oldZyfiTvl": 400000,
"oldTvlStable": 1,
"oldUtilizationStable": 1,
"oldCollateralHealth": 1,
"oldZyfiTVLCheck": 1,
"supportsCurrentPool": 1
}
Example Output
{
"isValid": 1 (or 0)
}
Off-chain Checks
More checks are in done off-chain which are not a part of the zk circuit but the above covers all the basic checks that're being proven on-chain.
Security Considerations
Current Implementation
- ✅ All inputs are public signals (transparency-focused)
- ✅ Boolean validation prevents malformed inputs
- ✅ Circom 2.x syntax with explicit public input declaration
- ✅ Comparison circuits from audited circomlib
- ✅ Edge case handling for APY checks
Performance Metrics
| Operation | Time | Gas Cost |
|---|---|---|
| Circuit Compilation | ~30s | - |
| Witness Generation | ~100ms | - |
| Proof Generation | ~500ms | - |
| Off-Chain Verification | ~50ms | - |
| On-Chain Verification | ~50ms | - |