#!/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}")