sov33 / sigil_ed25519.py
Nicholastempleman's picture
Upload sigil_ed25519.py with huggingface_hub
94afb63 verified
Raw
History Blame Contribute Delete
6.2 kB
#!/usr/bin/env python3
"""Ed25519 SIGIL signing for SOV33 API endpoints.
Usage:
from sigil_ed25519 import SigilSigner
signer = SigilSigner()
sigil = signer.sign({"action": "test", "timestamp": "2026-07-26"})
assert signer.verify(sigil)
"""
import json
import hashlib
import time
from pathlib import Path
try:
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey
from cryptography.hazmat.primitives import serialization
HAS_CRYPTO = True
except ImportError:
HAS_CRYPTO = False
class SigilSigner:
"""Ed25519 SIGIL signer for sovereign actions."""
def __init__(self, key_path=None):
if not HAS_CRYPTO:
raise ImportError("pip install cryptography")
if key_path and Path(key_path).exists():
self._load_key(key_path)
else:
self._generate_key(key_path)
def _generate_key(self, save_path=None):
"""Generate new Ed25519 key pair."""
self.private_key = Ed25519PrivateKey.generate()
self.public_key = self.private_key.public_key()
if save_path:
Path(save_path).parent.mkdir(parents=True, exist_ok=True)
# Save private key
priv_pem = self.private_key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
Path(save_path).write_bytes(priv_pem)
Path(save_path).chmod(0o600)
# Save public key
pub_path = str(save_path) + ".pub"
pub_pem = self.public_key.public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
Path(pub_path).write_bytes(pub_pem)
def _load_key(self, key_path):
"""Load existing Ed25519 key pair."""
priv_pem = Path(key_path).read_bytes()
self.private_key = serialization.load_pem_private_key(priv_pem, password=None)
self.public_key = self.private_key.public_key()
def sign(self, payload):
"""Create SIGIL signature for payload."""
if isinstance(payload, dict):
payload = json.dumps(payload, sort_keys=True).encode()
elif isinstance(payload, str):
payload = payload.encode()
signature = self.private_key.sign(payload)
return {
"payload": payload.decode() if isinstance(payload, bytes) else payload,
"signature": signature.hex(),
"algorithm": "Ed25519",
"timestamp": time.time(),
"sha256": hashlib.sha256(payload).hexdigest()
}
def verify(self, sigil):
"""Verify SIGIL signature."""
try:
payload = sigil["payload"]
if isinstance(payload, str):
payload = payload.encode()
signature = bytes.fromhex(sigil["signature"])
self.public_key.verify(signature, payload)
# Verify SHA256
expected_hash = hashlib.sha256(payload).hexdigest()
if sigil.get("sha256") != expected_hash:
return False
return True
except Exception:
return False
def get_public_key_hex(self):
"""Get public key as hex string."""
pub_bytes = self.public_key.public_bytes(
encoding=serialization.Encoding.Raw,
format=serialization.PublicFormat.Raw
)
return pub_bytes.hex()
class SigilChain:
"""Hash-chained SIGIL ledger."""
def __init__(self, signer):
self.signer = signer
self.chain = []
self.prev_hash = "genesis"
def append(self, payload):
"""Append new SIGIL to chain."""
chain_payload = {
"payload": payload,
"prev_hash": self.prev_hash,
"chain_index": len(self.chain),
}
sigil = self.signer.sign(chain_payload)
sigil["chain_hash"] = hashlib.sha256(
(self.prev_hash + sigil["sha256"]).encode()
).hexdigest()
self.chain.append(sigil)
self.prev_hash = sigil["chain_hash"]
return sigil
def verify_chain(self):
"""Verify entire chain integrity."""
prev_hash = "genesis"
for i, sigil in enumerate(self.chain):
# Verify signature
if not self.signer.verify(sigil):
return False, f"Signature invalid at index {i}"
# Verify chain linkage
expected_hash = hashlib.sha256(
(prev_hash + sigil["sha256"]).encode()
).hexdigest()
if sigil.get("chain_hash") != expected_hash:
return False, f"Chain broken at index {i}"
prev_hash = sigil["chain_hash"]
return True, "Chain valid"
# Global signer instance
_signer = None
def get_signer(key_path="/workspace/sigil_key.pem"):
"""Get or create global signer."""
global _signer
if _signer is None:
_signer = SigilSigner(key_path)
return _signer
def sign_response(response_data):
"""Sign API response with SIGIL."""
signer = get_signer()
return signer.sign(response_data)
def verify_sigil(sigil):
"""Verify SIGIL signature."""
signer = get_signer()
return signer.verify(sigil)
if __name__ == "__main__":
# Demo
signer = SigilSigner("/tmp/sigil_demo_key.pem")
# Sign
sigil = signer.sign({"action": "test", "data": "hello world"})
print("Signed:", json.dumps(sigil, indent=2)[:200])
# Verify
valid = signer.verify(sigil)
print("Valid:", valid)
# Chain demo
chain = SigilChain(signer)
chain.append({"step": 1, "data": "first"})
chain.append({"step": 2, "data": "second"})
chain.append({"step": 3, "data": "third"})
valid, msg = chain.verify_chain()
print(f"Chain: {len(chain.chain)} entries, valid={valid}, msg={msg}")