Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 42 additions & 0 deletions .github/workflows/krylov.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Krylov application and browser lab

on:
push:
branches: [main]
pull_request:
workflow_dispatch:

permissions:
contents: read

jobs:
python:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.12'
- run: pip install numpy pytest -r requirements-ckks.txt
# These standalone NumPy tests do not use the legacy Cora fixtures.
- run: python -m pytest --noconftest -q tests/test_krylov.py
- run: python -m applications.krylov --backend plain --degree 2 --output /tmp/krylov-plain.json
- run: python -m applications.krylov --backend ckks --degree 2 --output /tmp/krylov-ckks.json

browser:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
- run: npm ci
- run: npm test
- run: npm run build
- run: npx playwright install --with-deps chromium
- run: npm run test:browser
- uses: actions/upload-artifact@v4
with:
name: krylov-browser-lab
path: dist
50 changes: 50 additions & 0 deletions .github/workflows/pages.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: Publish PrivnetAI Pages

on:
push:
branches: [main]
paths:
- 'site/**'
- 'package*.json'
- 'vite.config.js'
- '.github/workflows/pages.yml'
workflow_dispatch:

permissions:
contents: read
pages: write
id-token: write

concurrency:
group: github-pages
cancel-in-progress: false

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: '24'
cache: npm
- run: npm ci
- run: npm test
- run: npm run build
- run: npx playwright install --with-deps chromium
- run: npm run test:browser
- uses: actions/configure-pages@v5
- uses: actions/upload-pages-artifact@v3
with:
path: dist

deploy:
needs: build
runs-on: ubuntu-latest
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
steps:
- name: Deploy tested Pages artifact
id: deployment
uses: actions/deploy-pages@v4
9 changes: 8 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,11 @@
.venv
.venv/
__pycache__/
data/
data/
!site/data/
!site/data/demo.json
node_modules/
dist/
playwright-report/
test-results/
krylov-results.json
28 changes: 18 additions & 10 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,22 @@ Welcome to **PrivNet.AI** — an open-source platform combining **post-quantum c
## 🚀 Project Vision
Build a privacy-centric infrastructure where users can train graph-based models **without ever decrypting their data**.

We aim to use **isogeny-based cryptography** (post-quantum secure) and **graph neural networks (GNNs)** to perform secure, structure-aware learning on encrypted data.
We explore **lattice-based homomorphic encryption** and **graph neural networks (GNNs)** for structure-aware inference on encrypted data. Authenticated post-quantum communication and encrypted training remain separate future protocol work.

> **Current Status**: This is a proof-of-concept (PoC) demonstrating encrypted linear operations on graph neural network features. The current implementation uses `EncryptionShim` (a cryptographic simulator) for demonstration purposes. See [Limitations](#-current-limitations) below.
> **Current Status**: The original GCN proof of concept uses `EncryptionShim`, an educational simulator. The separate Krylov application supports real CKKS inference on a small demonstration model. Encrypted training is not implemented. See [Limitations](#-current-limitations) and [Krylov boundaries](docs/krylov.md).

## Interactive Krylov application

The [Krylov lab](https://aliaslany.github.io/PrivnetAI/) adds client-side graph compilation, interactive predictions, and an optional real-CKKS demonstration in the browser. It implements the method from [PrivNet-Krylov](https://github.com/chimans/PrivNet-Krylov) independently, with attribution to Ehsan Hakimian and Chiman Soltanian. The sample is a public synthetic graph; displayed accuracy is illustrative all-node accuracy.

Run the application locally with `python -m applications.krylov --backend plain --output /tmp/krylov.json`. For real encrypted execution, install `requirements-ckks.txt` and select `--backend ckks`. See [Krylov usage and boundaries](docs/krylov.md) for the model, browser development, serialized key boundary and validation commands.

---

## 🧩 Key Technologies
- **Graph Neural Networks** for structured data learning (PyTorch Geometric)
- **Post-Quantum Cryptography** (PQC) modules for future isogeny-based encryption
- **Homomorphic Encryption** operations framework (in development)
- **Post-Quantum Cryptography** (PQC) protocol integration (planned; current module is a mock)
- **Homomorphic Encryption** through optional CKKS backends for the Krylov application
- **PyTorch** for deep learning infrastructure
- **Federated Learning & Differential Privacy** (planned for future integration)

Expand Down Expand Up @@ -126,14 +132,15 @@ Encrypted linear head test acc: 0.492

## 📚 Documentation
- [roadmap.md](roadmap.md) – Project roadmap and development phases
- [Krylov application](docs/krylov.md) – Client-side compilation, real CKKS, and interactive GitHub Pages lab
- [Research archive (10 October 2026)](docs/research/2026-10-10/README.md) – 2025–2026 literature findings, numerical results, and a proposed roadmap review
- [README_INSTRUCTION.md](README_INSTRUCTION.md) – Detailed PoC documentation
- [CONTRIBUTING.md](CONTRIBUTING.md) – Contribution guidelines
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md) – Community guidelines

---

## 🔬 Current Implementation Details
## 🔬 Original GCN Proof-of-Concept Details

### Architecture Overview
The current PoC demonstrates:
Expand Down Expand Up @@ -215,9 +222,9 @@ We welcome contributions from cryptographers, ML engineers, researchers, and dev
---

## 📍 Project Status
We're in **early development** — currently building a proof-of-concept demonstrating encrypted linear operations on GNN features.
We’re in **early development**, with the original GCN simulator and a separate Krylov application offering optional real CKKS inference.

**Current Phase**: PoC with EncryptionShim simulator
**Current Phase**: GCN simulator plus experimental Krylov CKKS inference
**Next Steps**: See [roadmap.md](roadmap.md) for detailed development phases

Use `Issues` to suggest features or `Discussions` to brainstorm with us.
Expand All @@ -231,11 +238,12 @@ See [roadmap.md](roadmap.md) for the complete development plan.
- [x] Repo bootstrapping & structure setup
- [x] PoC with EncryptionShim and GCN on Planetoid datasets
- [x] Basic test suite
- [x] Independent Krylov application and interactive browser lab with optional real CKKS

**In Progress / Planned:**
- [ ] Real homomorphic encryption integration (CKKS/BFV)
- [ ] Full encrypted inference pipeline
- [ ] Post-quantum cryptography (isogeny-based)
- [ ] Broader homomorphic backend and model integration
- [ ] Full encrypted inference for the original GCN architecture
- [ ] Authenticated post-quantum communication
- [ ] Encrypted graph structure support
- [ ] Federated learning integration
- [ ] MVP deployment
Expand Down
67 changes: 67 additions & 0 deletions applications/krylov/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Independent Krylov demonstration

This application implements client-side Chebyshev compilation and polynomial
classification independently, inspired by the published mathematical approach
in [PrivNet-Krylov](https://github.com/chimans/PrivNet-Krylov), inspected at commit
`0976e41881192da938ef2ab988c2fe3fdb92835b`. The upstream repository has no license;
none of its code, artifacts, figures, timings, or benchmark claims are copied.

From the repository root:

```bash
python -m applications.krylov --backend plain --degree 2 \
--output /tmp/krylov-plain.json --export-demo /tmp/krylov-demo.json
pip install -r requirements-ckks.txt
python -m applications.krylov --backend ckks --degree 2 \
--output /tmp/krylov-ckks.json
```

NumPy is the only mandatory application dependency. TenSEAL 0.3.16 is optional;
requesting CKKS without it exits with a clear error rather than simulating HE.
The existing project environment can run these commands with `.venv/bin/python`.
Use `--fixture /tmp/krylov-demo.json` to evaluate an existing fixture with the
same schema. Demo import is bounded to 1 MiB, 120 nodes, 32 feature/hidden
channels and 16 classes. Input, result and exported-fixture paths must differ.

The client normalizes an undirected graph as
`S = D^-1/2 (A + I) D^-1/2` and compiles `B0 = X`, `B1 = SX`, and
`Bk = 2 S B{k-1} - B{k-2}`. It then evaluates
`Z = sum(Bk theta_k) + bias`, `H = Z²`, and `logits = H W + output_bias`.
The complete degree-0-through-4 bank and one fixed model are exported for the
browser. Selecting a degree truncates the bank and weights; it does not retrain.
Exported edges are unique ordered pairs, interpreted symmetrically; the
normalization adds a self loop even at an isolated node.

The deterministic 24-node graph has two communities and noisy features. Two
regularized least-squares fits train the classifier on **all nodes**, using the
degree-4 bank. All reported accuracy is illustrative training/all-node accuracy.
There is no held-out split, comparison with the upstream results, or novelty
claim. The browser fixture includes all features, edges and labels publicly, and
browser evaluation uses plaintext arithmetic.

The optional CKKS backend packs each block/feature column across nodes. The
client generates keys, encrypts the columns and serializes a context without a
secret key. `evaluate_server` deserializes that public context and ciphertexts,
executes the affine/square/readout circuit, and returns ciphertext bytes.
Only `ClientSession.decrypt` holds the secret key. The local runner orchestrates
both roles in one process, demonstrating an actual serialization boundary
rather than claiming a deployed remote protocol. The server does not receive
the adjacency, plaintext features or labels, but sees shapes, degree, the clear
model and message sizes. Client compilation and upload costs remain relevant.

The fixed parameters are degree 16384, coefficient-modulus sizes
`[60, 40, 40, 40, 40, 60]`, and scale `2^40`. They pass the backend's default
SEAL classical parameter bound. There is **no independent security estimate or
post-quantum security claim**. The circuit uses depth three and no rotations;
only the square activation is supported by this CKKS runner. Authentication,
malicious-server integrity, decryption-oracle protections, padding, key lifecycle
and transport are outside this demonstration.

Results distinguish executed plaintext from executed genuine CKKS, and record
same-model accuracy/agreement, numerical errors, parameters, serialized sizes
and individual timing stages. A result is created only after the selected
backend succeeds. Reproduce the focused checks with:

```bash
python -m pytest -q tests/test_krylov.py
```
21 changes: 21 additions & 0 deletions applications/krylov/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
"""Independent client-side Chebyshev compilation and polynomial inference.

Mathematical inspiration: https://github.com/chimans/PrivNet-Krylov,
commit 0976e41881192da938ef2ab988c2fe3fdb92835b. No upstream code is used.
"""

from .core import (
MAX_DEGREE,
compile_chebyshev_blocks,
evaluate_affine,
evaluate_model,
evaluate_polynomial,
evaluate_readout,
normalize_adjacency,
)

__all__ = [
"MAX_DEGREE", "compile_chebyshev_blocks", "evaluate_affine",
"evaluate_model", "evaluate_polynomial", "evaluate_readout",
"normalize_adjacency",
]
97 changes: 97 additions & 0 deletions applications/krylov/__main__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
"""Run the public synthetic demonstration with plaintext or genuine CKKS."""

import argparse
import json
from pathlib import Path
import platform
import sys
import time

import numpy as np

from .core import MAX_DEGREE, compile_chebyshev_blocks, evaluate_affine, evaluate_model, normalize_adjacency
from .demo import DEFAULT_DEGREE, fixture_arrays, make_demo


def write_json(path: Path, value):
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(json.dumps(value, indent=2, allow_nan=False) + "\n", encoding="utf-8")


def run(args) -> int:
if args.fixture:
if args.fixture.stat().st_size > 1024 * 1024:
raise ValueError("fixture JSON must be at most 1 MiB")
fixture = json.loads(args.fixture.read_text(encoding="utf-8"))
else:
fixture = make_demo()
adjacency, features, labels = fixture_arrays(fixture)
degree = fixture["default_degree"] if args.degree is None else args.degree
if degree >= len(fixture["model"]["theta"]):
raise ValueError("selected degree exceeds the fixture theta bank")
start = time.perf_counter()
blocks = compile_chebyshev_blocks(normalize_adjacency(adjacency), features, degree)
compile_seconds = time.perf_counter() - start
start = time.perf_counter()
reference = evaluate_model(blocks, fixture["model"], degree)
plain_seconds = time.perf_counter() - start
details = {"plaintext_evaluation_seconds": plain_seconds}
logits = reference
if args.backend == "ckks":
from .ckks import run_ckks

logits, details = run_ckks(blocks, fixture["model"], degree)
affine = evaluate_affine(blocks, fixture["model"]["theta"], fixture["model"]["bias"], degree)
predictions = logits.argmax(axis=1)
reference_predictions = reference.argmax(axis=1)
result = {
"schema_version": 1, "application": "PrivnetAI independent Krylov demonstration",
"backend": args.backend, "measurement_kind": "executed real CKKS" if args.backend == "ckks" else "executed plaintext",
"degree": degree, "nodes": len(labels), "features": features.shape[1],
"hidden_channels": affine.shape[1], "classes": logits.shape[1],
"accuracy_scope": fixture["provenance"].get("evaluation_scope", "Illustrative all-node accuracy; no held-out split evaluated"),
"all_node_accuracy": float(np.mean(predictions == labels)),
"plaintext_all_node_accuracy": float(np.mean(reference_predictions == labels)),
"prediction_agreement_with_same_plain_model": float(np.mean(predictions == reference_predictions)),
"max_absolute_logit_error": float(np.max(np.abs(logits - reference))),
"mean_absolute_logit_error": float(np.mean(np.abs(logits - reference))),
"observed_affine_range": [float(affine.min()), float(affine.max())],
"client_compilation_seconds": compile_seconds,
"predictions": predictions.tolist(), "logits": logits.tolist(),
"provenance": fixture["provenance"],
"environment": {"python": platform.python_version(), "numpy": np.__version__,
"platform": platform.platform(), "machine": platform.machine()},
"details": details,
}
write_json(args.output, result)
if args.export_demo:
write_json(args.export_demo, fixture)
print(json.dumps({"backend": args.backend, "degree": degree,
"all_node_accuracy": result["all_node_accuracy"],
"accuracy_scope": result["accuracy_scope"],
"same_model_agreement": result["prediction_agreement_with_same_plain_model"],
"max_absolute_logit_error": result["max_absolute_logit_error"],
"output": str(args.output)}, indent=2))
return 0


def main(argv=None) -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument("--backend", choices=("plain", "ckks"), default="plain")
parser.add_argument("--degree", type=int, choices=range(MAX_DEGREE + 1),
help=f"Truncation degree; defaults to fixture default_degree ({DEFAULT_DEGREE} for the synthetic demo)")
parser.add_argument("--fixture", type=Path, help="Optional public browser-schema fixture JSON, at most 1 MiB")
parser.add_argument("--output", type=Path, default=Path("krylov-results.json"), help="Result JSON file")
parser.add_argument("--export-demo", type=Path, help="Export public browser fixture JSON")
args = parser.parse_args(argv)
paths = [path.resolve() for path in (args.fixture, args.output, args.export_demo) if path]
if len(paths) != len(set(paths)):
parser.error("fixture, output and export-demo must use distinct paths")
try:
return run(args)
except (OSError, UnicodeError, ValueError, ImportError, RuntimeError) as exc:
parser.exit(2, f"Krylov run failed: {exc}\n")


if __name__ == "__main__":
sys.exit(main())
Loading
Loading