chore: initialize monorepo with project scaffolding, configs, and CI setup

This commit is contained in:
oib
2025-09-27 06:05:25 +02:00
commit fe29631a86
170 changed files with 13708 additions and 0 deletions

View File

@@ -0,0 +1,11 @@
"""AITBC cryptographic helpers for receipts."""
from .receipt import canonical_json, receipt_hash
from .signing import ReceiptSigner, ReceiptVerifier
__all__ = [
"canonical_json",
"receipt_hash",
"ReceiptSigner",
"ReceiptVerifier",
]

View File

@@ -0,0 +1,23 @@
from __future__ import annotations
from typing import Any, Dict
import json
from hashlib import sha256
def canonical_json(receipt: Dict[str, Any]) -> str:
def remove_none(obj: Any) -> Any:
if isinstance(obj, dict):
return {k: remove_none(v) for k, v in obj.items() if v is not None}
if isinstance(obj, list):
return [remove_none(x) for x in obj if x is not None]
return obj
cleaned = remove_none(receipt)
return json.dumps(cleaned, separators=(",", ":"), sort_keys=True)
def receipt_hash(receipt: Dict[str, Any]) -> bytes:
data = canonical_json(receipt).encode("utf-8")
return sha256(data).digest()

View File

@@ -0,0 +1,51 @@
from __future__ import annotations
from typing import Any, Dict
import base64
from nacl.signing import SigningKey, VerifyKey
from .receipt import canonical_json
def _urlsafe_b64encode(data: bytes) -> str:
return base64.urlsafe_b64encode(data).decode("utf-8").rstrip("=")
def _urlsafe_b64decode(data: str) -> bytes:
padding = '=' * (-len(data) % 4)
return base64.urlsafe_b64decode(data + padding)
class ReceiptSigner:
def __init__(self, signing_key: bytes):
self._key = SigningKey(signing_key)
def sign(self, payload: Dict[str, Any]) -> Dict[str, Any]:
message = canonical_json(payload).encode("utf-8")
signed = self._key.sign(message)
return {
"alg": "Ed25519",
"key_id": _urlsafe_b64encode(self._key.verify_key.encode()),
"sig": _urlsafe_b64encode(signed.signature),
}
class ReceiptVerifier:
def __init__(self, verify_key: bytes):
self._key = VerifyKey(verify_key)
def verify(self, payload: Dict[str, Any], signature: Dict[str, Any]) -> bool:
if signature.get("alg") != "Ed25519":
return False
sig_field = signature.get("sig")
if not isinstance(sig_field, str):
return False
message = canonical_json(payload).encode("utf-8")
sig_bytes = _urlsafe_b64decode(sig_field)
try:
self._key.verify(message, sig_bytes)
return True
except Exception:
return False