proof-aware-crypto-tooling-.../src/pacta/transparency.py

365 lines
14 KiB
Python
Raw Normal View History

2026-07-03 12:09:34 +00:00
from __future__ import annotations
import hashlib
from dataclasses import dataclass, field
from pathlib import Path
from typing import Any
from .postquantum import detect_ml_dsa
The provider eats its own dogfood: root signatures via the merkleized library The dogfood principle now runs in BOTH directions. Agents already verified signatures through the proven dalek path; now the provider SIGNS with it too, and proves to itself that the signing code is in its own log before every signature: - dogfood binary gains a `sign` mode (seed over stdin, never argv; ed25519_dalek::SigningKey from the same pinned merkleized workspace). Honesty ledger unchanged: the library's VERIFY path is certificate-covered; its signing path is declared trusted base - but it is the ATTESTED artifact, not an un-attested third implementation. - sign_payload_ed25519_detailed: signing dispatch mirroring the verify dispatch; the backend that actually signed is recorded in every attestation signature block and STH. - THE SELF-REFERENTIAL CHECK: before signing any tree head, the provider runs the SAME Merkle inclusion verification an agent runs - against the very tree it is about to sign - for the newest leaf attesting the signing library itself, and embeds the result in the signature block: signing_provenance: signing_backend: verified-dalek-serial signing_library_component: dalek-ed25519-verified signing_library_source_commit: aa0f6ab... self_inclusion: verified signing_library_leaf_index: 4 signing_library_certificates_proven: 16/16 A root signature that names the leaf vouching for the code that produced it. First-append chicken-and-egg is handled honestly (self_inclusion: library_not_in_log). - Evidence refreshed: all four receipts re-issued under dogfood-signed STHs; the full agent verify loop re-run green. 50/50 tests (new signing roundtrip test, skip-safe where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:21:17 +00:00
from .signing import canonical_json, public_key_fingerprint, sign_payload_ed25519_detailed, verify_payload_ed25519_detailed
2026-07-03 12:09:34 +00:00
from .yamlio import load_data
HASH_ALGORITHM = "RFC9162_SHA256"
LEAF_TYPE = "pacta.transparency.attestation_leaf.v1"
RECEIPT_TYPE = "pacta.transparency.receipt.v1"
STH_TYPE = "pacta.transparency.signed_tree_head.v1"
@dataclass(slots=True)
class ReceiptVerificationResult:
accepted: bool
diagnostics: list[str] = field(default_factory=list)
log_id: str | None = None
tree_size: int | None = None
root_hash: str | None = None
leaf_hash: str | None = None
signatures: dict[str, str] = field(default_factory=dict)
def evidence(self) -> dict[str, Any]:
return {
"transparency_receipt_status": "accepted" if self.accepted else "rejected",
"transparency_log_id": self.log_id,
"transparency_tree_size": self.tree_size,
"transparency_root_hash": self.root_hash,
"transparency_leaf_hash": self.leaf_hash,
"transparency_signature_status": self.signatures,
}
def leaf_hash(data: bytes) -> bytes:
return hashlib.sha256(b"\x00" + data).digest()
def node_hash(left: bytes, right: bytes) -> bytes:
return hashlib.sha256(b"\x01" + left + right).digest()
def merkle_root(leaves: list[bytes]) -> bytes:
if not leaves:
return hashlib.sha256(b"").digest()
if len(leaves) == 1:
return leaf_hash(leaves[0])
split = _largest_power_of_two_less_than(len(leaves))
return node_hash(merkle_root(leaves[:split]), merkle_root(leaves[split:]))
def inclusion_proof(leaves: list[bytes], index: int) -> list[bytes]:
if index < 0 or index >= len(leaves):
raise ValueError(f"Leaf index {index} is outside tree size {len(leaves)}")
return _inclusion_path(leaves, index)
def verify_inclusion(leaf: bytes, index: int, tree_size: int, proof: list[bytes], root_hash: bytes) -> bool:
if tree_size <= 0 or index < 0 or index >= tree_size:
return False
try:
calculated, consumed = _calculate_inclusion_root(leaf_hash(leaf), index, tree_size, proof, 0)
except ValueError:
return False
return consumed == len(proof) and calculated == root_hash
def consistency_proof(leaves: list[bytes], old_tree_size: int) -> list[bytes]:
if old_tree_size < 0 or old_tree_size > len(leaves):
raise ValueError("old_tree_size must be between 0 and the current tree size")
if old_tree_size in (0, len(leaves)):
return []
return _consistency_path(old_tree_size, leaves, complete=True)
def verify_consistency(
old_tree_size: int,
new_tree_size: int,
old_root_hash: bytes,
new_root_hash: bytes,
proof: list[bytes],
) -> bool:
if old_tree_size < 0 or new_tree_size < old_tree_size:
return False
if old_tree_size == 0:
return True
if old_tree_size == new_tree_size:
return old_root_hash == new_root_hash and not proof
if not proof:
return False
nodes = list(proof)
if _is_power_of_two(old_tree_size):
nodes.insert(0, old_root_hash)
fn = old_tree_size - 1
sn = new_tree_size - 1
while fn & 1:
fn >>= 1
sn >>= 1
old_hash = nodes.pop(0)
new_hash = old_hash
while nodes:
node = nodes.pop(0)
if sn == 0:
return False
if fn & 1 or fn == sn:
old_hash = node_hash(node, old_hash)
new_hash = node_hash(node, new_hash)
if not (fn & 1):
while fn & 1 == 0 and fn != 0:
fn >>= 1
sn >>= 1
else:
new_hash = node_hash(new_hash, node)
fn >>= 1
sn >>= 1
fix(verify_consistency): restore RFC 9162 Step-7 terminal sn==0 check The deployed consistency verifier implemented the RFC 9162 2.1.4.2 bit-navigation loop but its final return checked only the two reconstructed roots, omitting the terminal condition that the new-size navigation counter reach zero. That condition couples the consumed proof length to the claimed tree sizes; without it, a valid proof for one transition verifies under a lied (power-of-two) old size. Flagship: a valid 2->3 proof is accepted under the false claim 1->3 with the size-2 root. Fix: add `and sn == 0` to the final return. This is the corpus's Known Gap 14 (3,867 deployed-accepts-only cases in a pinned 73,573-case family, recorded in public log entry 13). It was found by the project's own differential harness; a post-appeal review round added a faithful RFC oracle as a third comparison, which showed the deployed verifier — not the mechanized model — was the one deviating from RFC 9162, and traced it to the missing terminal check. Scope: verify_consistency's only production caller is the consumer-side pin store, reached only behind a verified head signature. Generation is RFC-correct and unaffected; the live provider service does not run this verifier; the published standalone verify.py has no consistency verifier. An empirical search found 0 realizable pin-advance poisons against an honestly pinned consumer, consistent with Known Gap 14's non-claim. Verification: - New fail-first three-way regression test test_consistency_lied_size_three_way_agreement (deployed / recursive ConsRec model / independent faithful RFC 9162 transliteration) over the honest AND lied-size families; fails pre-fix, passes post-fix. - Historical differential tests (164,479 inclusion; 164,224 consistency) unchanged — the fix rejects nothing honest. - Full suite: 145 passed, 0 failed. Public log entry 13, the attested accumulator commit, and the IACR submission PDF are all unchanged. Vulnerable state tagged vulnerable/sn0-consistency-fd2f6ba. See docs/security-2026-07-23-consistency-terminal-check.md. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-23 16:34:23 +00:00
# RFC 9162 2.1.4.2 Step 7 requires the new-size navigation counter to reach
# zero: the consumed proof length must match the claimed tree sizes. Without
# it, distinct (false) old-size claims can navigate one proof to the same
# reconstructed roots, so a valid proof for one transition verifies under a
# lied size. Reconstructing both roots is necessary but not sufficient.
return old_hash == old_root_hash and new_hash == new_root_hash and sn == 0
2026-07-03 12:09:34 +00:00
def attestation_leaf(attestation: dict[str, Any]) -> dict[str, Any]:
return {
"schema_version": 1,
"type": LEAF_TYPE,
"attestation": attestation,
}
def leaf_bytes_for_attestation(attestation: dict[str, Any]) -> bytes:
return canonical_json(attestation_leaf(attestation))
def leaf_hash_hex_for_attestation(attestation: dict[str, Any]) -> str:
return leaf_hash(leaf_bytes_for_attestation(attestation)).hex()
def signed_tree_head_payload(sth: dict[str, Any]) -> bytes:
payload = {key: value for key, value in sth.items() if key != "signatures"}
return canonical_json(payload)
def make_signed_tree_head(
log_id: str,
tree_size: int,
root_hash_hex: str,
timestamp: str,
private_key_path: str | Path,
public_key_path: str | Path,
The provider eats its own dogfood: root signatures via the merkleized library The dogfood principle now runs in BOTH directions. Agents already verified signatures through the proven dalek path; now the provider SIGNS with it too, and proves to itself that the signing code is in its own log before every signature: - dogfood binary gains a `sign` mode (seed over stdin, never argv; ed25519_dalek::SigningKey from the same pinned merkleized workspace). Honesty ledger unchanged: the library's VERIFY path is certificate-covered; its signing path is declared trusted base - but it is the ATTESTED artifact, not an un-attested third implementation. - sign_payload_ed25519_detailed: signing dispatch mirroring the verify dispatch; the backend that actually signed is recorded in every attestation signature block and STH. - THE SELF-REFERENTIAL CHECK: before signing any tree head, the provider runs the SAME Merkle inclusion verification an agent runs - against the very tree it is about to sign - for the newest leaf attesting the signing library itself, and embeds the result in the signature block: signing_provenance: signing_backend: verified-dalek-serial signing_library_component: dalek-ed25519-verified signing_library_source_commit: aa0f6ab... self_inclusion: verified signing_library_leaf_index: 4 signing_library_certificates_proven: 16/16 A root signature that names the leaf vouching for the code that produced it. First-append chicken-and-egg is handled honestly (self_inclusion: library_not_in_log). - Evidence refreshed: all four receipts re-issued under dogfood-signed STHs; the full agent verify loop re-run green. 50/50 tests (new signing roundtrip test, skip-safe where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:21:17 +00:00
signing_provenance: dict[str, Any] | None = None,
slhdsa: the post-quantum signing path (deterministic, parameter-locked, additive) Phase 2b+3 of the step-3 rehearsal, under the four operator decisions of 2026-08-06: deterministic signing, separate slh_dsa block, additive posture, keygen executed same day (key in provider state, 0600, git-ignored — verified before generation, not after). src/pacta/slhdsa.py — the module that did not exist (register: pq-slot-names-unproven-algorithm). Parameter set LOCKED to SLH-DSA-SHA2-128s: every entry point asserts the key's reported algorithm and refuses anything else, because any other set sits outside all eleven certificates while looking like dogfood. Deterministic via -pkeyopt deterministic:1, so the byte-level reproducibility check that caught a real defect on the Ed25519 side survives here. Verification runs two ways: OpenSSL, and pacta-verify-slhdsa built from the pinned proven source — the one signature check in the estate performed by code whose verify path the certificates cover. The proven-verifier path is package-anchored, not cwd-relative: the lesson of signer-backend-depends-on-cwd applied on day one, not retrofitted. make_signed_tree_head grows optional slhdsa key parameters. With them, the head carries a signed slh_dsa block; without, an honest not-configured slot exactly as ml_dsa always has. ml_dsa itself is untouched. Signatures stay outside the signed payload for both algorithms — tested by asserting the payload is byte-identical with and without the slh_dsa key. Honesty carried in the artifact: signing_backend says "openssl" because no proven signer exists for any algorithm; the module docstring states that nothing here is Lean-proven and that the certificates cover the verify path of the extracted model only. Tests: 7 new, suite 152 passed, 0 failed, 0 skipped — including determinism (two signings, identical bytes), the foreign-key refusal (Ed25519 key raises), corruption rejected by both verifiers, and the proven/OpenSSL agreement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 20:03:39 +00:00
slhdsa_private_key_path: str | Path | None = None,
slhdsa_public_key_path: str | Path | None = None,
2026-07-03 12:09:34 +00:00
) -> dict[str, Any]:
sth: dict[str, Any] = {
"schema_version": 1,
"type": STH_TYPE,
"log_id": log_id,
"tree_size": tree_size,
"timestamp": timestamp,
"root_hash": root_hash_hex,
"hash_algorithm": HASH_ALGORITHM,
}
payload = signed_tree_head_payload(sth)
The provider eats its own dogfood: root signatures via the merkleized library The dogfood principle now runs in BOTH directions. Agents already verified signatures through the proven dalek path; now the provider SIGNS with it too, and proves to itself that the signing code is in its own log before every signature: - dogfood binary gains a `sign` mode (seed over stdin, never argv; ed25519_dalek::SigningKey from the same pinned merkleized workspace). Honesty ledger unchanged: the library's VERIFY path is certificate-covered; its signing path is declared trusted base - but it is the ATTESTED artifact, not an un-attested third implementation. - sign_payload_ed25519_detailed: signing dispatch mirroring the verify dispatch; the backend that actually signed is recorded in every attestation signature block and STH. - THE SELF-REFERENTIAL CHECK: before signing any tree head, the provider runs the SAME Merkle inclusion verification an agent runs - against the very tree it is about to sign - for the newest leaf attesting the signing library itself, and embeds the result in the signature block: signing_provenance: signing_backend: verified-dalek-serial signing_library_component: dalek-ed25519-verified signing_library_source_commit: aa0f6ab... self_inclusion: verified signing_library_leaf_index: 4 signing_library_certificates_proven: 16/16 A root signature that names the leaf vouching for the code that produced it. First-append chicken-and-egg is handled honestly (self_inclusion: library_not_in_log). - Evidence refreshed: all four receipts re-issued under dogfood-signed STHs; the full agent verify loop re-run green. 50/50 tests (new signing roundtrip test, skip-safe where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:21:17 +00:00
signature_base64, signing_backend = sign_payload_ed25519_detailed(payload, private_key_path)
slhdsa: the post-quantum signing path (deterministic, parameter-locked, additive) Phase 2b+3 of the step-3 rehearsal, under the four operator decisions of 2026-08-06: deterministic signing, separate slh_dsa block, additive posture, keygen executed same day (key in provider state, 0600, git-ignored — verified before generation, not after). src/pacta/slhdsa.py — the module that did not exist (register: pq-slot-names-unproven-algorithm). Parameter set LOCKED to SLH-DSA-SHA2-128s: every entry point asserts the key's reported algorithm and refuses anything else, because any other set sits outside all eleven certificates while looking like dogfood. Deterministic via -pkeyopt deterministic:1, so the byte-level reproducibility check that caught a real defect on the Ed25519 side survives here. Verification runs two ways: OpenSSL, and pacta-verify-slhdsa built from the pinned proven source — the one signature check in the estate performed by code whose verify path the certificates cover. The proven-verifier path is package-anchored, not cwd-relative: the lesson of signer-backend-depends-on-cwd applied on day one, not retrofitted. make_signed_tree_head grows optional slhdsa key parameters. With them, the head carries a signed slh_dsa block; without, an honest not-configured slot exactly as ml_dsa always has. ml_dsa itself is untouched. Signatures stay outside the signed payload for both algorithms — tested by asserting the payload is byte-identical with and without the slh_dsa key. Honesty carried in the artifact: signing_backend says "openssl" because no proven signer exists for any algorithm; the module docstring states that nothing here is Lean-proven and that the certificates cover the verify path of the extracted model only. Tests: 7 new, suite 152 passed, 0 failed, 0 skipped — including determinism (two signings, identical bytes), the foreign-key refusal (Ed25519 key raises), corruption rejected by both verifiers, and the proven/OpenSSL agreement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 20:03:39 +00:00
# slh_dsa is a SEPARATE block (operator decision 2026-08-06): ml_dsa keeps
# its truthful not-configured disclosure; no algorithm is swapped inside a
# field that names a different one. ADDITIVE: ed25519 remains the signature
# consumers must check; a head without an SLH-DSA key carries the honest
# not-configured slot, exactly as ml_dsa always has.
from .slhdsa import slh_dsa_not_configured_block, slh_dsa_signature_block
if slhdsa_private_key_path and slhdsa_public_key_path:
slh_block = slh_dsa_signature_block(payload, slhdsa_private_key_path, slhdsa_public_key_path)
else:
slh_block = slh_dsa_not_configured_block()
2026-07-03 12:09:34 +00:00
sth["signatures"] = {
"ed25519": {
"scheme": "openssl-ed25519",
The provider eats its own dogfood: root signatures via the merkleized library The dogfood principle now runs in BOTH directions. Agents already verified signatures through the proven dalek path; now the provider SIGNS with it too, and proves to itself that the signing code is in its own log before every signature: - dogfood binary gains a `sign` mode (seed over stdin, never argv; ed25519_dalek::SigningKey from the same pinned merkleized workspace). Honesty ledger unchanged: the library's VERIFY path is certificate-covered; its signing path is declared trusted base - but it is the ATTESTED artifact, not an un-attested third implementation. - sign_payload_ed25519_detailed: signing dispatch mirroring the verify dispatch; the backend that actually signed is recorded in every attestation signature block and STH. - THE SELF-REFERENTIAL CHECK: before signing any tree head, the provider runs the SAME Merkle inclusion verification an agent runs - against the very tree it is about to sign - for the newest leaf attesting the signing library itself, and embeds the result in the signature block: signing_provenance: signing_backend: verified-dalek-serial signing_library_component: dalek-ed25519-verified signing_library_source_commit: aa0f6ab... self_inclusion: verified signing_library_leaf_index: 4 signing_library_certificates_proven: 16/16 A root signature that names the leaf vouching for the code that produced it. First-append chicken-and-egg is handled honestly (self_inclusion: library_not_in_log). - Evidence refreshed: all four receipts re-issued under dogfood-signed STHs; the full agent verify loop re-run green. 50/50 tests (new signing roundtrip test, skip-safe where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:21:17 +00:00
"signing_backend": signing_backend,
2026-07-03 12:09:34 +00:00
"status": "signed",
"payload_digest_sha256": hashlib.sha256(payload).hexdigest(),
The provider eats its own dogfood: root signatures via the merkleized library The dogfood principle now runs in BOTH directions. Agents already verified signatures through the proven dalek path; now the provider SIGNS with it too, and proves to itself that the signing code is in its own log before every signature: - dogfood binary gains a `sign` mode (seed over stdin, never argv; ed25519_dalek::SigningKey from the same pinned merkleized workspace). Honesty ledger unchanged: the library's VERIFY path is certificate-covered; its signing path is declared trusted base - but it is the ATTESTED artifact, not an un-attested third implementation. - sign_payload_ed25519_detailed: signing dispatch mirroring the verify dispatch; the backend that actually signed is recorded in every attestation signature block and STH. - THE SELF-REFERENTIAL CHECK: before signing any tree head, the provider runs the SAME Merkle inclusion verification an agent runs - against the very tree it is about to sign - for the newest leaf attesting the signing library itself, and embeds the result in the signature block: signing_provenance: signing_backend: verified-dalek-serial signing_library_component: dalek-ed25519-verified signing_library_source_commit: aa0f6ab... self_inclusion: verified signing_library_leaf_index: 4 signing_library_certificates_proven: 16/16 A root signature that names the leaf vouching for the code that produced it. First-append chicken-and-egg is handled honestly (self_inclusion: library_not_in_log). - Evidence refreshed: all four receipts re-issued under dogfood-signed STHs; the full agent verify loop re-run green. 50/50 tests (new signing roundtrip test, skip-safe where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:21:17 +00:00
"signature_base64": signature_base64,
2026-07-03 12:09:34 +00:00
"public_key_fingerprint_sha256": public_key_fingerprint(public_key_path),
The provider eats its own dogfood: root signatures via the merkleized library The dogfood principle now runs in BOTH directions. Agents already verified signatures through the proven dalek path; now the provider SIGNS with it too, and proves to itself that the signing code is in its own log before every signature: - dogfood binary gains a `sign` mode (seed over stdin, never argv; ed25519_dalek::SigningKey from the same pinned merkleized workspace). Honesty ledger unchanged: the library's VERIFY path is certificate-covered; its signing path is declared trusted base - but it is the ATTESTED artifact, not an un-attested third implementation. - sign_payload_ed25519_detailed: signing dispatch mirroring the verify dispatch; the backend that actually signed is recorded in every attestation signature block and STH. - THE SELF-REFERENTIAL CHECK: before signing any tree head, the provider runs the SAME Merkle inclusion verification an agent runs - against the very tree it is about to sign - for the newest leaf attesting the signing library itself, and embeds the result in the signature block: signing_provenance: signing_backend: verified-dalek-serial signing_library_component: dalek-ed25519-verified signing_library_source_commit: aa0f6ab... self_inclusion: verified signing_library_leaf_index: 4 signing_library_certificates_proven: 16/16 A root signature that names the leaf vouching for the code that produced it. First-append chicken-and-egg is handled honestly (self_inclusion: library_not_in_log). - Evidence refreshed: all four receipts re-issued under dogfood-signed STHs; the full agent verify loop re-run green. 50/50 tests (new signing roundtrip test, skip-safe where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 13:21:17 +00:00
**({"signing_provenance": signing_provenance} if signing_provenance else {}),
2026-07-03 12:09:34 +00:00
},
"ml_dsa": detect_ml_dsa().to_signature_slot(),
slhdsa: the post-quantum signing path (deterministic, parameter-locked, additive) Phase 2b+3 of the step-3 rehearsal, under the four operator decisions of 2026-08-06: deterministic signing, separate slh_dsa block, additive posture, keygen executed same day (key in provider state, 0600, git-ignored — verified before generation, not after). src/pacta/slhdsa.py — the module that did not exist (register: pq-slot-names-unproven-algorithm). Parameter set LOCKED to SLH-DSA-SHA2-128s: every entry point asserts the key's reported algorithm and refuses anything else, because any other set sits outside all eleven certificates while looking like dogfood. Deterministic via -pkeyopt deterministic:1, so the byte-level reproducibility check that caught a real defect on the Ed25519 side survives here. Verification runs two ways: OpenSSL, and pacta-verify-slhdsa built from the pinned proven source — the one signature check in the estate performed by code whose verify path the certificates cover. The proven-verifier path is package-anchored, not cwd-relative: the lesson of signer-backend-depends-on-cwd applied on day one, not retrofitted. make_signed_tree_head grows optional slhdsa key parameters. With them, the head carries a signed slh_dsa block; without, an honest not-configured slot exactly as ml_dsa always has. ml_dsa itself is untouched. Signatures stay outside the signed payload for both algorithms — tested by asserting the payload is byte-identical with and without the slh_dsa key. Honesty carried in the artifact: signing_backend says "openssl" because no proven signer exists for any algorithm; the module docstring states that nothing here is Lean-proven and that the certificates cover the verify path of the extracted model only. Tests: 7 new, suite 152 passed, 0 failed, 0 skipped — including determinism (two signings, identical bytes), the foreign-key refusal (Ed25519 key raises), corruption rejected by both verifiers, and the proven/OpenSSL agreement. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-08-06 20:03:39 +00:00
"slh_dsa": slh_block,
2026-07-03 12:09:34 +00:00
}
return sth
def verify_signed_tree_head(
sth: dict[str, Any],
public_key_path: str | Path,
require_signatures: str = "ed25519",
) -> tuple[bool, list[str], dict[str, str]]:
diagnostics: list[str] = []
statuses: dict[str, str] = {}
if sth.get("type") != STH_TYPE:
diagnostics.append(f"Unexpected signed tree head type: {sth.get('type')}")
if sth.get("hash_algorithm") != HASH_ALGORITHM:
diagnostics.append(f"Unexpected hash algorithm: {sth.get('hash_algorithm')}")
signatures = sth.get("signatures") or {}
ed25519 = signatures.get("ed25519") or {}
if ed25519.get("scheme") != "openssl-ed25519" or ed25519.get("status") != "signed":
diagnostics.append("Signed tree head is missing a usable Ed25519 signature.")
statuses["ed25519"] = str(ed25519.get("status") or "missing")
else:
payload = signed_tree_head_payload(sth)
expected_digest = ed25519.get("payload_digest_sha256")
actual_digest = hashlib.sha256(payload).hexdigest()
if expected_digest and expected_digest != actual_digest:
diagnostics.append("Signed tree head digest does not match signature metadata.")
statuses["ed25519"] = "digest_mismatch"
else:
expected_fingerprint = ed25519.get("public_key_fingerprint_sha256")
if expected_fingerprint and expected_fingerprint != public_key_fingerprint(public_key_path):
diagnostics.append("Signed tree head Ed25519 public-key fingerprint mismatch.")
statuses["ed25519"] = "key_mismatch"
else:
Dogfood cryptography: pacta verifies signatures through the PROVEN code path "Eat your own dogfood": pacta consumes certificates about a verified Ed25519 implementation while checking those certificates' signatures with OpenSSL. Now it can use the object of its own evidence: - dogfood/pacta-verified-verify: a ~90-line Rust binary built against the PINNED proven source workspace (saymrwulf/curve25519-dalek-source at the exact commit the dalek certificates pin - the build records it: aa0f6ab...) with the serial backend pinned via RUSTFLAGS exactly as the verified extraction pins it. Cargo.toml is committed as a template ({{SOURCE}} placeholder) so no machine path is hardcoded; the rendered file, target/, and the built binary are gitignored. - pacta dogfood-build --source <workspace>: renders, builds, installs to dogfood/state/, and writes a provenance sidecar (source commit, backend cfg, rustc, and an honest coverage note: the certificates cover verify_sha512, the extraction-refactored image of this verify path; SHA-512 and the wire glue remain the theorems' documented boundary). pacta dogfood-status reports the active backend. - signing.verify_payload_ed25519_detailed: dispatch - the dogfood binary when present (backend "verified-dalek-serial"), OpenSSL fallback otherwise, and the backend that ACTUALLY ran is recorded in receipt signature statuses and attestation evidence. Fallback is never silent. - --require-verified-verifier (receipt-verify + agent): policy fails closed when verification did not run on the certificate-covered path. - ML-DSA is deliberately unchanged: no proven implementation exists, so the slot stays fail-closed "unavailable" - the honest hybrid-PQC posture is one proven-classical signature plus one required-but- unproven PQC slot, never a pretend backend. Validated live: receipt verification through the proven verifier (backend recorded), a corrupted signature bit rejected BY the proven binary, tampered attestations rejected, and the policy failing closed when the binary is absent. 49/49 tests green (incl. PEM-SPKI raw-key cross-check against openssl, dispatch/backend recording with a stub, and a real-binary roundtrip that skips gracefully where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:48 +00:00
ok, error, backend = verify_payload_ed25519_detailed(payload, str(ed25519.get("signature_base64") or ""), public_key_path)
2026-07-03 12:09:34 +00:00
statuses["ed25519"] = "verified" if ok else "failed"
Dogfood cryptography: pacta verifies signatures through the PROVEN code path "Eat your own dogfood": pacta consumes certificates about a verified Ed25519 implementation while checking those certificates' signatures with OpenSSL. Now it can use the object of its own evidence: - dogfood/pacta-verified-verify: a ~90-line Rust binary built against the PINNED proven source workspace (saymrwulf/curve25519-dalek-source at the exact commit the dalek certificates pin - the build records it: aa0f6ab...) with the serial backend pinned via RUSTFLAGS exactly as the verified extraction pins it. Cargo.toml is committed as a template ({{SOURCE}} placeholder) so no machine path is hardcoded; the rendered file, target/, and the built binary are gitignored. - pacta dogfood-build --source <workspace>: renders, builds, installs to dogfood/state/, and writes a provenance sidecar (source commit, backend cfg, rustc, and an honest coverage note: the certificates cover verify_sha512, the extraction-refactored image of this verify path; SHA-512 and the wire glue remain the theorems' documented boundary). pacta dogfood-status reports the active backend. - signing.verify_payload_ed25519_detailed: dispatch - the dogfood binary when present (backend "verified-dalek-serial"), OpenSSL fallback otherwise, and the backend that ACTUALLY ran is recorded in receipt signature statuses and attestation evidence. Fallback is never silent. - --require-verified-verifier (receipt-verify + agent): policy fails closed when verification did not run on the certificate-covered path. - ML-DSA is deliberately unchanged: no proven implementation exists, so the slot stays fail-closed "unavailable" - the honest hybrid-PQC posture is one proven-classical signature plus one required-but- unproven PQC slot, never a pretend backend. Validated live: receipt verification through the proven verifier (backend recorded), a corrupted signature bit rejected BY the proven binary, tampered attestations rejected, and the policy failing closed when the binary is absent. 49/49 tests green (incl. PEM-SPKI raw-key cross-check against openssl, dispatch/backend recording with a stub, and a real-binary roundtrip that skips gracefully where unbuilt). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 08:13:48 +00:00
statuses["ed25519_backend"] = backend
2026-07-03 12:09:34 +00:00
if not ok:
diagnostics.append(f"Signed tree head Ed25519 verification failed: {error}")
ml_dsa = signatures.get("ml_dsa") or {}
statuses["ml_dsa"] = str(ml_dsa.get("status") or "missing")
if require_signatures == "both" and statuses["ml_dsa"] != "verified":
diagnostics.append("ML-DSA signed tree head signature is required by policy but is not verified.")
elif require_signatures not in {"ed25519", "both"}:
diagnostics.append(f"Unsupported transparency signature policy: {require_signatures}")
return not diagnostics, diagnostics, statuses
def load_receipt(path: str | Path) -> dict[str, Any]:
raw = load_data(path)
if not isinstance(raw, dict):
raise ValueError(f"Transparency receipt must be a mapping: {path}")
return raw
def verify_receipt(
attestation: dict[str, Any],
receipt: dict[str, Any],
log_public_key_path: str | Path,
require_signatures: str = "ed25519",
) -> ReceiptVerificationResult:
diagnostics: list[str] = []
if receipt.get("type") != RECEIPT_TYPE:
diagnostics.append(f"Unexpected transparency receipt type: {receipt.get('type')}")
if receipt.get("hash_algorithm") != HASH_ALGORITHM:
diagnostics.append(f"Unexpected transparency receipt hash algorithm: {receipt.get('hash_algorithm')}")
sth = receipt.get("sth") or {}
sth_ok, sth_diagnostics, statuses = verify_signed_tree_head(sth, log_public_key_path, require_signatures=require_signatures)
diagnostics.extend(sth_diagnostics)
try:
tree_size = int(receipt.get("tree_size"))
leaf_index = int(receipt.get("leaf_index"))
except (TypeError, ValueError):
tree_size = -1
leaf_index = -1
diagnostics.append("Transparency receipt has invalid tree_size or leaf_index.")
if sth.get("tree_size") != tree_size:
diagnostics.append("Transparency receipt tree_size does not match signed tree head.")
if receipt.get("log_id") != sth.get("log_id"):
diagnostics.append("Transparency receipt log_id does not match signed tree head.")
leaf_bytes = leaf_bytes_for_attestation(attestation)
expected_leaf_hash = leaf_hash(leaf_bytes).hex()
if receipt.get("leaf_hash") != expected_leaf_hash:
diagnostics.append("Transparency receipt leaf hash does not match attestation.")
expected_attestation_digest = hashlib.sha256(canonical_json(attestation)).hexdigest()
if receipt.get("attestation_digest_sha256") and receipt.get("attestation_digest_sha256") != expected_attestation_digest:
diagnostics.append("Transparency receipt attestation digest does not match attestation.")
try:
root = bytes.fromhex(str(sth.get("root_hash") or ""))
proof = [bytes.fromhex(str(item)) for item in (receipt.get("inclusion_proof") or [])]
except ValueError as exc:
root = b""
proof = []
diagnostics.append(f"Transparency proof contains invalid hex: {exc}")
if root and tree_size >= 0:
if not verify_inclusion(leaf_bytes, leaf_index, tree_size, proof, root):
diagnostics.append("Transparency inclusion proof does not verify against the signed tree head.")
accepted = not diagnostics and sth_ok
return ReceiptVerificationResult(
accepted=accepted,
diagnostics=diagnostics,
log_id=str(receipt.get("log_id") or sth.get("log_id") or "") or None,
tree_size=tree_size if tree_size >= 0 else None,
root_hash=str(sth.get("root_hash") or "") or None,
leaf_hash=str(receipt.get("leaf_hash") or "") or None,
signatures=statuses,
)
def proof_to_hex(proof: list[bytes]) -> list[str]:
return [item.hex() for item in proof]
def _inclusion_path(leaves: list[bytes], index: int) -> list[bytes]:
if len(leaves) == 1:
return []
split = _largest_power_of_two_less_than(len(leaves))
if index < split:
return _inclusion_path(leaves[:split], index) + [merkle_root(leaves[split:])]
return _inclusion_path(leaves[split:], index - split) + [merkle_root(leaves[:split])]
def _calculate_inclusion_root(
current_hash: bytes,
index: int,
tree_size: int,
proof: list[bytes],
proof_index: int,
) -> tuple[bytes, int]:
if tree_size == 1:
return current_hash, proof_index
split = _largest_power_of_two_less_than(tree_size)
if index < split:
left, consumed = _calculate_inclusion_root(current_hash, index, split, proof, proof_index)
if consumed >= len(proof):
raise ValueError("proof exhausted")
return node_hash(left, proof[consumed]), consumed + 1
right, consumed = _calculate_inclusion_root(current_hash, index - split, tree_size - split, proof, proof_index)
if consumed >= len(proof):
raise ValueError("proof exhausted")
return node_hash(proof[consumed], right), consumed + 1
def _consistency_path(old_tree_size: int, leaves: list[bytes], complete: bool) -> list[bytes]:
if old_tree_size == len(leaves):
return [] if complete else [merkle_root(leaves)]
split = _largest_power_of_two_less_than(len(leaves))
if old_tree_size <= split:
return _consistency_path(old_tree_size, leaves[:split], complete) + [merkle_root(leaves[split:])]
return _consistency_path(old_tree_size - split, leaves[split:], complete=False) + [merkle_root(leaves[:split])]
def _largest_power_of_two_less_than(value: int) -> int:
if value <= 1:
raise ValueError("value must be greater than one")
return 1 << ((value - 1).bit_length() - 1)
def _is_power_of_two(value: int) -> bool:
return value > 0 and value & (value - 1) == 0