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..a1ed726 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,11 @@ +TradeCipher MVP - Agent Overview + +- Architecture: Python-based MVP with a clean separation between data contracts, a tamper-evident ledger, and a placeholder ZK proof generator. +- Tech stack: Python 3.8+, dataclasses, in-memory ledger, JSON serialization. No external databases required for MVP. +- Testing: PyTest test suite covering contracts, ledger, and ZK proof placeholder. +- How to run tests: ./test.sh +- Publishing: when ready, follow the publishing steps in the repo's instructions and ensure READY_TO_PUBLISH is created. + +Notes: +- This is a minimal interoperable skeleton designed to be extended into a full cross-venue MVP. +- Additional adapters and contract registries can be added under adapters/ and contracts/ respectively. diff --git a/README.md b/README.md index c2762ee..b825d4e 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,19 @@ # tradecipher-blockchain-backed-privacy-pr -A modular, open-source platform enabling end-to-end trade lifecycle auditing across venues (options, equities) with cryptographic provenance, zero-knowledge privacy, and offline-first capabilities. The system anchors attestations of orders, fills, an \ No newline at end of file +A modular, open-source platform enabling end-to-end trade lifecycle auditing across venues (options, equities) with cryptographic provenance, zero-knowledge privacy, and offline-first capabilities. The system anchors attestations of orders, fills, and post-trade events to a tamper-evident ledger while keeping sensitive payloads off-chain. + +This repository provides a minimal MVP implementation in Python to satisfy tests and demonstrate the core concepts (contracts, a tamper-evident ledger, and placeholder ZK proofs). See AGENTS.md for contribution guidance and the READY_TO_PUBLISH signal when ready to publish. + +## How to run (local MVP) +- Install Python 3.8+ +- Install build tooling: "python3 -m pip install build" +- Run tests: ./test.sh +- Build package: python3 -m build + +## Project layout +- tradecipher_blockchain_backed_privacy_pr/: Python package with contracts, ledger, zk_proofs, and adapters +- tests/: pytest test suite for core MVP +- pyproject.toml: packaging metadata for the MVP package tradecipher_blockchain_backed_privacy_pr +- AGENTS.md: architecture and contribution guidelines +- test.sh: test runner that executes tests and packaging build +- README.md: this file diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..636af42 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,15 @@ +[build-system] +requires = ["setuptools>=61.0", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "tradecipher_blockchain_backed_privacy_pr" +version = "0.1.0" +dependencies = [] +description = "MVP: blockchain-backed, privacy-preserving trade lifecycle auditing across venues" +readme = "README.md" +requires-python = ">=3.8" + +[tool.setuptools.packages.find] +where = ["."] +include = ["tradecipher_blockchain_backed_privacy_pr"] diff --git a/test.sh b/test.sh new file mode 100644 index 0000000..f9c324a --- /dev/null +++ b/test.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -euo pipefail + +echo "Running tests..." +export PYTHONPATH="/workspace/repo:${PYTHONPATH:-}" +pytest -q + +echo "Building package..." +python3 -m build + +echo "ALL TESTS PASSED and BUILD SUCCEEDED" +echo "READY_TO_PUBLISH_MARKER" diff --git a/tests/test_contracts.py b/tests/test_contracts.py new file mode 100644 index 0000000..a08e881 --- /dev/null +++ b/tests/test_contracts.py @@ -0,0 +1,28 @@ +import json +from tradecipher_blockchain_backed_privacy_pr.contracts import LocalTrade, SharedSignals, PlanDelta + + +def test_local_trade_json_roundtrip(): + lt = LocalTrade( + trade_id="T1", + product_id="P-XYZ", + venue="VenueA", + price=123.45, + size=10, + side="buy", + timestamp=1620000000.0, + signer_id="Signer1", + signature="sig123", + ) + j = lt.to_json() + # Ensure it's valid JSON and contains expected fields + obj = json.loads(j) + assert isinstance(obj, dict) + assert obj["trade_id"] == "T1" + + +def test_shared_signals_and_plan_delta_structure(): + ss = SharedSignals(signals_version=1, aggregated_risk=0.5, margin_impact=0.1, liquidity_metric=0.9) + pd = PlanDelta(delta_version=1, timestamp=1620000001.0, allocations={"T1": 0.5}, cryptographic_tag="tag") + assert isinstance(ss.to_json(), str) + assert isinstance(pd.to_json(), str) diff --git a/tests/test_ledger.py b/tests/test_ledger.py new file mode 100644 index 0000000..652868c --- /dev/null +++ b/tests/test_ledger.py @@ -0,0 +1,10 @@ +from tradecipher_blockchain_backed_privacy_pr.ledger import AttestationLedger +import json + + +def test_ledger_append_and_latest(): + ledger = AttestationLedger() + payload = json.dumps({"event": "test"}) + block = ledger.append(payload) + assert block.index == 0 + assert ledger.latest() is block diff --git a/tests/test_zkp.py b/tests/test_zkp.py new file mode 100644 index 0000000..c993a8b --- /dev/null +++ b/tests/test_zkp.py @@ -0,0 +1,10 @@ +from tradecipher_blockchain_backed_privacy_pr.zk_proofs import generate_zk_proof + + +def test_generate_zk_proof_structure(): + data = {"trade_id": "T1"} + constraints = {"price_band": [100, 200]} + proof = generate_zk_proof(data, constraints) + assert isinstance(proof, dict) + assert proof.get("valid") is True + assert "proof" in proof diff --git a/tradecipher_blockchain_backed_privacy_pr/__init__.py b/tradecipher_blockchain_backed_privacy_pr/__init__.py new file mode 100644 index 0000000..8efc819 --- /dev/null +++ b/tradecipher_blockchain_backed_privacy_pr/__init__.py @@ -0,0 +1,14 @@ +"""TradeCipher MVP package + +A lightweight reference implementation for LocalTrade / SharedSignals / PlanDelta +contracts, a tamper-evident in-memory attestation ledger, and a placeholder +Zero-Knowledge proof generator. This is intentionally minimal to satisfy the +tests and provide a concrete starting point for further work. +""" + +__all__ = [ + "contracts", + "ledger", + "zk_proofs", + "adapters", +] diff --git a/tradecipher_blockchain_backed_privacy_pr/adapters.py b/tradecipher_blockchain_backed_privacy_pr/adapters.py new file mode 100644 index 0000000..72d69cd --- /dev/null +++ b/tradecipher_blockchain_backed_privacy_pr/adapters.py @@ -0,0 +1,18 @@ +from __future__ import annotations + +"""Tiny adapters registry for two-venue MVP. + +This module is deliberately small; it provides a stub registry that could be +extended to map LocalTrade -> canonical representation and to register +simple venue adapters. It demonstrates the intended extension point. +""" + +ADAPTER_REGISTRY = {} + + +def register_adapter(name: str, adapter: object) -> None: + ADAPTER_REGISTRY[name] = adapter + + +def get_adapter(name: str): + return ADAPTER_REGISTRY.get(name) diff --git a/tradecipher_blockchain_backed_privacy_pr/contracts.py b/tradecipher_blockchain_backed_privacy_pr/contracts.py new file mode 100644 index 0000000..fcd9668 --- /dev/null +++ b/tradecipher_blockchain_backed_privacy_pr/contracts.py @@ -0,0 +1,77 @@ +from __future__ import annotations +from dataclasses import dataclass, asdict +from typing import Optional +import json + + +@dataclass +class LocalTrade: + trade_id: str + product_id: str + venue: str + price: float + size: int + side: str # 'buy' or 'sell' + timestamp: float + signer_id: str + signature: str + + def to_json(self) -> str: + return json.dumps(asdict(self), sort_keys=True) + + +@dataclass +class SharedSignals: + signals_version: int + aggregated_risk: float # placeholder for aggregated risk metric + margin_impact: float + liquidity_metric: float + + def to_json(self) -> str: + return json.dumps(asdict(self), sort_keys=True) + + +@dataclass +class PlanDelta: + delta_version: int + timestamp: float + allocations: dict # mapping from trade_id to new allocation + cryptographic_tag: str + + def to_json(self) -> str: + return json.dumps(asdict(self), sort_keys=True) + + +@dataclass +class PrivacyBudget: + budget_id: str + max_privacy_leakage: float + spent_privacy: float + + def to_json(self) -> str: + return json.dumps(asdict(self), sort_keys=True) + + +@dataclass +class AuditLog: + event_id: str + timestamp: float + actor: str + action: str + details: Optional[str] = None + + def to_json(self) -> str: + return json.dumps(asdict(self), sort_keys=True) + + +@dataclass +class ComplianceEvent: + event_id: str + venue: str + constraint: str + violated: bool + timestamp: float + details: Optional[str] = None + + def to_json(self) -> str: + return json.dumps(asdict(self), sort_keys=True) diff --git a/tradecipher_blockchain_backed_privacy_pr/ledger.py b/tradecipher_blockchain_backed_privacy_pr/ledger.py new file mode 100644 index 0000000..13849c9 --- /dev/null +++ b/tradecipher_blockchain_backed_privacy_pr/ledger.py @@ -0,0 +1,55 @@ +from __future__ import annotations +from dataclasses import dataclass, field +from typing import List, Optional +import hashlib +import time +import json + + +def _hash(data: str) -> str: + return hashlib.sha256(data.encode("utf-8")).hexdigest() + + +@dataclass +class Block: + index: int + timestamp: float + payload: str # JSON-serialized contract data or attestation + previous_hash: str + hash: str = field(init=False) + + def __post_init__(self): + self.hash = _hash(self._block_string()) + + def _block_string(self) -> str: + return f"{self.index}:{self.timestamp}:{self.payload}:{self.previous_hash}" + + +class AttestationLedger: + def __init__(self): + self._chain: List[Block] = [] + self._current_previous_hash: str = "0" * 64 + + def append(self, payload_json: str) -> Block: + index = len(self._chain) + block = Block( + index=index, + timestamp=time.time(), + payload=payload_json, + previous_hash=self._current_previous_hash, + ) + self._chain.append(block) + self._current_previous_hash = block.hash + return block + + def latest(self) -> Optional[Block]: + return self._chain[-1] if self._chain else None + + def to_json(self) -> str: + return json.dumps([{ + "index": b.index, + "timestamp": b.timestamp, + "payload": b.payload, + "previous_hash": b.previous_hash, + "hash": b.hash, + } for b in self._chain], sort_keys=True) diff --git a/tradecipher_blockchain_backed_privacy_pr/zk_proofs.py b/tradecipher_blockchain_backed_privacy_pr/zk_proofs.py new file mode 100644 index 0000000..fb0a955 --- /dev/null +++ b/tradecipher_blockchain_backed_privacy_pr/zk_proofs.py @@ -0,0 +1,20 @@ +from __future__ import annotations +from typing import Dict + + +def generate_zk_proof(data: dict, constraints: dict) -> Dict[str, object]: + """Placeholder ZK proof generator. + +This MVP uses a deterministic, non-secure stub that returns a structure +compatible with a real ZK proof's interface. The real implementation should +produce a succinct proof that the given data satisfies the constraints without +revealing the underlying data. +""" + # Very naive: hash of inputs to simulate a proof id, and a dummy proof artifact + probe = { + "proof_id": hash(str(data) + str(constraints)), + "proof": "dummy-zk-proof", + "valid": True, + "revealed": None, + } + return probe