"""魂籍(portable_identity_record):可验证、版本化的身份与状态包。 完整性模型: - payload 规范化序列化(排序键、紧凑分隔符)后取 SHA-256 摘要; - 签名 = HMAC-SHA256(密钥, "{character_id}|{version}|{digest}"),绑定人物与版本; - 校验:重算摘要与签名比对。篡改 payload 破坏摘要;把记录复制到别的人物/版本, 签名不匹配——文件相同或人物 ID 相同不足以冒充原主体(data-model.md §1)。 """ from __future__ import annotations import hashlib import hmac import json from app.core.config import settings from app.models.jingsheng import IdentityRecord def _canonical(payload: dict) -> str: return json.dumps(payload, ensure_ascii=False, sort_keys=True, separators=(",", ":")) def _digest(payload: dict) -> str: return hashlib.sha256(_canonical(payload).encode("utf-8")).hexdigest() def _signature(character_id: str, version: int, digest: str) -> str: secret = settings.identity_record_secret message = f"{character_id}|{version}|{digest}".encode("utf-8") return hmac.new(secret.encode("utf-8"), message, hashlib.sha256).hexdigest() def build_identity_record( *, character_id: str, version: int, identity: dict, runtime_state: dict, data_state: dict | None = None, ) -> IdentityRecord: """构建一条魂籍版本记录(调用方负责落库)。""" payload = { "spec": "hunjing.portable_identity_record.v1", "character_id": character_id, "version": version, "identity": identity, "data_state": data_state or {"note": "数据状态由各域自报(M1 占位)"}, "runtime_state": runtime_state, } digest = _digest(payload) return IdentityRecord( character_id=character_id, version=version, payload_json=json.dumps(payload, ensure_ascii=False), digest=digest, signature=_signature(character_id, version, digest), ) class IdentityVerificationError(Exception): """校验失败:摘要或签名不匹配。""" def __init__(self, reason: str) -> None: super().__init__(reason) self.reason = reason def verify_identity_record(record: IdentityRecord) -> dict: """校验魂籍记录,返回规范化 payload;失败抛 IdentityVerificationError。""" try: payload = json.loads(record.payload_json) except json.JSONDecodeError as exc: raise IdentityVerificationError("payload 无法解析") from exc if _digest(payload) != record.digest: raise IdentityVerificationError("摘要不匹配(payload 已被篡改)") expected = _signature(record.character_id, record.version, record.digest) if not hmac.compare_digest(expected, record.signature): raise IdentityVerificationError("签名不匹配(非本平台签发或被移植冒用)") if payload.get("character_id") != record.character_id or payload.get("version") != record.version: raise IdentityVerificationError("payload 声明的身份与记录不符") return payload