fix: cryptographically bind DGR-001 evidence
This commit is contained in:
@@ -25,16 +25,23 @@ the release gate is allowed to reach.
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import binascii
|
||||
import hashlib
|
||||
import json
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
from typing import Any, Mapping
|
||||
|
||||
from cryptography.exceptions import InvalidSignature
|
||||
from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
|
||||
|
||||
from .recipe_benchmark import Lane, REPORT_SCHEMA_VERSION
|
||||
|
||||
# Layout of the contract document understood by this reader.
|
||||
CONTRACT_SCHEMA_VERSION = 1
|
||||
PROVENANCE_SCHEMA_VERSION = 1
|
||||
REAL_REPORT_PRODUCER = "meshnet_node.recipe_drivers.run_configured_benchmark/v1"
|
||||
|
||||
VERDICT_PROMOTE = "promote"
|
||||
VERDICT_OPTIMIZE = "optimize"
|
||||
@@ -146,6 +153,66 @@ def _canonical_sha256(value: Any) -> str:
|
||||
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
||||
|
||||
|
||||
def report_signing_payload(report: Mapping[str, Any]) -> bytes:
|
||||
"""Canonical report bytes covered by the Ed25519 signature."""
|
||||
provenance = report.get("provenance")
|
||||
if not isinstance(provenance, Mapping):
|
||||
raise PerformanceContractError("real benchmark report lacks signed provenance")
|
||||
unsigned = dict(report)
|
||||
unsigned_provenance = dict(provenance)
|
||||
unsigned_provenance.pop("signature", None)
|
||||
unsigned["provenance"] = unsigned_provenance
|
||||
return json.dumps(
|
||||
unsigned, sort_keys=True, separators=(",", ":"), ensure_ascii=False
|
||||
).encode("utf-8")
|
||||
|
||||
|
||||
def _decode_base64(value: Any, label: str) -> bytes:
|
||||
if not isinstance(value, str):
|
||||
raise PerformanceContractError(f"report lacks {label}")
|
||||
try:
|
||||
return base64.b64decode(value, validate=True)
|
||||
except (binascii.Error, ValueError) as exc:
|
||||
raise PerformanceContractError(f"report carries invalid {label}") from exc
|
||||
|
||||
|
||||
def _verify_real_provenance(
|
||||
contract: PerformanceContract, report: Mapping[str, Any]
|
||||
) -> None:
|
||||
provenance = report.get("provenance")
|
||||
if not isinstance(provenance, Mapping):
|
||||
raise PerformanceContractError("real benchmark report lacks signed provenance")
|
||||
if provenance.get("schema_version") != PROVENANCE_SCHEMA_VERSION:
|
||||
raise PerformanceContractError("report provenance schema is unsupported")
|
||||
if provenance.get("producer") != REAL_REPORT_PRODUCER:
|
||||
raise PerformanceContractError("report was not emitted by the canonical real runner")
|
||||
if provenance.get("signature_algorithm") != "ed25519":
|
||||
raise PerformanceContractError("report provenance is not Ed25519 signed")
|
||||
for field in ("run_id", "started_at", "completed_at"):
|
||||
if not provenance.get(field):
|
||||
raise PerformanceContractError(f"report provenance lacks {field}")
|
||||
|
||||
required_config = contract.baseline.get("required_config_sha256")
|
||||
if not required_config or provenance.get("config_sha256") != required_config:
|
||||
raise PerformanceContractError("report config digest does not match the locked config")
|
||||
|
||||
encoded_public_key = contract.baseline.get("required_signer_public_key")
|
||||
public_key_bytes = _decode_base64(encoded_public_key, "locked signer public key")
|
||||
if len(public_key_bytes) != 32:
|
||||
raise PerformanceContractError("locked Ed25519 public key must be 32 bytes")
|
||||
expected_fingerprint = hashlib.sha256(public_key_bytes).hexdigest()
|
||||
if provenance.get("signer_public_key_sha256") != expected_fingerprint:
|
||||
raise PerformanceContractError("report signer fingerprint does not match the contract")
|
||||
|
||||
signature = _decode_base64(provenance.get("signature"), "Ed25519 signature")
|
||||
try:
|
||||
Ed25519PublicKey.from_public_bytes(public_key_bytes).verify(
|
||||
signature, report_signing_payload(report)
|
||||
)
|
||||
except (InvalidSignature, ValueError) as exc:
|
||||
raise PerformanceContractError("report Ed25519 signature verification failed") from exc
|
||||
|
||||
|
||||
def _validate_report(contract: PerformanceContract, report: Mapping[str, Any]) -> None:
|
||||
"""Fail closed when a report is not the experiment the contract locked."""
|
||||
try:
|
||||
@@ -187,6 +254,8 @@ def _validate_report(contract: PerformanceContract, report: Mapping[str, Any]) -
|
||||
raise PerformanceContractError(
|
||||
f"report evidence class {evidence_class!r} does not satisfy {required_evidence!r}"
|
||||
)
|
||||
if evidence_class in {"local-real", "multi-machine-real"}:
|
||||
_verify_real_provenance(contract, report)
|
||||
if required_evidence and (
|
||||
not isinstance(host, Mapping)
|
||||
or any(key not in host for key in ("hostname", "platform", "python", "cpu_count"))
|
||||
@@ -221,8 +290,18 @@ def _validate_report(contract: PerformanceContract, report: Mapping[str, Any]) -
|
||||
model_id = plan.get("model_id")
|
||||
model_revision = plan.get("model_revision")
|
||||
required_device = contract.baseline.get("required_device")
|
||||
required_artifacts = dict(contract.baseline.get("required_artifact_sha256") or {})
|
||||
required_runtimes = dict(contract.baseline.get("required_recipe_runtime") or {})
|
||||
required_backends = dict(contract.baseline.get("required_backend_detail") or {})
|
||||
required_host = dict(contract.baseline.get("required_host_identity") or {})
|
||||
for field, expected in required_host.items():
|
||||
if host.get(field) != expected:
|
||||
raise PerformanceContractError(
|
||||
f"report host/runtime field {field!r} does not match the locked identity"
|
||||
)
|
||||
for entry in recipes:
|
||||
recipe = entry.get("recipe", {})
|
||||
recipe_id = recipe.get("id")
|
||||
if required_device and recipe.get("device") != required_device:
|
||||
raise PerformanceContractError(
|
||||
f"recipe {recipe.get('id')!r} did not run on locked device {required_device!r}"
|
||||
@@ -235,6 +314,30 @@ def _validate_report(contract: PerformanceContract, report: Mapping[str, Any]) -
|
||||
digest = recipe.get("artifact_sha256", "")
|
||||
if not isinstance(digest, str) or len(digest) != 64:
|
||||
raise PerformanceContractError("report lacks an artifact SHA-256 digest")
|
||||
expected_digest = required_artifacts.get(recipe_id)
|
||||
if not expected_digest or digest != expected_digest:
|
||||
raise PerformanceContractError(
|
||||
f"recipe {recipe_id!r} artifact digest does not match the contract"
|
||||
)
|
||||
expected_runtime = required_runtimes.get(recipe_id)
|
||||
if not isinstance(expected_runtime, Mapping):
|
||||
raise PerformanceContractError(
|
||||
f"contract lacks runtime identity for recipe {recipe_id!r}"
|
||||
)
|
||||
actual_runtime = {
|
||||
field: recipe.get(field) for field in expected_runtime
|
||||
}
|
||||
if actual_runtime != dict(expected_runtime):
|
||||
raise PerformanceContractError(
|
||||
f"recipe {recipe_id!r} runtime identity does not match the contract"
|
||||
)
|
||||
if entry.get("available"):
|
||||
expected_backend = required_backends.get(recipe_id)
|
||||
actual_backend = entry.get("load", {}).get("backend_detail")
|
||||
if not expected_backend or actual_backend != expected_backend:
|
||||
raise PerformanceContractError(
|
||||
f"recipe {recipe_id!r} backend identity does not match the contract"
|
||||
)
|
||||
if entry.get("available"):
|
||||
cells = entry.get("concurrency", {})
|
||||
missing_cells = required_levels - {int(level) for level in cells}
|
||||
@@ -666,6 +769,23 @@ def baseline_from_report(report: Mapping[str, Any]) -> dict[str, Any]:
|
||||
"plan_sha256": _canonical_sha256(report["plan"]),
|
||||
"reference_recipe_id": report["reference_recipe_id"],
|
||||
"host": report["host"],
|
||||
"provenance": dict(report.get("provenance") or {}),
|
||||
"artifact_sha256": {
|
||||
recipe_id: entry["recipe"]["artifact_sha256"]
|
||||
for recipe_id, entry in entries.items()
|
||||
},
|
||||
"recipe_runtime": {
|
||||
recipe_id: {
|
||||
field: entry["recipe"].get(field)
|
||||
for field in ("runtime", "weight_format", "weight_quantization", "device")
|
||||
}
|
||||
for recipe_id, entry in entries.items()
|
||||
},
|
||||
"backend_detail": {
|
||||
recipe_id: entry.get("load", {}).get("backend_detail")
|
||||
for recipe_id, entry in entries.items()
|
||||
if entry.get("available")
|
||||
},
|
||||
"recipes": {},
|
||||
}
|
||||
for recipe_id, entry in entries.items():
|
||||
|
||||
Reference in New Issue
Block a user