diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..bd5590b --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +node_modules/ +.npmrc +.env +.env.* +__tests__/ +coverage/ +.nyc_output/ +dist/ +build/ +.cache/ +*.log +.DS_Store +tmp/ +.tmp/ +__pycache__/ +*.pyc +.venv/ +venv/ +*.egg-info/ +.pytest_cache/ +READY_TO_PUBLISH diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..dee6a16 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,37 @@ +# AGENTS.md + +## Repository Shape + +- `src/idea109_secureex_hardware_accelerated/`: package code. +- `tests/`: unit tests. +- `test.sh`: canonical verification entrypoint. +- `README.md`: architecture and usage. + +## Tech Stack + +- Python 3.10+ +- Standard library only for runtime code. +- SQLite for the governance ledger. +- Setuptools for packaging. + +## Architecture + +- `primitives.py`: canonical trading and governance dataclasses. +- `adapters.py`: FIX and WebSocket-to-canonical adapters. +- `enclave.py`: deterministic enclave execution and attestation model. +- `governance.py`: append-only, hash-chained SQLite ledger. +- `delta_sync.py`: deterministic reconciliation helpers. +- `replay.py`: offline deterministic replay harness and CLI. + +## Rules + +- Keep new logic deterministic unless explicit randomness is required and seeded. +- Preserve the hash chain in the governance ledger. +- Do not weaken validation in adapters or attestation verification. +- Prefer the smallest correct change. +- Update tests when changing canonical payload shapes or replay behavior. + +## Verification + +- `bash test.sh` +- The script must run unit tests and `python3 -m build`. diff --git a/README.md b/README.md index 73d7b58..4d5ecd8 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,86 @@ -# idea109-secureex-hardware-accelerated +# SecureEx -Source logic for Idea #109 \ No newline at end of file +SecureEx is a deterministic compliance and replay core for cross-venue trading workflows. + +It provides: + +- Canonical market primitives for venue normalization. +- FIX and WebSocket adapters. +- A deterministic enclave execution model with attestation. +- A SQLite-backed hash-chained governance ledger. +- Delta reconciliation helpers for disconnect recovery. +- An offline replay harness for regression testing. + +## What This Repository Does + +This codebase models the minimum trusted control plane needed for a SecureEx-style system: + +- normalize market data into `LocalMarket` +- aggregate it into `SharedSignals` +- produce a deterministic `PlanDelta` +- attest the enclave measurement and decision path +- persist the decision trail into an auditable SQLite ledger +- replay the same inputs deterministically after a disconnect + +## Architecture + +### Canonical primitives + +- `LocalMarket`: venue-level bid/ask state. +- `SharedSignals`: aggregated cross-venue signal output. +- `PlanDelta`: the next hedge or quote action. +- `PrivacyBudget`: guards signal leakage. +- `AuditLogEntry`: the attested event record. + +### Adapters + +- `FixAdapter`: parses FIX-style tag/value payloads. +- `WebSocketAdapter`: parses JSON market updates. + +### Enclave and attestation + +- `SecureEnclave.compute()` is deterministic for a given market set. +- `SecureEnclave.attest()` returns a signed measurement report. +- `verify_attestation()` checks the report signature and measurement. + +### Governance ledger + +- `GovernanceLedger` stores an append-only hash chain in SQLite. +- `verify_chain()` recomputes the full chain for audit validation. + +### Delta sync and replay + +- `DeltaSync` tracks sequence progress per venue and asset. +- `reconcile_markets()` computes the deterministic recovery delta. +- `replay.py` can run a local replay from a JSON event file. + +## Install + +```bash +python3 -m pip install -e . +``` + +## Run Tests + +```bash +bash test.sh +``` + +## Replay CLI + +```bash +secureex-replay --events events.json +``` + +Example `events.json`: + +```json +[ + {"adapter": "fix", "venue": "VENUE_A", "message": "55=ABC|132=10.1|133=10.2|134=100|135=120|34=7|52=1.5"}, + {"adapter": "ws", "venue": "VENUE_B", "message": {"asset": "ABC", "bid": 10.0, "ask": 10.3, "bid_size": 80, "ask_size": 60, "sequence": 8, "timestamp": 1.7}} +] +``` + +## Packaging + +The package name is `idea109-secureex-hardware-accelerated` and the long description is sourced from this `README.md`. diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..9d97755 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,21 @@ +[build-system] +requires = ["setuptools>=68", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "idea109-secureex-hardware-accelerated" +version = "0.1.0" +description = "SecureEx: a verifiable compliance and deterministic replay engine for cross-venue trading" +readme = "README.md" +requires-python = ">=3.10" +license = "MIT" +authors = [{name = "OpenCode"}] + +[project.scripts] +secureex-replay = "idea109_secureex_hardware_accelerated.replay:main" + +[tool.setuptools] +package-dir = {"" = "src"} + +[tool.setuptools.packages.find] +where = ["src"] diff --git a/src/idea109_secureex_hardware_accelerated/__init__.py b/src/idea109_secureex_hardware_accelerated/__init__.py new file mode 100644 index 0000000..41757bc --- /dev/null +++ b/src/idea109_secureex_hardware_accelerated/__init__.py @@ -0,0 +1,28 @@ +"""SecureEx core package.""" + +from .adapters import FixAdapter, WebSocketAdapter +from .delta_sync import DeltaSync, reconcile_markets +from .enclave import SecureEnclave, verify_attestation +from .governance import GovernanceLedger +from .primitives import ( + AuditLogEntry, + LocalMarket, + PlanDelta, + PrivacyBudget, + SharedSignals, +) + +__all__ = [ + "AuditLogEntry", + "DeltaSync", + "FixAdapter", + "GovernanceLedger", + "LocalMarket", + "PlanDelta", + "PrivacyBudget", + "SecureEnclave", + "SharedSignals", + "WebSocketAdapter", + "reconcile_markets", + "verify_attestation", +] diff --git a/src/idea109_secureex_hardware_accelerated/adapters.py b/src/idea109_secureex_hardware_accelerated/adapters.py new file mode 100644 index 0000000..7b17c42 --- /dev/null +++ b/src/idea109_secureex_hardware_accelerated/adapters.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +import json +from dataclasses import dataclass +from collections.abc import Mapping + +from .primitives import LocalMarket + + +def _coerce_float(value: object) -> float: + if isinstance(value, (str, int, float)): + return float(value) + raise TypeError(f"cannot coerce {type(value)!r} to float") + + +def _coerce_int(value: object) -> int: + if isinstance(value, (str, int, float)): + return int(float(value)) + raise TypeError(f"cannot coerce {type(value)!r} to int") + + +@dataclass(frozen=True, slots=True) +class FixAdapter: + venue: str + + def parse_market(self, message: str) -> LocalMarket: + fields: dict[str, str] = {} + for pair in message.split("|"): + if not pair: + continue + if "=" not in pair: + continue + key, value = pair.split("=", 1) + fields[key] = value + + return LocalMarket( + venue=self.venue, + asset=fields.get("55", fields.get("symbol", "UNKNOWN")), + bid=_coerce_float(fields.get("132", fields.get("bid", 0.0))), + ask=_coerce_float(fields.get("133", fields.get("ask", 0.0))), + bid_size=_coerce_float(fields.get("134", fields.get("bid_size", 0.0))), + ask_size=_coerce_float(fields.get("135", fields.get("ask_size", 0.0))), + sequence=_coerce_int(fields.get("34", fields.get("sequence", 0))), + timestamp=_coerce_float(fields.get("52", fields.get("timestamp", 0.0))), + ) + + +@dataclass(frozen=True, slots=True) +class WebSocketAdapter: + venue: str + + def parse_market(self, message: str | Mapping[str, object]) -> LocalMarket: + payload = json.loads(message) if isinstance(message, str) else dict(message) + return LocalMarket( + venue=self.venue, + asset=str(payload.get("asset", payload.get("symbol", "UNKNOWN"))), + bid=_coerce_float(payload.get("bid", 0.0)), + ask=_coerce_float(payload.get("ask", 0.0)), + bid_size=_coerce_float(payload.get("bid_size", payload.get("bidSize", 0.0))), + ask_size=_coerce_float(payload.get("ask_size", payload.get("askSize", 0.0))), + sequence=_coerce_int(payload.get("sequence", payload.get("seq", 0))), + timestamp=_coerce_float(payload.get("timestamp", 0.0)), + ) diff --git a/src/idea109_secureex_hardware_accelerated/delta_sync.py b/src/idea109_secureex_hardware_accelerated/delta_sync.py new file mode 100644 index 0000000..3bc4309 --- /dev/null +++ b/src/idea109_secureex_hardware_accelerated/delta_sync.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from dataclasses import dataclass +from typing import Iterable, Sequence + +from .primitives import LocalMarket + + +@dataclass(frozen=True, slots=True) +class MarketDelta: + asset: str + venue: str + previous_sequence: int + current_sequence: int + bid: float + ask: float + + +class DeltaSync: + def __init__(self) -> None: + self._last_sequences: dict[tuple[str, str], int] = {} + + def snapshot(self, markets: Sequence[LocalMarket]) -> list[MarketDelta]: + deltas: list[MarketDelta] = [] + for market in sorted(markets, key=lambda m: (m.asset, m.venue, m.sequence)): + key = (market.asset, market.venue) + previous_sequence = self._last_sequences.get(key, 0) + if market.sequence != previous_sequence: + deltas.append( + MarketDelta( + asset=market.asset, + venue=market.venue, + previous_sequence=previous_sequence, + current_sequence=market.sequence, + bid=market.bid, + ask=market.ask, + ) + ) + self._last_sequences[key] = market.sequence + return deltas + + +def reconcile_markets(local: Sequence[LocalMarket], remote: Sequence[LocalMarket]) -> list[MarketDelta]: + remote_index = {(m.asset, m.venue): m for m in remote} + deltas: list[MarketDelta] = [] + for market in sorted(local, key=lambda m: (m.asset, m.venue, m.sequence)): + counterpart = remote_index.get((market.asset, market.venue)) + if counterpart is None or counterpart.sequence != market.sequence or counterpart.bid != market.bid or counterpart.ask != market.ask: + deltas.append( + MarketDelta( + asset=market.asset, + venue=market.venue, + previous_sequence=counterpart.sequence if counterpart else 0, + current_sequence=market.sequence, + bid=market.bid, + ask=market.ask, + ) + ) + return deltas diff --git a/src/idea109_secureex_hardware_accelerated/enclave.py b/src/idea109_secureex_hardware_accelerated/enclave.py new file mode 100644 index 0000000..6b5e801 --- /dev/null +++ b/src/idea109_secureex_hardware_accelerated/enclave.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +import hashlib +import hmac +import json +from dataclasses import dataclass +from typing import Iterable, Sequence + +from .primitives import LocalMarket, PlanDelta, PrivacyBudget, SharedSignals, EnclaveDecision + + +def _stable_json(value: object) -> str: + return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + +@dataclass(frozen=True, slots=True) +class AttestationReport: + measurement: str + nonce: str + signature: str + + +class SecureEnclave: + def __init__(self, enclave_id: str, secret: str, measurement: str | None = None) -> None: + self.enclave_id = enclave_id + self._secret = secret.encode("utf-8") + self.measurement = measurement or self._measure() + + def _measure(self) -> str: + return hashlib.sha256(f"{self.enclave_id}:{self.__class__.__name__}".encode("utf-8")).hexdigest() + + def attest(self, nonce: str) -> AttestationReport: + message = f"{self.measurement}:{nonce}:{self.enclave_id}".encode("utf-8") + signature = hmac.new(self._secret, message, hashlib.sha256).hexdigest() + return AttestationReport(measurement=self.measurement, nonce=nonce, signature=signature) + + def compute(self, markets: Sequence[LocalMarket], privacy_budget: PrivacyBudget | None = None) -> EnclaveDecision: + if not markets: + raise ValueError("markets are required") + + grouped: dict[str, list[LocalMarket]] = {} + for market in markets: + grouped.setdefault(market.asset, []).append(market) + + asset, asset_markets = sorted(grouped.items())[0] + bids = sum(m.bid * m.bid_size for m in asset_markets) + asks = sum(m.ask * m.ask_size for m in asset_markets) + depth = sum(m.bid_size + m.ask_size for m in asset_markets) + bid = min(m.bid for m in asset_markets) + ask = max(m.ask for m in asset_markets) + mid = sum(m.mid for m in asset_markets) / len(asset_markets) + spread = ask - bid + budget = privacy_budget or PrivacyBudget(total_units=8) + budget = budget.consume(1) + + signals = SharedSignals( + asset=asset, + mid=mid, + spread=spread, + venue_count=len({m.venue for m in asset_markets}), + aggregate_depth=depth, + privacy_budget_used=budget.used_units, + ) + + venue = sorted(asset_markets, key=lambda market: (market.ask, market.sequence, market.venue))[0].venue + side = "BUY" if bids < asks else "SELL" + quantity = max(1.0, round(depth / max(len(asset_markets), 1), 4)) + limit_price = round(mid + (0.01 if side == "BUY" else -0.01), 4) + plan_source = _stable_json({"asset": asset, "mid": mid, "spread": spread, "venue": venue, "side": side}) + plan_id = hashlib.sha256(plan_source.encode("utf-8")).hexdigest()[:16] + plan = PlanDelta( + plan_id=plan_id, + asset=asset, + venue=venue, + side=side, + quantity=quantity, + limit_price=limit_price, + rationale=f"cross-venue-deterministic-{side.lower()}", + sequence=max(m.sequence for m in asset_markets), + ) + return EnclaveDecision(signals=signals, plan=plan, privacy_budget=budget) + + +def verify_attestation(report: AttestationReport, expected_measurement: str, secret: str, enclave_id: str) -> bool: + message = f"{expected_measurement}:{report.nonce}:{enclave_id}".encode("utf-8") + expected_signature = hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest() + return report.measurement == expected_measurement and hmac.compare_digest(report.signature, expected_signature) diff --git a/src/idea109_secureex_hardware_accelerated/governance.py b/src/idea109_secureex_hardware_accelerated/governance.py new file mode 100644 index 0000000..55bcd88 --- /dev/null +++ b/src/idea109_secureex_hardware_accelerated/governance.py @@ -0,0 +1,140 @@ +from __future__ import annotations + +import hashlib +import json +import sqlite3 +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Iterable + +from .enclave import AttestationReport +from .primitives import AuditLogEntry + + +def _stable_json(value: Any) -> str: + return json.dumps(value, sort_keys=True, separators=(",", ":"), ensure_ascii=True) + + +@dataclass(frozen=True, slots=True) +class LedgerRecord: + sequence: int + entry_type: str + payload: dict[str, Any] + timestamp: float + previous_hash: str + entry_hash: str + attestation_measurement: str + attestation_nonce: str + attestation_signature: str + + +class GovernanceLedger: + def __init__(self, path: str | Path = ":memory:") -> None: + self.path = str(path) + self._conn = sqlite3.connect(self.path) + self._conn.row_factory = sqlite3.Row + self._initialize() + + def _initialize(self) -> None: + self._conn.execute( + """ + CREATE TABLE IF NOT EXISTS governance_log ( + sequence INTEGER PRIMARY KEY AUTOINCREMENT, + entry_type TEXT NOT NULL, + payload TEXT NOT NULL, + timestamp REAL NOT NULL, + previous_hash TEXT NOT NULL, + entry_hash TEXT NOT NULL, + attestation_measurement TEXT NOT NULL, + attestation_nonce TEXT NOT NULL, + attestation_signature TEXT NOT NULL + ) + """ + ) + self._conn.commit() + + def head_hash(self) -> str: + row = self._conn.execute( + "SELECT entry_hash FROM governance_log ORDER BY sequence DESC LIMIT 1" + ).fetchone() + return row["entry_hash"] if row else "GENESIS" + + def append(self, entry_type: str, payload: dict[str, Any], timestamp: float, attestation: AttestationReport) -> LedgerRecord: + previous_hash = self.head_hash() + body = _stable_json({ + "entry_type": entry_type, + "payload": payload, + "timestamp": timestamp, + "previous_hash": previous_hash, + "measurement": attestation.measurement, + "nonce": attestation.nonce, + "signature": attestation.signature, + }) + entry_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + cursor = self._conn.execute( + """ + INSERT INTO governance_log ( + entry_type, payload, timestamp, previous_hash, entry_hash, + attestation_measurement, attestation_nonce, attestation_signature + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?) + """, + ( + entry_type, + _stable_json(payload), + timestamp, + previous_hash, + entry_hash, + attestation.measurement, + attestation.nonce, + attestation.signature, + ), + ) + self._conn.commit() + return LedgerRecord( + sequence=int(cursor.lastrowid or 0), + entry_type=entry_type, + payload=payload, + timestamp=timestamp, + previous_hash=previous_hash, + entry_hash=entry_hash, + attestation_measurement=attestation.measurement, + attestation_nonce=attestation.nonce, + attestation_signature=attestation.signature, + ) + + def records(self) -> list[LedgerRecord]: + rows = self._conn.execute( + "SELECT * FROM governance_log ORDER BY sequence ASC" + ).fetchall() + return [ + LedgerRecord( + sequence=row["sequence"], + entry_type=row["entry_type"], + payload=json.loads(row["payload"]), + timestamp=row["timestamp"], + previous_hash=row["previous_hash"], + entry_hash=row["entry_hash"], + attestation_measurement=row["attestation_measurement"], + attestation_nonce=row["attestation_nonce"], + attestation_signature=row["attestation_signature"], + ) + for row in rows + ] + + def verify_chain(self) -> bool: + previous_hash = "GENESIS" + for record in self.records(): + body = _stable_json({ + "entry_type": record.entry_type, + "payload": record.payload, + "timestamp": record.timestamp, + "previous_hash": record.previous_hash, + "measurement": record.attestation_measurement, + "nonce": record.attestation_nonce, + "signature": record.attestation_signature, + }) + entry_hash = hashlib.sha256(body.encode("utf-8")).hexdigest() + if record.previous_hash != previous_hash or record.entry_hash != entry_hash: + return False + previous_hash = record.entry_hash + return True diff --git a/src/idea109_secureex_hardware_accelerated/primitives.py b/src/idea109_secureex_hardware_accelerated/primitives.py new file mode 100644 index 0000000..ba3b24c --- /dev/null +++ b/src/idea109_secureex_hardware_accelerated/primitives.py @@ -0,0 +1,76 @@ +from __future__ import annotations + +from dataclasses import dataclass, field, asdict +from typing import Any, Mapping + + +@dataclass(frozen=True, slots=True) +class LocalMarket: + venue: str + asset: str + bid: float + ask: float + bid_size: float = 0.0 + ask_size: float = 0.0 + sequence: int = 0 + timestamp: float = 0.0 + + @property + def mid(self) -> float: + return (self.bid + self.ask) / 2.0 + + +@dataclass(frozen=True, slots=True) +class SharedSignals: + asset: str + mid: float + spread: float + venue_count: int + aggregate_depth: float + privacy_budget_used: int = 0 + + +@dataclass(frozen=True, slots=True) +class PlanDelta: + plan_id: str + asset: str + venue: str + side: str + quantity: float + limit_price: float + rationale: str + sequence: int + + +@dataclass(frozen=True, slots=True) +class PrivacyBudget: + total_units: int + used_units: int = 0 + + def consume(self, units: int = 1) -> "PrivacyBudget": + new_used = self.used_units + units + if new_used > self.total_units: + raise ValueError("privacy budget exhausted") + return PrivacyBudget(total_units=self.total_units, used_units=new_used) + + +@dataclass(frozen=True, slots=True) +class AuditLogEntry: + entry_type: str + payload: Mapping[str, Any] + timestamp: float + previous_hash: str + entry_hash: str + attestation_measurement: str + attestation_nonce: str + attestation_signature: str + + def to_record(self) -> dict[str, Any]: + return asdict(self) + + +@dataclass(frozen=True, slots=True) +class EnclaveDecision: + signals: SharedSignals + plan: PlanDelta + privacy_budget: PrivacyBudget = field(default_factory=lambda: PrivacyBudget(total_units=1)) diff --git a/src/idea109_secureex_hardware_accelerated/replay.py b/src/idea109_secureex_hardware_accelerated/replay.py new file mode 100644 index 0000000..f1b74c9 --- /dev/null +++ b/src/idea109_secureex_hardware_accelerated/replay.py @@ -0,0 +1,74 @@ +from __future__ import annotations + +import argparse +import json +from dataclasses import asdict +from pathlib import Path +from collections.abc import Mapping +from typing import Iterable, cast + +from .adapters import FixAdapter, WebSocketAdapter +from .enclave import SecureEnclave, verify_attestation +from .governance import GovernanceLedger +from .primitives import LocalMarket + + +def replay_events(events: Iterable[dict[str, object]], *, enclave: SecureEnclave, ledger: GovernanceLedger) -> dict[str, object]: + markets: list[LocalMarket] = [] + for event in events: + adapter_type = str(event["adapter"]) + venue = str(event["venue"]) + message = event["message"] + if adapter_type == "fix": + market = FixAdapter(venue=venue).parse_market(str(message)) + elif adapter_type == "ws": + if isinstance(message, (str, Mapping)): + market = WebSocketAdapter(venue=venue).parse_market( + cast(str | Mapping[str, object], message) + ) + else: + raise ValueError("ws message must be JSON string or mapping") + else: + raise ValueError(f"unknown adapter: {adapter_type}") + markets.append(market) + + decision = enclave.compute(markets) + attestation = enclave.attest(nonce=f"replay-{decision.plan.plan_id}") + ledger.append( + "plan_delta", + {"signals": asdict(decision.signals), "plan": asdict(decision.plan)}, + timestamp=float(len(markets)), + attestation=attestation, + ) + return { + "signals": asdict(decision.signals), + "plan": asdict(decision.plan), + "attestation_verified": verify_attestation(attestation, enclave.measurement, enclave._secret.decode("utf-8"), enclave.enclave_id), + "ledger_head": ledger.head_hash(), + "ledger_consistent": ledger.verify_chain(), + } + + +def build_parser() -> argparse.ArgumentParser: + parser = argparse.ArgumentParser(description="Run a deterministic SecureEx replay") + parser.add_argument("--events", required=True, help="Path to JSON event file") + parser.add_argument("--ledger", default=":memory:", help="SQLite ledger path") + parser.add_argument("--enclave-id", default="edge-node-1") + parser.add_argument("--secret", default="secureex-demo-secret") + return parser + + +def main(argv: list[str] | None = None) -> int: + args = build_parser().parse_args(argv) + payload = json.loads(Path(args.events).read_text()) + if not isinstance(payload, list): + raise SystemExit("events file must contain a JSON array") + enclave = SecureEnclave(enclave_id=args.enclave_id, secret=args.secret) + ledger = GovernanceLedger(args.ledger) + result = replay_events(payload, enclave=enclave, ledger=ledger) + print(json.dumps(result, sort_keys=True, indent=2)) + return 0 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..7de3c5f --- /dev/null +++ b/test.sh @@ -0,0 +1,7 @@ +#!/usr/bin/env bash +set -euo pipefail + +export PYTHONPATH="src" +python3 -m pip install --quiet --upgrade build +python3 -m unittest discover -s tests -v +python3 -m build diff --git a/tests/test_secureex.py b/tests/test_secureex.py new file mode 100644 index 0000000..ecc6924 --- /dev/null +++ b/tests/test_secureex.py @@ -0,0 +1,58 @@ +from __future__ import annotations + +import json +import tempfile +import unittest +from pathlib import Path + +from idea109_secureex_hardware_accelerated.adapters import FixAdapter, WebSocketAdapter +from idea109_secureex_hardware_accelerated.delta_sync import DeltaSync, reconcile_markets +from idea109_secureex_hardware_accelerated.enclave import SecureEnclave, verify_attestation +from idea109_secureex_hardware_accelerated.governance import GovernanceLedger + + +class SecureExTests(unittest.TestCase): + def test_adapters_parse_canonical_markets(self) -> None: + fix_market = FixAdapter(venue="VENUE_A").parse_market("35=W|55=ABC|132=10.1|133=10.2|134=100|135=120|34=7|52=1.5") + ws_market = WebSocketAdapter(venue="VENUE_B").parse_market( + {"asset": "ABC", "bid": 10.0, "ask": 10.3, "bid_size": 80, "ask_size": 60, "sequence": 8, "timestamp": 1.7} + ) + self.assertEqual(fix_market.asset, "ABC") + self.assertEqual(ws_market.venue, "VENUE_B") + self.assertAlmostEqual(fix_market.mid, 10.15) + + def test_enclave_attestation_and_deterministic_decision(self) -> None: + markets = [ + FixAdapter(venue="VENUE_A").parse_market("55=ABC|132=10.1|133=10.2|134=100|135=120|34=7|52=1.5"), + WebSocketAdapter(venue="VENUE_B").parse_market("{\"asset\": \"ABC\", \"bid\": 10.0, \"ask\": 10.3, \"bid_size\": 80, \"ask_size\": 60, \"sequence\": 8, \"timestamp\": 1.7}") + ] + enclave = SecureEnclave(enclave_id="edge-node-1", secret="demo-secret") + decision1 = enclave.compute(markets) + decision2 = enclave.compute(markets) + self.assertEqual(decision1.plan, decision2.plan) + report = enclave.attest("nonce-1") + self.assertTrue(verify_attestation(report, enclave.measurement, "demo-secret", enclave.enclave_id)) + + def test_ledger_chain_and_replay(self) -> None: + with tempfile.TemporaryDirectory() as tmp: + ledger = GovernanceLedger(Path(tmp) / "ledger.sqlite3") + enclave = SecureEnclave(enclave_id="edge-node-1", secret="demo-secret") + report = enclave.attest("nonce-2") + ledger.append("plan_delta", {"plan_id": "x"}, 1.0, report) + self.assertTrue(ledger.verify_chain()) + + sync = DeltaSync() + markets = [ + FixAdapter(venue="VENUE_A").parse_market("55=ABC|132=10.1|133=10.2|134=100|135=120|34=7|52=1.5"), + WebSocketAdapter(venue="VENUE_B").parse_market({"asset": "ABC", "bid": 10.0, "ask": 10.3, "sequence": 8}), + ] + first = sync.snapshot(markets) + second = sync.snapshot(markets) + self.assertEqual(len(first), 2) + self.assertEqual(second, []) + delta = reconcile_markets(markets, markets[:1]) + self.assertEqual(len(delta), 1) + + +if __name__ == "__main__": + unittest.main()