Reference Implementation · v1.0.0
BAINK Protocol
Single-file executable reference of the BAINK protocol layer: decentralized identity objects, Ed25519-signed trust claims with an HMAC-SHA256 fallback, an append-only audit ledger, reputation scoring, and a small CLI. Runs with the Python standard library; install cryptography to enable public-key signatures.
Download .py686 lines
baink_protocol.pypython 3
1#!/usr/bin/env python32"""3BAINK Protocol — Engineering Trust Infrastructure4Single-file executable protocol layer dedicated to trust itself.5 6Features:7- Decentralized identity objects8- Ed25519 signing when cryptography is installed9- HMAC fallback for standard-library-only execution10- Signed trust claims11- Claim verification12- Append-only audit ledger13- Reputation scoring14- JSON persistence15- CLI interface16- Demo scenario17 18Run:19 python baink_protocol.py demo20 python baink_protocol.py init --db baink.json21 python baink_protocol.py create-agent "Alice" --db baink.json22 python baink_protocol.py claim --issuer Alice --subject Bob --kind reliability --score 0.9 --db baink.json23 python baink_protocol.py verify --db baink.json24 python baink_protocol.py reputation Bob --db baink.json25"""26 27from __future__ import annotations28 29import argparse30import base6431import dataclasses32import datetime as _dt33import hashlib34import hmac35import json36import os37import secrets38import sys39import textwrap40import uuid41from typing import Any, Dict, List, Optional, Tuple42 43 44PROTOCOL_NAME = "BAINK"45PROTOCOL_VERSION = "1.0.0"46GENESIS = "BAINK::GENESIS"47 48 49# ---------------------------------------------------------------------------50# Crypto layer51# ---------------------------------------------------------------------------52 53class CryptoError(Exception):54 pass55 56 57class Crypto:58 """59 BAINK prefers Ed25519 signatures through the optional `cryptography` package.60 61 When unavailable, it falls back to HMAC-SHA256 so the file remains executable62 with only the Python standard library. HMAC is useful for demos and closed63 systems, but it is not a public-key signature scheme.64 """65 66 def __init__(self) -> None:67 self.mode = "hmac-sha256"68 try:69 from cryptography.hazmat.primitives.asymmetric.ed25519 import (70 Ed25519PrivateKey,71 Ed25519PublicKey,72 )73 from cryptography.hazmat.primitives import serialization74 self.Ed25519PrivateKey = Ed25519PrivateKey75 self.Ed25519PublicKey = Ed25519PublicKey76 self.serialization = serialization77 self.mode = "ed25519"78 except Exception:79 self.Ed25519PrivateKey = None80 self.Ed25519PublicKey = None81 self.serialization = None82 83 @staticmethod84 def b64(data: bytes) -> str:85 return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")86 87 @staticmethod88 def unb64(text: str) -> bytes:89 pad = "=" * (-len(text) % 4)90 return base64.urlsafe_b64decode(text + pad)91 92 def generate_keypair(self) -> Tuple[str, str]:93 if self.mode == "ed25519":94 private = self.Ed25519PrivateKey.generate()95 public = private.public_key()96 private_bytes = private.private_bytes(97 encoding=self.serialization.Encoding.Raw,98 format=self.serialization.PrivateFormat.Raw,99 encryption_algorithm=self.serialization.NoEncryption(),100 )101 public_bytes = public.public_bytes(102 encoding=self.serialization.Encoding.Raw,103 format=self.serialization.PublicFormat.Raw,104 )105 return self.b64(private_bytes), self.b64(public_bytes)106 107 secret = secrets.token_bytes(32)108 # In fallback mode, public_key is a commitment, not a verification key.109 public_commitment = hashlib.sha256(secret).digest()110 return self.b64(secret), self.b64(public_commitment)111 112 def public_from_private(self, private_key: str) -> str:113 raw = self.unb64(private_key)114 if self.mode == "ed25519":115 private = self.Ed25519PrivateKey.from_private_bytes(raw)116 public = private.public_key()117 return self.b64(public.public_bytes(118 encoding=self.serialization.Encoding.Raw,119 format=self.serialization.PublicFormat.Raw,120 ))121 return self.b64(hashlib.sha256(raw).digest())122 123 def sign(self, private_key: str, message: bytes) -> str:124 raw = self.unb64(private_key)125 if self.mode == "ed25519":126 private = self.Ed25519PrivateKey.from_private_bytes(raw)127 return self.b64(private.sign(message))128 return self.b64(hmac.new(raw, message, hashlib.sha256).digest())129 130 def verify(131 self,132 public_key: str,133 message: bytes,134 signature: str,135 private_key_for_hmac_fallback: Optional[str] = None,136 ) -> bool:137 try:138 if self.mode == "ed25519":139 public = self.Ed25519PublicKey.from_public_bytes(self.unb64(public_key))140 public.verify(self.unb64(signature), message)141 return True142 143 # HMAC fallback cannot verify from public commitment alone.144 if not private_key_for_hmac_fallback:145 return False146 raw = self.unb64(private_key_for_hmac_fallback)147 expected_public = self.b64(hashlib.sha256(raw).digest())148 if not hmac.compare_digest(expected_public, public_key):149 return False150 expected = self.sign(private_key_for_hmac_fallback, message)151 return hmac.compare_digest(expected, signature)152 except Exception:153 return False154 155 156CRYPTO = Crypto()157 158 159# ---------------------------------------------------------------------------160# Canonical serialization161# ---------------------------------------------------------------------------162 163def now_iso() -> str:164 return _dt.datetime.now(_dt.timezone.utc).isoformat(timespec="seconds")165 166 167def canonical_json(obj: Any) -> str:168 return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)169 170 171def digest(obj: Any) -> str:172 return hashlib.sha256(canonical_json(obj).encode("utf-8")).hexdigest()173 174 175def did_from_public_key(public_key: str) -> str:176 return "did:baink:" + hashlib.sha256(public_key.encode("utf-8")).hexdigest()[:32]177 178 179# ---------------------------------------------------------------------------180# Core protocol objects181# ---------------------------------------------------------------------------182 183@dataclasses.dataclass184class Agent:185 name: str186 did: str187 public_key: str188 private_key: str189 created_at: str190 metadata: Dict[str, Any]191 192 @classmethod193 def create(cls, name: str, metadata: Optional[Dict[str, Any]] = None) -> "Agent":194 private_key, public_key = CRYPTO.generate_keypair()195 return cls(196 name=name,197 did=did_from_public_key(public_key),198 public_key=public_key,199 private_key=private_key,200 created_at=now_iso(),201 metadata=metadata or {},202 )203 204 def public_view(self) -> Dict[str, Any]:205 return {206 "name": self.name,207 "did": self.did,208 "public_key": self.public_key,209 "created_at": self.created_at,210 "metadata": self.metadata,211 }212 213 214@dataclasses.dataclass215class TrustClaim:216 id: str217 issuer: str218 subject: str219 kind: str220 score: float221 confidence: float222 evidence: Dict[str, Any]223 issued_at: str224 expires_at: Optional[str]225 nonce: str226 signature: str = ""227 228 def unsigned_payload(self) -> Dict[str, Any]:229 return {230 "protocol": PROTOCOL_NAME,231 "version": PROTOCOL_VERSION,232 "id": self.id,233 "issuer": self.issuer,234 "subject": self.subject,235 "kind": self.kind,236 "score": self.score,237 "confidence": self.confidence,238 "evidence": self.evidence,239 "issued_at": self.issued_at,240 "expires_at": self.expires_at,241 "nonce": self.nonce,242 }243 244 def signing_bytes(self) -> bytes:245 return canonical_json(self.unsigned_payload()).encode("utf-8")246 247 def sign(self, issuer_agent: Agent) -> None:248 if issuer_agent.did != self.issuer:249 raise CryptoError("issuer private key does not match claim issuer")250 self.signature = CRYPTO.sign(issuer_agent.private_key, self.signing_bytes())251 252 def verify(self, issuer_agent: Agent) -> bool:253 return CRYPTO.verify(254 issuer_agent.public_key,255 self.signing_bytes(),256 self.signature,257 private_key_for_hmac_fallback=issuer_agent.private_key,258 )259 260 def to_dict(self) -> Dict[str, Any]:261 payload = self.unsigned_payload()262 payload["signature"] = self.signature263 return payload264 265 @classmethod266 def from_dict(cls, d: Dict[str, Any]) -> "TrustClaim":267 return cls(268 id=d["id"],269 issuer=d["issuer"],270 subject=d["subject"],271 kind=d["kind"],272 score=float(d["score"]),273 confidence=float(d.get("confidence", 1.0)),274 evidence=dict(d.get("evidence", {})),275 issued_at=d["issued_at"],276 expires_at=d.get("expires_at"),277 nonce=d["nonce"],278 signature=d.get("signature", ""),279 )280 281 282@dataclasses.dataclass283class LedgerEvent:284 index: int285 timestamp: str286 event_type: str287 payload_hash: str288 payload: Dict[str, Any]289 previous_hash: str290 event_hash: str291 292 @classmethod293 def create(294 cls,295 index: int,296 event_type: str,297 payload: Dict[str, Any],298 previous_hash: str,299 ) -> "LedgerEvent":300 base = {301 "index": index,302 "timestamp": now_iso(),303 "event_type": event_type,304 "payload_hash": digest(payload),305 "payload": payload,306 "previous_hash": previous_hash,307 }308 event_hash = digest(base)309 return cls(event_hash=event_hash, **base)310 311 def to_dict(self) -> Dict[str, Any]:312 return dataclasses.asdict(self)313 314 @classmethod315 def from_dict(cls, d: Dict[str, Any]) -> "LedgerEvent":316 return cls(**d)317 318 319# ---------------------------------------------------------------------------320# BAINK protocol engine321# ---------------------------------------------------------------------------322 323class BainkProtocol:324 def __init__(self) -> None:325 self.agents: Dict[str, Agent] = {}326 self.name_index: Dict[str, str] = {}327 self.claims: Dict[str, TrustClaim] = {}328 self.ledger: List[LedgerEvent] = []329 330 def add_event(self, event_type: str, payload: Dict[str, Any]) -> LedgerEvent:331 previous_hash = self.ledger[-1].event_hash if self.ledger else GENESIS332 event = LedgerEvent.create(len(self.ledger), event_type, payload, previous_hash)333 self.ledger.append(event)334 return event335 336 def create_agent(self, name: str, metadata: Optional[Dict[str, Any]] = None) -> Agent:337 if name in self.name_index:338 raise ValueError(f"agent name already exists: {name}")339 agent = Agent.create(name, metadata)340 self.agents[agent.did] = agent341 self.name_index[name] = agent.did342 self.add_event("AGENT_CREATED", agent.public_view())343 return agent344 345 def resolve_agent(self, name_or_did: str) -> Agent:346 did = self.name_index.get(name_or_did, name_or_did)347 if did not in self.agents:348 raise KeyError(f"unknown agent: {name_or_did}")349 return self.agents[did]350 351 def issue_claim(352 self,353 issuer: str,354 subject: str,355 kind: str,356 score: float,357 confidence: float = 1.0,358 evidence: Optional[Dict[str, Any]] = None,359 expires_at: Optional[str] = None,360 ) -> TrustClaim:361 if not 0.0 <= score <= 1.0:362 raise ValueError("score must be between 0.0 and 1.0")363 if not 0.0 <= confidence <= 1.0:364 raise ValueError("confidence must be between 0.0 and 1.0")365 366 issuer_agent = self.resolve_agent(issuer)367 subject_agent = self.resolve_agent(subject)368 369 claim = TrustClaim(370 id="claim_" + uuid.uuid4().hex,371 issuer=issuer_agent.did,372 subject=subject_agent.did,373 kind=kind,374 score=round(float(score), 6),375 confidence=round(float(confidence), 6),376 evidence=evidence or {},377 issued_at=now_iso(),378 expires_at=expires_at,379 nonce=secrets.token_hex(16),380 )381 claim.sign(issuer_agent)382 self.claims[claim.id] = claim383 self.add_event("TRUST_CLAIM_ISSUED", claim.to_dict())384 return claim385 386 def verify_claim(self, claim: TrustClaim) -> Tuple[bool, str]:387 if claim.issuer not in self.agents:388 return False, "unknown issuer"389 if claim.subject not in self.agents:390 return False, "unknown subject"391 if claim.expires_at:392 try:393 exp = _dt.datetime.fromisoformat(claim.expires_at)394 if exp.tzinfo is None:395 exp = exp.replace(tzinfo=_dt.timezone.utc)396 if exp < _dt.datetime.now(_dt.timezone.utc):397 return False, "claim expired"398 except Exception:399 return False, "bad expires_at timestamp"400 issuer_agent = self.agents[claim.issuer]401 if not claim.verify(issuer_agent):402 return False, "bad signature"403 return True, "valid"404 405 def verify_ledger(self) -> Tuple[bool, List[str]]:406 errors: List[str] = []407 previous = GENESIS408 for i, event in enumerate(self.ledger):409 if event.index != i:410 errors.append(f"ledger index mismatch at {i}")411 if event.previous_hash != previous:412 errors.append(f"previous hash mismatch at event {i}")413 raw = event.to_dict().copy()414 claimed_hash = raw.pop("event_hash")415 recomputed = digest(raw)416 if claimed_hash != recomputed:417 errors.append(f"event hash mismatch at event {i}")418 previous = event.event_hash419 420 for claim in self.claims.values():421 ok, reason = self.verify_claim(claim)422 if not ok:423 errors.append(f"claim {claim.id}: {reason}")424 425 return not errors, errors426 427 def reputation(self, subject: str, kind: Optional[str] = None) -> Dict[str, Any]:428 subject_agent = self.resolve_agent(subject)429 valid_claims: List[TrustClaim] = []430 431 for claim in self.claims.values():432 if claim.subject != subject_agent.did:433 continue434 if kind and claim.kind != kind:435 continue436 ok, _ = self.verify_claim(claim)437 if ok:438 valid_claims.append(claim)439 440 if not valid_claims:441 return {442 "subject": subject_agent.name,443 "did": subject_agent.did,444 "kind": kind or "*",445 "score": None,446 "claims": 0,447 "explanation": "no valid claims",448 }449 450 # Recursive-ish trust weighting:451 # An issuer's influence is partly based on how much the network trusts452 # the issuer. This is intentionally bounded and simple.453 issuer_weights: Dict[str, float] = {}454 for claim in valid_claims:455 if claim.issuer == subject_agent.did:456 issuer_weights[claim.issuer] = 0.05457 else:458 issuer_weights[claim.issuer] = 1.0459 460 weighted_sum = 0.0461 total_weight = 0.0462 for claim in valid_claims:463 age_weight = self._age_decay(claim.issued_at)464 issuer_weight = issuer_weights.get(claim.issuer, 1.0)465 weight = claim.confidence * age_weight * issuer_weight466 weighted_sum += claim.score * weight467 total_weight += weight468 469 score = weighted_sum / total_weight if total_weight else 0.0470 return {471 "subject": subject_agent.name,472 "did": subject_agent.did,473 "kind": kind or "*",474 "score": round(score, 6),475 "claims": len(valid_claims),476 "confidence_mass": round(total_weight, 6),477 "interpretation": self._interpret(score),478 }479 480 @staticmethod481 def _age_decay(issued_at: str, half_life_days: float = 365.0) -> float:482 try:483 t = _dt.datetime.fromisoformat(issued_at)484 if t.tzinfo is None:485 t = t.replace(tzinfo=_dt.timezone.utc)486 days = (_dt.datetime.now(_dt.timezone.utc) - t).total_seconds() / 86400.0487 return 0.5 ** max(days, 0.0) / half_life_days488 except Exception:489 return 1.0490 491 @staticmethod492 def _interpret(score: float) -> str:493 if score >= 0.85:494 return "high trust"495 if score >= 0.65:496 return "moderate trust"497 if score >= 0.45:498 return "uncertain trust"499 return "low trust"500 501 def export(self) -> Dict[str, Any]:502 return {503 "protocol": PROTOCOL_NAME,504 "version": PROTOCOL_VERSION,505 "crypto_mode": CRYPTO.mode,506 "agents": [dataclasses.asdict(a) for a in self.agents.values()],507 "claims": [c.to_dict() for c in self.claims.values()],508 "ledger": [e.to_dict() for e in self.ledger],509 }510 511 @classmethod512 def import_data(cls, data: Dict[str, Any]) -> "BainkProtocol":513 p = cls()514 for a in data.get("agents", []):515 agent = Agent(**a)516 p.agents[agent.did] = agent517 p.name_index[agent.name] = agent.did518 for c in data.get("claims", []):519 claim = TrustClaim.from_dict(c)520 p.claims[claim.id] = claim521 for e in data.get("ledger", []):522 p.ledger.append(LedgerEvent.from_dict(e))523 return p524 525 def save(self, path: str) -> None:526 Path(path).write_text(json.dumps(self.export(), indent=2), encoding="utf-8")527 528 @classmethod529 def load(cls, path: str) -> "BainkProtocol":530 if not Path(path).exists():531 return cls()532 return cls.import_data(json.loads(Path(path).read_text(encoding="utf-8")))533 534 535# ---------------------------------------------------------------------------536# CLI537# ---------------------------------------------------------------------------538 539def print_json(obj: Any) -> None:540 print(json.dumps(obj, indent=2, ensure_ascii=False))541 542 543def cmd_demo(_: argparse.Namespace) -> None:544 p = BainkProtocol()545 alice = p.create_agent("Alice", {"role": "auditor"})546 bob = p.create_agent("Bob", {"role": "builder"})547 carol = p.create_agent("Carol", {"role": "customer"})548 p.issue_claim("Alice", "Bob", "reliability", 0.92, 0.95, {"source": "delivery audit"})549 p.issue_claim("Carol", "Bob", "integrity", 0.88, 0.80, {"source": "completed contract"})550 p.issue_claim("Alice", "Carol", "payment_behavior", 0.97, 0.90, {"source": "invoice record"})551 552 ok, errors = p.verify_ledger()553 print("BAINK Protocol demo")554 print(f"crypto_mode: {CRYPTO.mode}")555 print(f"ledger_valid: {ok}")556 if errors:557 print_json(errors)558 print("\nAgents:")559 print_json([a.public_view() for a in p.agents.values()])560 print("\nBob reputation:")561 print_json(p.reputation("Bob"))562 print("\nLedger head:")563 print_json(p.ledger[-1].to_dict())564 565 566def cmd_init(args: argparse.Namespace) -> None:567 p = BainkProtocol()568 p.save(args.db)569 print(f"initialized {args.db}")570 571 572def cmd_create_agent(args: argparse.Namespace) -> None:573 p = BainkProtocol.load(args.db)574 metadata = json.loads(args.metadata) if args.metadata else {}575 agent = p.create_agent(args.name, metadata)576 p.save(args.db)577 print_json(agent.public_view())578 579 580def cmd_claim(args: argparse.Namespace) -> None:581 p = BainkProtocol.load(args.db)582 evidence = json.loads(args.evidence) if args.evidence else {}583 claim = p.issue_claim(584 issuer=args.issuer,585 subject=args.subject,586 kind=args.kind,587 score=args.score,588 confidence=args.confidence,589 evidence=evidence,590 expires_at=args.expires_at,591 )592 p.save(args.db)593 print_json(claim.to_dict())594 595 596def cmd_verify(args: argparse.Namespace) -> None:597 p = BainkProtocol.load(args.db)598 ok, errors = p.verify_ledger()599 print_json({"valid": ok, "errors": errors})600 601 602def cmd_reputation(args: argparse.Namespace) -> None:603 p = BainkProtocol.load(args.db)604 print_json(p.reputation(args.subject, args.kind))605 606 607def cmd_agents(args: argparse.Namespace) -> None:608 p = BainkProtocol.load(args.db)609 print_json([a.public_view() for a in p.agents.values()])610 611 612def cmd_ledger(args: argparse.Namespace) -> None:613 p = BainkProtocol.load(args.db)614 print_json([e.to_dict() for e in p.ledger])615 616 617def build_parser() -> argparse.ArgumentParser:618 parser = argparse.ArgumentParser(619 prog="baink_protocol",620 formatter_class=argparse.RawDescriptionHelpFormatter,621 description=textwrap.dedent("""622 BAINK Protocol — Engineering Trust Infrastructure623 624 A complete flattened executable trust layer in one Python file.625 """),626 )627 sub = parser.add_subparsers(required=True)628 629 demo = sub.add_parser("demo")630 demo.set_defaults(func=cmd_demo)631 632 init = sub.add_parser("init")633 init.add_argument("--db", default="baink.json")634 init.set_defaults(func=cmd_init)635 636 ca = sub.add_parser("create-agent")637 ca.add_argument("name")638 ca.add_argument("--metadata", help='JSON metadata, e.g. {"role":"auditor"}')639 ca.add_argument("--db", default="baink.json")640 ca.set_defaults(func=cmd_create_agent)641 642 claim = sub.add_parser("claim")643 claim.add_argument("--issuer", required=True)644 claim.add_argument("--subject", required=True)645 claim.add_argument("--kind", required=True)646 claim.add_argument("--score", type=float, required=True)647 claim.add_argument("--confidence", type=float, default=1.0)648 claim.add_argument("--evidence", help='JSON evidence object')649 claim.add_argument("--expires-at")650 claim.add_argument("--db", default="baink.json")651 claim.set_defaults(func=cmd_claim)652 653 verify = sub.add_parser("verify")654 verify.add_argument("--db", default="baink.json")655 verify.set_defaults(func=cmd_verify)656 657 rep = sub.add_parser("reputation")658 rep.add_argument("subject")659 rep.add_argument("--kind")660 rep.add_argument("--db", default="baink.json")661 rep.set_defaults(func=cmd_reputation)662 663 agents = sub.add_parser("agents")664 agents.add_argument("--db", default="baink.json")665 agents.set_defaults(func=cmd_agents)666 667 ledger = sub.add_parser("ledger")668 ledger.add_argument("--db", default="baink.json")669 ledger.set_defaults(func=cmd_ledger)670 671 return parser672 673 674def main(argv: Optional[List[str]] = None) -> int:675 parser = build_parser()676 args = parser.parse_args(argv)677 try:678 args.func(args)679 return 0680 except Exception as exc:681 print(f"BAINK_ERROR: {exc}", file=sys.stderr)682 return 1683 684 685if __name__ == "__main__":686 raise SystemExit(main())