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
28 changes: 28 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: CI

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

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.9", "3.11", "3.13"]

steps:
- uses: actions/checkout@v4

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}

- name: Install dependencies
run: pip install -e ".[dev]"

- name: Run tests
run: python -m pytest tests/ -v
4 changes: 2 additions & 2 deletions CHANGELOG-v02.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@ Problem: Timestamp validation is now implemented via check_timestamp=True in ver
Fix: Remove the note. Replace with: "Timestamp validation is implemented in the reference implementation via the check_timestamp parameter of verify_identity(). Nonce-based deduplication remains a future work item."

## 2. Section 4 — Resolve @context version (v1.4 vs v1.5)
Problem: Draft shows v1.4; reference implementation used v1.5. Tests realigned to v1.4 for now.
Fix: Confirm canonical version. Update draft and implementation to single consistent value. Define versioning policy (what a context version bump implies).
Status: **Resolved.** v1.4 is confirmed canonical. All code, tests, and documentation now consistently use `https://litzki-systems.com/protocol/v1.4`. The v1.5 reference was a transient inconsistency; the draft and implementation are aligned.
Remaining: Define a versioning policy (what a context version bump implies) — deferred to draft-litzki-sovp-03.

## 3. Section 6 / New sub-section — DNS TXT Record Format (normative)
Problem: Section 9.1 registers the _sovp DNS label but specifies no TXT record value format. README uses v=SOVP1; k=<base64> non-normatively.
Expand Down
50 changes: 44 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

> **Protocol specification:** [draft-litzki-sovp-01](https://datatracker.ietf.org/doc/draft-litzki-sovp/) — IETF Internet-Draft

[![CI](https://github.com/litzki-systems/sovp-python/actions/workflows/ci.yml/badge.svg)](https://github.com/litzki-systems/sovp-python/actions/workflows/ci.yml)
[![License](https://img.shields.io/badge/License-Apache_2.0-blue.svg)](LICENSE)
[![Python](https://img.shields.io/badge/Python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![IETF Draft](https://img.shields.io/badge/IETF-draft--litzki--sovp--05-lightgrey.svg)](https://datatracker.ietf.org/doc/draft-litzki-sovp/)
Expand Down Expand Up @@ -51,6 +52,42 @@ This installs the `sovp` CLI and the `sovp.core` library. Dependencies (`cryptog

---

## End-to-end example

The fastest way to see SOVP in action — generate a keypair, sign an identity document, verify it, and watch tamper detection fire:

```python
from sovp.core import generate_keypair, generate_identity_document, verify_identity

# 1. Generate keys — publish public_key_b64 in your DNS TXT record
private_key_b64, public_key_b64 = generate_keypair()

# 2. Build and sign — serve this JSON at /.well-known/sovp-identity.json
document = generate_identity_document(
private_key_b64=private_key_b64,
entity_uid="urn:sovp:example-entity",
canonical_url="https://example.com",
)

# 3. Verify (Psi_core)
signature = document["integrity_proof"]["signature"]
psi_core = verify_identity(document, signature, public_key_b64)
print("Psi_core =", 1 if psi_core else 0) # → 1

# 4. Tamper detection
document["entity"]["canonical_url"] = "https://attacker.com"
psi_core = verify_identity(document, signature, public_key_b64)
print("Psi_core =", 1 if psi_core else 0) # → 0 (blocked)
```

A runnable version with annotated output is in [`examples/end_to_end.py`](examples/end_to_end.py):

```bash
python examples/end_to_end.py
```

---

## Usage

### Python API
Expand Down Expand Up @@ -187,13 +224,14 @@ Recommended TTL: 300 seconds (per draft Section 6.1). DNSSEC recommended for the

| Feature | Status |
|---|---|
| `sovp.core` library (`generate_keypair`, `sign_identity`, `verify_identity`) | Implemented |
| `sovp.core` primitives (`generate_keypair`, `sign_identity`, `verify_identity`) | Implemented |
| `sovp.core` document builder (`generate_identity_document`) | Implemented |
| CLI (`generate-keypair`, `sign`, `verify`) | Implemented |
| `SOVPIdentity` / `SOVPSigner` / `SOVPValidator` class API | Not yet implemented |
| DNS + HTTP resolution in `SOVPValidator` | Not yet implemented |
| Replay protection (timestamp validation, `check_timestamp=True`) | Implemented (`verify_identity`) |
| Replay protection (nonce deduplication) | Not yet implemented |
| RFC conformance test vectors | Not yet implemented |
| Replay protection — timestamp validation (`check_timestamp=True`) | Implemented |
| Replay protection — nonce deduplication | Planned |
| `SOVPIdentity` / `SOVPSigner` / `SOVPValidator` class API | Planned |
| DNS + HTTP resolution in `SOVPValidator` | Planned |
| RFC conformance test vectors | Planned |
| IETF Internet-Draft | [draft-litzki-sovp-01](https://datatracker.ietf.org/doc/draft-litzki-sovp/) — submitted |
| U.S. Provisional Patent | Filed — No. 64/005,737 |

Expand Down
55 changes: 55 additions & 0 deletions examples/end_to_end.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
#!/usr/bin/env python3
# Copyright (c) 2026 Litzki Systems LLC
# SPDX-License-Identifier: Apache-2.0
#
# End-to-end SOVP walkthrough: generate keys, sign an identity document,
# verify it, and confirm that tampering is detected.
#
# Run: python examples/end_to_end.py

import json
import sys
import os

sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))

from sovp.core import generate_keypair, generate_identity_document, verify_identity

print("=== SOVP End-to-End Example ===\n")

# ── Step 1: Generate an Ed25519 keypair ────────────────────────────────────────
private_key_b64, public_key_b64 = generate_keypair()
print(f"Public key : {public_key_b64}")
print(f"Private key : {private_key_b64[:20]}... (keep secret)\n")

# Publish public_key_b64 in DNS:
# _sovp.yourdomain.tld IN TXT "v=SOVP1; k=<public_key_b64>"

# ── Step 2: Build and sign a sovp-identity.json document ──────────────────────
document = generate_identity_document(
private_key_b64=private_key_b64,
entity_uid="urn:sovp:example-entity",
canonical_url="https://example.com",
)
print("Signed sovp-identity.json:")
print(json.dumps(document, indent=2))
print()

# Serve this at: https://example.com/.well-known/sovp-identity.json

# ── Step 3: Verify — Psi_core resonance check ─────────────────────────────────
signature = document["integrity_proof"]["signature"]

psi_core = verify_identity(document, signature, public_key_b64)
print(f"Psi_core = {'1 ✓ Verified — ingestion may proceed.' if psi_core else '0 ✗ Blocked.'}")
assert psi_core, "Verification should pass for an unmodified document."

# ── Step 4: Tamper detection ───────────────────────────────────────────────────
tampered = json.loads(json.dumps(document))
tampered["entity"]["canonical_url"] = "https://attacker.com"

psi_tampered = verify_identity(tampered, signature, public_key_b64)
print(f"Psi_core = {'1' if psi_tampered else '0 ✗ Tamper detected — ingestion blocked.'}")
assert not psi_tampered, "Verification should fail after tampering."

print("\n=== Done ===")
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -13,5 +13,11 @@ dependencies = [
"jcs",
]

[project.optional-dependencies]
dev = ["pytest"]

[project.scripts]
sovp = "sovp.cli:main"

[tool.pytest.ini_options]
testpaths = ["tests"]
2 changes: 2 additions & 0 deletions sovp/core.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
# Copyright (c) 2026 Litzki Systems LLC
# SPDX-License-Identifier: Apache-2.0

from __future__ import annotations

import base64
import jcs
import uuid
Expand Down
Loading