chore: initialize monorepo with project scaffolding, configs, and CI setup
This commit is contained in:
4
packages/py/aitbc-crypto/src/__init__.py
Normal file
4
packages/py/aitbc-crypto/src/__init__.py
Normal file
@ -0,0 +1,4 @@
|
||||
"""AITBC crypto utilities package."""
|
||||
|
||||
from . import receipt # noqa: F401
|
||||
from . import signing # noqa: F401
|
||||
11
packages/py/aitbc-crypto/src/aitbc_crypto/__init__.py
Normal file
11
packages/py/aitbc-crypto/src/aitbc_crypto/__init__.py
Normal 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",
|
||||
]
|
||||
23
packages/py/aitbc-crypto/src/aitbc_crypto/receipt.py
Normal file
23
packages/py/aitbc-crypto/src/aitbc_crypto/receipt.py
Normal 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()
|
||||
51
packages/py/aitbc-crypto/src/aitbc_crypto/signing.py
Normal file
51
packages/py/aitbc-crypto/src/aitbc_crypto/signing.py
Normal 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
|
||||
47
packages/py/aitbc-crypto/src/receipt.py
Normal file
47
packages/py/aitbc-crypto/src/receipt.py
Normal file
@ -0,0 +1,47 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Dict
|
||||
|
||||
import json
|
||||
from hashlib import sha256
|
||||
|
||||
from pydantic import BaseModel
|
||||
|
||||
|
||||
class Receipt(BaseModel):
|
||||
version: str
|
||||
receipt_id: str
|
||||
job_id: str
|
||||
provider: str
|
||||
client: str
|
||||
units: float
|
||||
unit_type: str
|
||||
started_at: int
|
||||
completed_at: int
|
||||
price: float | None = None
|
||||
model: str | None = None
|
||||
prompt_hash: str | None = None
|
||||
duration_ms: int | None = None
|
||||
artifact_hash: str | None = None
|
||||
coordinator_id: str | None = None
|
||||
nonce: str | None = None
|
||||
chain_id: int | None = None
|
||||
metadata: Dict[str, Any] | None = None
|
||||
|
||||
|
||||
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()
|
||||
40
packages/py/aitbc-crypto/src/signing.py
Normal file
40
packages/py/aitbc-crypto/src/signing.py
Normal file
@ -0,0 +1,40 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Dict, Any
|
||||
|
||||
import base64
|
||||
from hashlib import sha256
|
||||
|
||||
from nacl.signing import SigningKey, VerifyKey
|
||||
|
||||
from .receipt import canonical_json
|
||||
|
||||
|
||||
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")
|
||||
signature = self._key.sign(message)
|
||||
return {
|
||||
"alg": "Ed25519",
|
||||
"key_id": base64.urlsafe_b64encode(self._key.verify_key.encode()).decode("utf-8").rstrip("="),
|
||||
"sig": base64.urlsafe_b64encode(signature.signature).decode("utf-8").rstrip("="),
|
||||
}
|
||||
|
||||
|
||||
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_bytes = base64.urlsafe_b64decode(signature["sig"] + "==")
|
||||
message = canonical_json(payload).encode("utf-8")
|
||||
try:
|
||||
self._key.verify(message, sig_bytes)
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
Reference in New Issue
Block a user