Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 10 additions & 7 deletions proof/region/v1/PROTOCOL.md
Original file line number Diff line number Diff line change
Expand Up @@ -663,13 +663,16 @@ u64be(encoded_length) || claim identity || Arb receipt identity ||
MPFI receipt identity || первый semantic identity || второй semantic identity)`.

Full-domain gate: `claim_spans_full_domain_v1` возвращает истину только когда
`domain_point_count` claim равен `2^24` (OUTPUT_CARDINALITY_V1); неканонический
claim возвращает typed rejection `foreign_input` вместо panic. Reduced-domain
proof несёт `full_domain=False` и никогда не авторизует family mint;
family mint дополнительно разрешает `domain_identity` и допускает отдельно
exact full manifest: единственный range `[0, 2^24)` и point count `2^24`.
Совпадение только point count или reduced-domain candidate этот gate не
проходят.
`domain_point_count` claim равен `2^24` (OUTPUT_CARDINALITY_V1) И его
`domain_identity` совпадает с content identity exact full manifest —
единственного range `[0, 2^24)` с point count `2^24`.
`exact_full_domain_manifest_v1` выводится из канонической кодировки протокола
и никогда не пинится отдельным hash; каноническая range-грамматика отвергает
overlap и adjacency, поэтому полного покрытия достигает ровно один manifest.
Неканонический claim возвращает typed rejection `foreign_input` вместо panic.
Reduced-domain proof несёт `full_domain=False` и никогда не авторизует family
mint. Совпадение только point count, только identity, чужая или мутированная
identity или reduced-domain candidate этот gate не проходят.

## Semantic verification и `SemanticVerificationReceiptV1`

Expand Down
13 changes: 11 additions & 2 deletions proof/region/v1/dual_proof.py
Original file line number Diff line number Diff line change
Expand Up @@ -230,9 +230,18 @@ def binds(


def _claim_spans_full_domain_v1(claim: protocol.DualComparisonClaimV1) -> bool:
"""Total predicate for an already canonical claim."""
"""Total predicate for an already canonical claim.

return claim.domain_point_count == protocol.OUTPUT_CARDINALITY_V1
The mint gate binds the exact full manifest's content identity: a raw
claim keeps its domain identity as an unverified coordinate, so a bare
`2^24` point count never authorizes a family mint on its own.
"""

return (
claim.domain_point_count == protocol.OUTPUT_CARDINALITY_V1
and claim.domain_identity
== protocol.exact_full_domain_manifest_v1().identity
)


def claim_spans_full_domain_v1(claim: object) -> bool | DualProofRejectedV1:
Expand Down
18 changes: 17 additions & 1 deletion proof/region/v1/region_proof_protocol.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from dataclasses import dataclass, field, fields
from enum import IntEnum, StrEnum
from fractions import Fraction
from functools import cached_property
from functools import cache, cached_property
from itertools import pairwise
from typing import Callable, Iterable, Iterator, NoReturn, TypeAlias

Expand Down Expand Up @@ -543,6 +543,22 @@ def index_of(self, ordinal: int) -> int | None:
return None


@cache
def exact_full_domain_manifest_v1() -> ReducedDomainManifestV1:
"""The exact full manifest of the whole sRGB8 point space.

The canonical range grammar rejects overlap and adjacency, so exactly one
manifest covers all `2^24` points: the single range `[0, 2^24)`. Its
content identity is the only domain identity the full-domain mint gate
admits; the value is derived from the canonical encoding, never pinned
as a standalone hash.
"""

return ReducedDomainManifestV1(
((0, OUTPUT_CARDINALITY_V1),), OUTPUT_CARDINALITY_V1
)


class ComparatorKindV1(IntEnum):
ARB = 1
MPFI = 2
Expand Down
19 changes: 17 additions & 2 deletions proof/region/v1/tests/test_dual_proof.py
Original file line number Diff line number Diff line change
Expand Up @@ -483,10 +483,11 @@ def test_reduced_domain_proof_does_not_permit_family_mint(self) -> None:
self.assertFalse(dual_proof.claim_spans_full_domain_v1(candidate.claim))

claim = candidate.claim
full_identity = protocol.exact_full_domain_manifest_v1().identity
full_claim = protocol.DualComparisonClaimV1(
claim.job_identity,
claim.definition_digest,
claim.domain_identity,
full_identity,
claim.policy_identity,
protocol.OUTPUT_CARDINALITY_V1,
claim.comparator_identities,
Expand All @@ -495,11 +496,25 @@ def test_reduced_domain_proof_does_not_permit_family_mint(self) -> None:
claim.decision_digest,
)
self.assertTrue(dual_proof.claim_spans_full_domain_v1(full_claim))
one_short = protocol.DualComparisonClaimV1(
# A bare point count never authorizes the mint: the reduced-domain
# identity proves the domain is not the exact full manifest.
foreign_identity = protocol.DualComparisonClaimV1(
claim.job_identity,
claim.definition_digest,
claim.domain_identity,
claim.policy_identity,
protocol.OUTPUT_CARDINALITY_V1,
claim.comparator_identities,
claim.run_claim_identities,
claim.transcript_identities,
claim.decision_digest,
)
self.assertFalse(dual_proof.claim_spans_full_domain_v1(foreign_identity))
one_short = protocol.DualComparisonClaimV1(
claim.job_identity,
claim.definition_digest,
full_identity,
claim.policy_identity,
protocol.OUTPUT_CARDINALITY_V1 - 1,
claim.comparator_identities,
claim.run_claim_identities,
Expand Down
116 changes: 116 additions & 0 deletions proof/region/v1/tests/test_full_domain_gate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
#!/usr/bin/env python3
"""Hostile contract for the full-domain mint gate.

The family mint admits exactly one domain: the exact full manifest of the
whole sRGB8 point space, the single canonical range `[0, 2^24)` with
`point_count = 2^24`. Its content identity is the only domain identity a
full-domain claim may carry; a bare point count proves nothing, because a
parsed raw claim keeps the domain identity as an unverified coordinate.
"""

from __future__ import annotations

import hashlib
import sys
import unittest
from pathlib import Path

PROOF = Path(__file__).resolve().parents[1]
sys.path.insert(0, str(PROOF))

import dual_proof # noqa: E402
import region_proof_protocol as protocol # noqa: E402


def digest(label: int) -> bytes:
return hashlib.sha256(f"full-domain-gate-{label}".encode("ascii")).digest()


def make_claim(
*, domain_identity: bytes, point_count: int
) -> protocol.DualComparisonClaimV1:
return protocol.DualComparisonClaimV1(
digest(1),
digest(2),
domain_identity,
digest(3),
point_count,
(digest(4), digest(5)),
(digest(6), digest(7)),
(digest(8), digest(9)),
digest(10),
)


class ExactFullDomainManifestTests(unittest.TestCase):
def test_manifest_is_the_single_canonical_range(self) -> None:
manifest = protocol.exact_full_domain_manifest_v1()
self.assertIs(type(manifest), protocol.ReducedDomainManifestV1)
self.assertEqual(manifest.ranges, ((0, protocol.OUTPUT_CARDINALITY_V1),))
self.assertEqual(manifest.point_count, protocol.OUTPUT_CARDINALITY_V1)

def test_manifest_reencode_is_byte_identical(self) -> None:
manifest = protocol.exact_full_domain_manifest_v1()
self.assertEqual(
protocol.ReducedDomainManifestV1.parse(manifest.encode()), manifest
)

def test_manifest_content_identity_is_stable(self) -> None:
first = protocol.exact_full_domain_manifest_v1().identity
second = protocol.exact_full_domain_manifest_v1().identity
self.assertEqual(len(first), 32)
self.assertEqual(first, second)

def test_split_full_coverage_is_not_a_manifest(self) -> None:
# Two adjacent ranges covering the whole domain must fail canonical
# grammar, so the exact single range remains the only full manifest.
half = protocol.OUTPUT_CARDINALITY_V1 // 2
with self.assertRaises(protocol.ProtocolErrorV1):
protocol.ReducedDomainManifestV1(
((0, half), (half, protocol.OUTPUT_CARDINALITY_V1)),
protocol.OUTPUT_CARDINALITY_V1,
)


class FullDomainGateTests(unittest.TestCase):
def test_exact_full_claim_spans_the_domain(self) -> None:
identity = protocol.exact_full_domain_manifest_v1().identity
claim = make_claim(
domain_identity=identity, point_count=protocol.OUTPUT_CARDINALITY_V1
)
self.assertTrue(dual_proof.claim_spans_full_domain_v1(claim))

def test_full_count_with_foreign_identity_does_not_span(self) -> None:
# Count alone cannot authorize a family mint: nothing verifies that a
# raw claim's domain identity belongs to the claimed point count.
claim = make_claim(
domain_identity=digest(42), point_count=protocol.OUTPUT_CARDINALITY_V1
)
self.assertFalse(dual_proof.claim_spans_full_domain_v1(claim))

def test_exact_identity_with_short_count_does_not_span(self) -> None:
identity = protocol.exact_full_domain_manifest_v1().identity
claim = make_claim(
domain_identity=identity, point_count=protocol.OUTPUT_CARDINALITY_V1 - 1
)
self.assertFalse(dual_proof.claim_spans_full_domain_v1(claim))

def test_mutated_identity_does_not_span(self) -> None:
mutated = bytearray(protocol.exact_full_domain_manifest_v1().identity)
mutated[0] ^= 0xFF
claim = make_claim(
domain_identity=bytes(mutated),
point_count=protocol.OUTPUT_CARDINALITY_V1,
)
self.assertFalse(dual_proof.claim_spans_full_domain_v1(claim))

def test_foreign_input_is_typed_rejection(self) -> None:
rejection = dual_proof.claim_spans_full_domain_v1(object())
self.assertIs(type(rejection), dual_proof.DualProofRejectedV1)
self.assertEqual(
rejection.reason, dual_proof.DualProofRejectionReasonV1.FOREIGN_INPUT
)


if __name__ == "__main__":
unittest.main()
Loading