# Vaultfire Codex — Batch 4: Belief / Attestation / Oracle / ZK-Proof Contracts

**Comprehensive Line-by-Line Technical Reference**

*40 contracts across DilithiumAttestor, ProductionBeliefAttestationVerifier, BeliefAttestationVerifier (dev), BeliefOracle/BeliefOracleV3, MultiOracleConsensus/V3, FlourishingMetricsOracle/V3, BaseOracle, VaultfireTrustOracle, interfaces IStarkVerifier/IRiscZeroVerifier/IFlourishingMetricsOracle, MockRiscZeroVerifier, Foundry test suites.*

---

## Table of Contents

1. [contracts/IStarkVerifier.sol](#1-contractsistarkverifiersol)
2. [theloopbreaker-site/risc0/interfaces/IStarkVerifier.sol](#2-theloopbreaker-siterisc0interfacesistarkverifiersol)
3. [contracts/IRiscZeroVerifier.sol](#3-contractsirisczeroveriferrsol)
4. [theloopbreaker-site/base-mini-app/contracts/IRiscZeroVerifier.sol](#4-theloopbreaker-sitebase-mini-appcontractsirisczeroversifiersol)
5. [theloopbreaker-site/risc0/interfaces/IRiscZeroVerifier.sol](#5-theloopbreaker-siterisc0interfacesirisczeroveriferrsol)
6. [vf-audit-contracts/IFlourishingMetricsOracle.sol](#6-vf-audit-contractsiflourishingmetricsoraclesol)
7. [contracts/DilithiumAttestor.sol](#7-contractsdilithiumattestorsol)
8. [theloopbreaker-site/base-mini-app/contracts/DilithiumAttestor.sol](#8-theloopbreaker-sitebase-mini-appcontractsdilithiumattestorsol)
9. [vf-audit-contracts/DilithiumAttestorV3.sol](#9-vf-audit-contractsdilithiumattestorv3sol)
10. [v3/DilithiumAttestorV3.sol](#10-v3dilithiumattestorv3sol)
11. [v3-pr-ready/contracts/v3/DilithiumAttestorV3.sol](#11-v3-pr-readycontractsv3dilithiumattestorv3sol)
12. [push_contracts_v3_DilithiumAttestorV3.sol](#12-push_contracts_v3_dilithiumattestorv3sol)
13. [contracts/BeliefAttestationVerifier.sol](#13-contractsbeliefattestationverifiersol)
14. [contracts/BeliefAttestationVerifierProduction.sol](#14-contractsbeliefattestationverifierproductionsol)
15. [contracts/ProductionBeliefAttestationVerifier.sol](#15-contractsproductionbeliefattestationverifiersol)
16. [vf-audit-contracts/ProductionBeliefAttestationVerifierV3.sol](#16-vf-audit-contractsproductionbeliefattestationverifierv3sol)
17. [v3/ProductionBeliefAttestationVerifierV3.sol](#17-v3productionbeliefattestationverifierv3sol)
18. [v3-pr-ready/contracts/v3/ProductionBeliefAttestationVerifierV3.sol](#18-v3-pr-readycontractsv3productionbeliefattestationverifierv3sol)
19. [push_contracts_v3_ProductionBeliefAttestationVerifierV3.sol](#19-push_contracts_v3_productionbeliefattestationverifierv3sol)
20. [theloopbreaker-site/risc0/src/ProductionBeliefAttestationVerifier.sol](#20-theloopbreaker-siterisc0srcproductionbeliefattestationverifiersol)
21. [contracts/BeliefOracle.sol](#21-contractsbeliefoaclesol)
22. [v3/BeliefOracleV3.sol](#22-v3beliefOraclev3sol)
23. [v3-pr-ready/contracts/v3/BeliefOracleV3.sol](#23-v3-pr-readycontractsv3beliefOraclev3sol)
24. [vf-audit-contracts/BeliefOracleV3.sol](#24-vf-audit-contractsbeliefOraclev3sol)
25. [push_contracts_v3_BeliefOracleV3.sol](#25-push_contracts_v3_beliefOraclev3sol)
26. [contracts/MultiOracleConsensus.sol](#26-contractsmultioracleconsensussol)
27. [theloopbreaker-site/contracts/MultiOracleConsensus.sol](#27-theloopbreaker-sitecontractsmultioracleconsensussol)
28. [vf-audit-contracts/MultiOracleConsensusV3.sol](#28-vf-audit-contractsmultioracleconsensusv3sol)
29. [v3-pr-ready/contracts/v3/MultiOracleConsensusV3.sol](#29-v3-pr-readycontractsv3multioracleconsensusv3sol)
30. [push_contracts_v3_MultiOracleConsensusV3.sol](#30-push_contracts_v3_multioracleconsensusv3sol)
31. [contracts/FlourishingMetricsOracle.sol](#31-contractsflourishingmetricsoraclesol)
32. [theloopbreaker-site/contracts/FlourishingMetricsOracle.sol](#32-theloopbreaker-sitecontractsflourishingmetricsoraclesol)
33. [push_contracts_v3_FlourishingMetricsOracleV3.sol](#33-push_contracts_v3_flourishingmetricsOraclev3sol)
34. [test_fixes/contracts/FlourishingMetricsOracleV3.sol](#34-test_fixescontractsflourishingmetricsOraclev3sol)
35. [vf-audit-contracts/FlourishingMetricsOracleV3.sol](#35-vf-audit-contractsflourishingmetricsOraclev3sol)
36. [v3-pr-ready/contracts/v3/FlourishingMetricsOracleV3.sol](#36-v3-pr-readycontractsv3flourishingmetricsOraclev3sol)
37. [contracts/BaseOracle.sol](#37-contractsbaseoraclesol)
38. [theloopbreaker-site/contracts/VaultfireTrustOracle.sol](#38-theloopbreaker-sitecontractsvaultfiretrustoraclesol)
39. [theloopbreaker-site/contracts/mocks/MockRiscZeroVerifier.sol](#39-theloopbreaker-sitecontractsmocksmockrisczeroverifiersol)
40. [theloopbreaker-site/risc0/test/ProductionBeliefAttestationVerifier.t.sol](#40-theloopbreaker-siterisc0testproductionbeliefattestationverifiertSol)

---

## Global Architecture Overview

```
┌─────────────────────────────────────────────────────────────────┐
│                   VAULTFIRE ZK BELIEF STACK                     │
├─────────────────────────────────────────────────────────────────┤
│  GUEST PROGRAM (Rust / RISC Zero zkVM)                          │
│  ─ Validates belief criteria off-chain                          │
│  ─ Commits journal: (beliefHash, attester, epoch, moduleId,     │
│      beliefScore, timestamp)                                    │
│  ─ Boundless prover wraps execution trace in Groth16 SNARK      │
├─────────────────────────────────────────────────────────────────┤
│  ON-CHAIN VERIFIERS                                             │
│  ┌──────────────────────┐  ┌──────────────────────┐            │
│  │ BeliefAttestationVer │  │ ProductionBeliefAtt-  │            │
│  │ ifier (DEV, 31337)   │  │ estationVerifier(V2/3)│            │
│  └──────────┬───────────┘  └──────────┬────────────┘            │
│             └────────────┬────────────┘                         │
│                          ▼                                      │
│            IStarkVerifier / IRiscZeroVerifier                   │
│                          │                                      │
│                    RiscZeroVerifierRouter                        │
│           (Base Mainnet: 0x0b144e07a0826182b6b59788c34b32bfa86fb711) │
├─────────────────────────────────────────────────────────────────┤
│  BELIEF ATTESTORS                                               │
│  ┌─────────────────────┐  ┌──────────────────────┐             │
│  │ DilithiumAttestor   │  │ DilithiumAttestorV3   │             │
│  │ (V1, global mapping)│  │ (per-user, ECDSA bind)│             │
│  └──────────┬──────────┘  └──────────┬────────────┘             │
├─────────────────────────────────────────────────────────────────┤
│  BELIEF ORACLE GATEWAY                                          │
│  ┌─────────────────────┐  ┌──────────────────────┐             │
│  │ BeliefOracle (V1)   │  │ BeliefOracleV3        │             │
│  │ resonance + bonus   │  │ + MIN_BELIEF_COUNT    │             │
│  └──────────┬──────────┘  └──────────┬────────────┘             │
│             └────────────┬────────────┘                         │
│                          ▼                                      │
│                   RewardStream                                  │
├─────────────────────────────────────────────────────────────────┤
│  ORACLE CONSENSUS LAYERS                                        │
│  MultiOracleConsensus (V1)  →  MultiOracleConsensusV3           │
│  FlourishingMetricsOracle   →  FlourishingMetricsOracleV3       │
│  BaseOracle (CID/zkHash pin)                                    │
│  VaultfireTrustOracle (off-chain performance attestations)      │
└─────────────────────────────────────────────────────────────────┘
```

**Journal format (V3 standard):**

```
abi.encode(
    bytes32 beliefHash,   // keccak256 of the belief content
    address attester,     // EOA submitting the proof
    uint32  epoch,        // protocol epoch
    uint32  moduleId,     // belief module identifier
    uint32  beliefScore,  // 0–10000 bps, must be ≥8000 to pass threshold
    uint64  timestamp     // block.timestamp at proof generation
)
journalDigest = sha256(journal)
```

**Attestation key formula:**
```
key = keccak256(abi.encodePacked(beliefHash, attester, epoch))
```

**ECDSA digest (V3 DilithiumAttestorV3):**
```
digest = keccak256(abi.encodePacked(beliefHash, msg.sender)).toEthSignedMessageHash()
```

---

## 1. contracts/IStarkVerifier.sol

**Path:** `/home/user/workspace/contracts/IStarkVerifier.sol`
**Role:** Interface — Stark/RiscZero proof verifier abstraction

### Summary

`IStarkVerifier` is the canonical interface that all Vaultfire belief verifier contracts implement. It defines three functions: `verifyProof` (the primary proof verification entry point accepting a raw proof and a `uint256[]` public inputs array), `getPublicInputsCount` (returns the expected length of that array), and `getProofSystemId` (returns a human-readable identifier string for the proof system). This interface decouples the attestor contracts (`DilithiumAttestor`, `DilithiumAttestorV3`) from any specific proof backend — an operator can swap between a STARK verifier, a Groth16 wrapper, or a RiscZero verifier router as long as it satisfies this interface.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  interface IStarkVerifier {
 5      /// @notice Verify a zero-knowledge proof.
 6      /// @param proofBytes The serialized proof bytes.
 7      /// @param publicInputs The public inputs to the proof.
 8      /// @return True if the proof is valid, false otherwise.
 9      function verifyProof(
10          bytes calldata proofBytes,
11          uint256[] calldata publicInputs
12      ) external returns (bool);
13
14      /// @notice Returns the number of public inputs expected by this verifier.
15      function getPublicInputsCount() external view returns (uint256);
16
17      /// @notice Returns the proof system identifier.
18      function getProofSystemId() external view returns (string memory);
19  }
```

### Line-by-Line Walkthrough

| Lines | Description |
|-------|-------------|
| 1 | SPDX-License-Identifier MIT — open source, permissive. |
| 2 | `pragma solidity ^0.8.25` — targets the 0.8.25 release train; overflow protection is built-in. |
| 4–18 | `interface IStarkVerifier` — pure ABI declaration, no storage, no constructor. |
| 9–12 | `verifyProof(bytes, uint256[])` — `external` (callable from outside), returns `bool`. Note: `external` on an interface function means any implementer exposes this to other contracts. The `calldata` location for both arguments avoids unnecessary memory copies. |
| 15 | `getPublicInputsCount()` — `view`, no state change. Lets callers pre-validate array length before calling `verifyProof`. |
| 18 | `getProofSystemId()` — returns a `string memory` identifier (e.g. `"RISC0-STARK-BeliefAttestation-Production-v2.0"`). Used for upgrade-safety checks and off-chain tooling. |

### State Variables

None — interface only.

### Functions

| Function | Mutability | Returns | Purpose |
|----------|------------|---------|---------|
| `verifyProof(bytes, uint256[])` | state-changing (non-view) | `bool` | Submit proof + public inputs; implementer writes attestation state on success. |
| `getPublicInputsCount()` | view | `uint256` | Returns expected length of the `publicInputs` array. V1 = 4; V3 audit = 6. |
| `getProofSystemId()` | view | `string memory` | Returns proof system string for off-chain routing and version checks. |

### Math

No numeric computation in the interface itself.

### Events

None.

### Security Invariants

- Callers must pre-validate `publicInputs.length == getPublicInputsCount()` to avoid reverts or silent mismatch.
- `verifyProof` is non-view despite returning bool, because implementations write attestation records on success.

### ZK Proof Flow

Not applicable — interface only. See implementing contracts (§14–§19) for flow diagrams.

### Dependencies + Rebuild Recipe

No imports. Compile with:
```bash
forge build --contracts contracts/IStarkVerifier.sol
```

---

## 2. theloopbreaker-site/risc0/interfaces/IStarkVerifier.sol

**Path:** `/home/user/workspace/theloopbreaker-site/risc0/interfaces/IStarkVerifier.sol`
**Role:** Interface — Duplicate of contracts/IStarkVerifier.sol in the risc0 sub-package

### Summary

Functionally identical to contract §1. The risc0 sub-package maintains its own copy of `IStarkVerifier` to allow the risc0 source tree to compile independently without circular import dependencies into the root `contracts/` folder. The three function signatures (`verifyProof`, `getPublicInputsCount`, `getProofSystemId`) are byte-for-byte identical to §1.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  interface IStarkVerifier {
 5      function verifyProof(
 6          bytes calldata proofBytes,
 7          uint256[] calldata publicInputs
 8      ) external returns (bool);
 9
10      function getPublicInputsCount() external view returns (uint256);
11
12      function getProofSystemId() external view returns (string memory);
13  }
```

### Line-by-Line Walkthrough

Identical to §1. No differences.

### State Variables / Functions / Events / Security Invariants

Same as §1.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts theloopbreaker-site/risc0/interfaces/IStarkVerifier.sol
```

---

## 3. contracts/IRiscZeroVerifier.sol

**Path:** `/home/user/workspace/contracts/IRiscZeroVerifier.sol`
**Role:** Interface — RiscZero proof verifier (root contracts package, returns bool)

### Summary

`IRiscZeroVerifier` is the local (root) package interface for the RISC Zero on-chain verifier. It exposes two functions: `verify(proof, imageId, journalDigest) returns (bool)` and `version() returns (string)`. The `verify` function takes a serialized proof (`bytes`), the expected image ID (`bytes32`, a hash of the compiled guest ELF binary), and the journal digest (`bytes32`, computed as `sha256(journalBytes)`), and returns `true` on success. Notably this interface uses a **boolean return** rather than the revert-on-failure semantics seen in the Apache-licensed interface at §5.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  interface IRiscZeroVerifier {
 5      /// @notice Verify a RISC Zero proof.
 6      /// @param proof The serialized proof bytes (Groth16 seal).
 7      /// @param imageId The expected image ID (hash of the compiled guest program).
 8      /// @param journalDigest SHA-256 digest of the journal committed by the guest.
 9      /// @return True if the proof is valid, false otherwise.
10      function verify(
11          bytes calldata proof,
12          bytes32 imageId,
13          bytes32 journalDigest
14      ) external returns (bool);
15
16      /// @notice Returns the verifier version string.
17      function version() external view returns (string memory);
18  }
```

### Line-by-Line Walkthrough

| Lines | Description |
|-------|-------------|
| 10–14 | `verify` — accepts three arguments. `proof` is the Groth16 SNARK seal bytes from a RISC Zero receipt. `imageId` is compared against what the verifier expects (anchors the proof to a specific guest binary). `journalDigest = sha256(abi.encode(...))` binds the proof to specific public outputs. Returns `bool` — callers must check return value and handle `false` paths. |
| 17 | `version()` — version string for upgrade routing and debugging. |

### Math

Journal digest: \(\text{journalDigest} = \text{SHA-256}(\text{journalBytes})\)

The SHA-256 is computed in Solidity using the built-in `sha256()` precompile (address `0x02`). This is distinct from `keccak256` — RISC Zero uses SHA-256 for compatibility with the off-chain Rust guest.

### Security Invariants

- Callers using this interface version must explicitly handle a `false` return — silent acceptance of a `false` proof is a critical vulnerability.
- The `imageId` parameter pins the verification to a specific guest program binary. Passing the wrong `imageId` causes all proofs to return `false`.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts contracts/IRiscZeroVerifier.sol
```

---

## 4. theloopbreaker-site/base-mini-app/contracts/IRiscZeroVerifier.sol

**Path:** `/home/user/workspace/theloopbreaker-site/base-mini-app/contracts/IRiscZeroVerifier.sol`
**Role:** Interface — RiscZero verifier for the Base mini-app sub-package (^0.8.20, returns bool)

### Summary

Mini-app local copy of `IRiscZeroVerifier`. Uses `pragma solidity ^0.8.20` (older than the root 0.8.25 but compatible). The `verify` function signature is identical to §3 — returns `bool`. Used by `DilithiumAttestor.sol` in the base-mini-app sub-tree to call the deployed RISC Zero verifier router on Base Mainnet.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.20;
 3
 4  interface IRiscZeroVerifier {
 5      function verify(
 6          bytes calldata proof,
 7          bytes32 imageId,
 8          bytes32 journalDigest
 9      ) external returns (bool);
10
11      function version() external view returns (string memory);
12  }
```

### Line-by-Line Walkthrough

Line 2: `^0.8.20` — allows any compiler from 0.8.20 to <0.9.0. This is slightly older than the root package but all semantics are equivalent.

All other lines are functionally identical to §3.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts theloopbreaker-site/base-mini-app/contracts/IRiscZeroVerifier.sol \
  --solc-version 0.8.20
```

---

## 5. theloopbreaker-site/risc0/interfaces/IRiscZeroVerifier.sol

**Path:** `/home/user/workspace/theloopbreaker-site/risc0/interfaces/IRiscZeroVerifier.sol`
**Role:** Interface — Canonical Apache-2.0 RISC Zero verifier interface (revert-on-failure semantics)

### Summary

This is the **Apache-2.0 licensed** RISC Zero interface — the upstream canonical definition used by the RISC Zero team. It differs critically from the MIT versions at §3 and §4: `verify` does **not** return a boolean; instead it **reverts on proof failure**. It also includes the `verifyIntegrity(Receipt calldata)` function for full receipt verification, and defines the `Receipt` struct containing both the journal and the seal. The Base Mainnet `RiscZeroVerifierRouter` at `0x0b144e07a0826182b6b59788c34b32bfa86fb711` implements this interface.

### Full Source

```solidity
 1  // SPDX-License-Identifier: Apache-2.0
 2  pragma solidity ^0.8.25;
 3
 3  // Upstream RISC Zero Ethereum interface
 4  // Base Mainnet: 0x0b144e07a0826182b6b59788c34b32bfa86fb711
 5
 6  struct Receipt {
 7      bytes seal;
 8      bytes journal;
 9  }
 10
 11 interface IRiscZeroVerifier {
 12     /// @notice Verifies a RISC Zero receipt.
 13     /// @dev REVERTS on invalid proof. Does NOT return bool.
 14     function verify(
 15         bytes calldata seal,
 16         bytes32 imageId,
 17         bytes32 journalDigest
 18     ) external;
 19
 20     /// @notice Verifies a full Receipt (seal + journal).
 21     function verifyIntegrity(Receipt calldata receipt) external view;
 22
 23     /// @notice Returns the verifier version string.
 24     function version() external view returns (string memory);
 25 }
```

### Line-by-Line Walkthrough

| Lines | Description |
|-------|-------------|
| 1 | Apache-2.0 license — upstream RISC Zero code. |
| 6–9 | `Receipt` struct — packages the Groth16 `seal` (proof bytes) and `journal` (raw output bytes committed by the guest) into a single struct for `verifyIntegrity`. |
| 14–18 | `verify(seal, imageId, journalDigest)` — **no return value**; reverts on any verification failure. Callers must use `try/catch` to handle failures without bubbling the revert. This is the interface implemented by `ProductionBeliefAttestationVerifier` (§20) via `try riscZeroVerifier.verify(...)`. |
| 21 | `verifyIntegrity(Receipt)` — verifies both the seal and matches `sha256(receipt.journal)` against the journal digest. Marked `view` — no state writes. |

### Math

\[\text{journalDigest} = \text{SHA-256}(\text{receipt.journal})\]

The verifier computes this digest internally and compares it against what the guest committed.

### Security Invariants

- **Critical:** This interface uses revert-on-failure, not boolean returns. Any wrapper that treats the `bool` return (from §3/§4 signatures) as though it came from this interface will experience a type mismatch or silent incorrect behavior.
- `verifyIntegrity` is `view` — safe to call in view contexts for integrity checks without gas-intensive state writes.

### ZK Proof Flow (Canonical RISC Zero)

```
Off-chain:
  Rust guest executes inside zkVM → commits journal
  Boundless/local prover generates Groth16 SNARK (seal)

On-chain:
  Caller → verify(seal, imageId, sha256(journal))
         → RiscZeroVerifierRouter (0x0b144e...)
              ├─ Routes by proof type prefix in seal
              ├─ Delegates to registered verifier for that type
              └─ Reverts if invalid; returns normally if valid
```

### Dependencies + Rebuild Recipe

```bash
forge build --contracts theloopbreaker-site/risc0/interfaces/IRiscZeroVerifier.sol
```

---

## 6. vf-audit-contracts/IFlourishingMetricsOracle.sol

**Path:** `/home/user/workspace/vf-audit-contracts/IFlourishingMetricsOracle.sol`
**Role:** Interface — FlourishingMetricsOracle consumer API

### Summary

`IFlourishingMetricsOracle` defines the two query functions that oracle consumers (e.g., DAO contracts, vault systems) call to read metrics: `getMetricTWAP(metricId, window)` returns the time-weighted average price for a metric, and `isMetricFresh(metricId)` returns whether the most recent update falls within the oracle's freshness window. This interface is implemented by all `FlourishingMetricsOracleV3` variants (§33–§36).

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  interface IFlourishingMetricsOracle {
 5      /// @notice Returns the Time-Weighted Average Price for a metric.
 6      /// @param metricId The metric identifier (bytes32).
 7      /// @param window   The TWAP lookback window in seconds (may be ignored by V3).
 8      function getMetricTWAP(bytes32 metricId, uint256 window)
 9          external view returns (uint256);
 10
 11     /// @notice Returns true if the metric was updated within the freshness window.
 12     /// @param metricId The metric identifier.
 13     function isMetricFresh(bytes32 metricId)
 14         external view returns (bool);
 15 }
```

### Line-by-Line Walkthrough

| Lines | Description |
|-------|-------------|
| 8–9 | `getMetricTWAP(bytes32, uint256)` — `view`, returns `uint256`. The `window` parameter is accepted for interface compatibility but is ignored by the V3 EMA-based implementation (which uses a fixed EMA coefficient of 3:1). |
| 13–14 | `isMetricFresh(bytes32)` — `view`, returns `bool`. V3 implementation checks `block.timestamp - lastUpdated ≤ MAX_METRIC_AGE` where `MAX_METRIC_AGE = 1 hours`. |

### Math

TWAP formula (EMA, implemented in FlourishingMetricsOracleV3):
\[\text{TWAP}_n = \begin{cases} v_0 & \text{if first update} \\ \frac{3 \cdot \text{TWAP}_{n-1} + v_n}{4} & \text{otherwise} \end{cases}\]

where \(v_n\) is the new value (or weighted mean in V3 vf-audit variant).

### Security Invariants

- Consumers must check `isMetricFresh` before trusting `getMetricTWAP` to avoid acting on stale data.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts vf-audit-contracts/IFlourishingMetricsOracle.sol
```

---

## 7. contracts/DilithiumAttestor.sol

**Path:** `/home/user/workspace/contracts/DilithiumAttestor.sol`
**Role:** Belief verifier — V1 Dilithium attestor, global mapping (vulnerable to cross-user replay)

### Summary

`DilithiumAttestor` is the V1 on-chain attestation contract. It receives a `(beliefHash, proof, signature)` tuple from a caller, validates the ECDSA signature against a signer address, constructs a 4-element `publicInputs` array, and calls the injected `IStarkVerifier` to verify the ZK proof. On success it records `attestedBeliefs[beliefHash] = true`. The critical vulnerability in V1 is that `attestedBeliefs` is a **global mapping** — once any user attests a belief hash, any subsequent attester for the same hash gets a false positive from `hasAttested()` even without submitting their own proof. This is the HIGH-1 finding fixed in V3.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  import {IStarkVerifier} from "./IStarkVerifier.sol";
 5  import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
 6
 7  contract DilithiumAttestor {
 8      using ECDSA for bytes32;
 9
10      IStarkVerifier public immutable verifier;
11      address public immutable signer;
12
13      // V1 BUG: global mapping — beliefHash → attested (not per-user)
14      mapping(bytes32 => bool) public attestedBeliefs;
15
15      event BeliefAttested(
16          address indexed attester,
17          bytes32 indexed beliefHash,
18          uint256 timestamp
19      );
20
21      error AlreadyAttested();
22      error InvalidSignature();
23      error ProofVerificationFailed();
24      error EmptyProof();
25
26      constructor(address verifier_, address signer_) {
27          require(verifier_ != address(0), "zero verifier");
28          require(signer_ != address(0), "zero signer");
29          verifier = IStarkVerifier(verifier_);
30          signer = signer_;
31      }
32
33      function attestBelief(
34          bytes32 beliefHash,
35          bytes calldata proof
36      ) external {
37          if (attestedBeliefs[beliefHash]) revert AlreadyAttested();
38          if (proof.length == 0) revert EmptyProof();
39
39          // ECDSA: verify that `signer` authorized this beliefHash
40          bytes32 digest = keccak256(abi.encodePacked(beliefHash))
41              .toEthSignedMessageHash();
42          address recovered = digest.recover(proof[0:65]);
43          if (recovered != signer) revert InvalidSignature();
44
44          // Build publicInputs for the ZK verifier
45          uint256[] memory publicInputs = new uint256[](4);
46          publicInputs[0] = uint256(beliefHash);
47          publicInputs[1] = uint256(uint160(msg.sender));
48          publicInputs[2] = 0; // epoch placeholder
49          publicInputs[3] = 0; // moduleID placeholder
50
51          bytes memory zkProof = proof[65:];
52          bool ok = verifier.verifyProof(zkProof, publicInputs);
53          if (!ok) revert ProofVerificationFailed();
54
55          attestedBeliefs[beliefHash] = true;
56          emit BeliefAttested(msg.sender, beliefHash, block.timestamp);
57      }
58
59      function hasAttested(bytes32 beliefHash) external view returns (bool) {
60          return attestedBeliefs[beliefHash];
61      }
62  }
```

### Line-by-Line Walkthrough

| Line | Description |
|------|-------------|
| 4 | Imports `IStarkVerifier` — the proof verification interface. |
| 5 | Imports OpenZeppelin `ECDSA` for `toEthSignedMessageHash()` and `recover()`. |
| 10 | `verifier` — immutable; set once in constructor, cannot be changed. Prevents verifier swap attacks. |
| 11 | `signer` — immutable authorized ECDSA signer address. Only attestations signed by this key pass. |
| 14 | **`attestedBeliefs[bytes32] => bool`** — global mapping. HIGH-1 vulnerability: attesting `beliefHash` once locks it for everyone. |
| 37 | Guard: `if (attestedBeliefs[beliefHash]) revert AlreadyAttested()` — once set globally, all subsequent callers revert even with valid proofs. |
| 40–43 | ECDSA digest construction: `keccak256(abi.encodePacked(beliefHash))` — note this digest does NOT include `msg.sender`. This is the HIGH-2 finding: any holder of a signed `beliefHash` can re-use it from a different address. |
| 45–49 | 4-element `publicInputs`: `[beliefHash, msg.sender, 0, 0]`. Epoch and moduleID are hardcoded to 0. |
| 51 | `proof[65:]` — the first 65 bytes of `proof` are the ECDSA signature; the remainder is the ZK proof body. |
| 52–53 | `verifier.verifyProof(zkProof, publicInputs)` — delegates to the injected verifier. Returns bool; reverts on false. |
| 55 | CEI violation: state is written AFTER external call. Mitigated by guard at line 37, but still a structural reentrancy risk. |

### State Variables

| Variable | Type | Mutability | Purpose |
|----------|------|------------|---------|
| `verifier` | `IStarkVerifier` | immutable | ZK proof verifier backend |
| `signer` | `address` | immutable | Authorized ECDSA signer |
| `attestedBeliefs` | `mapping(bytes32 => bool)` | mutable | Global attestation registry (V1 vulnerability) |

### Functions

**`constructor(address verifier_, address signer_)`**
- Validates non-zero addresses.
- Sets `verifier` and `signer` as immutable values.

**`attestBelief(bytes32 beliefHash, bytes calldata proof) external`**
- Guards: `AlreadyAttested` (global), `EmptyProof`.
- Extracts ECDSA signature from `proof[0:65]`, verifies against `signer`.
- Constructs 4-element `publicInputs`, calls `verifier.verifyProof`.
- Sets `attestedBeliefs[beliefHash] = true`.
- Emits `BeliefAttested`.

**`hasAttested(bytes32 beliefHash) external view returns (bool)`**
- Returns global attestation flag. Does not differentiate by user.

### Math

ECDSA digest:
\[\text{digest} = \text{EthSignedMessageHash}(\text{keccak256}(\text{beliefHash}))\]
where EthSignedMessageHash prepends `"\x19Ethereum Signed Message:\n32"`.

Public inputs array:
\[\text{publicInputs} = [\text{uint256(beliefHash)},\; \text{uint160(msg.sender)},\; 0,\; 0]\]

### Events

| Event | Trigger | Fields |
|-------|---------|--------|
| `BeliefAttested(address attester, bytes32 beliefHash, uint256 timestamp)` | Successful proof verification | `attester` (indexed), `beliefHash` (indexed), `block.timestamp` |

### Security Invariants

1. **HIGH-1 (unfixed):** `attestedBeliefs` is global. Once any user attests a beliefHash, it is permanently locked and subsequent valid attesters get `AlreadyAttested`.
2. **HIGH-2 (unfixed):** ECDSA digest does not bind `msg.sender`, enabling signature replay from different wallets.
3. **CEI violation:** `attestedBeliefs[beliefHash] = true` is set after the external `verifier.verifyProof` call. A malicious verifier could re-enter.

### ZK Proof Flow

```
Caller submits: attestBelief(beliefHash, proof)
  proof = [65 bytes ECDSA sig] ++ [N bytes ZK proof]

Step 1: recover ECDSA signer from proof[0:65]
        digest = keccak256(abi.encodePacked(beliefHash)).toEthSignedMessageHash()
        recovered = ECDSA.recover(digest, proof[0:65])
        assert recovered == signer

Step 2: publicInputs = [beliefHash, msg.sender, 0, 0]
        zkProof = proof[65:]
        ok = verifier.verifyProof(zkProof, publicInputs)
        assert ok == true

Step 3: attestedBeliefs[beliefHash] = true
        emit BeliefAttested(msg.sender, beliefHash, block.timestamp)
```

### Dependencies + Rebuild Recipe

```
contracts/
├── IStarkVerifier.sol
└── DilithiumAttestor.sol
@openzeppelin/contracts/utils/cryptography/ECDSA.sol
```

```bash
forge build
```

---

## 8. theloopbreaker-site/base-mini-app/contracts/DilithiumAttestor.sol

**Path:** `/home/user/workspace/theloopbreaker-site/base-mini-app/contracts/DilithiumAttestor.sol`
**Role:** Belief verifier — Base mini-app variant, uses IRiscZeroVerifier directly, mutable zkEnabled flag, per-user timestamp mapping

### Summary

The mini-app variant of `DilithiumAttestor` targets the Base Mainnet deployment of the RISC Zero verifier router. Unlike the root V1 (§7) it uses `IRiscZeroVerifier` directly (not `IStarkVerifier`), computes a `sha256` journal digest inline, and stores per-user attestation timestamps in `userAttestations[address][bytes32] => uint256`. It also has a mutable `zkEnabled` flag that the owner can toggle — when disabled, proofs are accepted without ZK verification. The per-user mapping is a partial fix for the global-mapping vulnerability, but the `zkEnabled` escape hatch is a significant centralization risk.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.20;
 3
 4  import {IRiscZeroVerifier} from "./IRiscZeroVerifier.sol";
 5  import "@openzeppelin/contracts/access/Ownable.sol";
 6
 7  contract DilithiumAttestor is Ownable {
 8      IRiscZeroVerifier public immutable riscZeroVerifier;
 9      bytes32 public imageId;
 10     bool public zkEnabled;
 11
 12     // Per-user attestation timestamp: user → beliefHash → timestamp
 13     mapping(address => mapping(bytes32 => uint256)) public userAttestations;
 14
 14     event BeliefAttested(address indexed user, bytes32 indexed beliefHash, uint256 timestamp);
 15     event ZKToggled(bool enabled);
 16     event ImageIdUpdated(bytes32 oldId, bytes32 newId);
 17
 18     error AlreadyAttested();
 19     error ZKVerificationFailed();
 20
 20     constructor(
 21         address _riscZeroVerifier,
 22         bytes32 _imageId,
 23         bool _zkEnabled
 24     ) Ownable(msg.sender) {
 25         riscZeroVerifier = IRiscZeroVerifier(_riscZeroVerifier);
 26         imageId = _imageId;
 27         zkEnabled = _zkEnabled;
 28     }
 29
 30     function setZkEnabled(bool enabled) external onlyOwner {
 31         zkEnabled = enabled;
 32         emit ZKToggled(enabled);
 33     }
 34
 35     function updateImageId(bytes32 newImageId) external onlyOwner {
 36         emit ImageIdUpdated(imageId, newImageId);
 37         imageId = newImageId;
 38     }
 39
 40     function attestBelief(
 41         bytes32 beliefHash,
 42         bytes calldata proof
 43     ) external {
 44         if (userAttestations[msg.sender][beliefHash] != 0) revert AlreadyAttested();
 45
 45         if (zkEnabled) {
 46             // Journal: encode beliefHash + msg.sender
 47             bytes memory journal = abi.encode(beliefHash, msg.sender);
 47             bytes32 journalDigest = sha256(journal);
 48             bool ok = riscZeroVerifier.verify(proof, imageId, journalDigest);
 49             if (!ok) revert ZKVerificationFailed();
 49         }
 50
 51         userAttestations[msg.sender][beliefHash] = block.timestamp;
 52         emit BeliefAttested(msg.sender, beliefHash, block.timestamp);
 53     }
 54
 55     function hasAttested(address user, bytes32 beliefHash) external view returns (bool) {
 56         return userAttestations[user][beliefHash] != 0;
 57     }
 58
 59     function getAttestationTime(address user, bytes32 beliefHash)
 60         external view returns (uint256)
 61     {
 62         return userAttestations[user][beliefHash];
 63     }
 64  }
```

### Line-by-Line Walkthrough

| Line | Description |
|------|-------------|
| 2 | `^0.8.20` — older pragma for mini-app compatibility. |
| 8 | `riscZeroVerifier` — immutable; targets the RiscZero router at deployment. |
| 9 | `imageId` — **mutable** (not immutable). Owner can update without timelock — centralization risk. |
| 10 | `zkEnabled` — mutable flag. When `false`, the proof is never verified — all attestations succeed trivially. |
| 13 | `userAttestations[address][bytes32] => uint256` — per-user fix for the global-mapping HIGH-1 vulnerability. Stores `block.timestamp` rather than `bool`. |
| 30–33 | `setZkEnabled` — owner can disable ZK verification entirely. This is a critical privilege: a compromised owner key can disable all security. |
| 44 | Guard uses `!= 0` (timestamp check) rather than a boolean. A user who attested at exactly timestamp 0 (never, in practice, since block.timestamp > 0) would be treated as unattested — an edge case that is harmless in practice. |
| 46–48 | Journal = `abi.encode(beliefHash, msg.sender)` — only 2 fields, simpler than the V3 6-field format. The journal digest is computed on-chain as `sha256(journal)`. |
| 48 | `riscZeroVerifier.verify(proof, imageId, journalDigest)` — uses `IRiscZeroVerifier` (§4) which returns `bool`. |

### State Variables

| Variable | Type | Mutability | Purpose |
|----------|------|------------|---------|
| `riscZeroVerifier` | `IRiscZeroVerifier` | immutable | RISC Zero verifier router |
| `imageId` | `bytes32` | mutable (owner) | Guest program ELF hash |
| `zkEnabled` | `bool` | mutable (owner) | ZK proof verification toggle |
| `userAttestations` | `mapping(address => mapping(bytes32 => uint256))` | mutable | Per-user attestation timestamp registry |

### Math

Journal digest:
\[\text{journalDigest} = \text{SHA-256}(\text{abi.encode}(\text{beliefHash},\, \text{msg.sender}))\]

### Events

| Event | When | Fields |
|-------|------|--------|
| `BeliefAttested(address user, bytes32 beliefHash, uint256 timestamp)` | Successful attestation | All indexed |
| `ZKToggled(bool enabled)` | Owner toggles `zkEnabled` | `enabled` |
| `ImageIdUpdated(bytes32 oldId, bytes32 newId)` | Owner updates `imageId` | Old and new values |

### Security Invariants

1. `zkEnabled = false` is a **critical bypass** — all attestations succeed with empty proof bytes.
2. `imageId` mutability without timelock is a centralization risk — instant guest program swap.
3. Per-user mapping correctly prevents cross-user replay (partial fix vs V1).

### Dependencies + Rebuild Recipe

```bash
forge build --contracts theloopbreaker-site/base-mini-app/contracts/DilithiumAttestor.sol \
  --solc-version 0.8.20
```

---

## 9. vf-audit-contracts/DilithiumAttestorV3.sol

**Path:** `/home/user/workspace/vf-audit-contracts/DilithiumAttestorV3.sol`
**Role:** Belief verifier — Full V3 audit, per-user mapping, ECDSA msg.sender binding, 6-element publicInputs, CEI fix

### Summary

`DilithiumAttestorV3` is the audited V3 production attestor. It fixes all critical and high findings from the V1 audit: (1) `attestedBeliefs` is replaced with `attestedBeliefs[bytes32][address] => bool` per-user mapping eliminating cross-user replay; (2) the ECDSA digest binds `msg.sender` — `keccak256(abi.encodePacked(beliefHash, msg.sender)).toEthSignedMessageHash()` — preventing signature portability across wallets; (3) the `publicInputs` array is expanded to 6 elements including `beliefScore = 8000` and `block.timestamp`, aligning with the V3 journal format; (4) the state write is moved before the external call (CEI compliance). This is the authoritative V3 attestor implementation.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  import {IStarkVerifier} from "./IStarkVerifier.sol";
 5  import "@openzeppelin/contracts/utils/cryptography/ECDSA.sol";
 6
 7  /// @title DilithiumAttestorV3
 8  /// @notice V3 belief attestor with per-user mapping, msg.sender ECDSA binding,
 9  ///         6-element publicInputs (beliefScore=8000, timestamp), and CEI fix.
10  contract DilithiumAttestorV3 {
11      using ECDSA for bytes32;
12
13      IStarkVerifier public immutable verifier;
14      address public immutable signer;
15
15      /// @dev HIGH-1 FIX: per-user mapping prevents cross-user replay
16      mapping(bytes32 => mapping(address => bool)) public attestedBeliefs;
17
18      event BeliefAttested(
19          address indexed attester,
20          bytes32 indexed beliefHash,
21          uint256 timestamp
22      );
23
24      error AlreadyAttested();
25      error InvalidSignature();
26      error ProofVerificationFailed();
27      error EmptyProof();
28
29      constructor(address verifier_, address signer_) {
30          require(verifier_ != address(0), "zero verifier");
31          require(signer_ != address(0), "zero signer");
32          verifier = IStarkVerifier(verifier_);
33          signer = signer_;
34      }
35
36      function attestBelief(
37          bytes32 beliefHash,
38          bytes calldata proof
39      ) external {
40          // HIGH-1 FIX: per-user attestation guard
41          if (attestedBeliefs[beliefHash][msg.sender]) revert AlreadyAttested();
42          if (proof.length == 0) revert EmptyProof();
43
44          // HIGH-2 FIX: bind ECDSA digest to msg.sender
45          bytes32 digest = keccak256(
46              abi.encodePacked(beliefHash, msg.sender)
47          ).toEthSignedMessageHash();
48          address recovered = digest.recover(proof[0:65]);
49          if (recovered != signer) revert InvalidSignature();
50
51          // CRIT-07 FIX: 6-element publicInputs including beliefScore and timestamp
52          uint256[] memory publicInputs = new uint256[](6);
53          publicInputs[0] = uint256(beliefHash);
54          publicInputs[1] = uint256(uint160(msg.sender));
55          publicInputs[2] = 0; // epoch
56          publicInputs[3] = 0; // moduleID
57          publicInputs[4] = 8000; // beliefScore (minimum threshold in bps)
58          publicInputs[5] = block.timestamp;
59
60          // CEI FIX: write state BEFORE external call
61          attestedBeliefs[beliefHash][msg.sender] = true;
62
63          bytes memory zkProof = proof[65:];
64          bool ok = verifier.verifyProof(zkProof, publicInputs);
65          if (!ok) revert ProofVerificationFailed();
66
67          emit BeliefAttested(msg.sender, beliefHash, block.timestamp);
68      }
69
70      function hasAttested(bytes32 beliefHash, address user)
71          external view returns (bool)
72      {
73          return attestedBeliefs[beliefHash][user];
74      }
75  }
```

### Line-by-Line Walkthrough

| Line | Description |
|------|-------------|
| 16 | `attestedBeliefs[bytes32][address] => bool` — **HIGH-1 FIX**. Each `(beliefHash, attester)` pair has its own boolean. User A attesting beliefHash X does not block user B. |
| 41 | Per-user guard: `attestedBeliefs[beliefHash][msg.sender]` — checks only the caller's slot. |
| 45–47 | **HIGH-2 FIX:** ECDSA digest = `keccak256(abi.encodePacked(beliefHash, msg.sender))`. The signer must sign `(beliefHash ++ msg.sender)` — the signature is cryptographically bound to the specific wallet. |
| 52–58 | **CRIT-07 FIX:** 6-element `publicInputs`: `[beliefHash, msg.sender, epoch=0, moduleID=0, beliefScore=8000, block.timestamp]`. This matches the V3 journal ABI encoding format expected by the verifier. |
| 61 | **CEI FIX:** `attestedBeliefs[beliefHash][msg.sender] = true` written BEFORE `verifier.verifyProof` external call. If the verifier re-enters, the guard at line 41 prevents double-entry. |
| 64–65 | `verifyProof` called with the ZK proof bytes (after stripping 65-byte ECDSA sig). Reverts on failure. |

### State Variables

| Variable | Type | Mutability | Purpose |
|----------|------|------------|---------|
| `verifier` | `IStarkVerifier` | immutable | ZK proof verifier backend |
| `signer` | `address` | immutable | Authorized ECDSA signer |
| `attestedBeliefs` | `mapping(bytes32 => mapping(address => bool))` | mutable | Per-user attestation registry (HIGH-1 fix) |

### Functions

**`attestBelief(bytes32 beliefHash, bytes calldata proof) external`**
- Per-user guard (HIGH-1 fix).
- ECDSA signer verification with `msg.sender` binding (HIGH-2 fix).
- 6-element `publicInputs` construction (CRIT-07 fix).
- CEI-compliant state write before external call.
- Emits `BeliefAttested`.

**`hasAttested(bytes32 beliefHash, address user) external view returns (bool)`**
- Returns per-user attestation flag.

### Math

ECDSA digest (HIGH-2 fix):
\[\text{digest} = \text{EthSignedMessageHash}(\text{keccak256}(\text{beliefHash} \| \text{msg.sender}))\]

Public inputs:
\[\text{publicInputs} = [\text{beliefHash},\; \text{msg.sender},\; 0,\; 0,\; 8000,\; \text{block.timestamp}]\]

### Events

| Event | Trigger | Fields |
|-------|---------|--------|
| `BeliefAttested(address attester, bytes32 beliefHash, uint256 timestamp)` | Successful attestation | All indexed where applicable |

### Security Invariants

1. Per-user mapping prevents global lockout (HIGH-1 fixed).
2. ECDSA digest includes `msg.sender` — no signature portability (HIGH-2 fixed).
3. CEI compliance: state written before external call (reentrancy risk mitigated).
4. `beliefScore = 8000` hardcoded — the guest program's belief threshold is enforced at the verifier level, not just at the oracle level.

### ZK Proof Flow

```
Caller: attestBelief(beliefHash, [65-byte ECDSA sig] ++ [ZK proof bytes])

Step 1 — ECDSA Verification:
  digest = keccak256(beliefHash ++ msg.sender).toEthSignedMessageHash()
  recovered = ECDSA.recover(digest, proof[0:65])
  assert recovered == signer ✓

Step 2 — State Write (CEI):
  attestedBeliefs[beliefHash][msg.sender] = true

Step 3 — ZK Proof Verification:
  publicInputs = [beliefHash, msg.sender, 0, 0, 8000, block.timestamp]
  zkProof = proof[65:]
  ok = verifier.verifyProof(zkProof, publicInputs)
  assert ok == true ✓

Step 4 — Event:
  emit BeliefAttested(msg.sender, beliefHash, block.timestamp)
```

### Dependencies + Rebuild Recipe

```
vf-audit-contracts/
├── IStarkVerifier.sol (local copy)
└── DilithiumAttestorV3.sol
@openzeppelin/contracts/utils/cryptography/ECDSA.sol
```

```bash
forge build --contracts vf-audit-contracts/DilithiumAttestorV3.sol
```

---

## 10. v3/DilithiumAttestorV3.sol

**Path:** `/home/user/workspace/v3/DilithiumAttestorV3.sol`
**Role:** Belief verifier — V3 staging copy; import path differs, 4-element publicInputs

### Summary

This is a staging copy of `DilithiumAttestorV3` in the `/v3/` directory. It applies the HIGH-1 per-user mapping fix and the HIGH-2 `msg.sender` ECDSA binding fix, but its `publicInputs` array has only **4 elements** (not 6) — meaning `beliefScore` and `block.timestamp` are absent. This is the intermediate state before the CRIT-07 fix was applied. It imports `IStarkVerifier` from `./IStarkVerifier.sol` (relative path within the v3 directory). The import for `RewardStream` uses `../deprecated/RewardStream.sol`.

### Full Source

*(Identical to §9 except `publicInputs` is 4 elements with epoch=0, moduleID=0, and state write is after the external call in this version.)*

Key differences vs §9:

```solidity
// Line 52: 4-element array (no beliefScore, no timestamp)
uint256[] memory publicInputs = new uint256[](4);
publicInputs[0] = uint256(beliefHash);
publicInputs[1] = uint256(uint160(msg.sender));
publicInputs[2] = 0; // epoch
publicInputs[3] = 0; // moduleID

// State write AFTER external call (CEI not fully applied)
bytes memory zkProof = proof[65:];
bool ok = verifier.verifyProof(zkProof, publicInputs);
if (!ok) revert ProofVerificationFailed();

attestedBeliefs[beliefHash][msg.sender] = true; // after external call
```

### Line-by-Line Walkthrough

Same structure as §9 except:
- **Line 52–57:** `publicInputs` has 4 elements. Missing `beliefScore` (index 4) and `block.timestamp` (index 5). This means the ZK verifier in this configuration expects `getPublicInputsCount() == 4`.
- **CEI:** state write appears after the external `verifyProof` call. Reentrancy guard is the `AlreadyAttested` check at the top, which is sufficient if the mapping is already set — but since it is not yet set when the external call occurs, a malicious verifier could re-enter.

### State Variables / Functions / Events

Same as §9 except `publicInputs` array length = 4.

### Security Invariants

- CRIT-07 **not fixed** in this version — 4-element publicInputs may not match a verifier configured for 6 inputs.
- CEI **partially violated** — state write after external call.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts v3/DilithiumAttestorV3.sol
```

---

## 11. v3-pr-ready/contracts/v3/DilithiumAttestorV3.sol

**Path:** `/home/user/workspace/v3-pr-ready/contracts/v3/DilithiumAttestorV3.sol`
**Role:** Belief verifier — PR-ready V3 with import path `../contracts/IStarkVerifier.sol`, 4-element publicInputs, no CRIT-07 fix

### Summary

The PR-ready staging version. Structurally equivalent to §10 — per-user mapping and ECDSA `msg.sender` binding are applied, but `publicInputs` remains 4 elements and the CRIT-07 fix (beliefScore + timestamp in public inputs) has not been applied. The import path differs: `import {IStarkVerifier} from "../contracts/IStarkVerifier.sol"` — referencing the root contracts folder rather than a local copy.

### Key Differences vs §9

```solidity
import {IStarkVerifier} from "../contracts/IStarkVerifier.sol"; // different import path

// 4-element publicInputs (CRIT-07 not applied)
uint256[] memory publicInputs = new uint256[](4);
```

All other logic identical to §10.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts v3-pr-ready/contracts/v3/DilithiumAttestorV3.sol
```

---

## 12. push_contracts_v3_DilithiumAttestorV3.sol

**Path:** `/home/user/workspace/push_contracts_v3_DilithiumAttestorV3.sol`
**Role:** Belief verifier — Final pushed V3 with CRIT-07 fix applied (6-element publicInputs)

### Summary

This is the **final pushed version** of `DilithiumAttestorV3`, generated during the automated `push_contracts_v3` workflow. It is identical to the audit version (§9) in all critical respects: per-user mapping (HIGH-1), `msg.sender`-bound ECDSA (HIGH-2), 6-element `publicInputs` including `beliefScore=8000` and `block.timestamp` (CRIT-07), and CEI-compliant state write. This is the version that should be deployed to production.

### Key Feature: CRIT-07 Fix

```solidity
uint256[] memory publicInputs = new uint256[](6);
publicInputs[0] = uint256(beliefHash);
publicInputs[1] = uint256(uint160(msg.sender));
publicInputs[2] = 0; // epoch
publicInputs[3] = 0; // moduleID
publicInputs[4] = 8000; // beliefScore — V3 journal field
publicInputs[5] = block.timestamp; // timestamp — V3 journal field
```

This matches the 6-field V3 journal ABI encoding and the `PUBLIC_INPUTS_COUNT = 6` constant in `ProductionBeliefAttestationVerifierV3` (§16, §19).

All other details identical to §9.

### Dependencies + Rebuild Recipe

Identical to §9. Use the root `forge build`.

---
## 13. contracts/BeliefAttestationVerifier.sol

**Path:** `/home/user/workspace/contracts/BeliefAttestationVerifier.sol`
**Role:** ZK proof verifier — DEV ONLY; chain ID guard (31337/1337); returns true for any proof ≥32 bytes

### Summary

`BeliefAttestationVerifier` is the **development-mode-only** verifier. It is intentionally non-functional on mainnet: its constructor enforces that `block.chainid` is either `31337` (Anvil/Hardhat default) or `1337` (Ganache). Any deployment attempt on a production chain reverts during construction. When operating on a dev chain, `verifyProof` accepts any proof whose length is at least 32 bytes and returns `true`, bypassing all cryptographic checks. It emits a `DevVerifierUsed` event as a loud warning. It implements `IStarkVerifier` so it is a drop-in for local development. `BeliefAttestationVerifierProduction` (§14) is its production replacement.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  import {IStarkVerifier} from "./IStarkVerifier.sol";
 5
 6  /// @title BeliefAttestationVerifier
 7  /// @notice DEV-ONLY verifier. Reverts at construction on mainnet (chain ≠ 31337/1337).
 8  /// @dev WARNING: Accepts any proof ≥ 32 bytes. NEVER deploy to mainnet.
 9  contract BeliefAttestationVerifier is IStarkVerifier {
10
11      string public constant PROOF_SYSTEM_ID =
12          "DEV-ONLY-BeliefAttestation-Do-Not-Use-In-Production";
13
14      uint256 public constant PUBLIC_INPUTS_COUNT = 4;
15
16      event DevVerifierUsed(
17          address indexed caller,
18          bytes32 indexed beliefHash,
19          uint256 chainId
20      );
21
22      error NotDevChain(uint256 chainId);
23
24      constructor() {
25          if (block.chainid != 31337 && block.chainid != 1337) {
26              revert NotDevChain(block.chainid);
27          }
28      }
29
30      /// @notice Dev stub: accepts any proof ≥ 32 bytes.
31      function verifyProof(
32          bytes calldata proofBytes,
33          uint256[] calldata publicInputs
34      ) external override returns (bool) {
35          // Minimum sanity check — reject obviously empty proofs
36          require(proofBytes.length >= 32, "proof too short");
37          require(publicInputs.length == PUBLIC_INPUTS_COUNT, "bad input count");
38
39          // Extract beliefHash from publicInputs[0] for event emission
40          bytes32 beliefHash = bytes32(publicInputs[0]);
41
42          emit DevVerifierUsed(msg.sender, beliefHash, block.chainid);
43          return true;
44      }
45
46      function getPublicInputsCount()
47          external pure override returns (uint256)
48      {
49          return PUBLIC_INPUTS_COUNT;
50      }
51
52      function getProofSystemId()
53          external pure override returns (string memory)
54      {
55          return PROOF_SYSTEM_ID;
56      }
57  }
```

### Line-by-Line Walkthrough

| Line | Description |
|------|-------------|
| 11–12 | `PROOF_SYSTEM_ID` contains the string `"DEV-ONLY"` — an explicit identifier that prevents confusion with production verifiers in off-chain tooling. |
| 14 | `PUBLIC_INPUTS_COUNT = 4` — matches V1/V2 verifier format. |
| 16–20 | `DevVerifierUsed` event — emitted on every `verifyProof` call, providing a clear on-chain audit trail if this verifier is accidentally used on a testnet with chain ID ≠ 31337. |
| 24–28 | Constructor chain guard: `block.chainid != 31337 && block.chainid != 1337` — deployment reverts on any other chain. This is a deployment-time safety net; it does not protect against state forks. |
| 36 | `proofBytes.length >= 32` — minimal sanity check to reject empty/very short proofs. No cryptographic verification. |
| 37 | `publicInputs.length == PUBLIC_INPUTS_COUNT` — enforces correct array length even in dev mode. |
| 43 | Returns `true` unconditionally for any passing input. |

### State Variables

None (stateless verifier).

### Functions

**`constructor()`** — Reverts if `block.chainid` is not 31337 or 1337.

**`verifyProof(bytes, uint256[]) external returns (bool)`** — Accepts any proof ≥32 bytes. Emits `DevVerifierUsed`. Returns `true`.

**`getPublicInputsCount() external pure returns (uint256)`** — Returns 4.

**`getProofSystemId() external pure returns (string memory)`** — Returns `"DEV-ONLY-BeliefAttestation-Do-Not-Use-In-Production"`.

### Events

| Event | When | Fields |
|-------|------|--------|
| `DevVerifierUsed(address caller, bytes32 beliefHash, uint256 chainId)` | Every `verifyProof` call | All fields logged; easy to monitor |

### Security Invariants

1. Construction-time chain ID guard is the primary protection. It cannot be bypassed without deploying on the correct chain ID.
2. **Critical:** If a state fork or replay makes `block.chainid == 31337` on a non-dev network, this contract would accept all proofs. This is a theoretical risk only.
3. The `DevVerifierUsed` event provides a loud on-chain signal if the contract is accidentally wired into a non-dev deployment.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts contracts/BeliefAttestationVerifier.sol
```

---

## 14. contracts/BeliefAttestationVerifierProduction.sol

**Path:** `/home/user/workspace/contracts/BeliefAttestationVerifierProduction.sol`
**Role:** ZK proof verifier — Production V2; immutable verifier+imageId; sha256 journalDigest (CRITICAL-004 fix); returns false on catch (not revert)

### Summary

`BeliefAttestationVerifierProduction` is the V2 production replacement for the dev verifier. It stores an immutable `IRiscZeroVerifier riscZeroVerifier` and an immutable `bytes32 imageId`, wraps proof verification in a `try/catch` returning `false` on failure (rather than propagating reverts), and emits a `ProofVerificationFailed` event on catch. The CRITICAL-004 fix is the use of `sha256` for journal digests (as required by RISC Zero) rather than `keccak256`. It stores a `hasAttestation[bytes32][address] => bool` mapping (not epoch-aware).

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  import {IRiscZeroVerifier} from "./IRiscZeroVerifier.sol";
 5  import {IStarkVerifier} from "./IStarkVerifier.sol";
 6
 7  /// @title BeliefAttestationVerifierProduction
 8  /// @notice V2 production verifier. Immutable verifier+imageId. CRITICAL-004: sha256 journal digest.
 9  contract BeliefAttestationVerifierProduction is IStarkVerifier {
10
11      string public constant PROOF_SYSTEM_ID =
12          "RISC0-STARK-BeliefAttestation-Production-v2.0";
13
14      uint256 public constant PUBLIC_INPUTS_COUNT = 4;
15      uint256 public constant MIN_BELIEF_THRESHOLD = 8000;
16
17      IRiscZeroVerifier public immutable riscZeroVerifier;
18      bytes32 public immutable imageId;
19
20      mapping(bytes32 => mapping(address => bool)) public hasAttestation;
21      uint256 public attestationCount;
22
23      struct VerifiedAttestation {
24          bytes32 beliefHash;
25          address attester;
26          uint256 epoch;
27          uint256 moduleId;
28          uint256 beliefScore;
29          uint256 verifiedAt;
30          bool exists;
31      }
32
33      mapping(bytes32 => VerifiedAttestation) public attestations;
34
35      event AttestationVerified(
36          bytes32 indexed beliefHash,
37          address indexed attester,
38          uint256 epoch,
35          uint256 moduleId,
36          uint256 beliefScore,
37          uint256 timestamp
38      );
39
40      event ProofVerificationFailed(
41          bytes32 indexed beliefHash,
42          address indexed attester,
43          string reason
44      );
45
46      error ZeroAddress();
47      error ZeroImageId();
48      error InvalidPublicInputsCount();
49      error InvalidBeliefHash();
50      error InvalidAttesterAddress();
51      error InvalidEpoch();
52      error EmptyProof();
53
54      constructor(address _riscZeroVerifier, bytes32 _imageId) {
55          if (_riscZeroVerifier == address(0)) revert ZeroAddress();
56          if (_imageId == bytes32(0)) revert ZeroImageId();
57          riscZeroVerifier = IRiscZeroVerifier(_riscZeroVerifier);
58          imageId = _imageId;
59      }
60
61      function verifyProof(
62          bytes calldata proofBytes,
63          uint256[] calldata publicInputs
64      ) external override returns (bool) {
65          if (publicInputs.length != PUBLIC_INPUTS_COUNT)
66              revert InvalidPublicInputsCount();
67
68          bytes32 beliefHash = bytes32(publicInputs[0]);
69          address attester   = address(uint160(publicInputs[1]));
70          uint256 epoch      = publicInputs[2];
71          uint256 moduleId   = publicInputs[3];
72
73          if (beliefHash == bytes32(0)) revert InvalidBeliefHash();
74          if (attester == address(0))   revert InvalidAttesterAddress();
75          if (epoch > type(uint32).max) revert InvalidEpoch();
76          if (proofBytes.length == 0)   revert EmptyProof();
77
78          // CRITICAL-004 FIX: sha256 journal digest (not keccak256)
79          bytes memory journal = abi.encode(beliefHash, attester, epoch, moduleId);
80          bytes32 journalDigest = sha256(journal);
81
82          try riscZeroVerifier.verify(proofBytes, imageId, journalDigest) returns (bool ok) {
83              if (!ok) {
84                  emit ProofVerificationFailed(beliefHash, attester, "verifier returned false");
85                  return false;
86              }
87          } catch Error(string memory reason) {
88              emit ProofVerificationFailed(beliefHash, attester, reason);
89              return false;
90          } catch {
91              emit ProofVerificationFailed(beliefHash, attester, "unknown revert");
92              return false;
93          }
94
95          bytes32 key = keccak256(abi.encodePacked(beliefHash, attester, epoch));
96          attestations[key] = VerifiedAttestation({
97              beliefHash:  beliefHash,
98              attester:    attester,
99              epoch:       epoch,
100             moduleId:    moduleId,
101             beliefScore: MIN_BELIEF_THRESHOLD,
102             verifiedAt:  block.timestamp,
103             exists:      true
104         });
105
106         hasAttestation[beliefHash][attester] = true;
107         attestationCount++;
108
109         emit AttestationVerified(
110             beliefHash, attester, epoch, moduleId,
111             MIN_BELIEF_THRESHOLD, block.timestamp
112         );
113         return true;
114     }
115
116     function getPublicInputsCount() external pure override returns (uint256) {
117         return PUBLIC_INPUTS_COUNT;
118     }
119
120     function getProofSystemId() external pure override returns (string memory) {
121         return PROOF_SYSTEM_ID;
122     }
123
124     function getAttestation(bytes32 beliefHash, address attester, uint256 epoch)
125         external view returns (VerifiedAttestation memory)
126     {
127         bytes32 key = keccak256(abi.encodePacked(beliefHash, attester, epoch));
128         return attestations[key];
129     }
130
131     function isAttested(bytes32 beliefHash, address attester)
132         external view returns (bool)
133     {
134         return hasAttestation[beliefHash][attester];
135     }
136  }
```

### Line-by-Line Walkthrough

| Line | Description |
|------|-------------|
| 17–18 | Both `riscZeroVerifier` and `imageId` are **immutable** — no owner, no upgrade path. Maximally trustless but inflexible. |
| 20 | `hasAttestation[beliefHash][attester]` — 2-level mapping, **not epoch-aware**. Any attestation for a `(beliefHash, attester)` pair marks the pair as attested across all epochs. |
| 78–80 | **CRITICAL-004 FIX:** `sha256(abi.encode(...))` rather than `keccak256`. SHA-256 is required because the RISC Zero guest program computes its journal digest using SHA-256. |
| 82 | `try ... returns (bool ok)` — uses the `IRiscZeroVerifier` variant that returns `bool`. The `catch` paths log events but return `false` rather than bubbling the revert. This means callers may silently receive `false` and not handle it appropriately. |
| 95 | Attestation key: `keccak256(abi.encodePacked(beliefHash, attester, epoch))`. |
| 101 | `beliefScore` recorded as `MIN_BELIEF_THRESHOLD` (8000) because the 4-element publicInputs format does not include the actual belief score from the journal. |

### Math

Journal digest (CRITICAL-004):
\[\text{journalDigest} = \text{SHA-256}(\text{abi.encode}(\text{beliefHash},\, \text{attester},\, \text{epoch},\, \text{moduleId}))\]

### Events

| Event | When | Fields |
|-------|------|--------|
| `AttestationVerified(beliefHash, attester, epoch, moduleId, beliefScore, timestamp)` | Successful verification | All indexed where applicable |
| `ProofVerificationFailed(beliefHash, attester, reason)` | Verification failure (catch branch) | Reason string included |

### Security Invariants

1. `riscZeroVerifier` and `imageId` are immutable — no upgrade path (intentional for trustlessness).
2. `hasAttestation` is not epoch-aware — attestations from any epoch mark the pair permanently.
3. Silent `false` return on verification failure may cause callers to silently accept invalid attestations if they don't check the return value.
4. No `MIN_BELIEF_THRESHOLD` enforcement at the smart contract level for the actual belief score from the proof (score is stored as the minimum threshold value, not the actual proved value).

### ZK Proof Flow

```
verifyProof(proofBytes, [beliefHash, attester, epoch, moduleId])
  │
  ├─ Validate inputs (length, non-zero checks)
  │
  ├─ journal = abi.encode(beliefHash, attester, epoch, moduleId)
  │  journalDigest = sha256(journal)  ← CRITICAL-004 fix
  │
  ├─ try riscZeroVerifier.verify(proofBytes, imageId, journalDigest)
  │   ├─ success → record attestation, emit AttestationVerified, return true
  │   ├─ catch Error(reason) → emit ProofVerificationFailed, return false
  │   └─ catch → emit ProofVerificationFailed("unknown revert"), return false
  │
  └─ attestationCount++
```

### Dependencies + Rebuild Recipe

```bash
forge build --contracts contracts/BeliefAttestationVerifierProduction.sol
```

---

## 15. contracts/ProductionBeliefAttestationVerifier.sol

**Path:** `/home/user/workspace/contracts/ProductionBeliefAttestationVerifier.sol`
**Role:** ZK proof verifier — V2 production verifier; PUBLIC_INPUTS_COUNT=4; hasAttestation not epoch-aware; verifyAttestation returns bool on catch; PROOF_SYSTEM_ID="…v2.0"

### Summary

`ProductionBeliefAttestationVerifier` is the V2 production verifier in the root `contracts/` directory. It wraps both the legacy `verifyProof(bytes, uint256[])` interface and a direct `verifyAttestation(seal, journalData)` interface. The attestation record mapping has two levels: `hasAttestation[beliefHash][attester] => bool` (not epoch-aware). On proof failure in `verifyAttestation`, it catches the revert and returns `false` without emitting a detailed reason. This is the version targeted for replacement by V3.

### Full Source

*(Summarized — key differences from §14 and §20 documented below.)*

**Key characteristics:**
- `PUBLIC_INPUTS_COUNT = 4` (legacy, no beliefScore or timestamp).
- `hasAttestation[bytes32][address] => bool` — 2-level, not epoch-aware.
- `verifyAttestation` returns `false` on catch (like §14).
- `PROOF_SYSTEM_ID = "RISC0-STARK-BeliefAttestation-Production-v2.0"`.
- No timelock on `imageId` updates (owner can update immediately).
- `attestationCount` tracks total verified attestations.

See §20 for the risc0 sub-package copy which adds a 48-hour timelock.

### State Variables

| Variable | Type | Mutability | Purpose |
|----------|------|------------|---------|
| `riscZeroVerifier` | `IRiscZeroVerifier` | immutable | RISC Zero verifier router |
| `imageId` | `bytes32` | mutable (owner) | Guest ELF hash |
| `owner` | `address` | mutable | Admin for imageId updates |
| `attestations` | `mapping(bytes32 => VerifiedAttestation)` | mutable | Attestation record store |
| `hasAttestation` | `mapping(bytes32 => mapping(address => bool))` | mutable | Cross-epoch attestation flag |
| `attestationCount` | `uint256` | mutable | Total verified count |

### Functions

**`verifyProof(bytes, uint256[]) external returns (bool)`** — Legacy 4-input interface. Reconstructs 4-field journal, calls `riscZeroVerifier.verify`, returns bool.

**`verifyAttestation(bytes seal, bytes journalData) external returns (bool)`** — Direct interface. Decodes full 6-field journal `(beliefHash, attester, epoch, moduleId, beliefScore, timestamp)` from `journalData`, calls verifier, records on success.

### Security Invariants

1. `imageId` mutable with no timelock — owner can silently change the expected guest program.
2. `hasAttestation` not epoch-aware — old proofs remain valid after epoch rotation.

---

## 16. vf-audit-contracts/ProductionBeliefAttestationVerifierV3.sol

**Path:** `/home/user/workspace/vf-audit-contracts/ProductionBeliefAttestationVerifierV3.sol`
**Role:** ZK proof verifier — V3 audit; PUBLIC_INPUTS_COUNT=6; 3-level epoch-aware hasAttestation; reverts on proof failure; timelocked imageId; MIN_BELIEF_THRESHOLD enforcement

### Summary

This is the full audit-grade V3 production verifier. It resolves every high and critical finding from the V2 audit: (1) `PUBLIC_INPUTS_COUNT = 6` to match the V3 journal format; (2) `hasAttestation[beliefHash][attester][epoch] => bool` — three-level mapping that is epoch-aware; (3) `verifyAttestation` **reverts** on proof failure rather than returning `false`; (4) `imageId` updates require a 48-hour timelock; (5) `_recordAttestation` enforces `MIN_BELIEF_THRESHOLD = 8000` and rejects attestations where `beliefScore < 8000`; (6) `attestationCount` is unique per attestation key (no double-counting). This contract is paired with `DilithiumAttestorV3` (§9) and is deployed to Base Mainnet.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  import {IRiscZeroVerifier} from "./IRiscZeroVerifier.sol";
 5  import {IStarkVerifier} from "./IStarkVerifier.sol";
 6
 7  /// @title ProductionBeliefAttestationVerifierV3
 8  /// @notice V3 audit: 6-element publicInputs, epoch-aware mapping, revert on failure,
 9  ///         48-hour imageId timelock, MIN_BELIEF_THRESHOLD enforcement.
10  contract ProductionBeliefAttestationVerifierV3 is IStarkVerifier {
11
12      string public constant PROOF_SYSTEM_ID =
13          "RISC0-STARK-BeliefAttestation-Production-v3.0";
14
15      uint256 public constant PUBLIC_INPUTS_COUNT = 6;
16      uint256 public constant MIN_BELIEF_THRESHOLD = 8000;
17      uint256 public constant MAX_EPOCH = type(uint32).max;
18      uint256 public constant IMAGE_ID_TIMELOCK_DELAY = 48 hours;
19
20      IRiscZeroVerifier public immutable riscZeroVerifier;
21
22      address public owner;
23      bytes32 public imageId;
24      uint256 public attestationCount;
25
26      // V3 FIX: 3-level epoch-aware mapping
27      mapping(bytes32 => mapping(address => mapping(uint256 => bool)))
28          public hasAttestation;
29
30      mapping(bytes32 => VerifiedAttestation) public attestations;
31
32      // Timelock state
33      bytes32 public pendingImageId;
34      uint256 public pendingImageIdEffectiveAt;
35
36      struct VerifiedAttestation {
37          bytes32 beliefHash;
38          address attester;
39          uint256 epoch;
40          uint256 moduleId;
41          uint256 beliefScore;
42          uint256 verifiedAt;
43          bool    exists;
44      }
45
46      event AttestationVerified(
47          bytes32 indexed beliefHash,
48          address indexed attester,
49          uint256 epoch,
50          uint256 moduleId,
51          uint256 beliefScore,
52          uint256 timestamp
53      );
54      event VerificationFailed(bytes32 indexed beliefHash, address indexed attester, string reason);
55      event ImageIdUpdated(bytes32 indexed oldId, bytes32 indexed newId);
56      event ImageIdChangeProposed(bytes32 indexed current, bytes32 indexed proposed, uint256 effectiveAt);
57      event ImageIdChangeCancelled(bytes32 indexed cancelledId);
58      event OwnershipTransferred(address indexed prev, address indexed next);
59
60      error ZeroAddress();
61      error ZeroImageId();
62      error InvalidPublicInputsCount();
63      error InvalidBeliefHash();
64      error InvalidAttesterAddress();
65      error InvalidEpoch();
66      error EmptyProof();
67      error ProofVerificationFailed(string reason);
68      error BelowMinBeliefThreshold(uint256 score, uint256 threshold);
69      error AlreadyAttested();
70      error OnlyOwner();
71      error TimelockNotExpired();
72      error NoPendingImageId();
73      error ImageIdAlreadyPending();
74
75      modifier onlyOwner() {
76          if (msg.sender != owner) revert OnlyOwner();
77          _;
78      }
79
80      constructor(address _riscZeroVerifier, bytes32 _imageId) {
81          if (_riscZeroVerifier == address(0)) revert ZeroAddress();
82          if (_imageId == bytes32(0)) revert ZeroImageId();
83          riscZeroVerifier = IRiscZeroVerifier(_riscZeroVerifier);
84          imageId = _imageId;
85          owner = msg.sender;
86          emit OwnershipTransferred(address(0), msg.sender);
87          emit ImageIdUpdated(bytes32(0), _imageId);
88      }
89
90      function verifyProof(
91          bytes calldata proofBytes,
92          uint256[] calldata publicInputs
93      ) external override returns (bool) {
94          if (publicInputs.length != PUBLIC_INPUTS_COUNT)
95              revert InvalidPublicInputsCount(); // expects 6
96
97          bytes32 beliefHash = bytes32(publicInputs[0]);
98          address attester   = address(uint160(publicInputs[1]));
99          uint256 epoch      = publicInputs[2];
100         uint256 moduleId   = publicInputs[3];
101         uint256 beliefScore= publicInputs[4];
102         uint256 timestamp  = publicInputs[5];
103
104         if (beliefHash == bytes32(0)) revert InvalidBeliefHash();
105         if (attester == address(0))   revert InvalidAttesterAddress();
106         if (epoch > MAX_EPOCH)        revert InvalidEpoch();
107         if (proofBytes.length == 0)   revert EmptyProof();
108
109         // Reconstruct journal from 6 fields
110         bytes memory journal = abi.encode(
111             beliefHash, attester, uint32(epoch), uint32(moduleId),
112             uint32(beliefScore), uint64(timestamp)
113         );
114         bytes32 journalDigest = sha256(journal);
115
116         try riscZeroVerifier.verify(proofBytes, imageId, journalDigest) {
117             // Reverts if proof invalid
118         } catch Error(string memory reason) {
119             revert ProofVerificationFailed(reason);
120         } catch {
121             revert ProofVerificationFailed("Verifier reverted");
122         }
123
124         _recordAttestation(beliefHash, attester, epoch, moduleId, beliefScore, timestamp);
125         return true;
126      }
127
128      function verifyAttestation(
129          bytes calldata seal,
130          bytes calldata journalData
131      ) external returns (bool) {
132          if (seal.length == 0) revert EmptyProof();
133
134          bytes32 journalDigest = sha256(journalData);
135
136          try riscZeroVerifier.verify(seal, imageId, journalDigest) {
137              // proceed
138          } catch Error(string memory reason) {
139              revert ProofVerificationFailed(reason);
140          } catch {
141              revert ProofVerificationFailed("Verifier reverted");
142          }
143
144          (
145              bytes32 beliefHash,
146              address attester,
147              uint32 epoch,
148              uint32 moduleId,
149              uint32 beliefScore,
150              uint64 timestamp
151          ) = abi.decode(journalData, (bytes32, address, uint32, uint32, uint32, uint64));
152
153          _recordAttestation(beliefHash, attester, epoch, moduleId, beliefScore, timestamp);
154          return true;
155      }
156
157      function _recordAttestation(
158          bytes32 beliefHash,
159          address attester,
160          uint256 epoch,
161          uint256 moduleId,
162          uint256 beliefScore,
163          uint256 timestamp
164      ) internal {
165          // MIN_BELIEF_THRESHOLD enforcement
166          if (beliefScore < MIN_BELIEF_THRESHOLD)
167              revert BelowMinBeliefThreshold(beliefScore, MIN_BELIEF_THRESHOLD);
168
169          // AlreadyAttested guard (per epoch)
170          if (hasAttestation[beliefHash][attester][epoch]) revert AlreadyAttested();
171
172          bytes32 key = keccak256(abi.encodePacked(beliefHash, attester, epoch));
173
174          attestations[key] = VerifiedAttestation({
175              beliefHash:  beliefHash,
176              attester:    attester,
177              epoch:       epoch,
178              moduleId:    moduleId,
179              beliefScore: beliefScore,
180              verifiedAt:  timestamp,
181              exists:      true
182          });
183
184          hasAttestation[beliefHash][attester][epoch] = true;
185          attestationCount++;
186
187          emit AttestationVerified(beliefHash, attester, epoch, moduleId, beliefScore, timestamp);
188      }
189
190      // Timelock functions
191      function proposeImageIdChange(bytes32 _newImageId) external onlyOwner {
192          if (_newImageId == bytes32(0)) revert ZeroImageId();
193          if (pendingImageId != bytes32(0)) revert ImageIdAlreadyPending();
194          pendingImageId = _newImageId;
195          pendingImageIdEffectiveAt = block.timestamp + IMAGE_ID_TIMELOCK_DELAY;
196          emit ImageIdChangeProposed(imageId, _newImageId, pendingImageIdEffectiveAt);
197      }
198
199      function executeImageIdChange() external onlyOwner {
200          if (pendingImageId == bytes32(0)) revert NoPendingImageId();
201          if (block.timestamp < pendingImageIdEffectiveAt) revert TimelockNotExpired();
202          bytes32 old = imageId;
203          imageId = pendingImageId;
204          pendingImageId = bytes32(0);
205          pendingImageIdEffectiveAt = 0;
206          emit ImageIdUpdated(old, imageId);
207      }
208
209      function cancelImageIdChange() external onlyOwner {
210          if (pendingImageId == bytes32(0)) revert NoPendingImageId();
211          bytes32 cancelled = pendingImageId;
212          pendingImageId = bytes32(0);
213          pendingImageIdEffectiveAt = 0;
214          emit ImageIdChangeCancelled(cancelled);
215      }
216
217      function transferOwnership(address newOwner) external onlyOwner {
218          if (newOwner == address(0)) revert ZeroAddress();
219          address old = owner;
220          owner = newOwner;
221          emit OwnershipTransferred(old, newOwner);
222      }
223
224      function getPublicInputsCount() external pure override returns (uint256) {
225          return PUBLIC_INPUTS_COUNT;
226      }
227
228      function getProofSystemId() external pure override returns (string memory) {
229          return PROOF_SYSTEM_ID;
230      }
231  }
```

### Line-by-Line Walkthrough

| Line | Description |
|------|-------------|
| 15 | `PUBLIC_INPUTS_COUNT = 6` — matches V3 journal format with beliefScore and timestamp. |
| 27–28 | **3-level `hasAttestation`**: `beliefHash → attester → epoch → bool`. Epoch-aware: different epochs produce independent attestation records. |
| 67 | `error ProofVerificationFailed(string reason)` — reverts on failure instead of returning `false`. Callers can only succeed by catching the revert. This is stronger than V2's silent `false`. |
| 68 | `error BelowMinBeliefThreshold(uint256 score, uint256 threshold)` — enforces that the actually proved belief score ≥ 8000 bps. V2 stored a hardcoded 8000; V3 validates the actual proved value. |
| 94–95 | `PUBLIC_INPUTS_COUNT` = 6 enforced on `verifyProof`. A V1/V2 caller with 4-element arrays will revert at this check. |
| 110–113 | Journal reconstruction includes all 6 fields using correct types: `uint32` for epoch/moduleId/beliefScore, `uint64` for timestamp. This must match the RISC Zero guest's `abi.encode` call exactly. |
| 116–122 | `try riscZeroVerifier.verify(...)` — uses the Apache `IRiscZeroVerifier` (reverts internally); the `catch` branches re-revert as `ProofVerificationFailed`. |
| 166–167 | `BelowMinBeliefThreshold` guard — actual `beliefScore` from the proof journal is checked. A proof attesting a low-confidence belief is rejected. |
| 170 | `AlreadyAttested` guard scoped to `(beliefHash, attester, epoch)` — can attest again in a different epoch. |
| 191–215 | Timelock functions: `proposeImageIdChange`, `executeImageIdChange`, `cancelImageIdChange`. The 48-hour delay is `IMAGE_ID_TIMELOCK_DELAY`. |

### Math

Journal encoding (V3 6-field standard):
\[\text{journal} = \text{abi.encode}(\underbrace{\text{beliefHash}}_{\text{bytes32}},\, \underbrace{\text{attester}}_{\text{address}},\, \underbrace{\text{epoch}}_{\text{uint32}},\, \underbrace{\text{moduleId}}_{\text{uint32}},\, \underbrace{\text{beliefScore}}_{\text{uint32}},\, \underbrace{\text{timestamp}}_{\text{uint64}})\]

\[\text{journalDigest} = \text{SHA-256}(\text{journal})\]

Timelock:
\[\text{effectiveAt} = \text{block.timestamp} + 172800\;\text{seconds}\]

### Events

| Event | When | Fields |
|-------|------|--------|
| `AttestationVerified` | Successful `_recordAttestation` | beliefHash, attester, epoch, moduleId, beliefScore, timestamp |
| `VerificationFailed` | (reserved, not emitted in this version — revert used instead) | — |
| `ImageIdUpdated` | After `executeImageIdChange` | old, new |
| `ImageIdChangeProposed` | After `proposeImageIdChange` | current, proposed, effectiveAt |
| `ImageIdChangeCancelled` | After `cancelImageIdChange` | cancelled |
| `OwnershipTransferred` | After `transferOwnership` | prev, next |

### Security Invariants

1. `revert` on proof failure — no silent acceptance of invalid proofs.
2. `beliefScore < MIN_BELIEF_THRESHOLD` rejected at contract level.
3. Epoch-aware `hasAttestation` — correct replay protection per epoch.
4. 48-hour timelock on `imageId` changes — stakeholders have time to react.
5. Single `pendingImageId` at a time — prevents proposal spam.

### ZK Proof Flow

```
verifyAttestation(seal, journalData)
  │
  ├─ journalDigest = sha256(journalData)
  │
  ├─ try riscZeroVerifier.verify(seal, imageId, journalDigest)
  │   ├─ success → continue
  │   └─ catch → revert ProofVerificationFailed(reason)
  │
  ├─ abi.decode(journalData) → (beliefHash, attester, epoch, moduleId, beliefScore, timestamp)
  │
  └─ _recordAttestation(...)
      ├─ beliefScore < 8000 → revert BelowMinBeliefThreshold
      ├─ hasAttestation[h][a][e] → revert AlreadyAttested
      ├─ write attestations[key]
      ├─ hasAttestation[h][a][e] = true
      ├─ attestationCount++
      └─ emit AttestationVerified
```

### Dependencies + Rebuild Recipe

```bash
forge build --contracts vf-audit-contracts/ProductionBeliefAttestationVerifierV3.sol
```

---

## 17. v3/ProductionBeliefAttestationVerifierV3.sol

**Path:** `/home/user/workspace/v3/ProductionBeliefAttestationVerifierV3.sol`
**Role:** ZK proof verifier — V3 staging; PUBLIC_INPUTS_COUNT=4 (different from audit!)

### Summary

Staging copy of the V3 verifier in `/v3/`. Structurally similar to §16 but with `PUBLIC_INPUTS_COUNT = 4` — a significant regression from the audit version. This means the verifier in this directory expects only 4 public inputs, which would be incompatible with `DilithiumAttestorV3` (§9) which constructs 6 elements. This is likely a merge conflict artifact where the V3 audit changes were partially applied. All timelock machinery is present.

### Key Difference vs §16

```solidity
uint256 public constant PUBLIC_INPUTS_COUNT = 4; // vs 6 in audit version
```

The journal reconstruction therefore only uses 4 fields:
```solidity
bytes memory journal = abi.encode(beliefHash, attester, uint32(epoch), uint32(moduleId));
// beliefScore and timestamp omitted
```

This creates a **critical mismatch**: if deployed alongside a `DilithiumAttestorV3` that sends 6-element publicInputs, the verifier at this path rejects all proofs with `InvalidPublicInputsCount`.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts v3/ProductionBeliefAttestationVerifierV3.sol
```

---

## 18. v3-pr-ready/contracts/v3/ProductionBeliefAttestationVerifierV3.sol

**Path:** `/home/user/workspace/v3-pr-ready/contracts/v3/ProductionBeliefAttestationVerifierV3.sol`
**Role:** ZK proof verifier — PR-ready staging; PUBLIC_INPUTS_COUNT=4

### Summary

Another PR-ready staging copy. Like §17, this version has `PUBLIC_INPUTS_COUNT = 4`. Same critical mismatch with the 6-element attestor. Import paths point to `../contracts/` for IStarkVerifier and `../../../` for IRiscZeroVerifier, reflecting the monorepo layout. Timelock code present. Should be replaced by §19 for deployment.

### Key Differences vs §16

- `PUBLIC_INPUTS_COUNT = 4`
- Import: `import {IStarkVerifier} from "../contracts/IStarkVerifier.sol"`

---

## 19. push_contracts_v3_ProductionBeliefAttestationVerifierV3.sol

**Path:** `/home/user/workspace/push_contracts_v3_ProductionBeliefAttestationVerifierV3.sol`
**Role:** ZK proof verifier — Final pushed V3; PUBLIC_INPUTS_COUNT=6; all audit fixes applied

### Summary

The **final pushed production version**. Equivalent to the audit version (§16) in all critical aspects: `PUBLIC_INPUTS_COUNT = 6`, epoch-aware 3-level `hasAttestation`, revert on proof failure, 48-hour timelock, `MIN_BELIEF_THRESHOLD` enforcement. This is the version that should be deployed to Base Mainnet alongside `push_contracts_v3_DilithiumAttestorV3.sol` (§12).

All details are identical to §16. See §16 for full documentation.

---

## 20. theloopbreaker-site/risc0/src/ProductionBeliefAttestationVerifier.sol

**Path:** `/home/user/workspace/theloopbreaker-site/risc0/src/ProductionBeliefAttestationVerifier.sol`
**Role:** ZK proof verifier — risc0 sub-package production verifier; PUBLIC_INPUTS_COUNT=4; implements IStarkVerifier; dual interface (legacy + direct); 48-hour timelock; returns false on catch

### Summary

This is the production verifier deployed from the risc0 sub-package. It implements the `IStarkVerifier` interface for backward compatibility with `DilithiumAttestor` (V1), while also providing a direct `verifyAttestation(seal, journalData)` method. Unlike §16 it has `PUBLIC_INPUTS_COUNT = 4` (legacy format). However, `verifyAttestation` uses the full 6-field journal format (decoding `beliefHash, attester, epoch, moduleId, beliefScore, timestamp`). Key distinction from §16: on proof failure in `verifyAttestation`, it returns `false` (like V2) rather than reverting. It features a **48-hour timelock** on `imageId` updates, a full ownership transfer function, and the complete complement of timelock management functions (`proposeImageIdChange`, `executeImageIdChange`, `cancelImageIdChange`).

### Line-by-Line Walkthrough (Key Lines)

| Line | Description |
|------|-------------|
| 47 | `contract ProductionBeliefAttestationVerifier is IStarkVerifier` — implements legacy interface. |
| 131–132 | `PROOF_SYSTEM_ID = "RISC0-STARK-BeliefAttestation-Production-v2.0"` — V2 identifier even though this has V3 features (timelock). |
| 135 | `PUBLIC_INPUTS_COUNT = 4` — legacy 4-field format for `verifyProof`. |
| 145 | `IMAGE_ID_TIMELOCK_DELAY = 48 hours`. |
| 153 | `riscZeroVerifier` — immutable. |
| 165 | `imageId` — mutable (timelock-protected). |
| 176 | `hasAttestation[beliefHash][attester] => bool` — 2-level, not epoch-aware (same as V2). |
| 235–252 | `verifyProof` — 4-element legacy path, reconstructs 4-field journal. |
| 268–305 | `verifyAttestation` — direct 6-field path; `try/catch` returns `false` on failure. |
| 278 | `try riscZeroVerifier.verify(seal, imageId, journalDigest)` — uses Apache IRiscZeroVerifier (reverts). |
| 298–303 | Catch branches return `false`, emit `VerificationFailed`. |
| 357–385 | `_recordAttestation` — stores attestation, sets `hasAttestation`, increments `attestationCount`. |
| 471–479 | `proposeImageIdChange` — timelock entry point. Emits `ImageIdChangeProposed`. |
| 483–495 | `executeImageIdChange` — executes after `pendingImageIdEffectiveAt`. |
| 500–508 | `cancelImageIdChange` — cancels pending proposal. |
| 512–519 | `transferOwnership` — single-step (not two-step, unlike OZ `Ownable2Step`). |

### Math

Legacy path journal (4 fields):
\[\text{journal} = \text{abi.encode}(\text{beliefHash},\, \text{attester},\, \text{uint32(epoch)},\, \text{uint32(moduleId)})\]

Direct path journal (6 fields):
\[\text{journal} = \text{abi.decode}(\text{journalData}) \rightarrow (\text{bytes32}, \text{address}, \text{uint32}, \text{uint32}, \text{uint32}, \text{uint64})\]

\[\text{journalDigest} = \text{SHA-256}(\text{journalData})\]

### Events

Same event set as §16 plus `VerificationFailed` (emitted on catch in `verifyAttestation`).

### Security Invariants

1. `hasAttestation` is not epoch-aware — same as V2.
2. `verifyAttestation` returns `false` on failure — callers must check return value.
3. 48-hour timelock protects `imageId`.
4. Ownership is single-step transfer (no acceptOwner pattern).

### ZK Proof Flow

```
verifyAttestation(seal, journalData)
  │
  ├─ journalDigest = sha256(journalData)
  │
  ├─ try riscZeroVerifier.verify(seal, imageId, journalDigest)
  │   ├─ success → decode journalData, _recordAttestation, return true
  │   ├─ catch Error → emit VerificationFailed, return false
  │   └─ catch    → emit VerificationFailed("Verifier reverted"), return false
  │
  └─ _recordAttestation:
       hasAttestation[beliefHash][attester] = true
       attestationCount++
       emit AttestationVerified
```

### Dependencies + Rebuild Recipe

```bash
forge build --contracts theloopbreaker-site/risc0/src/ProductionBeliefAttestationVerifier.sol
```

---

## 21. contracts/BeliefOracle.sol

**Path:** `/home/user/workspace/contracts/BeliefOracle.sol`
**Role:** Belief Oracle — V1 gateway; resonance scoring; global bonusApplied; unbounded batchQuery exposed

### Summary

`BeliefOracle` is the V1 oracle gateway contract. It receives vow strings from seekers, hashes them, delegates belief attestation to `DilithiumAttestor`, and computes a deterministic resonance score in the range [0, 100]. If the resonance exceeds `BONUS_THRESHOLD = 80` and no bonus has been applied for that `vowHash` (globally), it calls `RewardStream.updateMultiplier` with `BONUS_MULTIPLIER = 120`. The V1 vulnerabilities are: (1) `bonusApplied[vowHash]` is global — once any user triggers the bonus for a vow hash, no future user can get a bonus (HIGH-1); (2) `batchQuery` is unbounded (MEDIUM-1); (3) the attestor call happens before state writes (MEDIUM-2 CEI violation).

### Full Source

*(163 lines — full source held in context.)*

Key lines:

```solidity
mapping(bytes32 => bool) public bonusApplied; // V1: global, HIGH-1 vulnerability

function queryBelief(string memory vow, bytes calldata zkSig) public virtual returns (uint256) {
    bytes32 vowHash = keccak256(bytes(vow));
    attestor.attestBelief(vowHash, zkSig); // external call BEFORE state writes — CEI violation

    uint256 resonance = _resolveResonance(vowHash, msg.sender);
    _cachedResonance[vowHash] = resonance;
    lastResonance[msg.sender] = resonance;

    bool applied = false;
    if (!resonanceDrifted && resonance > BONUS_THRESHOLD && !bonusApplied[vowHash]) {
        bonusApplied[vowHash] = true; // global flag
        try rewardStream.updateMultiplier(msg.sender, BONUS_MULTIPLIER) {
            applied = true;
        } catch { applied = false; }
    }
    emit ResonanceQueried(msg.sender, vowHash, resonance, applied);
    return resonance;
}

function batchQuery(...) external { // UNBOUNDED — no length check
    ...
}
```

### State Variables

| Variable | Type | Mutability | Purpose |
|----------|------|------------|---------|
| `attestor` | `DilithiumAttestor` | immutable | Belief attestation backend |
| `rewardStream` | `RewardStream` | immutable | Reward multiplier updater |
| `guardian` | `address` | mutable | Admin for drift controls and guardian rotation |
| `ghostEcho` | `address` | immutable | Entropy source for resonance formula |
| `resonanceDrifted` | `bool` | mutable | Disables bonus multiplier when `true` |
| `_cachedResonance` | `mapping(bytes32 => uint256)` | private | Per-vowHash resonance cache |
| `lastResonance` | `mapping(address => uint256)` | public | Per-user last resonance score |
| `bonusApplied` | `mapping(bytes32 => bool)` | public | **Global** bonus flag (HIGH-1) |

### Functions

**`queryBelief(string, bytes) public virtual returns (uint256)`**
- Hashes vow → vowHash.
- Calls `attestor.attestBelief` (external, before state writes — CEI violation).
- Resolves resonance via `_resolveResonance`.
- Updates `_cachedResonance` and `lastResonance`.
- If bonus-eligible (global check), sets global `bonusApplied[vowHash] = true` and calls `rewardStream.updateMultiplier`.
- Emits `ResonanceQueried`.

**`batchQuery(string[], bytes[]) external`** — Unbounded loop. HIGH gas cost potential.

**`batchQueryBounded(string[], bytes[]) external`** — Bounded at `MAX_BATCH_QUERY = 50`.

**`previewResonance(string, address) external view`** — Returns `_resolveResonance` without state changes.

**`cachedResonance(bytes32) external view`** — Returns cached resonance for a vowHash.

**`setResonanceDrift(bool) external onlyGuardian`** — Toggles bonus eligibility.

**`updateGuardian(address) external onlyGuardian`** — Single-step guardian rotation (no two-step confirmation).

### Math

Resonance formula:
\[\text{resonance} = \text{uint256}(\text{keccak256}(\text{abi.encodePacked}(\text{vowHash},\, \text{seeker},\, \text{ghostEcho}))) \mod 101\]

Range: [0, 100] inclusive. The modulus is 101 because `MAX_RESONANCE + 1 = 101`.

Bonus multiplier: applied as 120% (not computed on-chain; `BONUS_MULTIPLIER = 120` is passed to `RewardStream.updateMultiplier`).

### Events

| Event | When | Fields |
|-------|------|--------|
| `ResonanceQueried(address seeker, bytes32 vowHash, uint256 resonance, bool multiplierApplied)` | Every `queryBelief` call | seeker, vowHash indexed |
| `ResonanceDriftSet(bool active, address guardian)` | `setResonanceDrift` | active, guardian |
| `GuardianUpdated(address prev, address next)` | `updateGuardian` | prev, next |

### Security Invariants

1. **HIGH-1 (unfixed):** Global `bonusApplied[vowHash]` — one user's bonus blocks all future bonuses for the same vow.
2. **MEDIUM-1 (partial):** `batchQuery` is unbounded; `batchQueryBounded` exists but the unbounded version is still callable.
3. **MEDIUM-2 (unfixed):** External `attestor.attestBelief` called before state writes.
4. Resonance formula is gameable via `previewResonance` (acknowledged LOW finding).

---

## 22. v3/BeliefOracleV3.sol

**Path:** `/home/user/workspace/v3/BeliefOracleV3.sol`
**Role:** Belief Oracle — V3 staging; all V3 audit fixes; identical to v3-pr-ready except import path `../deprecated/RewardStream.sol`

### Summary

Staging copy of `BeliefOracleV3` in the `/v3/` directory. Applies all V3 audit fixes: per-user `bonusApplied` (HIGH-1), `MIN_BELIEF_COUNT` gate (HIGH-2), CEI write before external calls (MEDIUM-2), unbounded `batchQuery` removed (MEDIUM-1). Import paths: `DilithiumAttestorV3` from `./DilithiumAttestorV3.sol`, `RewardStream` from `../deprecated/RewardStream.sol`. Functionally identical to §23 and §24 except for import paths and the additional two-step guardian fix present only in §24 (vf-audit).

See §23 for full documentation.

---

## 23. v3-pr-ready/contracts/v3/BeliefOracleV3.sol

**Path:** `/home/user/workspace/v3-pr-ready/contracts/v3/BeliefOracleV3.sol`
**Role:** Belief Oracle — V3 PR-ready; all V3 fixes; bonusApplied inside try block not yet applied

### Summary

The PR-ready version of `BeliefOracleV3`. All V3 high-severity audit fixes are applied. Import: `DilithiumAttestorV3` from `./DilithiumAttestorV3.sol`, `RewardStream` from `../contracts/deprecated/RewardStream.sol`. Key difference from the vf-audit version (§24): the `bonusApplied` flag is set **before** the `try rewardStream.updateMultiplier(...)` block (meaning if the reward call fails, the flag is still set and the user permanently loses bonus eligibility — this is the HIGH-01 finding fixed only in §24). Also uses single-step `updateGuardian` rather than two-step `proposeGuardian/acceptGuardian`.

### Full Source (Key Differences vs §24)

```solidity
if (bonusEligible) {
    bonusApplied[vowHash][msg.sender] = true; // set before try/catch — HIGH-01 unfixed

    try rewardStream.updateMultiplier(msg.sender, BONUS_MULTIPLIER) {
        applied = true;
    } catch {
        applied = false;
        // bonusApplied already set — user cannot retry
    }
}
```

vs §24 (fixed):
```solidity
try rewardStream.updateMultiplier(msg.sender, BONUS_MULTIPLIER) {
    bonusApplied[vowHash][msg.sender] = true; // set only on success
    applied = true;
} catch {
    applied = false;
    // bonusApplied NOT set — user can retry
}
```

All other details (constants, events, functions) identical to §24. See §24 for full documentation.

---

## 24. vf-audit-contracts/BeliefOracleV3.sol

**Path:** `/home/user/workspace/vf-audit-contracts/BeliefOracleV3.sol`
**Role:** Belief Oracle — Full V3 audit; all fixes including HIGH-01 bonusApplied-in-try-block and HIGH-21 two-step guardian

### Summary

This is the **complete final audit** version of `BeliefOracleV3`. It applies every audit finding including the subtler `HIGH-01` (bonusApplied written inside the try-success block, not before it) and `HIGH-21` (two-step `proposeGuardian/acceptGuardian` replaces single-step `updateGuardian`). The `beliefCount` counter is incremented only after successful `attestor.attestBelief` (H-04 fix). All state writes follow strict CEI ordering. This is the version that should be deployed to production.

### Full Source

*(248 lines — full source read above.)*

### Line-by-Line Walkthrough

| Lines | Description |
|-------|-------------|
| 16 | `attestedBeliefs` — now `DilithiumAttestorV3` (per-user). |
| 46 | `bonusApplied[bytes32][address] => bool` — per-user. HIGH-1 fix. |
| 51 | `beliefCount[address] => uint256` — tracks successful attestation count per user. HIGH-2 fix. |
| 62 | `event GuardianProposed(address current, address pending)` — HIGH-21 event. |
| 67 | `error NotPendingGuardian()` — HIGH-21 error. |
| 77 | `address public pendingGuardian` — HIGH-21 staging address. |
| 111–117 | `proposeGuardian(address newGuardian) external onlyGuardian` — sets `pendingGuardian`, emits `GuardianProposed`. |
| 122–130 | `acceptGuardian() external` — caller must be `pendingGuardian`; clears `pendingGuardian`, updates `guardian`, emits `GuardianUpdated`. |
| 139–141 | `queryBelief`: resolves resonance, writes `_cachedResonance` and `lastResonance` BEFORE any external calls. |
| 154–159 | Bonus eligibility check: `beliefCount[msg.sender] >= MIN_BELIEF_COUNT` (must have ≥1 prior attestation). |
| 161–164 | `bonusApplied` flag deliberately **not** set here (HIGH-01 fix note in comments). |
| 177 | `attestor.attestBelief(vowHash, zkSig)` — external call 1. |
| 180 | `beliefCount[msg.sender]++` — incremented AFTER successful attestation (H-04 fix). |
| 190–197 | `try rewardStream.updateMultiplier(...)`: on success, `bonusApplied[vowHash][msg.sender] = true` (HIGH-01 fix — flag only set on successful reward update). |

### State Variables

| Variable | Type | Mutability | Purpose |
|----------|------|------------|---------|
| `attestor` | `DilithiumAttestorV3` | immutable | Belief attestor |
| `rewardStream` | `RewardStream` | immutable | Reward multiplier |
| `guardian` | `address` | mutable (two-step) | Protocol guardian |
| `pendingGuardian` | `address` | mutable | Pending guardian (HIGH-21 two-step) |
| `ghostEcho` | `address` | immutable | Resonance entropy |
| `resonanceDrifted` | `bool` | mutable | Bonus on/off switch |
| `_cachedResonance` | `mapping(bytes32 => uint256)` | private | Per-vow resonance cache |
| `lastResonance` | `mapping(address => uint256)` | public | Per-user last resonance |
| `bonusApplied` | `mapping(bytes32 => mapping(address => bool))` | public | Per-user bonus flag |
| `beliefCount` | `mapping(address => uint256)` | public | Per-user attestation counter |

### Functions

**`queryBelief(string, bytes) public virtual returns (uint256)`**
- Resolves resonance.
- Writes `_cachedResonance`, `lastResonance` (CEI).
- Checks bonus eligibility (beliefCount gate, resonance threshold, drift flag, per-user bonusApplied).
- Calls `attestor.attestBelief` (external 1).
- Increments `beliefCount[msg.sender]` (after external 1 succeeds).
- `try rewardStream.updateMultiplier(...)`: sets `bonusApplied` only on success (HIGH-01 fix).
- Emits `ResonanceQueried`.

**`batchQueryBounded(string[], bytes[]) external`** — Max 50 vows; calls `queryBelief` per vow.

**`previewResonance(string, address) external view`** — View-only resonance preview.

**`proposeGuardian(address) external onlyGuardian`** — Proposes new guardian.

**`acceptGuardian() external`** — Confirms guardian transfer (must be `pendingGuardian`).

**`setResonanceDrift(bool) external onlyGuardian`** — Toggles bonus eligibility.

### Math

Resonance formula (unchanged from V1):
\[\text{resonance} = \text{uint256}(\text{keccak256}(\text{abi.encodePacked}(\text{vowHash},\, \text{seeker},\, \text{ghostEcho}))) \mod 101\]

Bonus eligibility predicate:
\[\text{bonusEligible} = \neg\text{resonanceDrifted} \land (\text{resonance} > 80) \land \neg\text{bonusApplied}[\text{vowHash}][\text{seeker}] \land (\text{beliefCount}[\text{seeker}] \ge 1)\]

### Events

| Event | When | Fields |
|-------|------|--------|
| `ResonanceQueried(address seeker, bytes32 vowHash, uint256 resonance, bool multiplierApplied)` | Every `queryBelief` | seeker, vowHash indexed |
| `ResonanceDriftSet(bool active, address guardian)` | `setResonanceDrift` | active, guardian |
| `GuardianProposed(address current, address pending)` | `proposeGuardian` | both indexed |
| `GuardianUpdated(address prev, address next)` | `acceptGuardian` | both indexed |

### Security Invariants

1. Per-user `bonusApplied` — no global lockout (HIGH-1 fixed).
2. `MIN_BELIEF_COUNT = 1` gate — new accounts cannot immediately claim bonus (HIGH-2 fixed).
3. CEI ordering — state written before external calls (MEDIUM-2 fixed).
4. `beliefCount` incremented after successful external attestation (H-04 fixed).
5. `bonusApplied` set only on successful reward update (HIGH-01 fixed).
6. Two-step guardian transfer — no instant privilege theft (HIGH-21 fixed).
7. `batchQuery` unbounded version removed (MEDIUM-1 fixed).

### ZK Proof Flow

```
queryBelief(vow, zkSig)
  │
  ├─ vowHash = keccak256(vow)
  ├─ resonance = uint256(keccak256(vowHash ++ seeker ++ ghostEcho)) % 101
  │
  ├─ [STATE WRITE — CEI]
  │   _cachedResonance[vowHash] = resonance
  │   lastResonance[seeker] = resonance
  │
  ├─ bonusEligible = !drifted ∧ resonance>80 ∧ !bonusApplied[h][s] ∧ beliefCount[s]≥1
  │
  ├─ [EXTERNAL 1] attestor.attestBelief(vowHash, zkSig)
  │   → DilithiumAttestorV3 verifies ECDSA + ZK proof
  │
  ├─ beliefCount[seeker]++
  │
  ├─ if bonusEligible:
  │   try rewardStream.updateMultiplier(seeker, 120)
  │     ✓ bonusApplied[vowHash][seeker] = true; applied = true
  │     ✗ applied = false (can retry)
  │
  └─ emit ResonanceQueried(seeker, vowHash, resonance, applied)
```

### Dependencies + Rebuild Recipe

```
vf-audit-contracts/
├── DilithiumAttestorV3.sol
├── BeliefOracleV3.sol
deprecated/
└── RewardStream.sol
```

```bash
forge build --contracts vf-audit-contracts/BeliefOracleV3.sol
```

---

## 25. push_contracts_v3_BeliefOracleV3.sol

**Path:** `/home/user/workspace/push_contracts_v3_BeliefOracleV3.sol`
**Role:** Belief Oracle — Final pushed V3; same as §24 with all audit fixes

### Summary

The final auto-generated pushed version of `BeliefOracleV3`. Contains all audit fixes from §24: per-user `bonusApplied`, `MIN_BELIEF_COUNT = 1`, CEI compliance, `bonusApplied` set inside try-success block (HIGH-01), and bounded `batchQueryBounded`. Import: `DilithiumAttestorV3` from `./DilithiumAttestorV3.sol`. Uses single-step `updateGuardian` (the two-step HIGH-21 fix from §24 may not be merged here).

All logic is identical to §24 except as noted. See §24 for full documentation.

---
## 26. contracts/MultiOracleConsensus.sol

**Path:** `/home/user/workspace/contracts/MultiOracleConsensus.sol`
**Role:** Multi-oracle consensus — V1; recursive quickSort; O(n) duplicate detection; no DISPUTE_WINDOW; slash=50%; min stake=10 ETH

### Summary

`MultiOracleConsensus` is the V1 multi-oracle consensus contract. It maintains a registry of staked oracles, collects per-round oracle submissions, computes a median consensus value via a recursive quickSort algorithm, and slashes oracles whose submissions deviate more than 20% from the consensus. Its key V1 characteristics and vulnerabilities: (1) the duplicate submission check `_hasOracleSubmitted` is O(n) linear scan; (2) there is no `DISPUTE_WINDOW` (submissions cannot be challenged post-finalization); (3) no `withdrawSlashPool` or `rewardPool` — slashed ETH is trapped; (4) minimum stake is 10 ETH; (5) recursive quickSort is susceptible to stack overflow on large inputs; (6) reputation formula is complex multi-factor scoring.

### Full Source

*(Key sections excerpted — full source held in context.)*

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

contract MultiOracleConsensus {
    uint256 public constant MIN_STAKE      = 10 ether;
    uint256 public constant MAX_ORACLES    = 100;
    uint256 public constant SLASH_PERCENT  = 50;
    uint256 public constant DEVIATION_BPS  = 2000; // 20%

    struct Oracle {
        address addr;
        uint256 stake;
        uint256 reputation;
        bool    registered;
        uint256 totalSubmissions;
        uint256 accurateSubmissions;
        uint256 slashCount;
        uint256 registeredAt;
    }

    struct Round {
        uint256 roundId;
        uint256 startTime;
        uint256 deadline;
        bool    finalized;
        uint256 consensusValue;
    }

    struct Submission {
        address oracle;
        uint256 value;
        uint256 timestamp;
    }

    mapping(address => Oracle)     public oracles;
    mapping(uint256 => Round)      public rounds;
    mapping(uint256 => Submission[]) internal _submissions;
    uint256 public nextRoundId = 1;
    address[] public oracleAddresses;

    // ... (functions: registerOracle, submitValue, finalizeRound, _quickSort, _computeMedian, _slashOracle)
}
```

### Line-by-Line Walkthrough

| Section | Description |
|---------|-------------|
| `registerOracle` | Requires `msg.value >= MIN_STAKE` (10 ETH). Adds to `oracleAddresses` array. Reputation initialized to 5000. |
| `submitValue(roundId, value)` | Calls `_hasOracleSubmitted(roundId, msg.sender)` — O(n) scan of `_submissions[roundId]`. |
| `finalizeRound(roundId)` | After deadline: calls `_quickSort` (recursive), computes median, slashes oracles outside 20% band. |
| `_quickSort(arr, lo, hi)` | Recursive quickSort. No depth limit → stack overflow risk for large submissions arrays. |
| `_computeMedian(arr)` | After sort: if even length, returns `(arr[n/2-1] + arr[n/2]) / 2` (note: potential overflow for large values, unlike V3's safer formula). |
| `_slashOracle(oracle)` | Reduces `oracles[oracle].stake` by `SLASH_PERCENT = 50%`. Slashed ETH stays in contract (no withdrawSlashPool). |
| `_computeReputation(oracle)` | Reputation = accuracy×7000 + stake×2000 + longevity×1000 − slashCount×2000 |

### State Variables

| Variable | Type | Mutability | Purpose |
|----------|------|------------|---------|
| `oracles` | `mapping(address => Oracle)` | mutable | Oracle registry |
| `rounds` | `mapping(uint256 => Round)` | mutable | Round data |
| `_submissions` | `mapping(uint256 => Submission[])` | internal | Per-round submissions |
| `nextRoundId` | `uint256` | mutable | Round counter |
| `oracleAddresses` | `address[]` | mutable | Oracle list |

### Functions

**`registerOracle() external payable`** — Requires ≥10 ETH stake. Adds oracle.

**`submitValue(uint256 roundId, uint256 value) external`** — O(n) duplicate check. Adds submission.

**`finalizeRound(uint256 roundId) external`** — Recursive sort, median, slash.

**`_hasOracleSubmitted(uint256 roundId, address oracle) internal view returns (bool)`** — O(n) scan. **HIGH-3 vulnerability in V1.**

**`unregisterOracle() external`** — Returns stake minus any slashes.

### Math

Median formula (V1 — potential overflow):
\[\text{median} = \frac{a_{n/2-1} + a_{n/2}}{2}\]
where overflow can occur if both values are near `type(uint256).max`.

Reputation formula:
\[\text{rep} = \left(\frac{\text{accurate}}{\text{total}} \times 10000\right) \times \frac{7000}{10000} + \min(\text{stake}, 50\text{ ETH}) \times \frac{2000}{50\text{ ETH}} + \min(\text{days}, 100) \times \frac{1000}{100} - \text{slashCount} \times 2000\]

Slash:
\[\text{newStake} = \text{stake} \times \frac{100 - \text{SLASH\_PERCENT}}{100} = \text{stake} \times 0.5\]

### Events

| Event | When |
|-------|------|
| `OracleRegistered(address oracle, uint256 stake)` | Registration |
| `ValueSubmitted(uint256 roundId, address oracle, uint256 value)` | Each submission |
| `RoundFinalized(uint256 roundId, uint256 consensusValue)` | Finalization |
| `OracleSlashed(address oracle, uint256 slashAmount)` | Deviation slash |

### Security Invariants

1. **HIGH-3:** O(n) `_hasOracleSubmitted` — gas cost grows with submissions per round.
2. **HIGH-1/HIGH-2:** No `withdrawSlashPool` or `rewardPool` — slashed ETH is permanently locked.
3. **MEDIUM-1:** No `DISPUTE_WINDOW` — finalized consensus cannot be challenged.
4. Recursive quickSort — stack depth = O(log n) average, O(n) worst case.
5. Median formula susceptible to overflow for very large uint256 values.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts contracts/MultiOracleConsensus.sol
```

---

## 27. theloopbreaker-site/contracts/MultiOracleConsensus.sol

**Path:** `/home/user/workspace/theloopbreaker-site/contracts/MultiOracleConsensus.sol`
**Role:** Multi-oracle consensus — V1 site copy; identical to §26

### Summary

Byte-for-byte copy of the root `contracts/MultiOracleConsensus.sol` (§26) maintained in the theloopbreaker-site sub-tree for independent compilation. All V1 vulnerabilities apply equally. See §26 for full documentation.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts theloopbreaker-site/contracts/MultiOracleConsensus.sol
```

---

## 28. vf-audit-contracts/MultiOracleConsensusV3.sol

**Path:** `/home/user/workspace/vf-audit-contracts/MultiOracleConsensusV3.sol`
**Role:** Multi-oracle consensus — Full V3 audit; Ownable2Step+ReentrancyGuard; commit-reveal; min stake=0.1 ETH; trim 10%/10%; outlier slash=10%; double-slash guard; withdrawStake

### Summary

`MultiOracleConsensusV3` is the full audit V3 consensus contract. It resolves all V1 high, medium, and low findings: (1) commit-reveal scheme — oracles first submit a hash `keccak256(abi.encodePacked(value, salt))` (commit phase), then reveal `(value, salt)` in a separate call, preventing front-running; (2) minimum stake reduced to 0.1 ETH; (3) top and bottom 10% of submissions are trimmed before median computation; (4) outlier slash rate reduced to 10% (from 50%); (5) `AlreadySlashedInRound` double-slash guard; (6) `withdrawStake()` function so unregistered oracles can retrieve their stake; (7) `DISPUTE_WINDOW = 7 days`; (8) O(1) `hasSubmittedRound[roundId][oracle]` mapping replaces O(n) scan; (9) iterative insertion sort replaces recursive quickSort; (10) `RoundVoided` for zero-reveal rounds.

### Full Source

*(Key structures and functions — full source held in context.)*

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import "@openzeppelin/contracts/access/Ownable2Step.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract MultiOracleConsensusV3 is Ownable2Step, ReentrancyGuard {
    uint256 public constant MIN_STAKE       = 0.1 ether;
    uint256 public constant MAX_ORACLES     = 100;
    uint256 public constant SLASH_PERCENT   = 10;       // V3: 10% not 50%
    uint256 public constant DEVIATION_BPS   = 2000;     // 20%
    uint256 public constant DISPUTE_WINDOW  = 7 days;   // V3: NEW
    uint256 public constant TRIM_PERCENT    = 10;       // trim top/bottom 10%

    // HIGH-3 FIX: O(1) submission check
    mapping(uint256 => mapping(address => bool)) public hasSubmittedRound;

    // Commit-reveal scheme
    mapping(uint256 => mapping(address => bytes32)) public commitments;
    mapping(uint256 => mapping(address => bool))    public revealed;

    // HIGH-2: Double-slash guard
    mapping(uint256 => mapping(address => bool))    public slashedInRound;

    // HIGH-1: Slash pool and reward pool
    uint256 public slashPool;
    uint256 public rewardPool;

    function withdrawStake() external nonReentrant { ... }
    function commitValue(uint256 roundId, bytes32 commitment) external { ... }
    function revealValue(uint256 roundId, uint256 value, bytes32 salt) external { ... }
    function finalizeRound(uint256 roundId) external { ... }
    function withdrawSlashPool() external onlyOwner { ... }
    function distributeRewards(address[] calldata recipients, uint256[] calldata amounts) external onlyOwner { ... }
}
```

### Line-by-Line Walkthrough

| Feature | Line Range | Description |
|---------|------------|-------------|
| Commit phase | `commitValue` | `commitments[roundId][msg.sender] = keccak256(abi.encodePacked(value, salt))`. Must be called before deadline. |
| Reveal phase | `revealValue` | Verifies `keccak256(value, salt) == commitment`. Stores value. Sets `revealed[roundId][msg.sender] = true`. |
| O(1) duplicate guard | `hasSubmittedRound` | `bool` mapping keyed by `(roundId, oracle)` — O(1) lookup. **HIGH-3 fix.** |
| Trim | `finalizeRound` | After sorting, removes bottom `n * TRIM_PERCENT / 100` and top `n * TRIM_PERCENT / 100` values before computing median. |
| Slash | `_slashOracle` | Reduces stake by 10%. Sets `slashedInRound[roundId][oracle] = true` before slash. |
| Double-slash | `_slashOracle` | `if (slashedInRound[roundId][oracle]) return` — **HIGH-2 fix.** |
| Slash pool | `_slashOracle` | Slash amount goes to `slashPool` (not trapped). |
| `withdrawStake` | `withdrawStake` | Unregistered oracles call this to recover stake. `nonReentrant`. |
| `withdrawSlashPool` | Owner function | Moves `slashPool` ETH to owner. |
| `distributeRewards` | Owner function | Distributes `rewardPool` to specified recipients. |
| `RoundVoided` | `finalizeRound` | Emitted when no oracles revealed (zero-reveal protection). |
| `DISPUTE_WINDOW` | Storage | Disputes must be filed within 7 days of finalization. **MEDIUM-1 fix.** |
| Insertion sort | `_insertionSort` | Iterative (no recursion) — safe against stack overflow. |

### Math

Trim bounds:
\[
\text{trimCount} = \left\lfloor\frac{n \times \text{TRIM\_PERCENT}}{100}\right\rfloor
\]
\[
\text{effective range} = [\text{trimCount},\; n - \text{trimCount})
\]

Median over trimmed set:
\[
\text{median} = \begin{cases}
\text{sorted}[m/2] & m \text{ odd} \\
\frac{\text{sorted}[m/2-1]}{2} + \frac{\text{sorted}[m/2]}{2} + \frac{(\text{sorted}[m/2-1] \bmod 2 + \text{sorted}[m/2] \bmod 2)}{2} & m \text{ even}
\end{cases}
\]
(overflow-safe formula)

Slash:
\[\text{slashAmount} = \text{stake} \times \frac{10}{100}\]

Reputation (unchanged from V1 formula, applied post-slash):
\[\text{rep} = \frac{\text{accurate}}{\text{total}} \times 7000 + \frac{\min(\text{stake}, 50\,\text{ETH})}{50\,\text{ETH}} \times 2000 + \frac{\min(\text{days}, 100)}{100} \times 1000 - \text{slashCount} \times 2000\]

Commit-reveal:
\[\text{commitment} = \text{keccak256}(\text{abi.encodePacked}(\text{value},\, \text{salt}))\]

### Events

| Event | When |
|-------|------|
| `OracleRegistered(address oracle, uint256 stake)` | Registration |
| `ValueCommitted(uint256 roundId, address oracle)` | Commit phase |
| `ValueRevealed(uint256 roundId, address oracle, uint256 value)` | Reveal phase |
| `RoundFinalized(uint256 roundId, uint256 consensusValue)` | Finalization |
| `RoundVoided(uint256 roundId)` | No reveals in round |
| `OracleSlashed(address oracle, uint256 slashAmount, uint256 roundId)` | Slash |
| `StakeWithdrawn(address oracle, uint256 amount)` | `withdrawStake` |

### Security Invariants

1. Commit-reveal prevents value front-running.
2. O(1) `hasSubmittedRound` — no gas bloat (HIGH-3 fixed).
3. Double-slash `slashedInRound` guard (HIGH-2 fixed).
4. `slashPool` is withdrawable by owner (HIGH-1 fixed).
5. `withdrawStake` nonReentrant — safe ETH transfer.
6. Iterative sort — no stack overflow.
7. `DISPUTE_WINDOW` enables post-finalization challenges (MEDIUM-1 fixed).
8. `Ownable2Step` — two-step ownership transfer.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts vf-audit-contracts/MultiOracleConsensusV3.sol
```

---

## 29. v3-pr-ready/contracts/v3/MultiOracleConsensusV3.sol

**Path:** `/home/user/workspace/v3-pr-ready/contracts/v3/MultiOracleConsensusV3.sol`
**Role:** Multi-oracle consensus — V3 PR-ready; all HIGH/MEDIUM/LOW audit fixes

### Summary

The PR-ready version of `MultiOracleConsensusV3`. This is the most complete V3 implementation reviewed in the codebase — it explicitly documents all audit fix annotations (`HIGH-1`, `HIGH-2`, `HIGH-3`, `MEDIUM-1`) in NatSpec comments. Key additions over §28: explicit `distributeRewards(address[], uint256[])` function, complete `withdrawSlashPool()`, and a full `DISPUTE_WINDOW = 7 days` enforcement mechanism. Functionally identical to §28. This is the version that was submitted in the PR.

All documentation from §28 applies. See §28.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts v3-pr-ready/contracts/v3/MultiOracleConsensusV3.sol
```

---

## 30. push_contracts_v3_MultiOracleConsensusV3.sol

**Path:** `/home/user/workspace/push_contracts_v3_MultiOracleConsensusV3.sol`
**Role:** Multi-oracle consensus — Final pushed V3; all fixes identical to §28/§29

### Summary

The final auto-generated pushed version. Identical to §28 in all respects. This is the deployment target. See §28 for full documentation.

---

## 31. contracts/FlourishingMetricsOracle.sol

**Path:** `/home/user/workspace/contracts/FlourishingMetricsOracle.sol`
**Role:** Flourishing metrics oracle — V1; median consensus; MINIMUM_QUORUM=3; MAXIMUM_ORACLES=50; insertion sort; overflow-safe median

### Summary

`FlourishingMetricsOracle` V1 collects metric submissions from registered oracles and computes a median consensus. It uses a round-based model where an owner starts rounds, oracles submit values, and any caller can finalize a round after the deadline. It applies an insertion sort (not recursive quickSort like `MultiOracleConsensus`) which is stack-safe. The median formula is overflow-safe. V1 lacks: per-submission-interval cooldown, weighted sources, EMA TWAP, manipulation detection flag storage.

### Full Source

*(Key sections.)*

```solidity
uint256 public constant MINIMUM_QUORUM  = 3;
uint256 public constant MAXIMUM_ORACLES = 50;
uint256 public constant SUBMISSION_WINDOW = 24 hours;

mapping(address => bool)     public isOracle;
mapping(uint256 => Round)    public rounds;
mapping(uint256 => Submission[]) internal _submissions;
mapping(bytes32 => uint256)  public latestValue;
```

### Line-by-Line Walkthrough

| Function | Description |
|----------|-------------|
| `addOracle(address)` | Owner only; checks `oracleCount < MAXIMUM_ORACLES`. |
| `startRound(bytes32 metricId)` | Owner only; creates round with `SUBMISSION_WINDOW = 24hr` deadline. |
| `submitMetric(uint256 roundId, uint256 value)` | Oracle only; checks round is open and oracle hasn't submitted. |
| `finalizeRound(uint256 roundId)` | Any caller after deadline; requires `MINIMUM_QUORUM = 3` submissions. |
| `_computeMedianFromValues(uint256[])` | Sorts in-place using insertion sort; overflow-safe even-length formula. |

### Math

Overflow-safe even-length median:
\[\text{median} = \frac{a}{2} + \frac{b}{2} + \frac{(a \bmod 2) + (b \bmod 2)}{2}\]

where \(a = \text{sorted}[n/2-1]\) and \(b = \text{sorted}[n/2]\). This avoids overflow that would result from \((a + b) / 2\) when \(a + b > 2^{256} - 1\).

### Events

| Event | When |
|-------|------|
| `OracleAdded(address oracle)` | `addOracle` |
| `RoundStarted(uint256 roundId, bytes32 metricId, uint256 deadline)` | `startRound` |
| `MetricSubmitted(uint256 roundId, address oracle, uint256 value)` | `submitMetric` |
| `ConsensusReached(uint256 roundId, bytes32 metricId, uint256 value, uint256 subCount)` | `finalizeRound` |
| `RoundExpired(uint256 roundId)` | `finalizeRound` when quorum not met |

### Security Invariants

1. Insertion sort — no stack overflow (unlike MultiOracleConsensus V1).
2. Overflow-safe median formula.
3. No deviation filtering — all values included in median (V3 adds ±20% filter).
4. No staleness check on `latestValue` (V3 adds `latestTimestamp`).

### Dependencies + Rebuild Recipe

```bash
forge build --contracts contracts/FlourishingMetricsOracle.sol
```

---

## 32. theloopbreaker-site/contracts/FlourishingMetricsOracle.sol

**Path:** `/home/user/workspace/theloopbreaker-site/contracts/FlourishingMetricsOracle.sol`
**Role:** Flourishing metrics oracle — V1 site copy; identical to §31

### Summary

Byte-for-byte copy of §31. See §31 for full documentation.

---

## 33. push_contracts_v3_FlourishingMetricsOracleV3.sol

**Path:** `/home/user/workspace/push_contracts_v3_FlourishingMetricsOracleV3.sol`
**Role:** Flourishing metrics oracle — Final pushed V3; Ownable2Step; weighted sources; EMA TWAP; manipulation detection; emergencyOverride; MIN_SOURCES=3; MAX_METRIC_AGE=1hr

### Summary

`FlourishingMetricsOracleV3` (pushed version) is the V3 continuous-feed metric oracle using a push model rather than round-based model. Sources submit values continuously; each submission triggers aggregation. It uses an EMA (exponential moving average) TWAP, weighted source aggregation, manipulation detection flagging, and emergency override. This is the contract that implements `IFlourishingMetricsOracle`. Key characteristics: per-source latest value tracking (prevents sybil multi-submission gaming), `DEVIATION_THRESHOLD = 2000 bps` (20%) manipulation flag, and `emergencyOverrideMetric` for owner intervention.

### Full Source

*(Key sections — full 177-line source at test_fixes/contracts/FlourishingMetricsOracleV3.sol was identical to this version's core logic.)*

```solidity
uint256 public constant MIN_SOURCES         = 3;
uint256 public constant MAX_METRIC_AGE      = 1 hours;
uint256 public constant DEVIATION_THRESHOLD = 2000; // 20% in bps

struct AggregatedMetric {
    uint256 twap;
    uint256 latestValue;
    uint64  lastUpdated;
    uint256 sourceCount;
    bool    manipulationFlag;
}

mapping(bytes32 => AggregatedMetric) public metrics;
mapping(bytes32 => mapping(address => uint256)) public latestSourceValue;
mapping(bytes32 => mapping(address => uint256)) public latestSourceTimestamp;
```

### Line-by-Line Walkthrough

| Function | Description |
|----------|-------------|
| `registerSource(address, uint256 weight)` | Owner: sets `SourceInfo{registered:true, weight, submissionCount:0}`. Increments `registeredSourceCount`. |
| `deregisterSource(address)` | Owner: sets `registered = false`. Decrements `registeredSourceCount`. |
| `submitMetric(bytes32 metricId, uint256 value)` | Source: validates registered, non-empty metricId. Appends to `metricHistory`, increments `submissionCount`, stores `latestSourceValue[metricId][msg.sender]` and `latestSourceTimestamp`. Calls `_aggregate`. |
| `_aggregate(bytes32 metricId, uint256 newValue)` | Iterates `sourceList`, sums latest values from registered active sources, checks `< MIN_SOURCES`, computes mean, checks deviation, updates TWAP. |
| `emergencyOverrideMetric(bytes32, uint256)` | Owner: directly writes `latestValue`, `twap`, `lastUpdated`, clears `manipulationFlag`, sets `emergencyOverrideActive[metricId] = true`. |
| `getMetricTWAP(bytes32, uint256)` | Returns `metrics[metricId].twap` (ignores window param). |
| `isMetricFresh(bytes32)` | Returns `block.timestamp - lastUpdated <= MAX_METRIC_AGE`. |
| `getMetric(bytes32)` | Returns full `AggregatedMetric` or reverts if not found or stale. |

### Math

Aggregation (plain mean for push_contracts version, before HIGH-13 weighted fix):
\[\text{mean} = \frac{\sum_{i \in \text{active}} \text{latestSourceValue}[m][s_i]}{|\text{active}|}\]

EMA TWAP:
\[\text{TWAP}_n = \begin{cases} v_0 & \text{if } \text{TWAP}_{n-1} = 0 \\ \frac{3 \times \text{TWAP}_{n-1} + v_n}{4} & \text{otherwise} \end{cases}\]

The coefficient 3:1 gives the previous TWAP 75% weight and the new value 25% weight — a 4-period EMA.

Manipulation detection:
\[\text{deviation} = \frac{|v_n - \text{mean}| \times 10000}{\text{mean}}\]
\[\text{manipulated} = \text{deviation} > \text{DEVIATION\_THRESHOLD} = 2000\]

### Events

| Event | When |
|-------|------|
| `MetricUpdated(bytes32 metricId, uint256 newValue, uint256 twap)` | Successful aggregation |
| `ManipulationFlagged(bytes32 metricId, address source, uint256 deviation)` | Deviation >20% |
| `SourceRegistered(address source, uint256 weight)` | `registerSource` |
| `SourceDeregistered(address source)` | `deregisterSource` |
| `EmergencyOverride(bytes32 metricId, uint256 value)` | `emergencyOverrideMetric` |
| `AggregationDeferred(bytes32 metricId, uint256 activeCount, uint256 required)` | Fewer than MIN_SOURCES active |

### Security Invariants

1. Per-source latest-value tracking prevents one source from submitting N times to dominate the mean (HIGH-FMO-01 fixed).
2. `MIN_SOURCES = 3` prevents single-source manipulation.
3. Manipulation flag does not halt processing — it is an advisory signal only.
4. `emergencyOverrideMetric` is owner-only but unrestricted in value — high privilege.
5. Deregistered source's historical values remain in `metricHistory` but are excluded from future aggregation.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts push_contracts_v3_FlourishingMetricsOracleV3.sol
```

---

## 34. test_fixes/contracts/FlourishingMetricsOracleV3.sol

**Path:** `/home/user/workspace/test_fixes/contracts/FlourishingMetricsOracleV3.sol`
**Role:** Flourishing metrics oracle — V3 test-fixes branch; no submission cooldown (CRIT-10 not yet applied); plain mean aggregation (HIGH-13 not yet applied)

### Summary

The `test_fixes` branch version. It has all V3 base fixes (per-source tracking, MIN_SOURCES gate, registeredSourceCount, manipulation detection, EMA TWAP, emergency override) but is missing two later findings: **CRIT-10** (per-source per-metric submission cooldown `MIN_SUBMISSION_INTERVAL = 1 hour`) and **HIGH-13** (weighted mean using `sources[src].weight`). The `_aggregate` function computes a plain unweighted mean, making the `weight` field in `SourceInfo` cosmetic only.

### Key Difference vs §35

```solidity
// MISSING in this version:
// uint256 public constant MIN_SUBMISSION_INTERVAL = 1 hours;
// mapping(address => mapping(bytes32 => uint256)) public lastSubmissionTime;

// submitMetric — NO cooldown check

// _aggregate — plain mean (not weighted):
uint256 sum = 0;
for (...) { sum += latestSourceValue[metricId][src]; }
uint256 mean = sum / activeCount; // unweighted
```

### Dependencies + Rebuild Recipe

```bash
forge build --contracts test_fixes/contracts/FlourishingMetricsOracleV3.sol
```

---

## 35. vf-audit-contracts/FlourishingMetricsOracleV3.sol

**Path:** `/home/user/workspace/vf-audit-contracts/FlourishingMetricsOracleV3.sol`
**Role:** Flourishing metrics oracle — Full V3 audit; CRIT-10 submission cooldown; HIGH-13 weighted mean; all fixes

### Summary

The full audit version. Adds `MIN_SUBMISSION_INTERVAL = 1 hour` (CRIT-10) and weighted mean aggregation (HIGH-13) on top of the base V3 features from §34. Sources now have meaningful `weight` fields that are respected in the mean calculation. The TWAP is updated using the weighted mean rather than the submitted raw value.

### Full Source

*(202 lines — full source read above.)*

### Line-by-Line Walkthrough

| Line | Description |
|------|-------------|
| 19 | `MIN_SUBMISSION_INTERVAL = 1 hours` — CRIT-10. Prevents a single registered source from submitting rapidly to manipulate TWAP. |
| 64 | `lastSubmissionTime[address][bytes32] => uint256` — per-source per-metric cooldown tracker. |
| 99–103 | Cooldown check in `submitMetric`: `block.timestamp - lastSubmissionTime[msg.sender][metricId] >= MIN_SUBMISSION_INTERVAL`. Sets new timestamp after check. |
| 121–122 | `weightedSum += latestSourceValue[metricId][src] * w` and `totalWeight += w` — HIGH-13. |
| 144 | `mean = totalWeight > 0 ? weightedSum / totalWeight : weightedSum / activeCount` — weighted mean with plain-mean fallback for zero-weight sources. |
| 158 | TWAP uses `mean` (weighted) rather than raw `newValue`: `(m.twap * 3 + mean) / 4`. |

### Math

Weighted mean:
\[\text{mean} = \begin{cases} \frac{\sum_{i} \text{latestValue}[m][s_i] \times w_i}{\sum_i w_i} & \sum_i w_i > 0 \\ \frac{\sum_i \text{latestValue}[m][s_i]}{|\text{active}|} & \text{otherwise (all weights zero)} \end{cases}\]

Weighted EMA TWAP:
\[\text{TWAP}_n = \frac{3 \times \text{TWAP}_{n-1} + \text{mean}_n}{4}\]

Cooldown invariant:
\[\text{block.timestamp} - \text{lastSubmissionTime}[\text{src}][m] \ge 3600\]

### Events

Same as §33, plus the semantics of `MetricUpdated` now reflect the weighted mean instead of raw value.

### Security Invariants

1. Per-source cooldown prevents rapid TWAP manipulation (CRIT-10 fixed).
2. Weighted mean respects source trust tiers (HIGH-13 fixed).
3. TWAP uses weighted mean as input — manipulation requires distorting the entire weighted aggregate.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts vf-audit-contracts/FlourishingMetricsOracleV3.sol
```

---

## 36. v3-pr-ready/contracts/v3/FlourishingMetricsOracleV3.sol

**Path:** `/home/user/workspace/v3-pr-ready/contracts/v3/FlourishingMetricsOracleV3.sol`
**Role:** Flourishing metrics oracle — PR-ready V3; round-based model (different design from §33–§35); RoundStatus enum; deviation filtering; HIGH-2 staleness; MEDIUM-3 active round gate

### Summary

This is a **fundamentally different design** from §33–§35. While §33–§35 use a push-based continuous feed model, this version uses a **round-based model** (like V1 `FlourishingMetricsOracle`) with explicit round start/finalize lifecycle. It introduces a `RoundStatus` enum (`Open`, `Finalized`, `Expired`), deviation filtering with `_computeFilteredMedian`, HIGH-2 forward-only `latestValue` update, MEDIUM-1 `latestTimestamp`, and MEDIUM-3 `activeRoundForMetric` single-active-round gate. This is the most complete round-based V3 implementation.

### Full Source

*(485 lines — full source read above.)*

### Line-by-Line Walkthrough

| Lines | Description |
|-------|-------------|
| 86 | `enum RoundStatus { Open, Finalized, Expired }` — replaces `bool finalized`. |
| 119 | `DEVIATION_THRESHOLD = MAX_DEVIATION_BPS = 2000` (20%). |
| 147–150 | `latestTimestamp[bytes32]` — MEDIUM-1: finalization timestamp for staleness. `activeRoundForMetric[bytes32]` — MEDIUM-3: single active round gate. |
| 217–246 | `startRound(bytes32 metricId)` — checks `activeRoundForMetric[metricId]` for existing open round before creating new one. MEDIUM-3 fix. |
| 252–272 | `submitMetric(uint256 roundId, uint256 value)` — LOW-2: `value == 0` rejected. |
| 280–324 | `finalizeRound(uint256 roundId)` — two-pass filtering: initial median → filtered set → consensus. HIGH-1 + HIGH-2 + MEDIUM-1 fixes. |
| 297–300 | `_computeFilteredMedian` call — emits `DeviationFlagged` for outliers. |
| 316 | `if (roundId > latestRoundForMetric[round.metricId])` — HIGH-2: only updates `latestValue` if this is a newer round. Prevents stale round from overwriting fresh data. |
| 380–384 | `transferOwnership` — emits `OwnershipTransferred`. |
| 401–413 | `_insertionSort` — iterative, O(n²) worst case, safe for `MAXIMUM_ORACLES = 50`. |
| 418–430 | `_computeMedianFromValues` — sorts in-place, overflow-safe even-length formula. |
| 441–483 | `_computeFilteredMedian` — two-pass: count in-band values (emitting `DeviationFlagged` for each outlier), then collect and compute filtered median. |

### Math

Deviation band:
\[
\text{lower} = \text{refMedian} \times \frac{10000 - 2000}{10000} = \text{refMedian} \times 0.8
\]
\[
\text{upper} = \text{refMedian} \times \frac{10000 + 2000}{10000} = \text{refMedian} \times 1.2
\]

A submission \(v\) is in-band iff \(\text{lower} \le v \le \text{upper}\).

Overflow-safe median (same as §31):
\[
\text{median} = \frac{a}{2} + \frac{b}{2} + \frac{(a \bmod 2) + (b \bmod 2)}{2}
\]

### Events

| Event | When |
|-------|------|
| `OracleAdded(address oracle)` | `addOracle` |
| `OracleRemoved(address oracle)` | `removeOracle` |
| `RoundStarted(uint256 roundId, bytes32 metricId, uint256 deadline)` | `startRound` |
| `MetricSubmitted(uint256 roundId, address oracle, uint256 value)` | `submitMetric` |
| `ConsensusReached(uint256 roundId, bytes32 metricId, uint256 value, uint256 subCount)` | `finalizeRound` |
| `RoundExpired(uint256 roundId)` | Quorum failure in `finalizeRound` |
| `DeviationFlagged(uint256 roundId, address oracle, uint256 value, uint256 median)` | Outlier detection |
| `OwnershipTransferred(address prev, address next)` | `transferOwnership` |

### Security Invariants

1. Deviation filtering — outliers emitted with `DeviationFlagged` and excluded from consensus median.
2. Forward-only `latestValue` update (HIGH-2) — stale finalization cannot overwrite fresh data.
3. Single active round per metricId (MEDIUM-3) — no concurrent round spam.
4. `latestTimestamp` for staleness detection (MEDIUM-1).
5. Zero-value submissions rejected (LOW-2).
6. Insertion sort — O(n²) but bounded by `MAXIMUM_ORACLES = 50`, so maximum 2500 comparisons per sort.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts v3-pr-ready/contracts/v3/FlourishingMetricsOracleV3.sol
```

---

## 37. contracts/BaseOracle.sol

**Path:** `/home/user/workspace/contracts/BaseOracle.sol`
**Role:** Base oracle — CID and zkHash attestation emitter for live visualizations; mission evolution broadcaster

### Summary

`BaseOracle` is a lightweight, self-contained oracle contract with no proof verification logic. Its sole function is to emit events anchoring off-chain content to the Base chain: `emitAttestation` pins an IPFS CID and a ZK hash placeholder, returning a deterministic `keccak256(abi.encode(cid, zkHash, block.timestamp))` attestation ID. `publishMissionEvo` broadcasts a mission evolution — an ordered list of virtues and their weight multipliers — after a DAO vote. The guardian is an immutable address, preventing privilege escalation post-deployment.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  /// @title Vaultfire Base Oracle
 5  contract BaseOracle {
 6      address public immutable guardian;
 7      uint256 public constant RESONANCE_THRESHOLD_BPS = 5_100;
 8
 9      event VisualizationPinned(
10          address indexed guardian, string cid, string zkHash, uint256 timestamp
11      );
12      event MissionEvoBroadcast(
13          address indexed guardian,
14          bytes32 indexed evoId,
15          string[] virtues,
16          uint256[] weights,
17          uint256 thresholdBps,
18          uint256 timestamp
19      );
20
21      constructor(address guardianAddress) {
22          require(guardianAddress != address(0), "guardian_required");
23          guardian = guardianAddress;
24      }
25
26      modifier onlyGuardian() {
27          require(msg.sender == guardian, "guardian_only");
28          _;
29      }
30
31      function emitAttestation(string calldata cid, string calldata zkHash)
32          external onlyGuardian returns (bytes32 attestationId)
33      {
34          attestationId = keccak256(abi.encode(cid, zkHash, block.timestamp));
35          emit VisualizationPinned(msg.sender, cid, zkHash, block.timestamp);
36      }
37
38      function publishMissionEvo(string[] calldata virtues, uint256[] calldata weights)
39          external onlyGuardian returns (bytes32 evoId)
40      {
41          require(virtues.length == weights.length, "mission_length");
42          require(virtues.length != 0, "mission_empty");
43          evoId = keccak256(abi.encode(virtues, weights, block.timestamp));
44          emit MissionEvoBroadcast(msg.sender, evoId, virtues, weights, RESONANCE_THRESHOLD_BPS, block.timestamp);
45      }
46  }
```

### Line-by-Line Walkthrough

| Line | Description |
|------|-------------|
| 6 | `guardian` — **immutable**. Set once in constructor; cannot be rotated. This prevents post-deployment guardian changes — a trade-off between simplicity and operational flexibility. |
| 7 | `RESONANCE_THRESHOLD_BPS = 5_100` — 51% threshold used in DAO governance quorum checks, stored here for downstream listeners to read. |
| 31–36 | `emitAttestation`: `keccak256(abi.encode(cid, zkHash, block.timestamp))` — uses `abi.encode` (not `encodePacked`) to prevent hash collision between two dynamic string parameters. The returned `attestationId` enables clients to reconcile the on-chain event with their off-chain record. |
| 38–45 | `publishMissionEvo`: length guard prevents empty or mismatched arrays. `abi.encode(virtues, weights, block.timestamp)` produces a deterministic `evoId` that anchors the mission evolution payload. The `RESONANCE_THRESHOLD_BPS` is included in the event for downstream DAO listeners. |

### State Variables

| Variable | Type | Mutability | Purpose |
|----------|------|------------|---------|
| `guardian` | `address` | immutable | Only address allowed to emit attestations |
| `RESONANCE_THRESHOLD_BPS` | `uint256` | constant (5100) | DAO quorum threshold (51%) |

### Functions

**`emitAttestation(string cid, string zkHash) external onlyGuardian returns (bytes32)`**
- Computes attestation ID.
- Emits `VisualizationPinned`.
- Returns attestation ID for client reconciliation.

**`publishMissionEvo(string[] virtues, uint256[] weights) external onlyGuardian returns (bytes32)`**
- Validates lengths.
- Computes evoId.
- Emits `MissionEvoBroadcast` including `RESONANCE_THRESHOLD_BPS`.

### Math

Attestation ID:
\[\text{attestationId} = \text{keccak256}(\text{abi.encode}(\text{cid},\, \text{zkHash},\, \text{block.timestamp}))\]

Evolution ID:
\[\text{evoId} = \text{keccak256}(\text{abi.encode}(\text{virtues},\, \text{weights},\, \text{block.timestamp}))\]

Note: IDs are time-bound — two identical `(cid, zkHash)` pairs produce different IDs if submitted in different blocks, preventing collision.

### Events

| Event | When | Fields |
|-------|------|--------|
| `VisualizationPinned(address guardian, string cid, string zkHash, uint256 timestamp)` | `emitAttestation` | guardian indexed |
| `MissionEvoBroadcast(address guardian, bytes32 evoId, string[] virtues, uint256[] weights, uint256 thresholdBps, uint256 timestamp)` | `publishMissionEvo` | guardian and evoId indexed |

### Security Invariants

1. `guardian` is immutable — no privilege escalation post-deploy.
2. `abi.encode` prevents hash collision between dynamic string parameters.
3. No state storage (only events) — minimal attack surface.
4. `onlyGuardian` on both functions — no public write paths.

### ZK Proof Flow

Not applicable. This contract emits a `zkHash` string (an opaque reference to an off-chain ZK computation) but performs no on-chain proof verification.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts contracts/BaseOracle.sol
```

---

## 38. theloopbreaker-site/contracts/VaultfireTrustOracle.sol

**Path:** `/home/user/workspace/theloopbreaker-site/contracts/VaultfireTrustOracle.sol`
**Role:** Trust oracle — Off-chain performance attestations; quality score 0–10000; running average / EMA; task-linked; double-attestation guard; self-contained (no OZ imports)

### Summary

`VaultfireTrustOracle` records off-chain performance attestations submitted by trusted attesters about AI agents. Each attestation links a specific task ID to an agent address and records a quality score in the range [0, 10000]. The running average for an agent's score uses a true arithmetic mean for the first 1000 attestations, then switches to an EMA with coefficient 9/10 to give more weight to recent scores. A per-`(attester, agent, taskId)` triple guard prevents double-attestation. The contract is self-contained with no OpenZeppelin imports — uses custom role management.

### Full Source

*(Key sections — full source held in context.)*

```solidity
uint256 public constant MAX_QUALITY_SCORE  = 10000;
uint256 public constant EMA_SWITCH_COUNT   = 1000;
uint256 public constant EMA_SMOOTH_DENOM   = 10; // alpha = 1/10, smooth = 9/10

struct Attestation {
    address attester;
    address agent;
    bytes32 taskId;
    uint256 qualityScore;
    uint256 timestamp;
}

struct AgentStats {
    uint256 totalScore;
    uint256 attestationCount;
    uint256 averageScore;     // EMA or true average
    bool    exists;
}

mapping(address => AgentStats) public agentStats;
mapping(address => mapping(address => mapping(bytes32 => bool)))
    public hasAttested; // attester → agent → taskId → bool
mapping(uint256 => Attestation) public attestations;
uint256 public attestationCount;

address[] public attesters;
mapping(address => bool) public isAttester;
```

### Line-by-Line Walkthrough

| Function | Description |
|----------|-------------|
| `addAttester(address)` | Owner only; adds to `attesters` list and `isAttester` mapping. |
| `removeAttester(address)` | Owner only; removes from mapping (not from array — array grows unboundedly). |
| `submitAttestation(address agent, bytes32 taskId, uint256 qualityScore)` | Attester only; validates `qualityScore <= MAX_QUALITY_SCORE`, `agent != address(0)`, `taskId != 0`. Guards `hasAttested[msg.sender][agent][taskId]`. Updates `agentStats`. |
| `_updateAgentStats(address agent, uint256 score)` | Internal: if `count < EMA_SWITCH_COUNT`: `totalScore += score; avg = totalScore / count`. If `count >= EMA_SWITCH_COUNT`: `avg = (avg * 9 + score) / 10` (EMA). |

### Math

Running average (first 1000 attestations — true arithmetic mean):
\[\overline{s}_n = \frac{\sum_{i=1}^n s_i}{n}\]

EMA (after 1000 attestations — α = 1/10):
\[\overline{s}_n = \frac{9 \times \overline{s}_{n-1} + s_n}{10}\]

This is equivalent to a 10-period exponential moving average. The smooth factor (9/10) means each new score contributes 10% to the average. For a score well above the current average, the EMA responds gradually.

Quality score range: [0, 10000] where 10000 = perfect performance.

Double-attestation guard:
\[\text{key} = (\text{attester}, \text{agent}, \text{taskId})\]

The same `(attester, agent, taskId)` triple cannot be submitted twice.

### Events

| Event | When |
|-------|------|
| `AttestationSubmitted(uint256 attestationId, address attester, address agent, bytes32 taskId, uint256 score)` | `submitAttestation` |
| `AttesterAdded(address attester)` | `addAttester` |
| `AttesterRemoved(address attester)` | `removeAttester` |

### Security Invariants

1. Double-attestation guard keyed on `(attester, agent, taskId)` — same task cannot be scored twice by the same attester for the same agent.
2. `qualityScore > MAX_QUALITY_SCORE` rejected.
3. EMA switch at 1000 prevents early-epoch score manipulation from permanently distorting the long-run average.
4. `removeAttester` does not remove from the `attesters` array — only from `isAttester` mapping. The array grows over time (minor gas inefficiency).
5. No on-chain ZK verification — attester trustworthiness is social/economic, not cryptographic.
6. Self-contained (no OZ) — minimizes external dependency risk.

### ZK Proof Flow

Not applicable — attestations are off-chain and trust is based on attester authorization, not ZK proofs.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts theloopbreaker-site/contracts/VaultfireTrustOracle.sol
```

---

## 39. theloopbreaker-site/contracts/mocks/MockRiscZeroVerifier.sol

**Path:** `/home/user/workspace/theloopbreaker-site/contracts/mocks/MockRiscZeroVerifier.sol`
**Role:** Test mock — Minimal IRiscZeroVerifier for local Hardhat tests; configurable expected values; forced revert mode

### Summary

`MockRiscZeroVerifier` is a test double that implements the `IRiscZeroVerifier` interface from the mocks' parent directory. It stores `expectedImageId` and `expectedJournalDigest` and verifies that `verify` is called with those exact values. A `shouldRevert` flag allows tests to simulate proof failure. The `version()` function returns a fixed string. This mock is used in Foundry/Hardhat test suites to unit-test the verifier integration without requiring a real RISC Zero prover.

### Full Source

```solidity
 1  // SPDX-License-Identifier: MIT
 2  pragma solidity ^0.8.25;
 3
 4  import "../IRiscZeroVerifier.sol";
 5
 6  contract MockRiscZeroVerifier is IRiscZeroVerifier {
 7      bytes32 public expectedImageId;
 8      bytes32 public expectedJournalDigest;
 9      bool    public shouldRevert;
10
11      function setExpected(bytes32 _imageId, bytes32 _journalDigest) external {
12          expectedImageId = _imageId;
13          expectedJournalDigest = _journalDigest;
14      }
15
16      function setShouldRevert(bool _shouldRevert) external {
17          shouldRevert = _shouldRevert;
18      }
19
20      function verify(
21          bytes calldata, /* proof */
22          bytes32 imageId,
23          bytes32 journalDigest
24      ) external view returns (bool) {
25          require(!shouldRevert, "Mock: forced revert");
26          require(imageId == expectedImageId, "Mock: bad imageId");
27          require(journalDigest == expectedJournalDigest, "Mock: bad journalDigest");
28          return true;
29      }
30
31      function version() external pure returns (string memory) {
32          return "mock-risc0-verifier";
33      }
34  }
```

### Line-by-Line Walkthrough

| Line | Description |
|------|-------------|
| 4 | `import "../IRiscZeroVerifier.sol"` — imports the local mock package's IRiscZeroVerifier (§4 variant — returns bool). |
| 7–9 | `expectedImageId`, `expectedJournalDigest`, `shouldRevert` — all configurable by test setup. |
| 11–14 | `setExpected(imageId, journalDigest)` — test setup function. Called in test `setUp()` or directly in test cases. No access control (test-only). |
| 16–18 | `setShouldRevert(bool)` — test setup for failure paths. |
| 20–29 | `verify` — `view` (not state-changing). Checks `shouldRevert` first (panic mode), then verifies `imageId` and `journalDigest` match expected values. Returns `true` on success. Proof bytes are ignored (`/* proof */`). |
| 31–33 | `version()` — returns `"mock-risc0-verifier"` for easy identification in test logs. |

### State Variables

| Variable | Type | Mutability | Purpose |
|----------|------|------------|---------|
| `expectedImageId` | `bytes32` | mutable (test) | Expected image ID for assertion |
| `expectedJournalDigest` | `bytes32` | mutable (test) | Expected journal digest for assertion |
| `shouldRevert` | `bool` | mutable (test) | Force revert mode for failure tests |

### Functions

**`setExpected(bytes32, bytes32) external`** — Configure expected values.

**`setShouldRevert(bool) external`** — Configure failure mode.

**`verify(bytes, bytes32, bytes32) external view returns (bool)`** — Mock implementation.

**`version() external pure returns (string memory)`** — Returns `"mock-risc0-verifier"`.

### Security Invariants

1. No access control — test-only contract. **MUST NOT be deployed to mainnet.**
2. `shouldRevert` mode causes the function to revert with `"Mock: forced revert"` — not return `false`. Callers using `try/catch` will enter the catch branch.
3. Proof bytes are completely ignored — any bytes pass verification as long as `imageId` and `journalDigest` match.

### Dependencies + Rebuild Recipe

```bash
forge build --contracts theloopbreaker-site/contracts/mocks/MockRiscZeroVerifier.sol
```

---

## 40. theloopbreaker-site/risc0/test/ProductionBeliefAttestationVerifier.t.sol

**Path:** `/home/user/workspace/theloopbreaker-site/risc0/test/ProductionBeliefAttestationVerifier.t.sol`
**Role:** Test suite — Foundry test for ProductionBeliefAttestationVerifier (§20); MockRiscZeroVerifier embedded; tests constructor, verifyProof, verifyAttestation paths

### Summary

Foundry test contract for `ProductionBeliefAttestationVerifier`. It embeds a `MockRiscZeroVerifier` (similar to §39) directly in the test file and exercises the full constructor, `verifyProof` (legacy 4-input path), and `verifyAttestation` (direct 6-field path). Tests cover: successful verification, proof failure (revert), invalid public inputs count, zero address rejection, zero image ID rejection, timelock mechanics (`proposeImageIdChange`, `executeImageIdChange` before/after delay, `cancelImageIdChange`), and ownership transfer.

### Full Source (Structure)

```solidity
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.25;

import "forge-std/Test.sol";
import "../src/ProductionBeliefAttestationVerifier.sol";
import "../interfaces/IRiscZeroVerifier.sol";

// Embedded mock verifier for tests
contract MockRiscZeroVerifier is IRiscZeroVerifier {
    bytes32 public expectedImageId;
    bytes32 public expectedJournalDigest;
    bool    public shouldRevert;

    function setExpected(bytes32 _imageId, bytes32 _journalDigest) external { ... }
    function setShouldRevert(bool v) external { shouldRevert = v; }
    function verify(bytes calldata, bytes32 imageId, bytes32 journalDigest) external {
        require(!shouldRevert, "Mock: forced revert");
        require(imageId == expectedImageId, "Mock: imageId mismatch");
        require(journalDigest == expectedJournalDigest, "Mock: digest mismatch");
        // No return value — uses Apache-2.0 revert-on-failure interface
    }
    function verifyIntegrity(Receipt calldata) external view {}
    function version() external pure returns (string memory) { return "mock"; }
}

contract ProductionBeliefAttestationVerifierTest is Test {
    ProductionBeliefAttestationVerifier verifier;
    MockRiscZeroVerifier mockRiscZero;

    bytes32 constant IMAGE_ID = bytes32(uint256(0xdeadbeef));

    function setUp() public {
        mockRiscZero = new MockRiscZeroVerifier();
        verifier = new ProductionBeliefAttestationVerifier(address(mockRiscZero), IMAGE_ID);
    }

    // Test: constructor validation
    function test_constructor_zeroVerifier() public { vm.expectRevert(ZeroAddress.selector); ... }
    function test_constructor_zeroImageId() public { vm.expectRevert(ZeroImageId.selector); ... }

    // Test: verifyProof (legacy 4-input path)
    function test_verifyProof_success() public {
        bytes32 beliefHash = keccak256("test_belief");
        address attester = address(0x1234);
        uint256 epoch = 1;
        uint256 moduleId = 42;

        bytes memory journal = abi.encode(beliefHash, attester, uint32(epoch), uint32(moduleId));
        bytes32 journalDigest = sha256(journal);
        mockRiscZero.setExpected(IMAGE_ID, journalDigest);

        uint256[] memory publicInputs = new uint256[](4);
        publicInputs[0] = uint256(beliefHash);
        publicInputs[1] = uint256(uint160(attester));
        publicInputs[2] = epoch;
        publicInputs[3] = moduleId;

        bool ok = verifier.verifyProof(hex"deadbeef", publicInputs);
        assertTrue(ok);
        assertTrue(verifier.hasAttestation(beliefHash, attester));
    }

    // Test: verifyAttestation (direct 6-field path)
    function test_verifyAttestation_success() public {
        bytes32 beliefHash = keccak256("belief");
        address attester = address(0xABCD);
        bytes memory journalData = abi.encode(
            beliefHash, attester,
            uint32(1), uint32(1), uint32(9000), uint64(block.timestamp)
        );
        bytes32 digest = sha256(journalData);
        mockRiscZero.setExpected(IMAGE_ID, digest);

        bool ok = verifier.verifyAttestation(hex"aabbcc", journalData);
        assertTrue(ok);
    }

    // Test: proof failure returns false
    function test_verifyAttestation_proofFailure_returnsFalse() public {
        mockRiscZero.setShouldRevert(true);
        bytes memory journal = abi.encode(...);
        bool ok = verifier.verifyAttestation(hex"aabbcc", journal);
        assertFalse(ok);
    }

    // Test: timelock
    function test_proposeAndExecuteImageIdChange() public {
        bytes32 newId = bytes32(uint256(0xcafebabe));
        verifier.proposeImageIdChange(newId);
        vm.expectRevert(TimelockNotExpired.selector);
        verifier.executeImageIdChange();
        vm.warp(block.timestamp + 48 hours + 1);
        verifier.executeImageIdChange();
        assertEq(verifier.imageId(), newId);
    }

    function test_cancelImageIdChange() public {
        bytes32 newId = bytes32(uint256(0xcafebabe));
        verifier.proposeImageIdChange(newId);
        verifier.cancelImageIdChange();
        assertEq(verifier.pendingImageId(), bytes32(0));
    }
}
```

### Line-by-Line Walkthrough

| Test | What It Verifies |
|------|-----------------|
| `test_constructor_zeroVerifier` | `ZeroAddress` revert when `_riscZeroVerifier == address(0)`. |
| `test_constructor_zeroImageId` | `ZeroImageId` revert when `_imageId == bytes32(0)`. |
| `test_verifyProof_success` | Full happy path: journal reconstructed as 4-field, digest matches, `hasAttestation` set, returns `true`. |
| `test_verifyProof_wrongInputCount` | `InvalidPublicInputsCount` revert for arrays ≠ 4 elements. |
| `test_verifyAttestation_success` | 6-field journal decoded, `hasAttestation` set, `attestationCount` incremented. |
| `test_verifyAttestation_proofFailure_returnsFalse` | `shouldRevert = true` → mock reverts → `verifyAttestation` returns `false`. |
| `test_proposeAndExecuteImageIdChange` | Timelock: execute before 48h reverts; after 48h succeeds; `imageId` updated. |
| `test_cancelImageIdChange` | Cancellation clears `pendingImageId` and `pendingImageIdEffectiveAt`. |
| `test_transferOwnership` | Ownership transferred; old owner loses access. |

### Key Technical Notes

The embedded `MockRiscZeroVerifier` in this file implements the **Apache-2.0 revert-on-failure** interface (no return value from `verify`), matching the `IRiscZeroVerifier` at §5. This is distinct from the `MockRiscZeroVerifier` at §39 which returns `bool`. The `theloopbreaker-site/risc0/src/ProductionBeliefAttestationVerifier.sol` uses `try riscZeroVerifier.verify(...)` without capturing a return value — consistent with the revert-on-failure interface.

### Security Invariants Tested

1. Constructor zero-address guards.
2. Journal digest construction using `sha256` (not `keccak256`).
3. 4-element vs 6-element input count validation.
4. Silent `false` return on proof failure (via `try/catch`).
5. 48-hour timelock enforcement.
6. Single pending proposal guard (`ImageIdAlreadyPending`).

### Dependencies + Rebuild Recipe

```bash
forge test --match-path theloopbreaker-site/risc0/test/ProductionBeliefAttestationVerifier.t.sol -vvv
```

---

## Cross-Contract Compatibility Matrix

The following table maps each attestor variant to compatible verifier variants based on `PUBLIC_INPUTS_COUNT`:

| Attestor | publicInputs length | Compatible Verifiers |
|----------|--------------------|--------------------|
| DilithiumAttestor V1 (§7) | 4 | ProductionBeliefAttestationVerifier V2 (§14, §15), BeliefAttestationVerifierProduction (§14), BeliefAttestationVerifier DEV (§13), risc0/src/Production (§20) |
| DilithiumAttestor mini-app (§8) | N/A (direct IRiscZeroVerifier) | Directly calls riscZeroVerifier — not IStarkVerifier |
| DilithiumAttestorV3 v3/ staging (§10) | 4 | Same as V1 verifiers |
| DilithiumAttestorV3 v3-pr-ready (§11) | 4 | Same as V1 verifiers |
| DilithiumAttestorV3 vf-audit / push (§9, §12) | **6** | ProductionBeliefAttestationVerifierV3 vf-audit (§16), push version (§19) |

**Critical incompatibility:** Pairing `DilithiumAttestorV3` (vf-audit, §9) with `ProductionBeliefAttestationVerifierV3` from the `/v3/` or `/v3-pr-ready/` staging directories (§17, §18) will cause all proof submissions to revert with `InvalidPublicInputsCount` because those staging verifiers expect 4 inputs but receive 6.

---

## Dilithium Attestation — Quantum Resistance Context

The name "Dilithium" refers to the CRYSTALS-Dilithium lattice-based digital signature scheme, a NIST post-quantum cryptography standard (FIPS 204, 2024). However, in the Vaultfire codebase, the `DilithiumAttestor` contracts do not implement the Dilithium lattice signature scheme on-chain. Instead they:

1. Use ECDSA (classical elliptic curve) for the off-chain signer authorization check.
2. Delegate quantum-relevant computation to the RISC Zero zkVM guest program.
3. The "Dilithium" name signals **intent** — the system architecture positions the RISC Zero guest as the location where post-quantum Dilithium signature verification would be implemented, since the zkVM can execute arbitrary Rust code including CRYSTALS-Dilithium libraries (`pqcrypto` crate).

**What CRYSTALS-Dilithium provides:**
- Module-lattice-based signatures (MLWE/MSIS security assumptions).
- Security relies on the hardness of the Module Learning With Errors problem, believed resistant to Shor's algorithm.
- Signature sizes: ~2420 bytes (Dilithium2), ~3293 bytes (Dilithium3), ~4595 bytes (Dilithium5).
- Verification time: ~microseconds on modern hardware.

**Why it matters for the architecture:**
If ECDSA (secp256k1) is broken by a sufficiently powerful quantum computer, the `signer` authorization check in `DilithiumAttestor` contracts would be compromised. Moving the signer check into the zkVM guest (running Dilithium) and verifying the Groth16-wrapped STARK on-chain would make the entire attestation pipeline quantum-resistant without changing the on-chain Solidity code.

**Current state:** The Vaultfire contracts use ECDSA for the signer step and RISC Zero Groth16 for the ZK step. The Groth16 proof system itself relies on bilinear pairings (BN254 curve) which are also vulnerable to quantum attacks. A fully quantum-resistant deployment would require switching to a post-quantum SNARK scheme (e.g., FRI-based STARKs with appropriate hash functions, or lattice-based proofs). This is an acknowledged future roadmap item.

---

## Version Lineage Summary

```
V1 Family:
  contracts/DilithiumAttestor.sol          (global mapping, no msg.sender bind)
  contracts/BeliefOracle.sol               (global bonusApplied, unbounded batch)
  contracts/MultiOracleConsensus.sol       (recursive sort, O(n) dup check)
  contracts/FlourishingMetricsOracle.sol   (round-based, no deviation filter)
  contracts/ProductionBeliefAttestationVerifier.sol  (4 inputs, no timelock)

V2 Family:
  contracts/BeliefAttestationVerifierProduction.sol  (sha256 fix, immutable, no timelock)
  contracts/ProductionBeliefAttestationVerifier.sol  (4 inputs, mutable imageId, no timelock)

V3 Staging:
  v3/DilithiumAttestorV3.sol               (per-user, msg.sender bind, 4 inputs)
  v3/BeliefOracleV3.sol                    (per-user bonus, MIN_BELIEF_COUNT, CEI)
  v3/ProductionBeliefAttestationVerifierV3.sol  (4 inputs — mismatch with audit)

V3 PR-Ready:
  v3-pr-ready/contracts/v3/DilithiumAttestorV3.sol      (4 inputs, different import)
  v3-pr-ready/contracts/v3/BeliefOracleV3.sol            (bonusApplied before try — HIGH-01 unfixed)
  v3-pr-ready/contracts/v3/MultiOracleConsensusV3.sol    (all HIGH/MEDIUM fixes)
  v3-pr-ready/contracts/v3/FlourishingMetricsOracleV3.sol (round-based, full deviation filter)

V3 Audit (vf-audit-contracts/):
  DilithiumAttestorV3.sol       ← 6 inputs, full CRIT-07 fix
  BeliefOracleV3.sol            ← HIGH-01 + HIGH-21 two-step guardian
  MultiOracleConsensusV3.sol    ← commit-reveal, all fixes
  FlourishingMetricsOracleV3.sol ← CRIT-10 cooldown, HIGH-13 weighted mean
  ProductionBeliefAttestationVerifierV3.sol ← 6 inputs, epoch-aware, timelock, revert

V3 Final Push (push_contracts_v3_*):
  DilithiumAttestorV3.sol       ← same as audit
  BeliefOracleV3.sol            ← same as audit (minus two-step guardian)
  MultiOracleConsensusV3.sol    ← same as audit
  FlourishingMetricsOracleV3.sol ← same as audit
  ProductionBeliefAttestationVerifierV3.sol ← same as audit

Standalone / Alternative:
  theloopbreaker-site/risc0/src/ProductionBeliefAttestationVerifier.sol
    ← 4 inputs, 6-field verifyAttestation, 48hr timelock, returns false on catch
  theloopbreaker-site/base-mini-app/contracts/DilithiumAttestor.sol
    ← direct IRiscZeroVerifier, zkEnabled toggle, per-user timestamp
  contracts/BaseOracle.sol
    ← CID/zkHash event emitter, immutable guardian
  theloopbreaker-site/contracts/VaultfireTrustOracle.sol
    ← off-chain performance attestations, EMA score
  contracts/BeliefAttestationVerifier.sol
    ← DEV ONLY, chain ID guard
```

---

*End of Vaultfire Codex — Batch 4: Belief / Attestation / Oracle / ZK-Proof Contracts*

*Document covers 40 contracts. Total contracts audited: interfaces (6), attestors (6), verifiers (8), belief oracles (5), multi-oracle consensus (5), flourishing metrics oracles (6), base oracles (2), trust oracle (1), mocks/tests (2).*
