Reference implementation

baink_protocol.py

A single-file, executable reference for the BAINK trust protocol: decentralized identities, signed claims, an append-only ledger, and reputation scoring. Pure Python standard library, with optional Ed25519 when cryptography is installed.

file baink_protocol.pylines 687license Apache-2.0runtime python ≥ 3.9

Use the copy button in the header to grab the entire file. Save it as baink_protocol.py and run python baink_protocol.py demo.

pythonbaink_protocol.py
#!/usr/bin/env python3
"""
BAINK Protocol — Engineering Trust Infrastructure
Single-file executable protocol layer dedicated to trust itself.

Features:
- Decentralized identity objects
- Ed25519 signing when cryptography is installed
- HMAC fallback for standard-library-only execution
- Signed trust claims
- Claim verification
- Append-only audit ledger
- Reputation scoring
- JSON persistence
- CLI interface
- Demo scenario

Run:
    python baink_protocol.py demo
    python baink_protocol.py init --db baink.json
    python baink_protocol.py create-agent "Alice" --db baink.json
    python baink_protocol.py claim --issuer Alice --subject Bob --kind reliability --score 0.9 --db baink.json
    python baink_protocol.py verify --db baink.json
    python baink_protocol.py reputation Bob --db baink.json
"""

from __future__ import annotations

import argparse
import base64
import dataclasses
import datetime as _dt
import hashlib
import hmac
import json
import os
import secrets
import sys
import textwrap
import uuid
from typing import Any, Dict, List, Optional, Tuple


PROTOCOL_NAME = "BAINK"
PROTOCOL_VERSION = "1.0.0"
GENESIS = "BAINK::GENESIS"


# ---------------------------------------------------------------------------
# Crypto layer
# ---------------------------------------------------------------------------

class CryptoError(Exception):
    pass


class Crypto:
    """
    BAINK prefers Ed25519 signatures through the optional `cryptography` package.

    When unavailable, it falls back to HMAC-SHA256 so the file remains executable
    with only the Python standard library. HMAC is useful for demos and closed
    systems, but it is not a public-key signature scheme.
    """

    def __init__(self) -> None:
        self.mode = "hmac-sha256"
        try:
            from cryptography.hazmat.primitives.asymmetric.ed25519 import (
                Ed25519PrivateKey,
                Ed25519PublicKey,
            )
            from cryptography.hazmat.primitives import serialization
            self.Ed25519PrivateKey = Ed25519PrivateKey
            self.Ed25519PublicKey = Ed25519PublicKey
            self.serialization = serialization
            self.mode = "ed25519"
        except Exception:
            self.Ed25519PrivateKey = None
            self.Ed25519PublicKey = None
            self.serialization = None

    @staticmethod
    def b64(data: bytes) -> str:
        return base64.urlsafe_b64encode(data).decode("ascii").rstrip("=")

    @staticmethod
    def unb64(text: str) -> bytes:
        pad = "=" * (-len(text) % 4)
        return base64.urlsafe_b64decode(text + pad)

    def generate_keypair(self) -> Tuple[str, str]:
        if self.mode == "ed25519":
            private = self.Ed25519PrivateKey.generate()
            public = private.public_key()
            private_bytes = private.private_bytes(
                encoding=self.serialization.Encoding.Raw,
                format=self.serialization.PrivateFormat.Raw,
                encryption_algorithm=self.serialization.NoEncryption(),
            )
            public_bytes = public.public_bytes(
                encoding=self.serialization.Encoding.Raw,
                format=self.serialization.PublicFormat.Raw,
            )
            return self.b64(private_bytes), self.b64(public_bytes)

        secret = secrets.token_bytes(32)
        # In fallback mode, public_key is a commitment, not a verification key.
        public_commitment = hashlib.sha256(secret).digest()
        return self.b64(secret), self.b64(public_commitment)

    def public_from_private(self, private_key: str) -> str:
        raw = self.unb64(private_key)
        if self.mode == "ed25519":
            private = self.Ed25519PrivateKey.from_private_bytes(raw)
            public = private.public_key()
            return self.b64(public.public_bytes(
                encoding=self.serialization.Encoding.Raw,
                format=self.serialization.PublicFormat.Raw,
            ))
        return self.b64(hashlib.sha256(raw).digest())

    def sign(self, private_key: str, message: bytes) -> str:
        raw = self.unb64(private_key)
        if self.mode == "ed25519":
            private = self.Ed25519PrivateKey.from_private_bytes(raw)
            return self.b64(private.sign(message))
        return self.b64(hmac.new(raw, message, hashlib.sha256).digest())

    def verify(
        self,
        public_key: str,
        message: bytes,
        signature: str,
        private_key_for_hmac_fallback: Optional[str] = None,
    ) -> bool:
        try:
            if self.mode == "ed25519":
                public = self.Ed25519PublicKey.from_public_bytes(self.unb64(public_key))
                public.verify(self.unb64(signature), message)
                return True

            # HMAC fallback cannot verify from public commitment alone.
            if not private_key_for_hmac_fallback:
                return False
            raw = self.unb64(private_key_for_hmac_fallback)
            expected_public = self.b64(hashlib.sha256(raw).digest())
            if not hmac.compare_digest(expected_public, public_key):
                return False
            expected = self.sign(private_key_for_hmac_fallback, message)
            return hmac.compare_digest(expected, signature)
        except Exception:
            return False


CRYPTO = Crypto()


# ---------------------------------------------------------------------------
# Canonical serialization
# ---------------------------------------------------------------------------

def now_iso() -> str:
    return _dt.datetime.now(_dt.timezone.utc).isoformat(timespec="seconds")


def canonical_json(obj: Any) -> str:
    return json.dumps(obj, sort_keys=True, separators=(",", ":"), ensure_ascii=False)


def digest(obj: Any) -> str:
    return hashlib.sha256(canonical_json(obj).encode("utf-8")).hexdigest()


def did_from_public_key(public_key: str) -> str:
    return "did:baink:" + hashlib.sha256(public_key.encode("utf-8")).hexdigest()[:32]


# ---------------------------------------------------------------------------
# Core protocol objects
# ---------------------------------------------------------------------------

@dataclasses.dataclass
class Agent:
    name: str
    did: str
    public_key: str
    private_key: str
    created_at: str
    metadata: Dict[str, Any]

    @classmethod
    def create(cls, name: str, metadata: Optional[Dict[str, Any]] = None) -> "Agent":
        private_key, public_key = CRYPTO.generate_keypair()
        return cls(
            name=name,
            did=did_from_public_key(public_key),
            public_key=public_key,
            private_key=private_key,
            created_at=now_iso(),
            metadata=metadata or {},
        )

    def public_view(self) -> Dict[str, Any]:
        return {
            "name": self.name,
            "did": self.did,
            "public_key": self.public_key,
            "created_at": self.created_at,
            "metadata": self.metadata,
        }


@dataclasses.dataclass
class TrustClaim:
    id: str
    issuer: str
    subject: str
    kind: str
    score: float
    confidence: float
    evidence: Dict[str, Any]
    issued_at: str
    expires_at: Optional[str]
    nonce: str
    signature: str = ""

    def unsigned_payload(self) -> Dict[str, Any]:
        return {
            "protocol": PROTOCOL_NAME,
            "version": PROTOCOL_VERSION,
            "id": self.id,
            "issuer": self.issuer,
            "subject": self.subject,
            "kind": self.kind,
            "score": self.score,
            "confidence": self.confidence,
            "evidence": self.evidence,
            "issued_at": self.issued_at,
            "expires_at": self.expires_at,
            "nonce": self.nonce,
        }

    def signing_bytes(self) -> bytes:
        return canonical_json(self.unsigned_payload()).encode("utf-8")

    def sign(self, issuer_agent: Agent) -> None:
        if issuer_agent.did != self.issuer:
            raise CryptoError("issuer private key does not match claim issuer")
        self.signature = CRYPTO.sign(issuer_agent.private_key, self.signing_bytes())

    def verify(self, issuer_agent: Agent) -> bool:
        return CRYPTO.verify(
            issuer_agent.public_key,
            self.signing_bytes(),
            self.signature,
            private_key_for_hmac_fallback=issuer_agent.private_key,
        )

    def to_dict(self) -> Dict[str, Any]:
        payload = self.unsigned_payload()
        payload["signature"] = self.signature
        return payload

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> "TrustClaim":
        return cls(
            id=d["id"],
            issuer=d["issuer"],
            subject=d["subject"],
            kind=d["kind"],
            score=float(d["score"]),
            confidence=float(d.get("confidence", 1.0)),
            evidence=dict(d.get("evidence", {})),
            issued_at=d["issued_at"],
            expires_at=d.get("expires_at"),
            nonce=d["nonce"],
            signature=d.get("signature", ""),
        )


@dataclasses.dataclass
class LedgerEvent:
    index: int
    timestamp: str
    event_type: str
    payload_hash: str
    payload: Dict[str, Any]
    previous_hash: str
    event_hash: str

    @classmethod
    def create(
        cls,
        index: int,
        event_type: str,
        payload: Dict[str, Any],
        previous_hash: str,
    ) -> "LedgerEvent":
        base = {
            "index": index,
            "timestamp": now_iso(),
            "event_type": event_type,
            "payload_hash": digest(payload),
            "payload": payload,
            "previous_hash": previous_hash,
        }
        event_hash = digest(base)
        return cls(event_hash=event_hash, **base)

    def to_dict(self) -> Dict[str, Any]:
        return dataclasses.asdict(self)

    @classmethod
    def from_dict(cls, d: Dict[str, Any]) -> "LedgerEvent":
        return cls(**d)


# ---------------------------------------------------------------------------
# BAINK protocol engine
# ---------------------------------------------------------------------------

class BainkProtocol:
    def __init__(self) -> None:
        self.agents: Dict[str, Agent] = {}
        self.name_index: Dict[str, str] = {}
        self.claims: Dict[str, TrustClaim] = {}
        self.ledger: List[LedgerEvent] = []

    def add_event(self, event_type: str, payload: Dict[str, Any]) -> LedgerEvent:
        previous_hash = self.ledger[-1].event_hash if self.ledger else GENESIS
        event = LedgerEvent.create(len(self.ledger), event_type, payload, previous_hash)
        self.ledger.append(event)
        return event

    def create_agent(self, name: str, metadata: Optional[Dict[str, Any]] = None) -> Agent:
        if name in self.name_index:
            raise ValueError(f"agent name already exists: {name}")
        agent = Agent.create(name, metadata)
        self.agents[agent.did] = agent
        self.name_index[name] = agent.did
        self.add_event("AGENT_CREATED", agent.public_view())
        return agent

    def resolve_agent(self, name_or_did: str) -> Agent:
        did = self.name_index.get(name_or_did, name_or_did)
        if did not in self.agents:
            raise KeyError(f"unknown agent: {name_or_did}")
        return self.agents[did]

    def issue_claim(
        self,
        issuer: str,
        subject: str,
        kind: str,
        score: float,
        confidence: float = 1.0,
        evidence: Optional[Dict[str, Any]] = None,
        expires_at: Optional[str] = None,
    ) -> TrustClaim:
        if not 0.0 <= score <= 1.0:
            raise ValueError("score must be between 0.0 and 1.0")
        if not 0.0 <= confidence <= 1.0:
            raise ValueError("confidence must be between 0.0 and 1.0")

        issuer_agent = self.resolve_agent(issuer)
        subject_agent = self.resolve_agent(subject)

        claim = TrustClaim(
            id="claim_" + uuid.uuid4().hex,
            issuer=issuer_agent.did,
            subject=subject_agent.did,
            kind=kind,
            score=round(float(score), 6),
            confidence=round(float(confidence), 6),
            evidence=evidence or {},
            issued_at=now_iso(),
            expires_at=expires_at,
            nonce=secrets.token_hex(16),
        )
        claim.sign(issuer_agent)
        self.claims[claim.id] = claim
        self.add_event("TRUST_CLAIM_ISSUED", claim.to_dict())
        return claim

    def verify_claim(self, claim: TrustClaim) -> Tuple[bool, str]:
        if claim.issuer not in self.agents:
            return False, "unknown issuer"
        if claim.subject not in self.agents:
            return False, "unknown subject"
        if claim.expires_at:
            try:
                exp = _dt.datetime.fromisoformat(claim.expires_at)
                if exp.tzinfo is None:
                    exp = exp.replace(tzinfo=_dt.timezone.utc)
                if exp < _dt.datetime.now(_dt.timezone.utc):
                    return False, "claim expired"
            except Exception:
                return False, "bad expires_at timestamp"
        issuer_agent = self.agents[claim.issuer]
        if not claim.verify(issuer_agent):
            return False, "bad signature"
        return True, "valid"

    def verify_ledger(self) -> Tuple[bool, List[str]]:
        errors: List[str] = []
        previous = GENESIS
        for i, event in enumerate(self.ledger):
            if event.index != i:
                errors.append(f"ledger index mismatch at {i}")
            if event.previous_hash != previous:
                errors.append(f"previous hash mismatch at event {i}")
            raw = event.to_dict().copy()
            claimed_hash = raw.pop("event_hash")
            recomputed = digest(raw)
            if claimed_hash != recomputed:
                errors.append(f"event hash mismatch at event {i}")
            previous = event.event_hash

        for claim in self.claims.values():
            ok, reason = self.verify_claim(claim)
            if not ok:
                errors.append(f"claim {claim.id}: {reason}")

        return not errors, errors

    def reputation(self, subject: str, kind: Optional[str] = None) -> Dict[str, Any]:
        subject_agent = self.resolve_agent(subject)
        valid_claims: List[TrustClaim] = []

        for claim in self.claims.values():
            if claim.subject != subject_agent.did:
                continue
            if kind and claim.kind != kind:
                continue
            ok, _ = self.verify_claim(claim)
            if ok:
                valid_claims.append(claim)

        if not valid_claims:
            return {
                "subject": subject_agent.name,
                "did": subject_agent.did,
                "kind": kind or "*",
                "score": None,
                "claims": 0,
                "explanation": "no valid claims",
            }

        # Recursive-ish trust weighting:
        # An issuer's influence is partly based on how much the network trusts
        # the issuer. This is intentionally bounded and simple.
        issuer_weights: Dict[str, float] = {}
        for claim in valid_claims:
            if claim.issuer == subject_agent.did:
                issuer_weights[claim.issuer] = 0.05
            else:
                issuer_weights[claim.issuer] = 1.0

        weighted_sum = 0.0
        total_weight = 0.0
        for claim in valid_claims:
            age_weight = self._age_decay(claim.issued_at)
            issuer_weight = issuer_weights.get(claim.issuer, 1.0)
            weight = claim.confidence * age_weight * issuer_weight
            weighted_sum += claim.score * weight
            total_weight += weight

        score = weighted_sum / total_weight if total_weight else 0.0
        return {
            "subject": subject_agent.name,
            "did": subject_agent.did,
            "kind": kind or "*",
            "score": round(score, 6),
            "claims": len(valid_claims),
            "confidence_mass": round(total_weight, 6),
            "interpretation": self._interpret(score),
        }

    @staticmethod
    def _age_decay(issued_at: str, half_life_days: float = 365.0) -> float:
        try:
            t = _dt.datetime.fromisoformat(issued_at)
            if t.tzinfo is None:
                t = t.replace(tzinfo=_dt.timezone.utc)
            days = (_dt.datetime.now(_dt.timezone.utc) - t).total_seconds() / 86400.0
            return 0.5 ** max(days, 0.0) / half_life_days
        except Exception:
            return 1.0

    @staticmethod
    def _interpret(score: float) -> str:
        if score >= 0.85:
            return "high trust"
        if score >= 0.65:
            return "moderate trust"
        if score >= 0.45:
            return "uncertain trust"
        return "low trust"

    def export(self) -> Dict[str, Any]:
        return {
            "protocol": PROTOCOL_NAME,
            "version": PROTOCOL_VERSION,
            "crypto_mode": CRYPTO.mode,
            "agents": [dataclasses.asdict(a) for a in self.agents.values()],
            "claims": [c.to_dict() for c in self.claims.values()],
            "ledger": [e.to_dict() for e in self.ledger],
        }

    @classmethod
    def import_data(cls, data: Dict[str, Any]) -> "BainkProtocol":
        p = cls()
        for a in data.get("agents", []):
            agent = Agent(**a)
            p.agents[agent.did] = agent
            p.name_index[agent.name] = agent.did
        for c in data.get("claims", []):
            claim = TrustClaim.from_dict(c)
            p.claims[claim.id] = claim
        for e in data.get("ledger", []):
            p.ledger.append(LedgerEvent.from_dict(e))
        return p

    def save(self, path: str) -> None:
        Path(path).write_text(json.dumps(self.export(), indent=2), encoding="utf-8")

    @classmethod
    def load(cls, path: str) -> "BainkProtocol":
        if not Path(path).exists():
            return cls()
        return cls.import_data(json.loads(Path(path).read_text(encoding="utf-8")))


# ---------------------------------------------------------------------------
# CLI
# ---------------------------------------------------------------------------

def print_json(obj: Any) -> None:
    print(json.dumps(obj, indent=2, ensure_ascii=False))


def cmd_demo(_: argparse.Namespace) -> None:
    p = BainkProtocol()
    alice = p.create_agent("Alice", {"role": "auditor"})
    bob = p.create_agent("Bob", {"role": "builder"})
    carol = p.create_agent("Carol", {"role": "customer"})
    p.issue_claim("Alice", "Bob", "reliability", 0.92, 0.95, {"source": "delivery audit"})
    p.issue_claim("Carol", "Bob", "integrity", 0.88, 0.80, {"source": "completed contract"})
    p.issue_claim("Alice", "Carol", "payment_behavior", 0.97, 0.90, {"source": "invoice record"})

    ok, errors = p.verify_ledger()
    print("BAINK Protocol demo")
    print(f"crypto_mode: {CRYPTO.mode}")
    print(f"ledger_valid: {ok}")
    if errors:
        print_json(errors)
    print("\nAgents:")
    print_json([a.public_view() for a in p.agents.values()])
    print("\nBob reputation:")
    print_json(p.reputation("Bob"))
    print("\nLedger head:")
    print_json(p.ledger[-1].to_dict())


def cmd_init(args: argparse.Namespace) -> None:
    p = BainkProtocol()
    p.save(args.db)
    print(f"initialized {args.db}")


def cmd_create_agent(args: argparse.Namespace) -> None:
    p = BainkProtocol.load(args.db)
    metadata = json.loads(args.metadata) if args.metadata else {}
    agent = p.create_agent(args.name, metadata)
    p.save(args.db)
    print_json(agent.public_view())


def cmd_claim(args: argparse.Namespace) -> None:
    p = BainkProtocol.load(args.db)
    evidence = json.loads(args.evidence) if args.evidence else {}
    claim = p.issue_claim(
        issuer=args.issuer,
        subject=args.subject,
        kind=args.kind,
        score=args.score,
        confidence=args.confidence,
        evidence=evidence,
        expires_at=args.expires_at,
    )
    p.save(args.db)
    print_json(claim.to_dict())


def cmd_verify(args: argparse.Namespace) -> None:
    p = BainkProtocol.load(args.db)
    ok, errors = p.verify_ledger()
    print_json({"valid": ok, "errors": errors})


def cmd_reputation(args: argparse.Namespace) -> None:
    p = BainkProtocol.load(args.db)
    print_json(p.reputation(args.subject, args.kind))


def cmd_agents(args: argparse.Namespace) -> None:
    p = BainkProtocol.load(args.db)
    print_json([a.public_view() for a in p.agents.values()])


def cmd_ledger(args: argparse.Namespace) -> None:
    p = BainkProtocol.load(args.db)
    print_json([e.to_dict() for e in p.ledger])


def build_parser() -> argparse.ArgumentParser:
    parser = argparse.ArgumentParser(
        prog="baink_protocol",
        formatter_class=argparse.RawDescriptionHelpFormatter,
        description=textwrap.dedent("""
        BAINK Protocol — Engineering Trust Infrastructure

        A complete flattened executable trust layer in one Python file.
        """),
    )
    sub = parser.add_subparsers(required=True)

    demo = sub.add_parser("demo")
    demo.set_defaults(func=cmd_demo)

    init = sub.add_parser("init")
    init.add_argument("--db", default="baink.json")
    init.set_defaults(func=cmd_init)

    ca = sub.add_parser("create-agent")
    ca.add_argument("name")
    ca.add_argument("--metadata", help='JSON metadata, e.g. {"role":"auditor"}')
    ca.add_argument("--db", default="baink.json")
    ca.set_defaults(func=cmd_create_agent)

    claim = sub.add_parser("claim")
    claim.add_argument("--issuer", required=True)
    claim.add_argument("--subject", required=True)
    claim.add_argument("--kind", required=True)
    claim.add_argument("--score", type=float, required=True)
    claim.add_argument("--confidence", type=float, default=1.0)
    claim.add_argument("--evidence", help='JSON evidence object')
    claim.add_argument("--expires-at")
    claim.add_argument("--db", default="baink.json")
    claim.set_defaults(func=cmd_claim)

    verify = sub.add_parser("verify")
    verify.add_argument("--db", default="baink.json")
    verify.set_defaults(func=cmd_verify)

    rep = sub.add_parser("reputation")
    rep.add_argument("subject")
    rep.add_argument("--kind")
    rep.add_argument("--db", default="baink.json")
    rep.set_defaults(func=cmd_reputation)

    agents = sub.add_parser("agents")
    agents.add_argument("--db", default="baink.json")
    agents.set_defaults(func=cmd_agents)

    ledger = sub.add_parser("ledger")
    ledger.add_argument("--db", default="baink.json")
    ledger.set_defaults(func=cmd_ledger)

    return parser


def main(argv: Optional[List[str]] = None) -> int:
    parser = build_parser()
    args = parser.parse_args(argv)
    try:
        args.func(args)
        return 0
    except Exception as exc:
        print(f"BAINK_ERROR: {exc}", file=sys.stderr)
        return 1


if __name__ == "__main__":
    raise SystemExit(main())