PRAHARI प्रहरी

Post-quantum Random Accumulator — Hardware-Assured Resilient Integrity

SHA3-256 sponge engine absorbing 5 hardware entropy sources for post-quantum key generation.

v3.3.0

The Sentinel at the Mountain Pass

A prahari stands watch at the mountain pass — vigilant, tireless, guarding what flows through. Nepal’s border sentinels carry this title. In yakmesh, PRAHARI guards the entropy pipeline: a SHA3-256 sponge that continuously absorbs hardware entropy from five independent sources, squeezing cryptographic seeds on demand. Every ML-KEM-768 key in the mesh traces its randomness back to this watchman’s vigil.

v3 — From Slots to Sponge

PRAHARI v3 replaces the v2 slot-array design (inherited from STEADYWATCH) with a clean SHA3-256 sponge. Gone are the 144-slot seed array, SST family grouping, and Fibonacci-cycle slot selection. The sponge design is simpler, smaller (~128 bytes state vs 4,608), and continuously refreshed from hardware — not a static constellation of pre-harvested seeds. The SST mathematics that powered those systems remain central to Yakmesh via TRIBHUJ, YPC-27, and 162T routing — PRAHARI simply no longer uses slot-based selection from SST families.

Overview

PRAHARI manages a SHA3-256 sponge that continuously absorbs entropy from multiple hardware sources. When keys are needed, getHybridSeed() squeezes 64 bytes from the sponge and XORs them with crypto.randomBytes(64) — a two-source extractor that guarantees cryptographic strength even if one source is compromised.

The system includes an Entropy Sentinel for quality scoring (NPU-accelerated via TIVRA) and TRIBHUJ ternary quality verdicts for mesh-consensus entropy assessment.

Two-Source Extractor

The security model is a two-source extractor. Two independent entropy pools are XORed, so an attacker must compromise both simultaneously:

hybridSeed = sponge.squeeze(64) ⊕ CSPRNG(64)

Sponge (Hardware Sources)

SHA3-256 accumulator absorbing RDRAND, GPS jitter, interrupt timing, mesh packet arrival, and CSPRNG. Continuously refreshed every 10 seconds.

OS CSPRNG

crypto.randomBytes(64) — independent of the sponge. Even if sponge state is fully known, the hybrid seed remains at least as strong as the OS entropy.

import { getHybridSeed } from 'yakmesh/security/prahari';

// Returns 64-byte Uint8Array for ML-KEM-768 keygen
const seed = getHybridSeed();

// Both sources must fail simultaneously for compromise.
// Sponge compromised → CSPRNG still protects.
// CSPRNG compromised → sponge (5 hardware sources) still protects.

Five Entropy Sources

The sponge absorbs from five independent hardware entropy sources. Sources register dynamically as subsystems come online via the pluggable EntropySourceRegistry.

Source Kind Weight Description
RDRAND/RDSEED rdrand 8 CPU hardware RNG (via VAES/AVX-512 extensions)
GPS Jitter gps-jitter 5 Hardware GPS timing residuals via MANI
Interrupt Timing interrupt 3 process.hrtime.bigint() nanosecond deltas — CPU TLB, cache, branch prediction jitter
Mesh Packets mesh-arrival 4 WebSocket message interarrival jitter from the P2P mesh
OS CSPRNG csprng 10 crypto.randomBytes — always available, always strong
import { registerEntropySource } from 'yakmesh/security/prahari';

// Register a custom entropy source (e.g., external TRNG)
registerEntropySource({
  kind: 'external-trng',
  name: 'USB Hardware RNG',
  weight: 7,
  available: () => trngDevice.isConnected(),
  harvest: () => trngDevice.read(32),
});

PrahariSpongeEngine

The core PrahariSpongeEngine is a SHA3-256 sponge accumulator. It absorbs entropy from all registered sources and squeezes domain-separated output on demand.

Sponge Operations

Absorb: SHA3-256(state || domain || newEntropy) → new 64-byte state.
Domain-separated with PRAHARI-ABSORB label. Counter-extended to maintain 64-byte state width.
Squeeze: SHA3-256(state || label-i) concatenated to desired length.
Domain-separated per output chunk. Never reveals the actual sponge state.
Reseed: Every 10 seconds, all available sources are harvested and absorbed with PRAHARI-RESEED domain label.
import { initialize, getHybridSeed, getStatus } from 'yakmesh/security/prahari';

// Initialize the sponge engine
await initialize({
  persistPath: './data/prahari-state.bin',  // optional persistence
  inferenceEngine: accelInference,           // optional: NPU for Sentinel
});

// Primary integration point (called by ANNEX before ML-KEM-768 keygen)
const seed = getHybridSeed();   // Uint8Array(64)

// Full status report
const status = getStatus();
// {
//   initialized: true,
//   sponge: { absorbCount: 847, state: '(hidden)', sizeBytes: 64 },
//   sources: { rdrand: { available: true, contributions: 312 }, ... },
//   sentinel: { available: true, method: 'npu' },
//   telemetry: { hybridSeeds: 42, sentinelPasses: 40, ... }
// }

Entropy Sentinel

The EntropySentinel scores the quality of entropy material. When an ONNX model is loaded and an NPU/GPU is available (via TIVRA), it uses hardware-accelerated pattern detection. Otherwise, it runs four software statistical tests:

Test Weight Detects
Bit-entropy (per byte) 40% Low information density
Chi-square uniformity 25% Byte frequency bias
Run-length test 15% Repeating byte sequences
Autocorrelation (lag 1) 20% Sequential patterns

The combined score maps to a TRIBHUJ ternary verdict:

POSITIVE
Score ≥ 0.85 — excellent
NEUTRAL
Score ≥ 0.50 — acceptable
NEGATIVE
Score < 0.50 — reject
import { scoreEntropy } from 'yakmesh/security/prahari';

const result = await scoreEntropy(seedBytes);
// {
//   score: 0.94,
//   verdict: Trit(POSITIVE),
//   method: 'npu',          // or 'gpu' or 'cpu'
//   details: { bitEntropyPerByte: 7.82, chiSquare: 241.3, ... }
// }

SST & Ternary Mathematics

While v3 no longer uses SST families for slot selection, the SST mathematics behind Yakmesh’s ternary stack remain fundamental. PRAHARI’s ternary quality verdicts flow directly from TRIBHUJ balanced ternary primitives:

TRIBHUJ

Balanced ternary algebra. Trit/TritArray primitives used by PRAHARI for quality verdicts (POSITIVE, NEUTRAL, NEGATIVE).

YPC-27

Polynomial ring Z[x]/(x27−1) mod 3. SIS-hard checksums used by trit-commitment for 162T integrity.

162T Routing

3×54-trit hierarchical addresses replacing 144T. 256.8-bit PQ security with zero YPC-27 padding waste. PRAHARI seeds feed into ML-KEM-768 keys that protect these addresses.

API Reference

initialize(options)Promise<{ initialized, sentinel }>

Start the sponge engine, register built-in entropy sources, load persisted state, begin reseed timer.

getHybridSeed()Uint8Array(64)

Squeeze 64 bytes ⊕ CSPRNG. Primary ANNEX integration point for ML-KEM-768 keygen.

scoreEntropy(data)Promise<{ score, verdict, method, details }>

Score entropy quality of arbitrary byte data. NPU/GPU/CPU tiered inference.

registerEntropySource(source)boolean

Register a custom entropy source into the pluggable registry.

getStatus()Object

Full status: sponge state, source contributions, sentinel state, telemetry counters.

seedStore.initializedboolean

Backward-compatible flag checked by ANNEX before keygen.

Public Entropy Beacon API

Every YAKMESH node exposes its mesh-consensus entropy as a paranoid entropy beacon. Pulses are signed with the node's ML-DSA-65 identity key, chained via previous_signature, and timestamped by AGUWA. Open CORS and 300 req/min rate limits make this suitable for external entropy consumers.

Pulse Format

Every pulse contains these fields:

Field Type Description
round number Commit-reveal round number
randomness string (hex) 64 bytes (128 hex chars) of consensus entropy
timestamp number (unix) AGUWA-synchronized time (not wall clock)
previous_signature string | null Signature of previous pulse (genesis = null)
signature string (hex) ML-DSA-65 signature over round+randomness+timestamp+previous_signature
public_key string (hex) Node's ML-DSA-65 public key for verification
node_id string ID of the beacon node
GET /infoJSON

Root-of-trust info: public_key, period (seconds), threshold (min contributors), next_expected, total_pulses. Call this once to establish trust before consuming pulses.

GET /public/latestPulse

Most recent signed pulse. Returns 503 if no pulse available yet (node still booting or no peers).

GET /public/{round}Pulse

Pulse for a specific round. Returns 404 if round not in history (pruned or not yet reached).

GET /public?limit=100&offset=0{ pulses, limit, offset }

Paginated pulse history, newest first. Max limit=1000. Nodes retain up to 1,000 pulses (bounded history).

POST /public/verify{ valid, verifiedAgainst }

Server-side signature verification. Accepts a pulse object in the body. Optional public_key parameter for cross-node verification.

Client Verification Checklist

  1. Signature: Verify signature against the pulse fields using the node's public_key. The signed payload is JSON.stringify({ round, randomness, timestamp, previous_signature }).
  2. Chain link: Confirm previous_signature matches the signature of the preceding round. Genesis pulse has null.
  3. Freshness: Check time.now() - timestamp ≤ 120s (or 2 × period). Note: timestamps are AGUWA-synchronized, not wall clock — allow for ±2s drift.
  4. Randomness length: Ensure randomness.length === 128 hex chars (64 bytes).

Edge Cases & Behavior

  • First boot: Nodes need at least 1 peer and one complete commit-reveal round before emitting pulses. /public/latest returns 503 until then.
  • Genesis pulse: Round 1 has previous_signature: null. Clients must accept this as the chain origin.
  • Failed rounds: If fewer than 2 contributors reveal, no pulse is created for that round. Round numbering skips nothing — the next successful round increments normally.
  • History pruning: Nodes retain only the most recent 1,000 pulses. Older rounds return 404.
  • Clock skew: Timestamps come from AGUWA (mesh-synchronized time), not the OS clock. A node with a GPS MA-902 may show an offset of ±1–2 seconds. Clients should use 2 × period as the freshness threshold, not strict wall-clock time.
  • Cross-node pulses: In a mesh, different nodes may produce pulses with the same round number but different randomness (each node runs its own commit-reveal). Always verify against the public_key embedded in the pulse.

Quick Verification Script

# Node.js (ships with yakmesh-node)
npm run verify:beacon https://time.yakmesh.dev

# Or use the Python version
python3 scripts/verify_beacon.py https://time.yakmesh.dev

Version History

Version Changes
v3.3.0 Complete rewrite: SHA3-256 sponge engine replaces 144-slot array. 5 hardware entropy sources, two-source extractor, pluggable registry, ~128 byte state (was 4,608). Entropy Sentinel & TRIBHUJ verdicts retained.
v2.x STEADYWATCH seed store: 144 quantum seeds, Hurwitz quaternion simulation, SST families, Fibonacci-cycle selection, batch quality consensus.
v1.0 Initial seed loading and hybrid seed algorithm.