ICN Deterministic Core (ICN-DC) Specification
Status: Specification
Version: 1.0
Date: 2026-02-11
Applies To: Legitimacy Compute (LC) execution paths
Overview
The ICN Deterministic Core (ICN-DC) is a hardened WASM execution environment that guarantees bit-exact reproducibility across supported architectures (x86_64, aarch64).
ICN-DC is required for all Legitimacy Compute (LC) - any computation that can affect authoritative shared state.
Design Goals
- Bit-exact reproducibility - Same inputs → same outputs on any compliant node
- Verifiable execution - Any node can replay and verify any LC execution
- Bounded resources - Guaranteed termination, predictable costs
- Defense in depth - Multiple layers prevent non-determinism
Wasmtime Configuration
Version Pin
/// Pinned Wasmtime version. Upgrading is a HARD FORK.
pub const WASMTIME_VERSION_PIN: &str = "24.0.5";
Policy: All configuration flags are defined against this version. Upgrading requires:
- Cross-architecture determinism re-certification
- "Nondeterministic Nasties" test suite pass on both archs
- Protocol-level coordination (all nodes upgrade together)
Fail-Closed Rule: If a config knob specified below is not available in Wasmtime 24.0.5, we DO NOT approximate it. LC execution is forbidden until the knob is available or we redesign. This prevents "imaginary determinism" where we claim a guarantee we can't deliver.
Engine Configuration
pub fn create_deterministic_engine() -> anyhow::Result<(Engine, DeterminismManifest)> {
let mut config = Config::new();
// 1. NaN Canonicalization
// Forces all NaNs to canonical bit pattern 0x7fc00000
// Critical for x86 vs ARM consistency
config.cranelift_nan_canonicalization(true);
// 2. Fuel Consumption
// Replaces wall-clock timeouts with instruction counting
// Guarantees same fuel consumption regardless of CPU speed
config.consume_fuel(true);
// 3. Sequential Compilation
// Reduces nondeterministic build variance and simplifies audit.
// NOTE: Determinism must not depend on JIT artifact identity.
config.parallel_compilation(false);
// 4. Memory Configuration
// Static limits prevent OOM-dependent behavior
// VERIFIED: All these APIs exist in Wasmtime 24.0.5 (config.rs:1356, 1427, 1498)
let max_memory_bytes = 128 * 1024 * 1024; // 128 MiB
config.static_memory_maximum_size(max_memory_bytes);
config.static_memory_guard_size(2 * 1024 * 1024);
config.dynamic_memory_reserved_for_growth(max_memory_bytes);
Ok((Engine::new(&config)?, manifest))
}
Configuration Rationale
| Setting | Value | Reason |
|---|---|---|
cranelift_nan_canonicalization |
true |
Different CPUs produce different NaN bit patterns |
consume_fuel |
true |
Wall-clock timeouts are non-deterministic |
parallel_compilation |
false |
Reduces build variance, simplifies audit (determinism must not depend on JIT identity) |
static_memory_maximum_size |
128 MiB | Prevents OOM-dependent behavior |
WASM Subset Restrictions
Overview
LC WASM modules are validated against a restricted subset. Modules violating the subset are rejected at load time.
Import Allowlist
Only these imports are allowed for LC modules:
| Module | Function | Signature | Behavior |
|---|---|---|---|
icn |
log |
(ptr: i32, len: i32) |
Log message (no side effects) |
icn |
timestamp |
() -> i64 |
Returns task.created_at (deterministic) |
icn |
entropy_seed |
(ptr: i32) |
Writes 32-byte seed to ptr (optional) |
Canonical naming: All host functions use module "icn" with function names like "log", "timestamp". In WASM: (import "icn" "log" ...). In Rust linker: icn::log.
Forbidden Imports
Any import not on the allowlist is forbidden:
wasi_*- WASI filesystem, network, clockenv::*- Environment variables- Custom host functions returning non-deterministic values
Opcode Restrictions
Forbidden in LC v1:
| Category | Opcodes | Reason |
|---|---|---|
| Floats | f32.*, f64.* |
NaN handling varies by architecture |
| SIMD | v128.*, *.relaxed_* |
Hardware-specific optimizations |
Rationale: Integer-only LC massively reduces the fork surface. Floats can be allowed in v2 with proven NaN canonicalization.
Control Flow Limits
| Limit | Value | Reason |
|---|---|---|
| Max control depth | 100 | Prevents stack exhaustion attacks |
| Max call depth | 100 | Prevents unbounded recursion |
Memory Limits
| Limit | Value | Reason |
|---|---|---|
Max memory.grow |
2048 pages (128 MiB) | Protocol-governed limit |
Max memory.copy |
1 MiB | Prevents witness bloat |
Max memory.fill |
1 MiB | Prevents witness bloat |
Deterministic Entropy
The Problem
WASM contracts may need randomness (e.g., shuffling, sampling). Host /dev/urandom is non-deterministic.
The Solution: Deterministic Seed Injection
/// Derive deterministic seed from execution context
///
/// ICN-native anchors (not blockchain terminology):
/// - scope_root: The Merkle root of the scope's ledger at task creation
/// - decision_hash: The canonical hash of the decision/receipt that triggered this task
pub fn derive_seed(scope_root: &[u8; 32], decision_hash: &[u8; 32]) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(b"icn:entropy:v1");
hasher.update(scope_root);
hasher.update(decision_hash);
*hasher.finalize().as_bytes()
}
Where these values come from:
scope_root: Obtained fromLedger::current_root()at task creation timedecision_hash: Obtained fromGovernanceDecisionReceipt::canonical_hash()(for governance LC) orTask::decision_hashfield (for compute LC)
Policy:
- Host entropy is BANNED
- Contracts receive deterministic seed derived from context
- Seed is reproducible by any verifier
Memory Growth Policy
The Problem
OOM (Out of Memory) errors dependent on host OS pressure cause consensus forks.
The Solution
- Deterministic Address Space: Nodes reserve full protocol-max (128 MiB) for LC instances
- Protocol-Governed Failure:
memory.growsuccess determined solely by protocol limit - Non-Compliance Handling: If host OS refuses allocation, host is faulty and must crash rather than fork
Implementation
impl ResourceLimiter for LcContext {
fn memory_growing(&mut self, current: usize, desired: usize, max: Option<usize>) -> bool {
// Success determined by protocol limit, not host RAM
let protocol_limit = 128 * 1024 * 1024; // 128 MiB
(current + desired) <= protocol_limit
}
}
Container Ordering Policy
The Problem
HashMap/HashSet use randomized seeding (SipHash), causing non-deterministic iteration order.
The Solution
Policy: HashMap is PROHIBITED for state-impacting logic in LC paths.
Allowed Alternatives:
BTreeMap- Key-sorted, deterministicIndexMap- Insertion-sorted, deterministicVec- Index-based, deterministic
CI Enforcement
# Detect HashMap in LC-relevant crates
rg "HashMap" icn/crates/icn-compute/src/deterministic_engine.rs
# Should return 0 matches
Fuel Metering
Overview
Fuel replaces wall-clock timeouts for deterministic execution bounds.
Fuel Allocation
// Convert task fuel_limit to Wasmtime fuel
let wasmtime_fuel = task.fuel_limit.0;
store.set_fuel(wasmtime_fuel)?;
Fuel Consumption Tracking
// After execution
let fuel_used = initial_fuel - store.get_fuel()?;
result.fuel_used = fuel_used;
Fuel Exhaustion
If fuel runs out mid-execution:
- WASM traps with
FuelExhausted - Execution result is
Err(ComputeError::FuelExhausted) - No state changes applied
- Task marked as failed (not misbehavior)
Verification Protocol
For Deterministic Replay
Any node can verify an LC execution:
1. Obtain canonical inputs (by inputs_hash)
2. Obtain WASM module (by wasm_hash)
3. Create deterministic engine with ICN-DC config
4. Execute with same fuel_limit
5. Compare output_hash with claimed output_hash
6. If mismatch → Proof of Misbehavior
Canonical Inputs
pub struct CanonicalInputs {
pub wasm_hash: [u8; 32],
pub input_data: Vec<u8>,
pub fuel_limit: u64,
pub timestamp: u64, // task.created_at
pub entropy_seed: [u8; 32], // derived from context
}
impl CanonicalInputs {
pub fn content_hash(&self) -> [u8; 32] {
let mut hasher = blake3::Hasher::new();
hasher.update(b"icn:inputs:v1");
hasher.update(&self.wasm_hash);
hasher.update(&self.input_data);
hasher.update(&self.fuel_limit.to_le_bytes());
hasher.update(&self.timestamp.to_le_bytes());
hasher.update(&self.entropy_seed);
*hasher.finalize().as_bytes()
}
}
Test Suite: Nondeterministic Nasties
Overview
The "Nasties" test suite verifies ICN-DC normalizes hardware differences.
Test Cases
| Test | Vulnerability | Success Condition |
|---|---|---|
nan_payload_miner |
NaN bit patterns | i32.reinterpret_f32(0.0/0.0) == 0x7fc00000 on x86 and ARM |
simd_fma_diff |
FMA rounding | Bit-exact vector results |
mem_grow_boundary |
OOM consensus | memory.grow fails at exactly page 2048 |
hashmap_chaos |
Iteration order | Output hash constant across 1000 runs |
Cross-Architecture CI
# .github/workflows/determinism.yml
jobs:
determinism-x86:
runs-on: ubuntu-latest
# ... run nasties, upload hashes
determinism-aarch64:
runs-on: [self-hosted, aarch64]
# ... run nasties, upload hashes
compare:
needs: [determinism-x86, determinism-aarch64]
# diff hashes, fail if mismatch
Error Handling
Deterministic Errors
These errors are deterministic and should produce identical results:
| Error | Handling |
|---|---|
FuelExhausted |
Trap, return error |
MemoryGrowFailed |
Trap, return error |
DivisionByZero |
Trap, return error |
SubsetViolation |
Reject at load time |
Non-Deterministic Errors
These errors must NOT occur in LC paths:
| Error | Prevention |
|---|---|
HostOOM |
Reserve full address space upfront |
Timeout |
Use fuel, not wall-clock |
IOError |
No IO allowed in LC |
Security Considerations
Attack Vectors Mitigated
- Subjectivity attacks: Exploiting hardware variance → Mitigated by ICN-DC config
- Resource exhaustion: Infinite loops → Mitigated by fuel metering
- Memory bombs: Excessive allocation → Mitigated by static limits
- Witness bloat: Large memory access → Mitigated by copy/fill limits
Defense in Depth
Layer 1: WASM subset validation (reject bad modules)
Layer 2: Deterministic engine config (normalize execution)
Layer 3: Fuel metering (bound execution)
Layer 4: Verification capability (detect misbehavior)
Implementation Checklist
- Create
deterministic_engine.rswithcreate_deterministic_engine() - Create
subset.rswithWasmSubsetValidator - Modify
wasm_executor.rsto use deterministic engine for LC - Add CI guard for
Engine::default()in LC paths - Implement "Nasties" test suite
- Set up cross-architecture CI
- Document in
docs/spec/wasm-subset.md
References
- Research Sprint #3 (Security & Determinism Spec v1.1)
docs/design/compute-classes.md- LC/UC specificationdocs/design/scope-scheduling.md- Scope and priority modeldocs/design/DETERMINISTIC_COMPUTE_SPRINT.md- Implementation plan- Wasmtime documentation: https://docs.wasmtime.dev/