diff --git a/certificates/partial-swap-network.json b/certificates/partial-swap-network.json index a72693d486..eccafa830c 100644 --- a/certificates/partial-swap-network.json +++ b/certificates/partial-swap-network.json @@ -1481,7 +1481,7 @@ "scripts/partial_swap/match_positive_dag.cpp": "8a6d64e2d0ac2c69210fb0b8a5bd0d714ad40b3609f75a5fddfcba3291a3d552", "scripts/partial_swap/paired.py": "ddcecace3a8e04d690914cbd64873ab8dde8c0c3477a255767b0ed6f639bb0c7", "scripts/partial_swap/positive.py": "1dadf771226d6e4f3bf9ce3aae2e6a2ad7173f4d1c900299d0663fc4c432f3e8", - "scripts/partial_swap/shared.py": "7290bd639600aecff11941d3faf95c9ce95589125cc6a7f7440fb86af7a0f17c", + "scripts/partial_swap/shared.py": "5a0646a50b9f3e8ea55168620a78e8853b0ab810517cefc8cfb8696aedee278a", "scripts/partial_swap_network.py": "acdd81b4dea2815f8838a0c3ea8ac4968f2bc13fd7c99173473d865779096782", "scripts/partial_swap_producer.py": "dba07d742c68e8e4aedd1d93fc4c47a0481778268f7480997463e2b269fdd0a0" }, diff --git a/docs/research/partial-swap-retained-total-fix.md b/docs/research/partial-swap-retained-total-fix.md new file mode 100644 index 0000000000..4100f361a2 --- /dev/null +++ b/docs/research/partial-swap-retained-total-fix.md @@ -0,0 +1,94 @@ +# Restore retained-total partial-swap verification + +This is a verifier regression fix, with no new κ or runtime-speedup claim. +The reviewed starting tree is `20ecf2891d80b5acf18d980b2164fb44ea7faa90`. + +`partial_swap.graph.graph(h)` adds the local empty-exclusion output `()`. +`SharedPointCircuit` lifts that output to the singleton target `(common,)`, +representing a retained full pair-star total. The digest optimization introduced +in [PR2039](https://github.com/CrocSwap/integer-mult-bounds/pull/2039) unpacks +all targets as triples. Consequently even `graph(6).verify()` raises +`ValueError: not enough values to unpack (expected 3, got 1)`. The h25 producer +used by `make copied-fixed-reversed-producer` and `make verify` fails there too. +Existing equivalence tests constructed exclusion-only graphs, missing this case. + +The repair joins the target's integer entries with commas inside the same JSON +array delimiters. For both singleton and triple targets these are exactly the +bytes emitted by the historical `json.dumps(..., separators=(',', ':'))` +verifier. Sorted traversal, newline boundaries, node encoding, topology, +disjointness, support, core and union assertions are unchanged. + +New tests compare whole receipts to the pre-optimization verifier on retained-total +graphs of sizes 6, 8, 10 and 25, and compare h25 against the frozen +`research/copied-fixed-reversed/producer-input-25.json` scalar receipt. A negative +control replaces a full retained total with a proper excluded-pair sum and +requires both verifiers to reject it. Existing exclusion-only equivalence and +support-corruption controls remain in place. + +The live partial-swap certificate is regenerated with its own generator. Exactly +one source hash changes; all mathematical values remain byte-identical. The two +existing optimization test suites and package verifiers bind the repaired source +hash. Their frozen baseline files, provenance manifests, performance receipts, +and historical source commits remain unchanged. No archived mathematical +certificate or expected circuit digest is edited. + +## Reproduction + +From the repository root, with Python 3.12 (the producer also needs a C++17 compiler): + +```sh +python3.12 -B scripts/partial_swap_network.py +python3.12 -B -m unittest discover -s tests -p 'test_partial_swap*.py' +python3.12 -B scripts/research_package.py --package research/partial-swap-digest +python3.12 -B scripts/research_package.py --package research/partial-swap-init +make copied-fixed-reversed-producer +``` + +The package checks include h46 receipt equivalence, optimized-Python digest +checks and certificate pin-only comparisons. Production support assertions are +historically disabled by `python -O`; the negative controls run without `-O`. +This repair does not alter that inherited interface. + +## Validation on the repaired source + +- Python 3.14.7 focused suite: 16 tests pass, including the new negative control. +- Python 3.12.11 registered `partial-swap-digest` and `partial-swap-init` + packages: both pass their complete normal/optimized verification and tests. +- `make copied-fixed-reversed-producer`: exit 0. Fresh h25 DAG, all 96,273 + distinct matrix profiles, five-prime reconstruction, positive labels, + matching and the separate copied CRT audit pass. +- `make verify`: exit 2 after the repaired producer and full geometry checks + pass. The next historical witness rejects its old source pin (see below). +- `git diff --check`: pass. + +The unchanged h25 scalar digest is +`69283632099c012ae00cf1e9ab5b1f0757912944ff0708aa6cbf7633d841fd88`; +the exported DAG hash is +`ab28c3eba56b4b494f659b833c5aba4bb3e1e2225c3294411edf51a3999f1d03`. +The repaired source hash is +`5a0646a50b9f3e8ea55168620a78e8853b0ab810517cefc8cfb8696aedee278a`. + +The broad historical sweep next fails in +`research/copied-fixed-reversed/witness.py:verify_sources`: its frozen manifest +requires `shared.py` hash `81e9af92cdf38243a540cdcff2062778e449467848d444da37a9553c12ca4c34`, +whereas pinned main already contains +`7290bd639600aecff11941d3faf95c9ce95589125cc6a7f7440fb86af7a0f17c`. +Comparing the manifest and source directly at the pinned main commit confirms +this mismatch predates this repair. The manifest remains unchanged; this PR does +not claim that the complete historical sweep passes. The selected cold replay, +Lean and unrelated historical sweeps were not rerun for this verifier-only change. + +Open next question: separately reconcile historical witness provenance with live +tooling, retaining explicit equivalence evidence and immutable original pins. + +## Scope and credit + +Douglas R. Colkitt authored the shared-point and exclusion circuits; icekylinx +adapted the partial-swap producer; Wentao Li (VictorLiwentao) contributed the +PR2039 optimizations. The h25 producer credits the PR36/PR21 base-two graph, +PR32 structured profiler and Zhihao Chen's PR29 label audit. All existing license +and attribution notices are preserved. SovereignSteak prepared this repair with +OpenAI Codex assistance. + +No asymptotic construction, conditional hypothesis or selected record changes. +Finite verification here does not prove the full conditional multiplication theorem. diff --git a/research/partial-swap-digest/verify.py b/research/partial-swap-digest/verify.py index b659164f63..557efa007f 100644 --- a/research/partial-swap-digest/verify.py +++ b/research/partial-swap-digest/verify.py @@ -27,7 +27,7 @@ BASE_COMMIT = "0b1cccf86e2f5132ed286a26ef0a681a495740ea" ORIGINAL_BLOB = "a2f0190704db83bd8ef48e46e723c49b925804fe" ORIGINAL_SHA256 = "81e9af92cdf38243a540cdcff2062778e449467848d444da37a9553c12ca4c34" -OPTIMIZED_SHA256 = "7290bd639600aecff11941d3faf95c9ce95589125cc6a7f7440fb86af7a0f17c" +OPTIMIZED_SHA256 = "5a0646a50b9f3e8ea55168620a78e8853b0ab810517cefc8cfb8696aedee278a" BASE_CERT_SHA256 = "f3c4be597b2f2136da4ae2cc80c7454f63c90aa01f84e5b8131cee4dd55de815" CERT_REL = "certificates/partial-swap-network.json" SOURCE_REL = "scripts/partial_swap/shared.py" diff --git a/research/partial-swap-init/verify.py b/research/partial-swap-init/verify.py index fd2290bff7..02b3a3d39f 100644 --- a/research/partial-swap-init/verify.py +++ b/research/partial-swap-init/verify.py @@ -28,7 +28,7 @@ BASE_COMMIT = "3b29a00f972bd15871baac0acf5ac204740a9863" ORIGINAL_BLOB = "77a3672dce3b377eee30a6c3e993a63c2d3840d5" ORIGINAL_SHA256 = "b27c8bfe1de3f7d2d5fb19ac742adf11597c2aaa7add68430443119997e646e8" -OPTIMIZED_SHA256 = "7290bd639600aecff11941d3faf95c9ce95589125cc6a7f7440fb86af7a0f17c" +OPTIMIZED_SHA256 = "5a0646a50b9f3e8ea55168620a78e8853b0ab810517cefc8cfb8696aedee278a" BASE_CERT_SHA256 = "6e6ebabf06ee125e58f6871748aaa1fddc4cbf5e70338c61742bca85b9b3e7d4" FULL_SIZE_SHA256 = "b0402f9eaaf138129939521c2a2ef1c3e8230b730de0a7bdea3596eb415dd173" CERT_REL = "certificates/partial-swap-network.json" diff --git a/scripts/partial_swap/shared.py b/scripts/partial_swap/shared.py index 9a48086000..44a0e454c7 100644 --- a/scripts/partial_swap/shared.py +++ b/scripts/partial_swap/shared.py @@ -168,10 +168,10 @@ def verify(self): for (common,target),node in sorted(self.outputs.items()): excluded=tuple(sorted(points[common].index(x) for x in target if x!=common)) assert support_in(node,common)==local_support[local_outputs[excluded]] - t0,t1,t2=target + target_text=','.join(map(str,target)) # Byte-identical to json.dumps((common,target,node), # separators=(',',':'))+'\n' - update(encode(f'[{common},[{t0},{t1},{t2}],{node}]\n')) + update(encode(f'[{common},[{target_text}],{node}]\n')) return dict(h=self.h,inputs=len(self.inputs),partial_outputs=len(self.outputs), additions=self.additions,roles=self.additions+len(self.outputs), merged_additions=self.merged, diff --git a/tests/test_partial_swap_digest.py b/tests/test_partial_swap_digest.py index 409e07e282..edf131a2b6 100644 --- a/tests/test_partial_swap_digest.py +++ b/tests/test_partial_swap_digest.py @@ -14,9 +14,10 @@ sys.path.insert(0, str(ROOT / "scripts")) from partial_swap import shared as production # noqa: E402 +from partial_swap.graph import graph # noqa: E402 ORIGINAL_SHA256 = "81e9af92cdf38243a540cdcff2062778e449467848d444da37a9553c12ca4c34" -OPTIMIZED_SHA256 = "7290bd639600aecff11941d3faf95c9ce95589125cc6a7f7440fb86af7a0f17c" +OPTIMIZED_SHA256 = "5a0646a50b9f3e8ea55168620a78e8853b0ab810517cefc8cfb8696aedee278a" def load_baseline(): @@ -74,6 +75,30 @@ def test_small_circuit_receipts_match(self): circuit.verify(), ) + def test_retained_total_receipts_match(self): + # graph() adds an empty-exclusion local output, lifted to (common,). + for h in (6, 8, 10, 25): + with self.subTest(h=h): + circuit = graph(h) + self.assertEqual(sum(len(t) == 1 for _, t in circuit.outputs), h) + receipt = circuit.verify() + self.assertEqual(self.baseline.SharedPointCircuit.verify(circuit), receipt) + if h == 25: + frozen = json.loads((ROOT / "research/copied-fixed-reversed/producer-input-25.json").read_text()) + self.assertEqual(receipt, frozen["scalar"]) + + def test_wrong_retained_total_rejected(self): + circuit = graph(8) + key = next(key for key in circuit.outputs if len(key[1]) == 1) + common = key[0] + # A proper excluded-pair sum cannot replace the full retained total. + circuit.outputs[key] = next(node for (c, t), node in circuit.outputs.items() + if c == common and len(t) == 3) + with self.assertRaises(AssertionError): + circuit.verify() + with self.assertRaises(AssertionError): + self.baseline.SharedPointCircuit.verify(circuit) + def test_support_rejection_preserved(self): circuit = production.SharedPointCircuit(8) node = next(n for n in circuit.active if circuit.args[n]) diff --git a/tests/test_partial_swap_init.py b/tests/test_partial_swap_init.py index 23f0505a28..76d641d069 100644 --- a/tests/test_partial_swap_init.py +++ b/tests/test_partial_swap_init.py @@ -19,7 +19,7 @@ BASE_COMMIT = "3b29a00f972bd15871baac0acf5ac204740a9863" ORIGINAL_SHA256 = "b27c8bfe1de3f7d2d5fb19ac742adf11597c2aaa7add68430443119997e646e8" -OPTIMIZED_SHA256 = "7290bd639600aecff11941d3faf95c9ce95589125cc6a7f7440fb86af7a0f17c" +OPTIMIZED_SHA256 = "5a0646a50b9f3e8ea55168620a78e8853b0ab810517cefc8cfb8696aedee278a" def load_baseline():