plugins/midnight-proofs/skills/proof-caching/SKILL.md
Use when caching ZK proofs for performance, implementing proof cache invalidation, storing verification results, caching intermediate proof components, or building distributed proof caches with Redis.
npx skillsauth add aaronbassett/midnight-knowledgebase midnight-proofs:proof-cachingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
4 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Cache proofs and proof components to improve performance and reduce redundant computation in proof generation services.
| Component | Cacheable? | TTL Considerations | |-----------|------------|-------------------| | Verification results | Yes | Long (proofs are immutable) | | Generated proofs | Depends | Short (state may change) | | Circuit keys | Yes | Until contract update | | Witness templates | Sometimes | Depends on use case |
A good proof cache key uniquely identifies the proof:
// For verification results
function verificationCacheKey(
circuitId: string,
proofHash: string,
publicInputsHash: string
): string {
return `verify:${circuitId}:${proofHash}:${publicInputsHash}`;
}
// For generated proofs
function proofCacheKey(
circuitId: string,
witnessHash: string
): string {
return `proof:${circuitId}:${witnessHash}`;
}
| Scenario | Invalidation Strategy | |----------|----------------------| | Contract upgrade | Clear all proofs for circuit | | State change | Clear proofs depending on changed state | | TTL expiration | Automatic removal | | Manual purge | Admin-triggered clear |
| Document | Description | |----------|-------------| | cache-strategies.md | Caching strategies and TTL policies |
| Example | Description | |---------|-------------| | redis-cache/ | Distributed cache with Redis | | lru-cache/ | In-memory LRU cache |
import { LRUCache } from 'lru-cache';
import { createHash } from 'crypto';
const verificationCache = new LRUCache<string, boolean>({
max: 10000,
ttl: 1000 * 60 * 60, // 1 hour
});
function hashData(data: unknown): string {
return createHash('sha256')
.update(JSON.stringify(data))
.digest('hex');
}
async function verifyWithCache(
circuitId: string,
proof: Uint8Array,
publicInputs: Record<string, unknown>
): Promise<boolean> {
const key = `${circuitId}:${hashData(proof)}:${hashData(publicInputs)}`;
// Check cache
const cached = verificationCache.get(key);
if (cached !== undefined) {
return cached;
}
// Verify
const result = await verifier.verify(circuitId, proof, publicInputs);
// Cache result
verificationCache.set(key, result.valid);
return result.valid;
}
import Redis from 'ioredis';
const redis = new Redis(process.env.REDIS_URL);
async function verifyWithRedisCache(
circuitId: string,
proof: Uint8Array,
publicInputs: Record<string, unknown>
): Promise<boolean> {
const key = `verify:${circuitId}:${hashData(proof)}:${hashData(publicInputs)}`;
// Check cache
const cached = await redis.get(key);
if (cached !== null) {
return cached === 'true';
}
// Verify
const result = await verifier.verify(circuitId, proof, publicInputs);
// Cache with 1 hour TTL
await redis.setex(key, 3600, result.valid ? 'true' : 'false');
return result.valid;
}
class MultiLevelProofCache {
private l1: LRUCache<string, boolean>; // In-memory
private l2: Redis; // Redis
constructor(redis: Redis) {
this.l1 = new LRUCache({ max: 1000, ttl: 60000 });
this.l2 = redis;
}
async get(key: string): Promise<boolean | undefined> {
// Check L1
const l1Result = this.l1.get(key);
if (l1Result !== undefined) {
return l1Result;
}
// Check L2
const l2Result = await this.l2.get(key);
if (l2Result !== null) {
const value = l2Result === 'true';
this.l1.set(key, value); // Populate L1
return value;
}
return undefined;
}
async set(key: string, value: boolean, ttlSeconds: number): Promise<void> {
this.l1.set(key, value);
await this.l2.setex(key, ttlSeconds, value ? 'true' : 'false');
}
}
async function verifyProofCacheAside(
circuitId: string,
proof: Uint8Array,
publicInputs: Record<string, unknown>
): Promise<boolean> {
const key = buildCacheKey(circuitId, proof, publicInputs);
// 1. Try cache
const cached = await cache.get(key);
if (cached !== undefined) {
metrics.cacheHit();
return cached;
}
metrics.cacheMiss();
// 2. Compute
const result = await verifier.verify(circuitId, proof, publicInputs);
// 3. Store in cache
await cache.set(key, result.valid, 3600);
return result.valid;
}
async function verifyBatchWithCache(
proofs: ProofItem[]
): Promise<Map<string, boolean>> {
const results = new Map<string, boolean>();
const uncached: ProofItem[] = [];
// Build keys
const keys = proofs.map((p) => buildCacheKey(p.circuitId, p.proof, p.publicInputs));
// Batch cache lookup
const cachedValues = await redis.mget(keys);
// Separate cached and uncached
proofs.forEach((proof, i) => {
if (cachedValues[i] !== null) {
results.set(proof.id, cachedValues[i] === 'true');
} else {
uncached.push(proof);
}
});
// Verify uncached
if (uncached.length > 0) {
const verifyResults = await verifyBatch(uncached);
// Cache new results
const pipeline = redis.pipeline();
uncached.forEach((proof, i) => {
const key = buildCacheKey(proof.circuitId, proof.proof, proof.publicInputs);
pipeline.setex(key, 3600, verifyResults[i] ? 'true' : 'false');
results.set(proof.id, verifyResults[i]);
});
await pipeline.exec();
}
return results;
}
async function warmCache(circuitIds: string[]): Promise<void> {
console.log('Warming cache for circuits:', circuitIds);
for (const circuitId of circuitIds) {
// Pre-generate common proofs
const commonWitnesses = await getCommonWitnesses(circuitId);
for (const witness of commonWitnesses) {
try {
const proof = await prover.prove(circuitId, witness);
const key = buildProofCacheKey(circuitId, witness);
await cache.set(key, proof, 3600);
} catch (error) {
console.warn(`Failed to warm cache for ${circuitId}:`, error);
}
}
}
console.log('Cache warming complete');
}
class CacheMetrics {
private hits = 0;
private misses = 0;
private evictions = 0;
hit(): void {
this.hits++;
}
miss(): void {
this.misses++;
}
evict(): void {
this.evictions++;
}
getStats(): {
hits: number;
misses: number;
hitRate: number;
evictions: number;
} {
const total = this.hits + this.misses;
return {
hits: this.hits,
misses: this.misses,
hitRate: total > 0 ? this.hits / total : 0,
evictions: this.evictions,
};
}
}
| Concern | Mitigation | |---------|------------| | Cache stampede | Use locks or probabilistic early expiration | | Memory pressure | Set appropriate max size, use LRU eviction | | Stale data | Set appropriate TTL, implement invalidation | | Network latency (Redis) | Use connection pooling, multi-level cache |
proof-generation - Generate proofs to cacheproof-verification - Verify proofs with cachingprover-optimization - Optimize proof generationNone currently defined.
tools
Use when setting up Midnight development environment, installing Compact compiler and developer tools, configuring proof server, verifying prerequisites, or getting started with Midnight development.
tools
--- name: midnight-tooling:midnight-debugging description: Use when encountering Midnight errors like "compact: command not found", "ERR_UNSUPPORTED_DIR_IMPORT", version mismatches, proof server failures, "@midnight-ntwrk" package errors, or compilation failures. --- # Midnight Environment Debugging Expert knowledge for identifying and resolving common Midnight development toolchain issues. ## Diagnostic Approach When encountering Midnight-related errors, follow this systematic approach: 1.
tools
Use when checking Midnight version compatibility, understanding pragma language_version, verifying compiler and runtime version relationships, or troubleshooting version mismatch errors between Midnight components.
tools
Use when setting up CI/CD for Midnight projects, configuring GitHub Actions for Compact contract compilation, running TypeScript tests in CI, validating version consistency, or automating contract builds.