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..5113695 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,32 @@ +# BeXProof Agent Guidelines + +Overview +- BeXProof is a modular verifier/enforcement stack for Best Execution in equity trading. The repository hosts a production-grade MVP intended for extension by follow-on agents. + +Tech Stack (initial MVP) +- Language: Python 3.9+ +- Core modules: policy DSL, verifiable logs, ledger, ZKP prototype, privacy toolkit, governance, adapters +- API surface (optional later): FastAPI for internal services; wiring can be added in future tasks. + +Testing & Build +- Tests are written with pytest. +- Packaging builds with setuptools via pyproject.toml/setup.py and python -m build. +- The repository includes test.sh that runs tests and packaging checks. + +How to contribute (high-level rules) +- Add small, well-scoped changes; prefer minimal, correct edits. +- Write tests for new functionality; ensure tests pass locally before proposing changes. +- Update AGENTS.md with any new architectural decisions that future agents must honor. +- Do not push to remote unless explicitly requested; the orchestration layer handles publishing steps. + +Repository rules +- Always start by examining the codebase with Glob and Grep (rg-based tooling). +- Use non-destructive edits; avoid altering unrelated files. +- Persist changes in this session; aim to complete a coherent feature chunk before moving to the next. + +Checklist for new features +- [ ] Design a cohesive public API for the feature +- [ ] Implement minimal viable integration points +- [ ] Add unit tests that exercise core logic +- [ ] Ensure packaging/test script runs cleanly +- [ ] Document how to deploy and test the feature locally diff --git a/README.md b/README.md index d744512..3247a13 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,20 @@ -# idea164-bexproof-verifiable-best +# BeXProof: Verifiable Best Execution and Compliance Verifier (Python MVP) -Source logic for Idea #164 \ No newline at end of file +BeXProof is a modular, open-source verifier and enforcement layer designed to accompany equity order routers and brokers. It guarantees and proves Best Execution while preserving data privacy, using a policy-driven DSL, verifiable routing logs, a ZKP-inspired proof substrate, and a tamper-evident ledger for auditable outcomes. + +What you will find in this repository +- A production-oriented Python MVP with a small, extensible architecture. +- Core primitives: policy DSL, verifiable routing logs, ZKP prototype, auditable ledger, adapters, privacy-preserving statistics, and governance. +- A test suite with basic unit tests for each primitive. +- A packaging and publishing readiness plan (AGENTS.md, READY_TO_PUBLISH). + +How to run locally +- Install dependencies and run tests via the included test script (test.sh). +- The MVP intentionally keeps crypto lightweight (HMAC-based signatures for demonstration) to enable fast iteration; replace with real cryptography when integrating into a production environment. + +This repository is organized to be production-friendly and test-driven from the start. + +Hooking into packaging +- This package is prepared for Python packaging under the name `idea164_bexproof_verifiable_best` as per the publishing requirements. + +Note: See AGENTS.md for architectural guidelines and how future agents should contribute. diff --git a/bexproof/__init__.py b/bexproof/__init__.py new file mode 100644 index 0000000..1b2ccbf --- /dev/null +++ b/bexproof/__init__.py @@ -0,0 +1,24 @@ +"""BeXProof Python package initializer.""" +from .logs import Signer, make_signed_log, verify_log +from .policy import Policy, load_policy, evaluate_policy +from .ledger import TamperProofLedger +from .zkp import generate_proof +from .adapters import BrokerGatewayAdapter, ExchangeGatewayAdapter +from .privacy import laplace_noise, privacy_aggregate +from .governance import Governance + +__all__ = [ + "Signer", + "make_signed_log", + "verify_log", + "Policy", + "load_policy", + "evaluate_policy", + "TamperProofLedger", + "generate_proof", + "BrokerGatewayAdapter", + "ExchangeGatewayAdapter", + "laplace_noise", + "privacy_aggregate", + "Governance", +] diff --git a/bexproof/adapters.py b/bexproof/adapters.py new file mode 100644 index 0000000..c27e74f --- /dev/null +++ b/bexproof/adapters.py @@ -0,0 +1,34 @@ +"""Lightweight adapters for broker and exchange gateways. + +These are minimal stubs that map internal decision signals to canonical signals +exposed to external systems. They are intentionally small MVP implementations. +""" +from __future__ import annotations +from typing import Dict, Any + + +class BrokerGatewayAdapter: + def __init__(self, config: Dict[str, Any] = None): + self.config = config or {} + + def adapt_order(self, order_event: Dict[str, Any]) -> Dict[str, Any]: + # Example mapping: extract a few key signals from an internal order event + return { + "type": "order_decision", + "order_id": order_event.get("order_id"), + "venue": order_event.get("venue"), + "signal": order_event.get("signal", "unknown"), + } + + +class ExchangeGatewayAdapter: + def __init__(self, config: Dict[str, Any] = None): + self.config = config or {} + + def adapt_execution(self, execution_event: Dict[str, Any]) -> Dict[str, Any]: + return { + "type": "execution_report", + "order_id": execution_event.get("order_id"), + "price": execution_event.get("price"), + "latency_ms": execution_event.get("latency_ms"), + } diff --git a/bexproof/governance.py b/bexproof/governance.py new file mode 100644 index 0000000..f6af775 --- /dev/null +++ b/bexproof/governance.py @@ -0,0 +1,28 @@ +"""Governance primitives for BeXProof policy changes.""" +from __future__ import annotations +from typing import Dict, Set + + +class Governance: + def __init__(self, signers: Set[str] = None, required: int = 2): + self.signers = set(signers or {"alice", "bob", "carol"}) + self.required = max(1, min(len(self.signers), required)) + self.proposals: Dict[str, Dict] = {} + self.approvals: Dict[str, Set[str]] = {} + + def propose(self, policy_id: str, policy_text: str) -> None: + self.proposals[policy_id] = {"policy": policy_text, "approved": False} + self.approvals[policy_id] = set() + + def approve(self, policy_id: str, signer: str) -> bool: + if signer not in self.signers: + return False + if policy_id not in self.proposals: + return False + self.approvals[policy_id].add(signer) + if len(self.approvals[policy_id]) >= self.required: + self.proposals[policy_id]["approved"] = True + return True + + def is_approved(self, policy_id: str) -> bool: + return bool(self.proposals.get(policy_id, {}).get("approved", False)) diff --git a/bexproof/ledger.py b/bexproof/ledger.py new file mode 100644 index 0000000..f64e343 --- /dev/null +++ b/bexproof/ledger.py @@ -0,0 +1,53 @@ +"""Tamper-evident ledger scaffold for BeXProof. + +This ledger appends entries with a simple hash-chain to provide an auditable trail. +It's a lightweight MVP; a production system would use a proper append-only store. +""" +from __future__ import annotations +import json +import time +import hashlib +from pathlib import Path +from typing import Dict, Any + + +class TamperProofLedger: + def __init__(self, path: str = "bexproof_ledger.log"): + self.path = Path(path) + self.path.parent.mkdir(parents=True, exist_ok=True) + self._last_hash = self._load_last_hash() + + def _load_last_hash(self) -> str: + if not self.path.exists(): + return "" # no previous hash + # read last line to pull last hash if present + last = None + with self.path.open("r", encoding="utf-8") as f: + for line in f: + last = line + if not last: + return "" + try: + obj = json.loads(last) + return obj.get("hash", "") + except Exception: + return "" + + def _hash_entry(self, entry: Dict[str, Any], prev_hash: str) -> str: + payload = json.dumps(entry, sort_keys=True).encode("utf-8") + data = payload + prev_hash.encode("utf-8") + return hashlib.sha256(data).hexdigest() + + def append(self, entry: Dict[str, Any]) -> Dict[str, Any]: + prev_hash = self._last_hash or "" + h = self._hash_entry(entry, prev_hash) + record = { + "entry": entry, + "timestamp": int(time.time() * 1000), + "prev_hash": prev_hash, + "hash": h, + } + with self.path.open("a", encoding="utf-8") as f: + f.write(json.dumps(record, sort_keys=True) + "\n") + self._last_hash = h + return record diff --git a/bexproof/logs.py b/bexproof/logs.py new file mode 100644 index 0000000..57cb95d --- /dev/null +++ b/bexproof/logs.py @@ -0,0 +1,39 @@ +"""Verifiable routing logs with lightweight signing. + +This module provides a Signer, log entry creator, and verifier. For demonstration, +signatures use HMAC-SHA256 with a secret key. Replace with proper public-key crypto +in production. +""" +from __future__ import annotations +import json +import time +import hmac +import hashlib + +class Signer: + def __init__(self, key: str): + self.key = key.encode("utf-8") + + def sign_payload(self, payload: dict) -> str: + data = json.dumps(payload, sort_keys=True).encode("utf-8") + return hmac.new(self.key, data, hashlib.sha256).hexdigest() + +def make_signed_log(order_id: str, venue: str, price: float, latency_ms: int, signer: Signer, timestamp: int | None = None) -> dict: + payload = { + "order_id": order_id, + "venue": venue, + "price": price, + "latency_ms": latency_ms, + "timestamp": timestamp or int(time.time() * 1000), + } + signature = signer.sign_payload(payload) + payload["signature"] = signature + return payload + +def verify_log(log: dict, signer: Signer) -> bool: + if "signature" not in log: + return False + sig = log["signature"] + payload = {k: v for k, v in log.items() if k != "signature"} + expected = signer.sign_payload(payload) + return hmac.compare_digest(sig, expected) diff --git a/bexproof/policy.py b/bexproof/policy.py new file mode 100644 index 0000000..b2aedd3 --- /dev/null +++ b/bexproof/policy.py @@ -0,0 +1,49 @@ +"""Simple policy DSL and evaluator for BeXProof. + +This module provides a lightweight policy container and a tiny evaluator. +Policies are represented as JSON-like strings for simplicity in this MVP. +In production, replace with a proper DSL parser and validator. +""" +from __future__ import annotations +import json +from dataclasses import dataclass +from typing import Dict, Any + + +@dataclass +class Policy: + version: int + rules: Dict[str, Any] + + +def load_policy(policy_text: str) -> Policy: + # Accept either JSON or Python-like dict string (with single quotes) + data = None + try: + data = json.loads(policy_text) + except Exception: + # try Python-style dict string + try: + data = json.loads(policy_text.replace("'", '"')) + except Exception: + raise ValueError("Policy text is not valid JSON or Python-like dict string") + if not isinstance(data, dict) or "version" not in data or "rules" not in data: + raise ValueError("Policy must contain 'version' and 'rules' keys") + return Policy(version=int(data["version"]), rules=data["rules"]) + + +def evaluate_policy(log: Dict[str, Any], policy: Policy) -> bool: + # Minimal evaluation: all top-level rules keys are checked if present in log + # This is a small MVP; in production, rules would be richer and more formal. + for key, thresh in policy.rules.items(): + if key == "price_improvement_min": + if log.get("price_improvement", 0) < float(thresh): + return False + elif key == "latency_budget_ms": + if log.get("latency_ms", float("inf")) > int(thresh): + return False + elif key == "slippage_max": + if log.get("slippage", float("inf")) > float(thresh): + return False + # additional rules can be added here + return True diff --git a/bexproof/privacy.py b/bexproof/privacy.py new file mode 100644 index 0000000..7c8af8e --- /dev/null +++ b/bexproof/privacy.py @@ -0,0 +1,25 @@ +"""Privacy-preserving statistics utilities. + +This module provides a simple differential-privacy-friendly aggregate function +by adding Laplace noise to the sum. The actual privacy guarantees are simplified +for MVP purposes and should be upgraded for production. +""" +from __future__ import annotations +import random +import math + + +def laplace_noise(scale: float) -> float: + # Inverse transform sampling for Laplace(0, scale) + u = random.uniform(-0.5, 0.5) + return scale * math.copysign(1.0, u) * math.log(1 - 2 * abs(u)) + + +def privacy_aggregate(values, epsilon: float) -> float: + if not values: + return 0.0 + true_sum = sum(values) + # Simple Laplace mechanism; scale = 1/epsilon + scale = 1.0 / max(epsilon, 1e-9) + noise = laplace_noise(scale) + return true_sum + noise diff --git a/bexproof/zkp.py b/bexproof/zkp.py new file mode 100644 index 0000000..5866706 --- /dev/null +++ b/bexproof/zkp.py @@ -0,0 +1,21 @@ +"""ZKP prototype for BeXProof. + +This is a lightweight stand-in for a real ZKP back-end. It produces a compact +certificate-like proof deterministically derived from the log entry and policy. +""" +from __future__ import annotations +import json +import hashlib +from .policy import Policy + + +def generate_proof(log_entry: dict, policy: Policy) -> dict: + # Produce a deterministic, compact proof representation (string hash) + seed = { + "log": log_entry, + "policy_version": policy.version, + "policy_rules": policy.rules, + } + blob = json.dumps(seed, sort_keys=True).encode("utf-8") + proof_hash = hashlib.sha256(blob).hexdigest() + return {"proof": f"zkp-{proof_hash}"} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7dcd205 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,22 @@ +[build-system] +requires = ["setuptools>=61", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "idea164_bexproof_verifiable_best" +version = "0.0.1" +description = "BeXProof: Verifiable Best Execution and Compliance Verifier for Equity Trading (Python MVP)" +readme = "README.md" +requires-python = ">=3.9" +license = { text = "MIT" } +authors = [ { name = "OpenCode SWARM" } ] +dependencies = [ + "setuptools>=61", + "wheel", + "pytest>=7", + "cryptography>=3.4; python_version >= '3.9'", + "pydantic>=1.9; python_version >= '3.9'", + "typing-extensions>=3.7; python_version < '3.11'", + "fastapi>=0.78; python_version >= '3.9'", + "uvicorn[standard]" # for potential API server in future +] diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..1df0d91 --- /dev/null +++ b/setup.py @@ -0,0 +1,10 @@ +from setuptools import setup, find_packages + +setup( + name="idea164_bexproof_verifiable_best", + version="0.0.1", + description="BeXProof: Verifiable Best Execution and Compliance Verifier for Equity Trading (Python MVP)", + packages=find_packages(exclude=("tests", "docs")), + python_requires=">=3.9", + include_package_data=True, +) diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..72edec3 --- /dev/null +++ b/test.sh @@ -0,0 +1,9 @@ +#!/bin/bash +set -euo pipefail +echo "Installing package in editable mode..." +python3 -m pip install -e . +echo "Running tests..." +pytest -q +echo "Building package..." +python3 -m build +echo "All tests passed and package built." diff --git a/tests/test_adapters.py b/tests/test_adapters.py new file mode 100644 index 0000000..bb8ea70 --- /dev/null +++ b/tests/test_adapters.py @@ -0,0 +1,6 @@ +def test_adapters_basic_mapping(): + from bexproof.adapters import BrokerGatewayAdapter, ExchangeGatewayAdapter + b = BrokerGatewayAdapter() + e = ExchangeGatewayAdapter() + assert isinstance(b.adapt_order({"order_id": "ORD1"}), dict) + assert isinstance(e.adapt_execution({"order_id": "ORD1"}), dict) diff --git a/tests/test_governance.py b/tests/test_governance.py new file mode 100644 index 0000000..8d7c825 --- /dev/null +++ b/tests/test_governance.py @@ -0,0 +1,10 @@ +from bexproof.governance import Governance + + +def test_governance_approvals(): + g = Governance(signers={"alice", "bob", "carol"}, required=2) + g.propose("p1", "policy content") + g.approve("p1", "alice") + assert g.is_approved("p1") is False + g.approve("p1", "bob") + assert g.is_approved("p1") is True diff --git a/tests/test_ledger.py b/tests/test_ledger.py new file mode 100644 index 0000000..87f62ac --- /dev/null +++ b/tests/test_ledger.py @@ -0,0 +1,14 @@ +import json +from pathlib import Path +from bexproof.ledger import TamperProofLedger + + +def test_ledger_append_and_hash(tmp_path: Path): + ledger_path = tmp_path / "ledger.log" + ledger = TamperProofLedger(str(ledger_path)) + entry1 = {"order_id": "ORD1", "venue": "VENUE1"} + rec1 = ledger.append(entry1) + assert "hash" in rec1 + entry2 = {"order_id": "ORD2", "venue": "VENUE2"} + rec2 = ledger.append(entry2) + assert rec2["prev_hash"] == rec1["hash"] diff --git a/tests/test_logs.py b/tests/test_logs.py new file mode 100644 index 0000000..dda5ff8 --- /dev/null +++ b/tests/test_logs.py @@ -0,0 +1,11 @@ +from bexproof.logs import Signer, make_signed_log, verify_log + + +def test_sign_and_verify(): + signer = Signer("supersecretkey") + log = make_signed_log("ORD1", "VENUE1", 101.0, 3, signer) + assert verify_log(log, signer) is True + # Tamper detection + log_bad = dict(log) + log_bad["price"] = 999.0 + assert verify_log(log_bad, signer) is False diff --git a/tests/test_policy.py b/tests/test_policy.py new file mode 100644 index 0000000..32ebc9c --- /dev/null +++ b/tests/test_policy.py @@ -0,0 +1,41 @@ +import json +import time +from bexproof.policy import Policy, load_policy, evaluate_policy + + +def test_policy_load_and_eval_pass(): + policy_text = json.dumps({ + "version": 1, + "rules": { + "price_improvement_min": 0.001, + "latency_budget_ms": 10, + }, + }) + policy = load_policy(policy_text) + log = { + "order_id": "ORD1", + "venue": "VENUE1", + "price": 100.5, + "latency_ms": 5, + "price_improvement": 0.002, + "timestamp": int(time.time() * 1000), + } + assert evaluate_policy(log, policy) is True + + +def test_policy_eval_fail_due_to_latency(): + policy_text = json.dumps({ + "version": 1, + "rules": { + "latency_budget_ms": 2, + }, + }) + policy = load_policy(policy_text) + log = { + "order_id": "ORD2", + "venue": "VENUE1", + "latency_ms": 5, + "price_improvement": 0.0005, + "timestamp": int(time.time() * 1000), + } + assert evaluate_policy(log, policy) is False diff --git a/tests/test_privacy.py b/tests/test_privacy.py new file mode 100644 index 0000000..6db2cd9 --- /dev/null +++ b/tests/test_privacy.py @@ -0,0 +1,9 @@ +from bexproof.privacy import privacy_aggregate + + +def test_privacy_aggregate_basic(): + values = [1.0, 2.0, 3.0, 4.0] + result = privacy_aggregate(values, epsilon=1.0) + # result should be within a plausible range around the true sum (10.0) + assert isinstance(result, float) + assert abs(result - 10.0) < 5.0 # allow some noise margin in MVP diff --git a/tests/test_proofs.py b/tests/test_proofs.py new file mode 100644 index 0000000..be545e0 --- /dev/null +++ b/tests/test_proofs.py @@ -0,0 +1,13 @@ +import json +from bexproof.policy import Policy +from bexproof.zkp import generate_proof + + +def test_generate_proof_consistency(): + policy = Policy(version=1, rules={"price_improvement_min": 0.001}) + log_entry = {"order_id": "ORD1", "venue": "VENUE1", "price": 100.0, "latency_ms": 5} + proof = generate_proof(log_entry, policy) + assert "proof" in proof + # Re-generating with same inputs should yield same proof + proof2 = generate_proof(log_entry, policy) + assert proof == proof2