From 1611b9d64b0dac7ae31fcb3b3e9b494c3b912fad Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 6 Oct 2026 13:33:50 -0700 Subject: [PATCH 1/2] test(weave): expose private-public recovery boundary with executable controls --- .github/workflows/weave.yml | 13 +- research/weave/PRIVATE_PUBLIC.md | 132 +++++++ research/weave/QUESTIONS.md | 8 + research/weave/README.md | 7 + .../weave/evidence/private-public-v1.json | 68 ++++ research/weave/private_public.py | 324 ++++++++++++++++++ research/weave/test.py | 8 +- research/weave/tests/test_private_public.py | 324 ++++++++++++++++++ 8 files changed, 881 insertions(+), 3 deletions(-) create mode 100644 research/weave/PRIVATE_PUBLIC.md create mode 100644 research/weave/evidence/private-public-v1.json create mode 100644 research/weave/private_public.py create mode 100644 research/weave/tests/test_private_public.py diff --git a/.github/workflows/weave.yml b/.github/workflows/weave.yml index b5dfe1c0..e835f812 100644 --- a/.github/workflows/weave.yml +++ b/.github/workflows/weave.yml @@ -43,6 +43,17 @@ jobs: PYTHONDONTWRITEBYTECODE=1 python3 research/weave/test.py --receipt "$RUNNER_TEMP/weave-check.json" cat "$RUNNER_TEMP/weave-check.json" PYTHONDONTWRITEBYTECODE=1 python3 research/weave/cycle.py demo | tee "$RUNNER_TEMP/weave-demo.json" + - name: Observe unclosed private-public distinction (expected exit 1) + run: | + set -euo pipefail + if python3 research/weave/private_public.py --sources "$GITHUB_WORKSPACE/.weave-inputs" --require-distinction > "$RUNNER_TEMP/weave-private-public.json"; then + echo "The frozen experiment unexpectedly accepted asymmetry; reassess its scope." + exit 1 + else + status=$? + test "$status" -eq 1 + fi + cat "$RUNNER_TEMP/weave-private-public.json" - name: Package tracked research source and observed evidence if: always() run: | @@ -52,7 +63,7 @@ jobs: git -C .weave-inputs/ucns archive --format=zip --output="$RUNNER_TEMP/weave-inspection/ucns.zip" HEAD git rev-parse HEAD > "$RUNNER_TEMP/weave-inspection/STACK_HEAD.txt" git -C .weave-inputs/ucns rev-parse HEAD > "$RUNNER_TEMP/weave-inspection/UCNS_HEAD.txt" - for evidence in weave-check.json weave-demo.json; do + for evidence in weave-check.json weave-demo.json weave-private-public.json; do if test -f "$RUNNER_TEMP/$evidence"; then cp "$RUNNER_TEMP/$evidence" "$RUNNER_TEMP/weave-inspection/"; fi done - name: Retain exact-source research bundle, not a security release diff --git a/research/weave/PRIVATE_PUBLIC.md b/research/weave/PRIVATE_PUBLIC.md new file mode 100644 index 00000000..f99de168 --- /dev/null +++ b/research/weave/PRIVATE_PUBLIC.md @@ -0,0 +1,132 @@ +# Private/public recovery experiment v1 + +Scope: the merged sequence cycle at Stack +`ccf707f933a9611d7b7fe13a0417378d8b453a14`. This experiment tests whether splitting +its existing reconstruction inputs establishes the requested distinction. It does +not invent the still-undefined native public-evaluation/private-recovery law. + +## Frozen acceptance criteria + +1. The sender receives message bytes, public artifact, public corpus and locked + source code only. No recipient-private artifact, callback or encoder trace. +2. A separate recipient recovers every byte using the transmitted record and + recipient-private artifact, without the original plaintext. +3. Attempt public-only recovery with the same disclosed inputs. An exact public + recovery is a counterexample to private-state necessity. An API refusing a + missing argument is not evidence of mathematical necessity. +4. Keep the intended whole-plus-one exposure and private-gonol relation explicit. + Publishing extra reconstruction data is a control, not satisfaction of Q2/Q12. + +All four are required. This experiment can return **BLOCKED** or **FALSIFIED**; +it cannot establish cryptographic security. Passing regression tests establish +that the experiment reports these outcomes honestly, not that asymmetry works. + +## Artifacts and the tested relation + +`partition()` accepts an existing exact cycle profile and actual corpus. It is an +input partitioner, **not asymmetric key generation**. + +| Artifact | Contents | Available to sender | +|---|---|---| +| Public, whole-plus-one | All non-space profile fields; whole-circle space 0 and one selected occurrence-circle space in each round; actual corpus bytes; native-source-lock identity | Yes | +| Private, reconstruction input | Full eight-space cycle profile and the public-artifact digest | No | +| Public, full-profile control | Full profile, corpus and native-source-lock identity; explicitly labeled as an overdisclosed control | Control only | +| Packet | Unmodified `cycle.forward` record, with all native occurrences, numeral definitions, framing and prime-derived interleaving | Yes | + +The six undisclosed spaces per round are JSON `null`, never guessed as zero and +never silently retrieved from a profile file. The private artifact is an existing +native-cycle configuration. It is **not claimed to be the missing private gonol**. +The selected public occurrence circle is an explicit experimental parameter; +circle 1 is the default, not a new universal placement law. + +The fixed cycle forward and reverse both require a complete `Profile`. Therefore: + +- Whole-plus-one projection: the sender has no defined way to compute the six + missing spaces or an alternative public operation. The experiment must report + **BLOCKED: native public evaluation law missing** before invoking the cycle. +- Full-profile control: the sender operates in a fresh process with only public + inputs; the recipient's private-path decoder recovers exactly. A separate + public-only process also runs the existing inverse and recovers exactly. + Private-state necessity is **FALSIFIED for this control**. + +Neither observation falsifies the intended Weave cryptosystem. No earlier +per-bit/star encoder or attack is restored. Prime routes remain public +deterministic schedule derivations, not a trapdoor. The actual native UCNS +geometry, complete 720-degree state, seven occurrence streams, corpus bits and +merged wire format remain unchanged. + +## Plan, boundaries and stopping rule + +Decision: can input partitioning alone close Q7 while retaining Q2/Q12? +Minimal action: run the two disclosed cases above on the real merged cycle. +Maximal closure would require a specified native private-gonol constructor, +public projection/evaluation law and private inverse, followed by adversarial +recovery work. That mathematical construction is not supplied by this patch. + +Positive outcome for a future law: public sender and private exact recovery +work, and the named public attack fails; continue to broader attacks, keeping the +result bounded to the tested attack. Negative: preserve the exact public recovery +counterexample and reject that candidate. Unresolved prerequisite: report BLOCKED, +identify the missing operation, and do not infer a design-wide impossibility. + +Resource preflight: one small message through the existing profile and a finite +set of fresh-process calls; standard library only, no search or network in the +experiment. Existing suite includes finite enumerations and a 65,536-byte cycle +case. Available local CPU, memory and disk suffice; no artificial timeout is used. +Stop when both controls and the regression suite finish. + +Code/runtime source files are copied by an explicit allowlist into a fresh +temporary directory. Child processes use a clean environment and `-S -B -E`. +The sender and attacker receive no private bytes; receiver receives no plaintext. +The input manifest and source hashes are recorded. This checks data flow, not +an OS sandbox or resistance to arbitrary hostile Python code. No network, +authentication, private storage service or production integration is added. + +Rollout: explicit experiment CLI and existing Weave research CI only. Rollback: +remove `private_public.py`, its tests and documentation/CI links. No source pins, +authority, geometry, prime-route logic or existing cycle format change. + +## Usage + +From `research/weave`, with the locked UCNS checkout at `/checkouts/ucns`: + +```sh +python private_public.py --sources /checkouts +python private_public.py --sources /checkouts --require-distinction +WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_private_public.py -v +WEAVE_SOURCES=/checkouts python test.py --receipt /tmp/weave-check.json +``` + +The first command emits a source-bound JSON report and exits zero when the +experiment executes. The second emits the same report and exits **1** because +the requested distinction is not established. Infrastructure/admission errors +exit **2**, separately from a scientific counterexample. The demo uses disclosed +test data, not user secrets. Reports contain no private artifact or plaintext. + +Programmatic use: `public, private = partition(profile, corpus, circle=1)`; +`send(message, public, sources)` refuses the unresolved projection; +`recover(packet, public, private, sources)` uses the admitted private profile. +`full_profile_control(public, private)` explicitly constructs the overdisclosed +control; `public_recover(packet, control, sources)` supplies the real counterexample. + +Observed execution is retained in [evidence/private-public-v1.json](evidence/private-public-v1.json) +with source hashes and full artifact byte counts. The complete Weave suite passed +196 tests (176 existing and 20 new), with zero failures, errors or skips; source +snapshot SHA-256 `1d71264a9e61b76289a77deb301f0f1916b67fcfb7920207bae01937aa87d0cb`. +The separate distinction gate exited 1 as specified. This is evidence of a working +experiment with an unclosed result, not an asymmetric implementation. + +Governance consulted: skill-lib `38c64332b840b2bbe1c07e53aeee8996644548e9` +(`the-interdependency`, `action-calibration`, `meta-module-build`, `msdmd`, +`test-build`, `ratios`); METAPAT `86415a5368c1a1417c2b6731f19967a6b1fce6bb` +axioms, postulates and domain restraint. Semantic/geometry resemblance does not +transfer cryptographic evidence. This is consultation provenance, not a runtime +dependency or change to Stack's pinned authorities. + +## hmmm + +The missing construction is an explicit `PublicEval(public, message, sender_state)` +and `PrivateRecover(private_gonol, packet)` relation whose sender needs no hidden +recipient inputs and whose public description does not simply supply the existing +inverse. The current experiment exposes that boundary; it does not close it. +Computational recovery advantage, randomness, authentication and replay remain open. diff --git a/research/weave/QUESTIONS.md b/research/weave/QUESTIONS.md index f076a1e1..b1b38a6f 100644 --- a/research/weave/QUESTIONS.md +++ b/research/weave/QUESTIONS.md @@ -49,6 +49,14 @@ and inverse, failures, budgets and size accounting are in `CYCLE.md`. ## Usage +The input-partition experiment in [PRIVATE_PUBLIC.md](PRIVATE_PUBLIC.md) now makes +Q7's acceptance boundary executable. The whole-plus-one projection leaves the +current sender blocked; the explicitly overdisclosed full-profile control allows +exact public recovery. These are scoped control results, not an implementation or +falsification of the intended private-gonol/public law. Run +`python private_public.py --sources /checkouts --require-distinction`; exit 1 +records that the required distinction remains unestablished. + Run `python cycle.py demo --sources /checkouts`. Change supported operating choices through an explicit profile; do not silently change a named profile's meaning. The current next mathematical question is the private/public relation, not another diff --git a/research/weave/README.md b/research/weave/README.md index aa5e6eb5..2ac3c69f 100644 --- a/research/weave/README.md +++ b/research/weave/README.md @@ -22,6 +22,13 @@ accounting, budgets and exact source lock. These choices are not universal laws. [The numeral layer](NUMERAL_CONSTRUCTION.md) separately records length-bearing integers, Unicode references and exact prime recipes. +[The private/public experiment](PRIVATE_PUBLIC.md) separates whole-plus-one public +inputs from the full private reconstruction profile and tests the sender boundary +in fresh processes. It reports the missing public operation as BLOCKED. Its +full-profile control produces an exact public-recovery counterexample; this does +not falsify the intended Weave relation. Run `python private_public.py --sources +/checkouts --require-distinction` to observe the still-failing acceptance gate. + `native_binary.py` is the Stack-owned binary-origin/sequence candidate, consuming actual source-verified UCNS `AxisCirclePosition` and `NativeMobiusState` objects. UCHC supplies the origin/axis architecture reference; the premature UCHC #7 diff --git a/research/weave/evidence/private-public-v1.json b/research/weave/evidence/private-public-v1.json new file mode 100644 index 00000000..bf66132c --- /dev/null +++ b/research/weave/evidence/private-public-v1.json @@ -0,0 +1,68 @@ +{ + "accounting": { + "full_profile_control_bytes": 1563, + "message_bytes": 14, + "packet_bytes": 5904, + "private_bytes": 984, + "public_bytes": 1537 + }, + "construction": "INPUT_PARTITION_ONLY", + "distinction_established": false, + "execution": "COMPLETED", + "full_profile_control": { + "public_only_exact": true, + "public_sender_completed": true, + "recipient_exact": true, + "status": "FALSIFIED", + "whole_plus_one_preserved": false + }, + "hmmm": "Native private-gonol constructor, public evaluation and private recovery law remain undefined.", + "inputs": { + "attacker": [ + "packet", + "public" + ], + "corpus": "actual bytes inside public artifact", + "recipient": [ + "packet", + "public", + "private" + ], + "sender": [ + "message", + "public" + ], + "shared": [ + "exact code", + "locked native sources" + ] + }, + "native_lock_sha256": "cdf11526ca93a8627eea036d7e56ac630d4be04ccb1fe14ba5973966b19d6f43", + "nonclaim": "No falsification of intended Weave, no trapdoor or cryptographic security claim.", + "packet_sha256": "2d2d20d9beb8824664184e293795884cfb3ac7b60adefd0e5990cf539960879c", + "projected_public_recovery": { + "scope": "control packet; API refusal is not private necessity", + "status": "BLOCKED" + }, + "schema": "weave.private-public-experiment/v1", + "source_files": { + "CYCLE_NATIVE.json": "cdf11526ca93a8627eea036d7e56ac630d4be04ccb1fe14ba5973966b19d6f43", + "affixiation.py": "15518004e43873515badefd951d6f32a0de79ce8002d3aebd41921bf8d7eb985", + "cycle.py": "25cbe0e5567562c1f2de3de0ca69d4e5a4e57e78a283aa7dcee882b0b16de562", + "cycle_native.py": "8a3cf679078b6ffc36a26ab99d0f4cbd593cfa2c404e05fd927d8fae09947219", + "inputs/ucns/src/ucns/axis_circle.py": "ab69c3cb84065d8f6332d31d0c9f128b2685954ecf4de5d3c5d29e0afe371f9c", + "inputs/ucns/src/ucns/direct_mobius.py": "d8d1360c753dac7431071e007c5105a21b5396dd9e2f7e5ba4089d99e056a5bf", + "native_binary.py": "3843b4fb7c735c2d3d45f28a13905397a4dd8ff988fba91b3fe1f92e75103e4d", + "numeral.py": "a3c2b67611b095f65ba4b355fac2275735a062fb76ba2170790e64d8b434f9e3", + "prime_schedule.py": "c5b59833d236c1f7dfe66102052e1796e8532a25f299820c68e2b417c6678242", + "private_public.py": "1e928325776ada4657a3803e703db195c56c6e5b913d6eb23f83845d3ce87248", + "safe_output.py": "5bdfc6ead9f7eb9341d3de669ece63cdf653e1e8ba7882b334360cbafad8b052" + }, + "source_sha256": "e7d8613f0cd6cdcbe2d3bbab9a9c61ee94f4d746777b31dc8851cab535b8ec97", + "whole_plus_one": { + "packet_created_by_public_sender": false, + "private_gonol_relation": "UNIMPLEMENTED", + "reason": "six spaces per round are private; native public evaluation law missing", + "status": "BLOCKED" + } +} diff --git a/research/weave/private_public.py b/research/weave/private_public.py new file mode 100644 index 00000000..3feb5539 --- /dev/null +++ b/research/weave/private_public.py @@ -0,0 +1,324 @@ +# ratios: loc_comments=229:55 imports_exports=14:8 calls_definitions=121:16 +# === MODULE_BUILD === +# id: weave_private_public_boundary_experiment +# module_name: private_public +# module_kind: experiment +# summary: falsifiable public/private input boundary on the unchanged native sequence cycle +# owner: Erin Spencer +# public_surface: partition, send, recover, public_recover, full_profile_control, experiment +# internal_surface: strict serialized artifacts and fresh-process evidence +# auth_boundary: none +# storage_boundary: write +# network_boundary: none +# user_data_boundary: read +# admin_only: false +# tests: tests/test_private_public.py +# rollout: explicit experiment; BLOCKED is not asymmetric success +# rollback: remove experiment and its tests, docs and CI invocation +# unresolved: native private-gonol generation and public evaluation law +# === END MODULE_BUILD === +# === CONTRACTS === +# id: weave_public_artifact_boundary +# given: a full cycle profile and actual corpus +# then: whole-plus-one public export carries exactly two spaces per round and no private reference +# id: weave_sender_no_private_fallback +# given: only the whole-plus-one projection +# then: refuse the undefined public operation before cycle execution rather than guessing hidden spaces +# id: weave_private_reconstruction_inputs +# given: a packet and the matching public and private reconstruction artifacts +# then: recover exactly using only those inputs and locked source code +# id: weave_public_recovery_falsifier +# given: full-profile public control and its packet +# then: execute public-only recovery and report exact recovery as a counterexample to private necessity +# id: weave_process_evidence_boundary +# given: an experiment run +# then: isolate sender and recovery invocations in fresh processes with explicit input and source manifests +# id: weave_distinction_gate +# given: a blocked public sender or an exact public recovery counterexample +# then: the requested distinction remains unaccepted and its acceptance command exits nonzero +# === END CONTRACTS === +"""Usage: python private_public.py --sources /checkouts [--require-distinction]. + +PRIVATE_PUBLIC.md freezes scope and acceptance criteria. This is an executable +boundary experiment, not a key generator. A private-argument check cannot prove +private-state necessity. The full-profile control deliberately tests that mistake. +""" +from __future__ import annotations + +import argparse +from copy import deepcopy +from hashlib import sha256 +from pathlib import Path +import os +import re +import subprocess +import sys +import tempfile + +import cycle +from cycle import Profile, CycleLimits, canonical, strict_json +from cycle_native import load_native +from numeral import Refused + +ROOT = Path(__file__).resolve().parent +PUBLIC_SCHEMA = 'weave.public-cycle-input/v1' +PRIVATE_SCHEMA = 'weave.private-cycle-input/v1' +PROJECTION = 'whole-plus-one' +CONTROL = 'full-profile-control' +LIMITS = CycleLimits(input_bytes=256, round_bytes=131072, rounds=3) +ARTIFACT_BYTES = 32768 +RUNTIME = ('private_public.py', 'cycle.py', 'cycle_native.py', 'native_binary.py', + 'numeral.py', 'affixiation.py', 'prime_schedule.py', 'safe_output.py', + 'CYCLE_NATIVE.json') + + +class MissingPublicRelation(Refused): + """Q7 is blocked by a missing native operation, not a proven secret.""" + + +def _object(data, fields): + if type(data) is not bytes or len(data) > ARTIFACT_BYTES: + raise Refused('bounded serialized artifact bytes required') + obj = strict_json(data) + if type(obj) is not dict or set(obj) != set(fields): + raise Refused('artifact fields do not match schema') + return obj + + +def _hex(value, maximum): + if (type(value) is not str or len(value) > maximum * 2 + or re.fullmatch(r'(?:[0-9a-f]{2})*', value) is None): + raise Refused('bounded canonical hexadecimal required') + return bytes.fromhex(value) + + +def _projection(profile, circle): + obj = Profile.as_dict(profile) + for row in obj['rounds']: + row['spaces'] = [value if i in (0, circle) else None + for i, value in enumerate(row['spaces'])] + return obj + + +def _public(data): + obj = _object(data, ('schema', 'disclosure', 'circle', 'profile', + 'corpus_hex', 'native_lock_sha256')) + if (obj['schema'] != PUBLIC_SCHEMA or type(obj['disclosure']) is not str + or obj['disclosure'] not in (PROJECTION, CONTROL) + or type(obj['circle']) is not int or not 1 <= obj['circle'] <= 7): + raise Refused('unsupported public artifact') + if obj['native_lock_sha256'] != sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).hexdigest(): + raise Refused('native lock identity mismatch') + corpus = _hex(obj['corpus_hex'], 4096) + if not corpus: + raise Refused('actual nonempty public corpus required') + profile = deepcopy(obj['profile']) + if type(profile) is not dict or type(profile.get('rounds')) is not list: + raise Refused('profile round list required') + for row in profile['rounds']: + if (type(row) is not dict or type(row.get('spaces')) is not list + or len(row['spaces']) != 8): + raise Refused('eight space positions required') + for i, value in enumerate(row['spaces']): + hidden = obj['disclosure'] == PROJECTION and i not in (0, obj['circle']) + if hidden: + if value is not None: + raise Refused('private space leaked into public projection') + # Syntax validation only. Never return this placeholder profile. + row['spaces'][i] = [0, 1] + elif value is None: + raise Refused('required public space absent') + Profile.read(canonical(profile), LIMITS) + return obj, corpus + + +def _complete(public): + obj, corpus = _public(public) + if obj['disclosure'] != CONTROL: + try: + Profile.read(canonical(obj['profile']), LIMITS) + except Refused as exc: + raise MissingPublicRelation('six spaces per round are private; native public evaluation law missing') from exc + raise RuntimeError('cycle now admits partial profiles; reassess the experiment') + return Profile.read(canonical(obj['profile']), LIMITS), corpus + + +def partition(profile: Profile, corpus: bytes, *, circle: int = 1) -> tuple[bytes, bytes]: + """Export reconstruction inputs; no entropy, private gonol or trapdoor invented.""" + if type(corpus) is not bytes: + raise Refused('actual corpus bytes required') + obj = {'schema': PUBLIC_SCHEMA, 'disclosure': PROJECTION, 'circle': circle, + 'profile': _projection(profile, circle), 'corpus_hex': corpus.hex(), + 'native_lock_sha256': sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).hexdigest()} + public = canonical(obj) + _public(public) + private = canonical({'schema': PRIVATE_SCHEMA, 'public_sha256': sha256(public).hexdigest(), + 'profile': Profile.as_dict(profile)}) + _private(public, private) + return public, private + + +def _private(public, private): + pub, corpus = _public(public) + obj = _object(private, ('schema', 'public_sha256', 'profile')) + if obj['schema'] != PRIVATE_SCHEMA or obj['public_sha256'] != sha256(public).hexdigest(): + raise Refused('private artifact does not bind these exact public bytes') + profile = Profile.read(canonical(obj['profile']), LIMITS) + projected = (_projection(profile, pub['circle']) if pub['disclosure'] == PROJECTION + else Profile.as_dict(profile)) + if canonical(projected) != canonical(pub['profile']): + raise Refused('private profile disagrees with public projection') + return profile, corpus + + +def full_profile_control(public: bytes, private: bytes) -> bytes: + """Publish all reconstruction fields explicitly as a negative control.""" + profile, _ = _private(public, private) + obj, _ = _public(public) + obj['profile'] = Profile.as_dict(profile) + obj['disclosure'] = CONTROL + result = canonical(obj) + _public(result) + return result + + +def send(message: bytes, public: bytes, sources: str | Path) -> bytes: + """Public inputs only. A missing native law blocks before any forward work.""" + profile, corpus = _complete(public) + return cycle.forward(message, corpus, profile, sources, LIMITS)[0] + + +def recover(packet: bytes, public: bytes, private: bytes, sources: str | Path) -> bytes: + """Private-path exact recovery; accepting a private argument is not asymmetry.""" + profile, corpus = _private(public, private) + return cycle.reverse(packet, corpus, profile, sources, LIMITS) + + +def public_recover(packet: bytes, public: bytes, sources: str | Path) -> bytes: + """Actual public inverse attack, not a call to the private-path wrapper.""" + profile, corpus = _complete(public) + return cycle.reverse(packet, corpus, profile, sources, LIMITS) + + +def _worker(): + request = strict_json(sys.stdin.buffer.read(1048577)) + if type(request) is not dict or request.get('operation') not in ('send', 'recover', 'public-recover'): + raise Refused('unknown worker operation') + operation = request['operation'] + fields = {'operation', 'public', 'data'} | ({'private'} if operation == 'recover' else set()) + if set(request) != fields: + raise Refused('worker input manifest mismatch') + public = _hex(request['public'], ARTIFACT_BYTES) + data = _hex(request['data'], LIMITS.round_bytes + 128) + try: + if operation == 'send': + result = send(data, public, ROOT/'inputs') + elif operation == 'public-recover': + result = public_recover(data, public, ROOT/'inputs') + else: + result = recover(data, public, _hex(request['private'], ARTIFACT_BYTES), ROOT/'inputs') + except MissingPublicRelation as exc: + return {'status': 'BLOCKED', 'reason': str(exc)} + return {'status': 'COMPLETED', 'data': result.hex()} + + +def _run(root, operation, public, data, private=None): + request = {'operation': operation, 'public': public.hex(), 'data': data.hex()} + if private is not None: + request['private'] = private.hex() + result = subprocess.run([sys.executable, '-S', '-B', '-E', str(root/'private_public.py'), '--worker'], + input=canonical(request), stdout=subprocess.PIPE, stderr=subprocess.PIPE, + cwd=root, env={}) + if result.returncode: + raise RuntimeError(f'{operation} worker failed: {result.stderr.decode(errors="replace")}') + response = strict_json(result.stdout) + if type(response) is not dict or response.get('status') not in ('BLOCKED', 'COMPLETED'): + raise RuntimeError('invalid worker response') + return response + + +def experiment(message: bytes, profile: Profile, corpus: bytes, sources: str | Path) -> dict: + """Run a bounded input-partition probe; return counts/identities, not secrets.""" + if type(message) is not bytes or len(message) > LIMITS.input_bytes: + raise Refused('experiment message must be at most 256 bytes') + public, private = partition(profile, corpus) + control = full_profile_control(public, private) + # Verify required sources first: missing geometry is an infrastructure error, + # never evidence that an attacker cannot recover. + load_native(sources) + with tempfile.TemporaryDirectory(prefix='weave-public-private-') as directory: + root = Path(directory) + for name in RUNTIME: + (root/name).write_bytes((ROOT/name).read_bytes()) + lock = strict_json((ROOT/'CYCLE_NATIVE.json').read_bytes()) + for name in lock['ucns_sources']: + target = root/'inputs'/'ucns'/name + target.parent.mkdir(parents=True, exist_ok=True) + target.write_bytes((Path(sources)/'ucns'/name).read_bytes()) + source_files = {str(p.relative_to(root)): sha256(p.read_bytes()).hexdigest() + for p in sorted(root.rglob('*')) if p.is_file()} + # Public evaluation is tried before the private recovery input is sent to + # any process. No profile fixture or private file exists in the bundle. + candidate = _run(root, 'send', public, message) + sent = _run(root, 'send', control, message) + if sent['status'] != 'COMPLETED': + raise RuntimeError('full-profile control did not produce a packet') + packet = _hex(sent['data'], LIMITS.round_bytes + 128) + recipient = _run(root, 'recover', public, packet, private) + projected_attack = _run(root, 'public-recover', public, packet) + control_attack = _run(root, 'public-recover', control, packet) + after = {str(p.relative_to(root)): sha256(p.read_bytes()).hexdigest() + for p in sorted(root.rglob('*')) if p.is_file()} + if source_files != after: + raise RuntimeError('worker bundle changed during the experiment') + recipient_exact = recipient['status'] == 'COMPLETED' and _hex(recipient['data'], LIMITS.input_bytes) == message + attack_exact = control_attack['status'] == 'COMPLETED' and _hex(control_attack['data'], LIMITS.input_bytes) == message + if not recipient_exact or not attack_exact or candidate['status'] != 'BLOCKED' or projected_attack['status'] != 'BLOCKED': + raise RuntimeError('frozen cycle control behavior changed; reassess the experiment') + return { + 'schema': 'weave.private-public-experiment/v1', 'execution': 'COMPLETED', + 'construction': 'INPUT_PARTITION_ONLY', 'distinction_established': False, + 'whole_plus_one': {'status': candidate['status'], 'reason': candidate['reason'], + 'packet_created_by_public_sender': False, + 'private_gonol_relation': 'UNIMPLEMENTED'}, + 'full_profile_control': {'status': 'FALSIFIED', 'public_sender_completed': True, + 'recipient_exact': recipient_exact, 'public_only_exact': attack_exact, + 'whole_plus_one_preserved': False}, + 'projected_public_recovery': {'status': projected_attack['status'], + 'scope': 'control packet; API refusal is not private necessity'}, + 'inputs': {'sender': ['message', 'public'], 'recipient': ['packet', 'public', 'private'], + 'attacker': ['packet', 'public'], 'shared': ['exact code', 'locked native sources'], + 'corpus': 'actual bytes inside public artifact'}, + 'accounting': {'message_bytes': len(message), 'packet_bytes': len(packet), + 'public_bytes': len(public), 'private_bytes': len(private), + 'full_profile_control_bytes': len(control)}, + 'source_files': source_files, + 'source_sha256': sha256(canonical(source_files)).hexdigest(), + 'native_lock_sha256': sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).hexdigest(), + 'packet_sha256': sha256(packet).hexdigest(), + 'nonclaim': 'No falsification of intended Weave, no trapdoor or cryptographic security claim.', + 'hmmm': 'Native private-gonol constructor, public evaluation and private recovery law remain undefined.'} + + +def main(): + parser = argparse.ArgumentParser(description=__doc__) + parser.add_argument('--sources', type=Path, default=Path(os.environ.get('WEAVE_SOURCES', ROOT/'sources'))) + parser.add_argument('--require-distinction', action='store_true') + parser.add_argument('--worker', action='store_true', help=argparse.SUPPRESS) + args = parser.parse_args() + try: + if args.worker: + result = _worker() + else: + profile = Profile.read((ROOT/'profiles'/'cycle-v1.json').read_bytes(), LIMITS) + result = experiment(b'ABxABy\x00ABxABy\xff', profile, bytes(range(256)), args.sources) + print(canonical(result).decode()) + return 1 if args.require_distinction and not result.get('distinction_established', False) else 0 + except (Refused, OSError, RuntimeError) as exc: + print(f'private/public experiment error: {exc}', file=sys.stderr) + return 2 + + +if __name__ == '__main__': + raise SystemExit(main()) +# ratios: loc_comments=229:55 imports_exports=14:8 calls_definitions=121:16 diff --git a/research/weave/test.py b/research/weave/test.py index 7d2941ab..17deedec 100644 --- a/research/weave/test.py +++ b/research/weave/test.py @@ -1,4 +1,5 @@ #!/usr/bin/env python3 +# ratios: loc_comments=82:7 imports_exports=9:2 calls_definitions=18:2 """Run the full repository Weave suite and emit a source-bound receipt. Usage: WEAVE_SOURCES=/checkouts python test.py --receipt /tmp/weave-check.json @@ -17,7 +18,7 @@ from assembly import Pipeline ROOT = Path(__file__).resolve().parent -EXPECTED_TESTS = 176 # 168 existing + 8 cheap-admission/public-helper regressions. +EXPECTED_TESTS = 196 # 176 merged-cycle regressions + 20 private/public boundary experiments. def snapshot(): @@ -65,11 +66,13 @@ def main(): 'native_origin_methods': 13, 'cycle_repair_methods': 12, 'review_closure_methods': 18, 'final_findings_methods': 9, 'terminal_record_methods': 9, 'header_admission_methods': 3, - 'numeral_single_replay_methods': 4, 'cheap_admission_methods': 8 + 'numeral_single_replay_methods': 4, 'cheap_admission_methods': 8, + 'private_public_boundary_methods': 20 }, 'cycle': 'EXPLICIT_CANDIDATE: corpus normalization once; native sequence affixiation and prime-derived bit interleave; exact reverse', 'full_weave': Pipeline().plan(), 'asymmetric_cipher': 'NOT_IMPLEMENTED: cycle/profile recovery is not a public/private trapdoor', + 'private_public_experiment': 'BLOCKED: whole-plus-one sender law missing; full-profile control permits public recovery', 'native_language_corpus_replay': 'NOT_EXECUTED: binary construction does not use or replace a language corpus', 'security_observations': [ 'The older fixed transport map remains recoverable in its declared attack experiment.', @@ -93,3 +96,4 @@ def main(): if __name__ == '__main__': raise SystemExit(main()) +# ratios: loc_comments=82:7 imports_exports=9:2 calls_definitions=18:2 diff --git a/research/weave/tests/test_private_public.py b/research/weave/tests/test_private_public.py new file mode 100644 index 00000000..f9e71ddb --- /dev/null +++ b/research/weave/tests/test_private_public.py @@ -0,0 +1,324 @@ +# ratios: loc_comments=184:111 imports_exports=14:1 calls_definitions=122:22 +# === CHECKS === +# id: check_private_public_exact_whole_plus_one_export_for_each_circle +# proves: weave_public_artifact_boundary +# call: self::test_exact_whole_plus_one_export_for_each_circle +# mutates: none +# cleanup: none +# id: check_private_public_sender_blocks_before_forward_or_native_loading +# proves: weave_sender_no_private_fallback +# call: self::test_sender_blocks_before_forward_or_native_loading +# mutates: none +# cleanup: none +# id: check_private_public_projected_public_recovery_refusal_is_not_security_evidence +# proves: weave_sender_no_private_fallback +# call: self::test_projected_public_recovery_refusal_is_not_security_evidence +# mutates: none +# cleanup: none +# id: check_private_public_private_recovery_exact_for_binary_edge_cases +# proves: weave_private_reconstruction_inputs +# call: self::test_private_recovery_exact_for_binary_edge_cases +# mutates: none +# cleanup: none +# id: check_private_public_public_only_inverse_is_a_real_counterexample +# proves: weave_public_recovery_falsifier +# call: self::test_public_only_inverse_is_a_real_counterexample +# mutates: none +# cleanup: none +# id: check_private_public_control_matches_unmodified_cycle_bytes +# proves: weave_public_recovery_falsifier +# call: self::test_control_matches_unmodified_cycle_bytes +# mutates: none +# cleanup: none +# id: check_private_public_private_input_is_required_by_recipient_interface +# proves: weave_private_reconstruction_inputs +# call: self::test_private_input_is_required_by_recipient_interface +# mutates: none +# cleanup: none +# id: check_private_public_wrong_hidden_private_space_fails_even_with_matching_public_projection +# proves: weave_private_reconstruction_inputs +# call: self::test_wrong_hidden_private_space_fails_even_with_matching_public_projection +# mutates: none +# cleanup: none +# id: check_private_public_wrong_pair_and_changed_visible_space_refused +# proves: weave_private_reconstruction_inputs +# call: self::test_wrong_pair_and_changed_visible_space_refused +# mutates: none +# cleanup: none +# id: check_private_public_private_leak_and_incomplete_control_are_refused +# proves: weave_public_artifact_boundary +# call: self::test_private_leak_and_incomplete_control_are_refused +# mutates: none +# cleanup: none +# id: check_private_public_strict_artifact_schema_and_types +# proves: weave_public_artifact_boundary +# call: self::test_strict_artifact_schema_and_types +# mutates: none +# cleanup: none +# id: check_private_public_invalid_public_profile_rejected_before_cycle +# proves: weave_public_artifact_boundary +# call: self::test_invalid_public_profile_rejected_before_cycle +# mutates: none +# cleanup: none +# id: check_private_public_artifact_sizes_and_corpus_limits +# proves: weave_public_artifact_boundary +# call: self::test_artifact_sizes_and_corpus_limits +# mutates: none +# cleanup: none +# id: check_private_public_corrupted_and_truncated_packet_are_not_recovery +# proves: weave_private_reconstruction_inputs +# call: self::test_corrupted_and_truncated_packet_are_not_recovery +# mutates: none +# cleanup: none +# id: check_private_public_three_round_fresh_process_input_manifests +# proves: weave_process_evidence_boundary +# call: self::test_three_round_fresh_process_input_manifests +# mutates: filesystem +# cleanup: tempdir_teardown +# id: check_private_public_report_does_not_include_private_artifact_or_plaintext +# proves: weave_process_evidence_boundary +# call: self::test_report_does_not_include_private_artifact_or_plaintext +# mutates: filesystem +# cleanup: tempdir_teardown +# id: check_private_public_missing_native_source_is_error_not_attacker_failure +# proves: weave_distinction_gate +# call: self::test_missing_native_source_is_error_not_attacker_failure +# mutates: none +# cleanup: none +# id: check_private_public_worker_crash_is_not_public_recovery_failure +# proves: weave_distinction_gate +# call: self::test_worker_crash_is_not_public_recovery_failure +# mutates: filesystem +# cleanup: tempdir_teardown +# id: check_private_public_acceptance_cli_exits_one_with_honest_report +# proves: weave_distinction_gate +# call: self::test_acceptance_cli_exits_one_with_honest_report +# mutates: filesystem +# cleanup: tempdir_teardown +# id: check_private_public_cli_infrastructure_error_exits_two +# proves: weave_distinction_gate +# call: self::test_cli_infrastructure_error_exits_two +# mutates: none +# cleanup: none +# === END CHECKS === +"""Usage: WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_private_public.py -v. + +These regressions protect honest experimental failure. They do not claim that +the intended asymmetric/private-gonol layer has been constructed. +""" +from copy import deepcopy +from dataclasses import replace +from fractions import Fraction +from pathlib import Path +import json +import os +import subprocess +import sys +import unittest +from unittest.mock import patch + +import cycle +import private_public as pp +from cycle import Profile, canonical +from numeral import Refused + +ROOT = Path(__file__).resolve().parents[1] +SOURCES = Path(os.environ.get('WEAVE_SOURCES', ROOT/'sources')) +BASE = Profile.read((ROOT/'profiles'/'cycle-v1.json').read_bytes()) +# Hidden values deliberately differ from the checked-in example. The explicit +# private input has no public filename, path, fixture lookup or PRNG seed. +ROUND = replace(BASE.rounds[0], spaces=tuple(Fraction(13 + i * 11, 113) for i in range(8))) +PROFILE = replace(BASE, scope='private-public-test', rounds=(ROUND,)) +CORPUS = b'actual shared corpus\x00\xff' + + +class PrivatePublicTests(unittest.TestCase): + def setUp(self): + self.public, self.private = pp.partition(PROFILE, CORPUS) + self.control = pp.full_profile_control(self.public, self.private) + + def test_exact_whole_plus_one_export_for_each_circle(self): + for circle in range(1, 8): + public, private = pp.partition(BASE, CORPUS, circle=circle) + obj = json.loads(public) + for original, row in zip(BASE.as_dict()['rounds'], obj['profile']['rounds']): + self.assertEqual([i for i, x in enumerate(row['spaces']) if x is not None], [0, circle]) + self.assertEqual(row['spaces'][0], original['spaces'][0]) + self.assertEqual(row['spaces'][circle], original['spaces'][circle]) + self.assertEqual({k: v for k, v in row.items() if k != 'spaces'}, + {k: v for k, v in original.items() if k != 'spaces'}) + self.assertEqual(json.loads(private)['profile'], BASE.as_dict()) + self.assertEqual(bytes.fromhex(obj['corpus_hex']), CORPUS) + + def test_sender_blocks_before_forward_or_native_loading(self): + with patch.object(cycle, 'forward', side_effect=AssertionError('private-dependent forward called')): + with self.assertRaisesRegex(pp.MissingPublicRelation, 'native public evaluation law missing'): + pp.send(b'message', self.public, SOURCES) + + def test_projected_public_recovery_refusal_is_not_security_evidence(self): + with patch.object(cycle, 'reverse', side_effect=AssertionError('guessed missing spaces')): + with self.assertRaises(pp.MissingPublicRelation): + pp.public_recover(b'not even a packet', self.public, SOURCES) + + def test_private_recovery_exact_for_binary_edge_cases(self): + for message in (b'', b'\x00\x00\x01', bytes(range(256)), b'ABxABy' * 12): + with self.subTest(length=len(message)): + packet = pp.send(message, self.control, SOURCES) + self.assertEqual(pp.recover(packet, self.public, self.private, SOURCES), message) + + def test_public_only_inverse_is_a_real_counterexample(self): + message = b'ABxABy\x00ABxABy\xff' + packet = pp.send(message, self.control, SOURCES) + # The attacker bypasses the entire private-path API rather than making + # that API accept a missing key. No private parameter goes to this call. + with patch.object(pp, 'recover', side_effect=AssertionError('private API called')): + self.assertEqual(pp.public_recover(packet, self.control, SOURCES), message) + + def test_control_matches_unmodified_cycle_bytes(self): + message = b'ABxABy' + expected, _ = cycle.forward(message, CORPUS, PROFILE, SOURCES, pp.LIMITS) + self.assertEqual(pp.send(message, self.control, SOURCES), expected) + control = json.loads(self.control) + self.assertEqual(control['disclosure'], pp.CONTROL) + self.assertEqual(control['profile'], PROFILE.as_dict()) + + def test_private_input_is_required_by_recipient_interface(self): + packet = pp.send(b'message', self.control, SOURCES) + for absent in (None, b'', self.public, self.control): + with self.assertRaises(Refused): + pp.recover(packet, self.public, absent, SOURCES) + # This interface refusal is deliberately paired with the public attack. + self.assertEqual(pp.public_recover(packet, self.control, SOURCES), b'message') + + def test_wrong_hidden_private_space_fails_even_with_matching_public_projection(self): + packet = pp.send(b'ABxABy', self.control, SOURCES) + obj = json.loads(self.private) + obj['profile']['rounds'][0]['spaces'][2] = [1, 97] + with self.assertRaisesRegex(Refused, 'profile identity mismatch'): + pp.recover(packet, self.public, canonical(obj), SOURCES) + + def test_wrong_pair_and_changed_visible_space_refused(self): + packet = pp.send(b'message', self.control, SOURCES) + other, _ = pp.partition(PROFILE, CORPUS + b'!') + with self.assertRaisesRegex(Refused, 'exact public bytes'): + pp.recover(packet, other, self.private, SOURCES) + obj = json.loads(self.private) + obj['profile']['rounds'][0]['spaces'][0] = [1, 97] + with self.assertRaisesRegex(Refused, 'public projection'): + pp.recover(packet, self.public, canonical(obj), SOURCES) + + def test_private_leak_and_incomplete_control_are_refused(self): + obj = json.loads(self.public) + obj['profile']['rounds'][0]['spaces'][2] = [0, 1] + with self.assertRaisesRegex(Refused, 'leaked'): + pp.send(b'hi', canonical(obj), SOURCES) + obj = json.loads(self.control) + obj['profile']['rounds'][0]['spaces'][2] = None + with self.assertRaisesRegex(Refused, 'public space absent'): + pp.send(b'hi', canonical(obj), SOURCES) + + def test_strict_artifact_schema_and_types(self): + base = json.loads(self.public) + cases = [] + for key, value in [('circle', True), ('circle', 0), ('circle', 8), ('schema', 'other'), + ('disclosure', 'asymmetric'), ('private', 'file://secret'), + ('corpus_hex', 'AA'), ('corpus_hex', '00 '), ('native_lock_sha256', '0' * 64)]: + obj = deepcopy(base); obj[key] = value; cases.append(canonical(obj)) + cases += [b'[]', b'{"schema":1,"schema":2}', b'{"x":NaN}', b'\xff', b'x' * 32769] + for data in cases: + with self.subTest(data=data[:80]), self.assertRaises(Refused): + pp.send(b'm', data, SOURCES) + + def test_invalid_public_profile_rejected_before_cycle(self): + for field, value in [('spaces', []), ('end_order', 'sideways'), ('arities', [True]), + ('circle_order', [1] * 7), ('prime_path', {'seed': 1, 'steps': []})]: + obj = json.loads(self.public) + obj['profile']['rounds'][0][field] = value + with self.assertRaises(Refused): + pp.send(b'm', canonical(obj), SOURCES) + obj = json.loads(self.public) + obj['profile']['rounds'][0]['spaces'][0] = [2, 4] + with self.assertRaises(Refused): + pp.send(b'm', canonical(obj), SOURCES) + + def test_artifact_sizes_and_corpus_limits(self): + for corpus in (b'', b'x' * 4097, 'corpus'): + with self.assertRaises(Refused): pp.partition(PROFILE, corpus) + with self.assertRaises(Refused): + pp.partition(replace(BASE, rounds=BASE.rounds * 2), CORPUS) + with self.assertRaises(Refused): + pp.send(b'x' * 257, self.control, SOURCES) + with self.assertRaises(Refused): + pp.experiment(b'x' * 257, PROFILE, CORPUS, SOURCES) + + def test_corrupted_and_truncated_packet_are_not_recovery(self): + packet = pp.send(b'message', self.control, SOURCES) + damaged = bytearray(packet); damaged[36] ^= 1 + for bad in (packet[:-1], packet + b'!', bytes(damaged)): + with self.assertRaises(Refused): pp.public_recover(bad, self.control, SOURCES) + with self.assertRaises(Refused): pp.recover(bad, self.public, self.private, SOURCES) + + def test_three_round_fresh_process_input_manifests(self): + calls = [] + original = pp._run + def observe(root, operation, public, data, private=None): + files = {str(p.relative_to(root)) for p in root.rglob('*') if p.is_file()} + self.assertFalse(any('profile' in p or 'test_' in p for p in files)) + self.assertEqual(private is not None, operation == 'recover') + calls.append((operation, set(json.loads(public)), private is not None)) + return original(root, operation, public, data, private) + with patch.object(pp, '_run', side_effect=observe): + report = pp.experiment(b'ABxABy\x00ABxABy', BASE, CORPUS, SOURCES) + self.assertEqual([c[0] for c in calls], ['send', 'send', 'recover', 'public-recover', 'public-recover']) + self.assertFalse(report['distinction_established']) + self.assertEqual(report['whole_plus_one']['status'], 'BLOCKED') + self.assertEqual(report['full_profile_control']['status'], 'FALSIFIED') + self.assertTrue(report['full_profile_control']['recipient_exact']) + self.assertTrue(report['full_profile_control']['public_only_exact']) + self.assertNotIn('private', report['inputs']['sender']) + self.assertNotIn('private', report['inputs']['attacker']) + self.assertNotIn('message', report['inputs']['recipient']) + self.assertEqual(set(report['source_files']), set(pp.RUNTIME) | { + 'inputs/ucns/src/ucns/axis_circle.py', 'inputs/ucns/src/ucns/direct_mobius.py'}) + + def test_report_does_not_include_private_artifact_or_plaintext(self): + message = b'not-a-secret-but-must-not-enter-report' + report = pp.experiment(message, PROFILE, CORPUS, SOURCES) + rendered = canonical(report) + self.assertNotIn(message, rendered) + self.assertNotIn(message.hex().encode(), rendered) + self.assertNotIn(self.private, rendered) + self.assertNotIn(b'corpus_hex', rendered) + self.assertEqual(report['accounting']['message_bytes'], len(message)) + + def test_missing_native_source_is_error_not_attacker_failure(self): + with patch.object(pp, 'load_native', side_effect=Refused('missing native source')): + with self.assertRaisesRegex(Refused, 'missing native source'): + pp.experiment(b'm', PROFILE, CORPUS, SOURCES) + + def test_worker_crash_is_not_public_recovery_failure(self): + result = subprocess.CompletedProcess([], 2, b'', b'infrastructure failure') + with patch.object(pp.subprocess, 'run', return_value=result): + with self.assertRaisesRegex(RuntimeError, 'worker failed'): + pp.experiment(b'm', PROFILE, CORPUS, SOURCES) + + def test_acceptance_cli_exits_one_with_honest_report(self): + result = subprocess.run([sys.executable, str(ROOT/'private_public.py'), '--sources', str(SOURCES), + '--require-distinction'], capture_output=True) + self.assertEqual(result.returncode, 1, result.stderr.decode()) + report = json.loads(result.stdout) + self.assertEqual(report['execution'], 'COMPLETED') + self.assertFalse(report['distinction_established']) + self.assertEqual(report['full_profile_control']['status'], 'FALSIFIED') + + def test_cli_infrastructure_error_exits_two(self): + result = subprocess.run([sys.executable, str(ROOT/'private_public.py'), '--sources', '/nonexistent-weave-inputs'], + capture_output=True) + self.assertEqual(result.returncode, 2) + self.assertEqual(result.stdout, b'') + self.assertIn(b'experiment error', result.stderr) + + +if __name__ == '__main__': + unittest.main() +# ratios: loc_comments=184:111 imports_exports=14:1 calls_definitions=122:22 From b25cc6858d2426465a6a92e4cf750bbdba0d7c5e Mon Sep 17 00:00:00 2001 From: Erin Spencer Date: Tue, 6 Oct 2026 13:56:18 -0700 Subject: [PATCH 2/2] feat(weave): construct and falsify native private public polynomial candidate --- .github/workflows/weave.yml | 2 +- research/weave/PRIVATE_PUBLIC.md | 240 +++++---- research/weave/QUESTIONS.md | 13 +- research/weave/README.md | 14 +- .../weave/evidence/private-public-v1.json | 49 +- research/weave/private_public.py | 453 +++++++++-------- research/weave/test.py | 6 +- research/weave/tests/test_private_public.py | 464 +++++++++--------- 8 files changed, 636 insertions(+), 605 deletions(-) diff --git a/.github/workflows/weave.yml b/.github/workflows/weave.yml index e835f812..fc9c9d3f 100644 --- a/.github/workflows/weave.yml +++ b/.github/workflows/weave.yml @@ -43,7 +43,7 @@ jobs: PYTHONDONTWRITEBYTECODE=1 python3 research/weave/test.py --receipt "$RUNNER_TEMP/weave-check.json" cat "$RUNNER_TEMP/weave-check.json" PYTHONDONTWRITEBYTECODE=1 python3 research/weave/cycle.py demo | tee "$RUNNER_TEMP/weave-demo.json" - - name: Observe unclosed private-public distinction (expected exit 1) + - name: Reproduce native candidate public-recovery attack (expected exit 1) run: | set -euo pipefail if python3 research/weave/private_public.py --sources "$GITHUB_WORKSPACE/.weave-inputs" --require-distinction > "$RUNNER_TEMP/weave-private-public.json"; then diff --git a/research/weave/PRIVATE_PUBLIC.md b/research/weave/PRIVATE_PUBLIC.md index f99de168..554bff61 100644 --- a/research/weave/PRIVATE_PUBLIC.md +++ b/research/weave/PRIVATE_PUBLIC.md @@ -1,132 +1,130 @@ -# Private/public recovery experiment v1 - -Scope: the merged sequence cycle at Stack -`ccf707f933a9611d7b7fe13a0417378d8b453a14`. This experiment tests whether splitting -its existing reconstruction inputs establishes the requested distinction. It does -not invent the still-undefined native public-evaluation/private-recovery law. - -## Frozen acceptance criteria - -1. The sender receives message bytes, public artifact, public corpus and locked - source code only. No recipient-private artifact, callback or encoder trace. -2. A separate recipient recovers every byte using the transmitted record and - recipient-private artifact, without the original plaintext. -3. Attempt public-only recovery with the same disclosed inputs. An exact public - recovery is a counterexample to private-state necessity. An API refusing a - missing argument is not evidence of mathematical necessity. -4. Keep the intended whole-plus-one exposure and private-gonol relation explicit. - Publishing extra reconstruction data is a control, not satisfaction of Q2/Q12. - -All four are required. This experiment can return **BLOCKED** or **FALSIFIED**; -it cannot establish cryptographic security. Passing regression tests establish -that the experiment reports these outcomes honestly, not that asymmetry works. - -## Artifacts and the tested relation - -`partition()` accepts an existing exact cycle profile and actual corpus. It is an -input partitioner, **not asymmetric key generation**. - -| Artifact | Contents | Available to sender | -|---|---|---| -| Public, whole-plus-one | All non-space profile fields; whole-circle space 0 and one selected occurrence-circle space in each round; actual corpus bytes; native-source-lock identity | Yes | -| Private, reconstruction input | Full eight-space cycle profile and the public-artifact digest | No | -| Public, full-profile control | Full profile, corpus and native-source-lock identity; explicitly labeled as an overdisclosed control | Control only | -| Packet | Unmodified `cycle.forward` record, with all native occurrences, numeral definitions, framing and prime-derived interleaving | Yes | - -The six undisclosed spaces per round are JSON `null`, never guessed as zero and -never silently retrieved from a profile file. The private artifact is an existing -native-cycle configuration. It is **not claimed to be the missing private gonol**. -The selected public occurrence circle is an explicit experimental parameter; -circle 1 is the default, not a new universal placement law. - -The fixed cycle forward and reverse both require a complete `Profile`. Therefore: - -- Whole-plus-one projection: the sender has no defined way to compute the six - missing spaces or an alternative public operation. The experiment must report - **BLOCKED: native public evaluation law missing** before invoking the cycle. -- Full-profile control: the sender operates in a fresh process with only public - inputs; the recipient's private-path decoder recovers exactly. A separate - public-only process also runs the existing inverse and recovers exactly. - Private-state necessity is **FALSIFIED for this control**. - -Neither observation falsifies the intended Weave cryptosystem. No earlier -per-bit/star encoder or attack is restored. Prime routes remain public -deterministic schedule derivations, not a trapdoor. The actual native UCNS -geometry, complete 720-degree state, seven occurrence streams, corpus bits and -merged wire format remain unchanged. - -## Plan, boundaries and stopping rule - -Decision: can input partitioning alone close Q7 while retaining Q2/Q12? -Minimal action: run the two disclosed cases above on the real merged cycle. -Maximal closure would require a specified native private-gonol constructor, -public projection/evaluation law and private inverse, followed by adversarial -recovery work. That mathematical construction is not supplied by this patch. - -Positive outcome for a future law: public sender and private exact recovery -work, and the named public attack fails; continue to broader attacks, keeping the -result bounded to the tested attack. Negative: preserve the exact public recovery -counterexample and reject that candidate. Unresolved prerequisite: report BLOCKED, -identify the missing operation, and do not infer a design-wide impossibility. - -Resource preflight: one small message through the existing profile and a finite -set of fresh-process calls; standard library only, no search or network in the -experiment. Existing suite includes finite enumerations and a 65,536-byte cycle -case. Available local CPU, memory and disk suffice; no artificial timeout is used. -Stop when both controls and the regression suite finish. - -Code/runtime source files are copied by an explicit allowlist into a fresh -temporary directory. Child processes use a clean environment and `-S -B -E`. -The sender and attacker receive no private bytes; receiver receives no plaintext. -The input manifest and source hashes are recorded. This checks data flow, not -an OS sandbox or resistance to arbitrary hostile Python code. No network, -authentication, private storage service or production integration is added. - -Rollout: explicit experiment CLI and existing Weave research CI only. Rollback: -remove `private_public.py`, its tests and documentation/CI links. No source pins, -authority, geometry, prime-route logic or existing cycle format change. - -## Usage - -From `research/weave`, with the locked UCNS checkout at `/checkouts/ucns`: +# Native-shift polynomial candidate v1 + +This replaces the earlier input-partition-only experiment, which did not fulfill +the construction task. Scope: a new, explicitly proposed public/private binding +after the merged native Weave cycle. The candidate may fail; no result transfers +to intended Weave as a whole. + +## Defined relation (frozen before execution) + +Generate 256 private material bytes. Construct their actual Stack `ByteOrigin` +using the unchanged, source-locked UCNS geometry. For circle i=0..7, select native +byte axis `material[i]` and complete frame `material[8+i] & 1`. Its complete turn +`t_i` includes the 360/720 distinction. Define integer displacement +`a_i = 1 + 256*t_i`, in 1..512. These are explicit candidate choices, not UCNS laws. + +The private eight-circle candidate consists of that source-bearing origin and +its eight native axes/states. It is Stack research, not a claim to have completed +UCNS's general higher-dimensional gonol construction. All eight states affect the +binding; six are withheld as individual geometric records. +This key-origin is separate from the cycle's retained message-origin; it does not +replace message identity or its sequential byte-occurrence axes. + +For the six hidden displacements: + + F_0(x) = x + F_j(x) = (F_(j-1)(x) + a_(j+1))^2, j=1..6 + +Publish the 65 exact integer coefficients of degree-64 `F_6`, the whole-circle +state 0 and occurrence-circle state 1, origin identity, corpus, existing cycle +profile and native-source identity. The coefficient payload is explicitly counted +as additional public evaluation information attached to the public view. It must +be attacked; calling a record “whole-plus-one” does not hide its disclosures. + +Sender: run the real merged sequence cycle, interpret its exact length-bearing +record as integer x, and emit `y = F_6(x+a_0)+a_1`, plus key identity and record +length. The sender has only public artifacts, message and code. + +Recipient: rebuild the private native states from private material; check the +public/private binding; subtract a_1; repeatedly take an **exact** nonnegative +integer square root and subtract a_7 through a_2; subtract a_0; restore exactly +the recorded byte length; run the unchanged cycle inverse. No plaintext copy, +encoder trace or sender callback is available to recovery. + +This is scalar algebra on an exact integer representation of an already-native +cycle record. It does not redefine UCNS geometry, replace sequence/occurrence +construction, reintroduce per-bit circles, alter prime routes, or import a +conventional cryptosystem. It is an explicit candidate for the previously missing +binding relation, not a ratified answer to Q2/Q7/Q12. + +## Falsification criterion and attack + +Required: public-only sending, exact private recovery, and failure of attempted +public-only recovery. Wrong/missing private input must be rejected by the private +API, but that refusal never establishes private necessity. + +Preregistered attack: coefficient decomposition. For degree d, coefficient +`[x^(d-1)]F = d*a_2` reveals the first hidden displacement. Substitute +`x = z-a_2`; all odd powers vanish. Replace `z^2` with a new variable and repeat +on the degree-d/2 polynomial. This peels all six displacements using **only the +public key**. Then perform exact recovery without the private material. + +Prediction: this candidate is FALSIFIED by that attack. The implementation must +nevertheless demonstrate the actual public sender/private receiver composition +and produce the counterexample. A successful public attack is not recast as an +interface success or a failure of intended Weave. If decomposition instead fails, +report the failure and preserve the exact case; do not infer security. + +The full private material need not be recovered: an equivalent inverse is enough +to falsify recipient-private necessity. The effective hidden inverse space is at +most 512^6 choices; the larger material origin does not confer additional inverse +entropy. The algebraic attack avoids enumerating that space. + +## Artifacts, boundaries and usage + +`keygen(profile, corpus, sources)` returns public and private serialized bytes. +The public artifact has two native complete turns plus all public evaluation +coefficients; the private artifact has the original private material. `send` takes +no private argument. `recover` requires private material. `public_recover` performs +the coefficient attack directly, without calling `recover` or `keygen`. ```sh +cd research/weave python private_public.py --sources /checkouts python private_public.py --sources /checkouts --require-distinction WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_private_public.py -v WEAVE_SOURCES=/checkouts python test.py --receipt /tmp/weave-check.json ``` -The first command emits a source-bound JSON report and exits zero when the -experiment executes. The second emits the same report and exits **1** because -the requested distinction is not established. Infrastructure/admission errors -exit **2**, separately from a scientific counterexample. The demo uses disclosed -test data, not user secrets. Reports contain no private artifact or plaintext. - -Programmatic use: `public, private = partition(profile, corpus, circle=1)`; -`send(message, public, sources)` refuses the unresolved projection; -`recover(packet, public, private, sources)` uses the admitted private profile. -`full_profile_control(public, private)` explicitly constructs the overdisclosed -control; `public_recover(packet, control, sources)` supplies the real counterexample. - -Observed execution is retained in [evidence/private-public-v1.json](evidence/private-public-v1.json) -with source hashes and full artifact byte counts. The complete Weave suite passed -196 tests (176 existing and 20 new), with zero failures, errors or skips; source -snapshot SHA-256 `1d71264a9e61b76289a77deb301f0f1916b67fcfb7920207bae01937aa87d0cb`. -The separate distinction gate exited 1 as specified. This is evidence of a working -experiment with an unclosed result, not an asymmetric implementation. - -Governance consulted: skill-lib `38c64332b840b2bbe1c07e53aeee8996644548e9` -(`the-interdependency`, `action-calibration`, `meta-module-build`, `msdmd`, -`test-build`, `ratios`); METAPAT `86415a5368c1a1417c2b6731f19967a6b1fce6bb` -axioms, postulates and domain restraint. Semantic/geometry resemblance does not -transfer cryptographic evidence. This is consultation provenance, not a runtime -dependency or change to Stack's pinned authorities. +The first command emits observed evidence. The second exits 1 on a successful +public recovery attack; malformed inputs/infrastructure errors exit 2. Regression +success does not mean the candidate survived. The demo uses explicitly disclosed +fixture material for reproducibility; ordinary keygen uses fresh system randomness. + +Sender, private receiver and public attacker run as separate fresh processes in +an allowlisted source bundle, with only their declared serialized inputs. This is +an information-flow test, not an OS sandbox. All packet/key bytes are counted. +Limits: 256 plaintext bytes, at most three existing cycle rounds, a 16 KiB cycle +round budget, and six algebraic layers. Source integrity and exact-square checks +are not authentication. No network or production integration is introduced. + +Resource preflight: degree-64 polynomial arithmetic, six integer square roots and +a finite existing regression suite fit the available CPU/memory/disk. Stop at the +exact construction/recovery/attack results, not an invented elapsed-time cutoff. +Rollback: remove this candidate, tests and CI links; merged-cycle code and source +pins remain unchanged. A surviving candidate would proceed to additional attacks; +a failed one remains scoped evidence and is not enabled as encryption. + +Observed result: [evidence/private-public-v1.json](evidence/private-public-v1.json) +records public-only sending, exact private recovery and exact public recovery by +coefficient decomposition: **FALSIFIED for this candidate**. The demo counts +5,904 inner-cycle bytes, 377,885 packet bytes, 8,091 public-artifact bytes and 572 +private-artifact bytes. No compression or fixed doubling is claimed. All 196 +Weave tests passed with zero failures/errors/skips; suite source SHA-256 +`b43b7329099175e54d0ac2eb7a6850bbe870e7ddd898027243fb6e6fa609a0f3`. +The three-process test also runs with fresh system-random private material. + +Domain claim: `weave.research.native-shift-polynomial/v1` is a provisional Stack +scalar-binding experiment. “Private gonol” here denotes the source-bearing eight- +state candidate described above, not canonical UCNS general gonol completion or +an established cryptographic primitive. No term collision licenses either claim. +Consulted skill-lib `38c64332b840b2bbe1c07e53aeee8996644548e9` and METAPAT +`86415a5368c1a1417c2b6731f19967a6b1fce6bb`; their authority does not establish +cryptographic validity. ## hmmm -The missing construction is an explicit `PublicEval(public, message, sender_state)` -and `PrivateRecover(private_gonol, packet)` relation whose sender needs no hidden -recipient inputs and whose public description does not simply supply the existing -inverse. The current experiment exposes that boundary; it does not close it. -Computational recovery advantage, randomness, authentication and replay remain open. +The candidate is expected to fail. A surviving native asymmetric relation, +confidentiality, authentication, replay handling and the broader private-corpus +binding remain open. Failure of this explicit algebraic candidate says nothing +about every possible private/public gonol construction. diff --git a/research/weave/QUESTIONS.md b/research/weave/QUESTIONS.md index b1b38a6f..bd338a66 100644 --- a/research/weave/QUESTIONS.md +++ b/research/weave/QUESTIONS.md @@ -49,13 +49,14 @@ and inverse, failures, budgets and size accounting are in `CYCLE.md`. ## Usage -The input-partition experiment in [PRIVATE_PUBLIC.md](PRIVATE_PUBLIC.md) now makes -Q7's acceptance boundary executable. The whole-plus-one projection leaves the -current sender blocked; the explicitly overdisclosed full-profile control allows -exact public recovery. These are scoped control results, not an implementation or -falsification of the intended private-gonol/public law. Run +The proposed native-shift polynomial relation in [PRIVATE_PUBLIC.md](PRIVATE_PUBLIC.md) +now constructs a source-bearing private eight-state candidate, publishes its two +public native states and expanded evaluation law, and executes public-only sending +and exact private recovery over the real cycle. Public coefficient decomposition +also recovers exactly: this candidate is FALSIFIED for private-state necessity. +This is a result about the specified candidate, not intended Weave as a whole. Run `python private_public.py --sources /checkouts --require-distinction`; exit 1 -records that the required distinction remains unestablished. +records the actual successful public-recovery attack. Run `python cycle.py demo --sources /checkouts`. Change supported operating choices through an explicit profile; do not silently change a named profile's meaning. diff --git a/research/weave/README.md b/research/weave/README.md index 2ac3c69f..f59908aa 100644 --- a/research/weave/README.md +++ b/research/weave/README.md @@ -22,12 +22,14 @@ accounting, budgets and exact source lock. These choices are not universal laws. [The numeral layer](NUMERAL_CONSTRUCTION.md) separately records length-bearing integers, Unicode references and exact prime recipes. -[The private/public experiment](PRIVATE_PUBLIC.md) separates whole-plus-one public -inputs from the full private reconstruction profile and tests the sender boundary -in fresh processes. It reports the missing public operation as BLOCKED. Its -full-profile control produces an exact public-recovery counterexample; this does -not falsify the intended Weave relation. Run `python private_public.py --sources -/checkouts --require-distinction` to observe the still-failing acceptance gate. +[The private/public candidate](PRIVATE_PUBLIC.md) derives an expanded public +polynomial from private native circle displacements. A public-only sender evaluates +it over the real cycle record; the recipient reverses exact square roots using +private native state. A public coefficient-decomposition attack also recovers +exactly, falsifying this candidate's private necessity. Run +`python private_public.py --sources /checkouts --require-distinction` to reproduce +the attack and failing acceptance gate. The earlier input-partition-only probe +has been replaced; no candidate failure transfers to all intended Weave relations. `native_binary.py` is the Stack-owned binary-origin/sequence candidate, consuming actual source-verified UCNS `AxisCirclePosition` and `NativeMobiusState` objects. diff --git a/research/weave/evidence/private-public-v1.json b/research/weave/evidence/private-public-v1.json index bf66132c..01acc899 100644 --- a/research/weave/evidence/private-public-v1.json +++ b/research/weave/evidence/private-public-v1.json @@ -1,28 +1,20 @@ { "accounting": { - "full_profile_control_bytes": 1563, + "inner_cycle_bytes": 5904, "message_bytes": 14, - "packet_bytes": 5904, - "private_bytes": 984, - "public_bytes": 1537 + "packet_bytes": 377885, + "private_bytes": 572, + "public_bytes": 8091 }, - "construction": "INPUT_PARTITION_ONLY", + "attack": "exact public coefficient decomposition", "distinction_established": false, - "execution": "COMPLETED", - "full_profile_control": { - "public_only_exact": true, - "public_sender_completed": true, - "recipient_exact": true, - "status": "FALSIFIED", - "whole_plus_one_preserved": false - }, - "hmmm": "Native private-gonol constructor, public evaluation and private recovery law remain undefined.", + "equivalent_inverse_recovered": true, + "hmmm": "A surviving asymmetric relation and cryptographic security remain unestablished.", "inputs": { "attacker": [ "packet", "public" ], - "corpus": "actual bytes inside public artifact", "recipient": [ "packet", "public", @@ -37,14 +29,16 @@ "locked native sources" ] }, + "law": "weave.native-shift-polynomial/v1", "native_lock_sha256": "cdf11526ca93a8627eea036d7e56ac630d4be04ccb1fe14ba5973966b19d6f43", - "nonclaim": "No falsification of intended Weave, no trapdoor or cryptographic security claim.", - "packet_sha256": "2d2d20d9beb8824664184e293795884cfb3ac7b60adefd0e5990cf539960879c", - "projected_public_recovery": { - "scope": "control packet; API refusal is not private necessity", - "status": "BLOCKED" - }, - "schema": "weave.private-public-experiment/v1", + "packet_sha256": "9b2c6593ea118d36ca722a4d3c827fbace309c677fe90485061209635299430c", + "private_material_given_to_attacker": false, + "private_recovery_exact": true, + "public_recovery_exact": true, + "public_sender_completed": true, + "public_sha256": "5ea45180a952ea9d52ce7d6eabd5d523e82045c8bae58d7c5ed388a88af5aceb", + "schema": "weave.private-public-experiment/v2", + "scope": "This candidate only; no verdict on all native private/public relations.", "source_files": { "CYCLE_NATIVE.json": "cdf11526ca93a8627eea036d7e56ac630d4be04ccb1fe14ba5973966b19d6f43", "affixiation.py": "15518004e43873515badefd951d6f32a0de79ce8002d3aebd41921bf8d7eb985", @@ -55,14 +49,9 @@ "native_binary.py": "3843b4fb7c735c2d3d45f28a13905397a4dd8ff988fba91b3fe1f92e75103e4d", "numeral.py": "a3c2b67611b095f65ba4b355fac2275735a062fb76ba2170790e64d8b434f9e3", "prime_schedule.py": "c5b59833d236c1f7dfe66102052e1796e8532a25f299820c68e2b417c6678242", - "private_public.py": "1e928325776ada4657a3803e703db195c56c6e5b913d6eb23f83845d3ce87248", + "private_public.py": "6619e1982d166748a61cc9448033f96dc0787b8bf32d41f0c37ebe3169ca665b", "safe_output.py": "5bdfc6ead9f7eb9341d3de669ece63cdf653e1e8ba7882b334360cbafad8b052" }, - "source_sha256": "e7d8613f0cd6cdcbe2d3bbab9a9c61ee94f4d746777b31dc8851cab535b8ec97", - "whole_plus_one": { - "packet_created_by_public_sender": false, - "private_gonol_relation": "UNIMPLEMENTED", - "reason": "six spaces per round are private; native public evaluation law missing", - "status": "BLOCKED" - } + "source_sha256": "496a529002bc66f82189cf64d5f866c0241f22304325722b6a34f67e936f08c2", + "status": "FALSIFIED" } diff --git a/research/weave/private_public.py b/research/weave/private_public.py index 3feb5539..e0cb0b1b 100644 --- a/research/weave/private_public.py +++ b/research/weave/private_public.py @@ -1,56 +1,58 @@ -# ratios: loc_comments=229:55 imports_exports=14:8 calls_definitions=121:16 +# ratios: loc_comments=271:50 imports_exports=16:7 calls_definitions=157:19 # === MODULE_BUILD === -# id: weave_private_public_boundary_experiment +# id: weave_native_shift_polynomial # module_name: private_public # module_kind: experiment -# summary: falsifiable public/private input boundary on the unchanged native sequence cycle +# summary: proposed public polynomial evaluation and native-private exact-root recovery after the unchanged Weave cycle # owner: Erin Spencer -# public_surface: partition, send, recover, public_recover, full_profile_control, experiment -# internal_surface: strict serialized artifacts and fresh-process evidence +# public_surface: keygen, send, recover, public_recover, decompose, experiment +# internal_surface: exact scalar binding, serialized artifacts and fresh-process witness # auth_boundary: none # storage_boundary: write # network_boundary: none # user_data_boundary: read # admin_only: false # tests: tests/test_private_public.py -# rollout: explicit experiment; BLOCKED is not asymmetric success -# rollback: remove experiment and its tests, docs and CI invocation -# unresolved: native private-gonol generation and public evaluation law +# rollout: research CLI only; public recovery falsifies this candidate +# rollback: remove candidate and its tests, evidence and links +# unresolved: surviving asymmetry, confidentiality, authentication and replay # === END MODULE_BUILD === # === CONTRACTS === -# id: weave_public_artifact_boundary -# given: a full cycle profile and actual corpus -# then: whole-plus-one public export carries exactly two spaces per round and no private reference -# id: weave_sender_no_private_fallback -# given: only the whole-plus-one projection -# then: refuse the undefined public operation before cycle execution rather than guessing hidden spaces -# id: weave_private_reconstruction_inputs -# given: a packet and the matching public and private reconstruction artifacts -# then: recover exactly using only those inputs and locked source code -# id: weave_public_recovery_falsifier -# given: full-profile public control and its packet -# then: execute public-only recovery and report exact recovery as a counterexample to private necessity -# id: weave_process_evidence_boundary -# given: an experiment run -# then: isolate sender and recovery invocations in fresh processes with explicit input and source manifests -# id: weave_distinction_gate -# given: a blocked public sender or an exact public recovery counterexample -# then: the requested distinction remains unaccepted and its acceptance command exits nonzero +# id: native_private_key_relation +# given: private source material and the exact native geometry +# then: eight complete native states determine two public displacements and six privately factored operations +# id: public_sender_evaluation +# given: public artifact and message only +# then: evaluate the published polynomial on the complete real Weave cycle record without recipient-private input +# id: native_private_exact_recovery +# given: matching private material, public artifact and packet +# then: native-derived inverse operations recover the exact cycle record and original bytes +# id: public_inverse_falsification +# given: published coefficients and packet only +# then: test coefficient decomposition and report exact public recovery as candidate falsification +# id: candidate_artifact_admission +# given: malformed, oversized or mismatched data +# then: reject without inventing keys, accepting approximate roots or returning partial plaintext +# id: candidate_process_boundary +# given: a research experiment +# then: sender, private receiver and public attack run in fresh processes with only their declared inputs # === END CONTRACTS === """Usage: python private_public.py --sources /checkouts [--require-distinction]. -PRIVATE_PUBLIC.md freezes scope and acceptance criteria. This is an executable -boundary experiment, not a key generator. A private-argument check cannot prove -private-state necessity. The full-profile control deliberately tests that mistake. +PRIVATE_PUBLIC.md defines the proposed law, geometry boundary and algebraic attack. +This is a candidate construction, not a security implementation. The old input- +partition-only experiment is replaced, not advertised as completing this work. """ from __future__ import annotations import argparse -from copy import deepcopy +from fractions import Fraction from hashlib import sha256 +from math import comb, isqrt from pathlib import Path import os import re +import secrets import subprocess import sys import tempfile @@ -58,30 +60,25 @@ import cycle from cycle import Profile, CycleLimits, canonical, strict_json from cycle_native import load_native -from numeral import Refused +from numeral import Refused, _Reader, _uint, _blob ROOT = Path(__file__).resolve().parent -PUBLIC_SCHEMA = 'weave.public-cycle-input/v1' -PRIVATE_SCHEMA = 'weave.private-cycle-input/v1' -PROJECTION = 'whole-plus-one' -CONTROL = 'full-profile-control' -LIMITS = CycleLimits(input_bytes=256, round_bytes=131072, rounds=3) -ARTIFACT_BYTES = 32768 +LAW = 'weave.native-shift-polynomial/v1' +MAGIC = b'WPP\x01' +LIMITS = CycleLimits(input_bytes=256, round_bytes=16384, rounds=3) +MAX_RECORD = LIMITS.round_bytes + 128 +MAX_BOUND_BITS = 64 * (8 * MAX_RECORD + 16) + 16 +MAX_WIRE = MAX_BOUND_BITS // 8 + 256 RUNTIME = ('private_public.py', 'cycle.py', 'cycle_native.py', 'native_binary.py', - 'numeral.py', 'affixiation.py', 'prime_schedule.py', 'safe_output.py', - 'CYCLE_NATIVE.json') - - -class MissingPublicRelation(Refused): - """Q7 is blocked by a missing native operation, not a proven secret.""" + 'numeral.py', 'affixiation.py', 'prime_schedule.py', 'safe_output.py', 'CYCLE_NATIVE.json') def _object(data, fields): - if type(data) is not bytes or len(data) > ARTIFACT_BYTES: - raise Refused('bounded serialized artifact bytes required') + if type(data) is not bytes or len(data) > 65536: + raise Refused('bounded serialized artifact required') obj = strict_json(data) - if type(obj) is not dict or set(obj) != set(fields): - raise Refused('artifact fields do not match schema') + if type(obj) is not dict or set(obj) != set(fields) or canonical(obj) != data: + raise Refused('exact canonical artifact schema required') return obj @@ -92,134 +89,202 @@ def _hex(value, maximum): return bytes.fromhex(value) -def _projection(profile, circle): - obj = Profile.as_dict(profile) - for row in obj['rounds']: - row['spaces'] = [value if i in (0, circle) else None - for i, value in enumerate(row['spaces'])] - return obj +def _native(material, sources): + if type(material) is not bytes or len(material) != 256: + raise Refused('exactly 256 private material bytes required') + api, geometry = load_native(sources) + origin = api.ByteOrigin(material, LAW + '/key-origin', 0, geometry) + turns = [] + for i in range(8): + axis = origin.byte_axis(material[i]) + state = api.Geometry.placed(geometry, axis, Fraction(-(material[8+i] & 1))) + turns.append(api.Geometry.lift(geometry, state)) + return origin.identity, tuple(turns) + + +def _offsets(turns): + return tuple(1 + int(t * 256) for t in turns) + + +def _expand(shifts): + coefficients = [0, 1] + for shift in shifts: + coefficients[0] += shift + squared = [0] * (2 * len(coefficients) - 1) + for i, a in enumerate(coefficients): + for j, b in enumerate(coefficients): + squared[i+j] += a*b + coefficients = squared + return coefficients + + +def _evaluate(coefficients, x): + value = 0 + for coefficient in reversed(coefficients): + value = value*x + coefficient + return value def _public(data): - obj = _object(data, ('schema', 'disclosure', 'circle', 'profile', + obj = _object(data, ('law', 'key_origin', 'public_turns', 'coefficients', 'profile', 'corpus_hex', 'native_lock_sha256')) - if (obj['schema'] != PUBLIC_SCHEMA or type(obj['disclosure']) is not str - or obj['disclosure'] not in (PROJECTION, CONTROL) - or type(obj['circle']) is not int or not 1 <= obj['circle'] <= 7): - raise Refused('unsupported public artifact') - if obj['native_lock_sha256'] != sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).hexdigest(): - raise Refused('native lock identity mismatch') + if obj['law'] != LAW or obj['native_lock_sha256'] != sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).hexdigest(): + raise Refused('law or native source identity mismatch') + if len(_hex(obj['key_origin'], 32)) != 32: + raise Refused('key origin identity required') + rows = obj['public_turns'] + if type(rows) is not list or len(rows) != 2: + raise Refused('exactly whole-plus-one native complete turns required') + turns = [] + for row in rows: + if (type(row) is not list or len(row) != 2 or any(type(v) is not int for v in row) + or not 1 <= row[1] <= 256 or not 0 <= row[0] < 2*row[1]): + raise Refused('invalid complete turn') + t = Fraction(*row) + if [t.numerator, t.denominator] != row or (t*256).denominator != 1: + raise Refused('turn must be a canonical native key-axis position') + turns.append(t) + c = obj['coefficients'] + if (type(c) is not list or len(c) != 65 or c[-1] != 1 + or any(type(v) is not int or v < 0 or v.bit_length() > 4096 for v in c)): + raise Refused('bounded exact monic degree-64 public polynomial required') + profile = Profile.read(canonical(obj['profile']), LIMITS) corpus = _hex(obj['corpus_hex'], 4096) if not corpus: raise Refused('actual nonempty public corpus required') - profile = deepcopy(obj['profile']) - if type(profile) is not dict or type(profile.get('rounds')) is not list: - raise Refused('profile round list required') - for row in profile['rounds']: - if (type(row) is not dict or type(row.get('spaces')) is not list - or len(row['spaces']) != 8): - raise Refused('eight space positions required') - for i, value in enumerate(row['spaces']): - hidden = obj['disclosure'] == PROJECTION and i not in (0, obj['circle']) - if hidden: - if value is not None: - raise Refused('private space leaked into public projection') - # Syntax validation only. Never return this placeholder profile. - row['spaces'][i] = [0, 1] - elif value is None: - raise Refused('required public space absent') - Profile.read(canonical(profile), LIMITS) - return obj, corpus - - -def _complete(public): - obj, corpus = _public(public) - if obj['disclosure'] != CONTROL: - try: - Profile.read(canonical(obj['profile']), LIMITS) - except Refused as exc: - raise MissingPublicRelation('six spaces per round are private; native public evaluation law missing') from exc - raise RuntimeError('cycle now admits partial profiles; reassess the experiment') - return Profile.read(canonical(obj['profile']), LIMITS), corpus - - -def partition(profile: Profile, corpus: bytes, *, circle: int = 1) -> tuple[bytes, bytes]: - """Export reconstruction inputs; no entropy, private gonol or trapdoor invented.""" + return obj, profile, corpus, _offsets(turns) + + +def keygen(profile: Profile, corpus: bytes, sources: str | Path, *, material: bytes | None = None): + """Construct this candidate's native private configuration and public law.""" + if material is None: + material = secrets.token_bytes(256) + origin, turns = _native(material, sources) if type(corpus) is not bytes: raise Refused('actual corpus bytes required') - obj = {'schema': PUBLIC_SCHEMA, 'disclosure': PROJECTION, 'circle': circle, - 'profile': _projection(profile, circle), 'corpus_hex': corpus.hex(), - 'native_lock_sha256': sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).hexdigest()} - public = canonical(obj) + public = canonical({'law': LAW, 'key_origin': origin, + 'public_turns': [[t.numerator, t.denominator] for t in turns[:2]], + 'coefficients': _expand(_offsets(turns)[2:]), + 'profile': Profile.as_dict(profile), 'corpus_hex': corpus.hex(), + 'native_lock_sha256': sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).hexdigest()}) _public(public) - private = canonical({'schema': PRIVATE_SCHEMA, 'public_sha256': sha256(public).hexdigest(), - 'profile': Profile.as_dict(profile)}) - _private(public, private) + private = canonical({'law': LAW, 'material_hex': material.hex()}) return public, private -def _private(public, private): - pub, corpus = _public(public) - obj = _object(private, ('schema', 'public_sha256', 'profile')) - if obj['schema'] != PRIVATE_SCHEMA or obj['public_sha256'] != sha256(public).hexdigest(): - raise Refused('private artifact does not bind these exact public bytes') - profile = Profile.read(canonical(obj['profile']), LIMITS) - projected = (_projection(profile, pub['circle']) if pub['disclosure'] == PROJECTION - else Profile.as_dict(profile)) - if canonical(projected) != canonical(pub['profile']): - raise Refused('private profile disagrees with public projection') - return profile, corpus - - -def full_profile_control(public: bytes, private: bytes) -> bytes: - """Publish all reconstruction fields explicitly as a negative control.""" - profile, _ = _private(public, private) - obj, _ = _public(public) - obj['profile'] = Profile.as_dict(profile) - obj['disclosure'] = CONTROL - result = canonical(obj) - _public(result) - return result - - def send(message: bytes, public: bytes, sources: str | Path) -> bytes: - """Public inputs only. A missing native law blocks before any forward work.""" - profile, corpus = _complete(public) - return cycle.forward(message, corpus, profile, sources, LIMITS)[0] + """Evaluate the expanded public law; no private material or keygen call.""" + obj, profile, corpus, (whole, one) = _public(public) + record = cycle.forward(message, corpus, profile, sources, LIMITS)[0] + x = int.from_bytes(record, 'big') + y = _evaluate(obj['coefficients'], x + whole) + one + if y.bit_length() > MAX_BOUND_BITS: + raise Refused('public evaluation exceeds bound integer budget') + encoded = y.to_bytes((y.bit_length()+7)//8, 'big') + return MAGIC + sha256(public).digest() + _uint(len(record)) + _blob(encoded) + + +def _packet(packet, public): + if type(packet) is not bytes or len(packet) > MAX_WIRE: + raise Refused('bounded packet bytes required') + reader = _Reader(packet) + if reader.take(4) != MAGIC or reader.take(32) != sha256(public).digest(): + raise Refused('packet law/key identity mismatch') + length = reader.uint() + if not 1 <= length <= MAX_RECORD: + raise Refused('cycle record length outside budget') + encoded = reader.blob(MAX_WIRE) + if reader.pos != len(packet) or not encoded or encoded[0] == 0: + raise Refused('noncanonical or trailing bound integer') + y = int.from_bytes(encoded, 'big') + if y.bit_length() > MAX_BOUND_BITS: + raise Refused('bound integer exceeds bit budget') + return length, y + + +def _invert(y, shifts): + for shift in reversed(shifts): + if y < 0: + raise Refused('negative inverse state') + root = isqrt(y) + if root*root != y: + raise Refused('non-exact square in private relation') + y = root - shift + return y + + +def _restore(packet, public, shifts, sources): + _, profile, corpus, (whole, one) = _public(public) + length, y = _packet(packet, public) + x = _invert(y-one, shifts) - whole + if x < 0 or x.bit_length() > length*8: + raise Refused('inverse does not fit declared record length') + return cycle.reverse(x.to_bytes(length, 'big'), corpus, profile, sources, LIMITS) def recover(packet: bytes, public: bytes, private: bytes, sources: str | Path) -> bytes: - """Private-path exact recovery; accepting a private argument is not asymmetry.""" - profile, corpus = _private(public, private) - return cycle.reverse(packet, corpus, profile, sources, LIMITS) + """Use actual private native states for exact square-root recovery.""" + _, profile, corpus, _ = _public(public) + secret = _object(private, ('law', 'material_hex')) + if secret['law'] != LAW: + raise Refused('private law mismatch') + material = _hex(secret['material_hex'], 256) + rebuilt, _ = keygen(profile, corpus, sources, material=material) + if rebuilt != public: + raise Refused('private material belongs to a different key') + _, turns = _native(material, sources) + return _restore(packet, public, _offsets(turns)[2:], sources) + + +def decompose(coefficients): + """Public algebraic attack: peel inner native displacements from coefficients.""" + if (type(coefficients) not in (list, tuple) or len(coefficients) != 65 + or any(type(v) is not int or v < 0 or v.bit_length() > 4096 for v in coefficients)): + raise Refused('bounded exact degree-64 coefficient list required') + p = list(coefficients) + shifts = [] + for _ in range(6): + degree = len(p)-1 + if degree < 2 or p[-1] != 1 or p[-2] % degree: + raise Refused('polynomial does not admit the candidate decomposition') + shift = p[-2] // degree + if not 1 <= shift <= 512: + raise Refused('recovered displacement outside native candidate domain') + # F(z-shift) = H(z^2). Exact cancellation, no numeric root estimates. + translated = [sum(p[j]*comb(j,i)*(-shift)**(j-i) for j in range(i,degree+1)) + for i in range(degree+1)] + if any(translated[1::2]): + raise Refused('public polynomial has nonzero odd residuals') + p = translated[::2] + shifts.append(shift) + if p != [0, 1]: + raise Refused('public decomposition did not end at identity') + return tuple(shifts) def public_recover(packet: bytes, public: bytes, sources: str | Path) -> bytes: - """Actual public inverse attack, not a call to the private-path wrapper.""" - profile, corpus = _complete(public) - return cycle.reverse(packet, corpus, profile, sources, LIMITS) + """Recover via public coefficients alone; never consume the private artifact.""" + obj, _, _, _ = _public(public) + return _restore(packet, public, decompose(obj['coefficients']), sources) def _worker(): - request = strict_json(sys.stdin.buffer.read(1048577)) + request = strict_json(sys.stdin.buffer.read(2*MAX_WIRE+262145)) if type(request) is not dict or request.get('operation') not in ('send', 'recover', 'public-recover'): raise Refused('unknown worker operation') operation = request['operation'] fields = {'operation', 'public', 'data'} | ({'private'} if operation == 'recover' else set()) if set(request) != fields: raise Refused('worker input manifest mismatch') - public = _hex(request['public'], ARTIFACT_BYTES) - data = _hex(request['data'], LIMITS.round_bytes + 128) - try: - if operation == 'send': - result = send(data, public, ROOT/'inputs') - elif operation == 'public-recover': - result = public_recover(data, public, ROOT/'inputs') - else: - result = recover(data, public, _hex(request['private'], ARTIFACT_BYTES), ROOT/'inputs') - except MissingPublicRelation as exc: - return {'status': 'BLOCKED', 'reason': str(exc)} - return {'status': 'COMPLETED', 'data': result.hex()} + public = _hex(request['public'], 65536) + data = _hex(request['data'], MAX_WIRE) + if operation == 'send': + result = send(data, public, ROOT/'inputs') + elif operation == 'public-recover': + result = public_recover(data, public, ROOT/'inputs') + else: + result = recover(data, public, _hex(request['private'], 65536), ROOT/'inputs') + return {'data': result.hex()} def _run(root, operation, public, data, private=None): @@ -232,21 +297,15 @@ def _run(root, operation, public, data, private=None): if result.returncode: raise RuntimeError(f'{operation} worker failed: {result.stderr.decode(errors="replace")}') response = strict_json(result.stdout) - if type(response) is not dict or response.get('status') not in ('BLOCKED', 'COMPLETED'): + if type(response) is not dict or set(response) != {'data'}: raise RuntimeError('invalid worker response') - return response - - -def experiment(message: bytes, profile: Profile, corpus: bytes, sources: str | Path) -> dict: - """Run a bounded input-partition probe; return counts/identities, not secrets.""" - if type(message) is not bytes or len(message) > LIMITS.input_bytes: - raise Refused('experiment message must be at most 256 bytes') - public, private = partition(profile, corpus) - control = full_profile_control(public, private) - # Verify required sources first: missing geometry is an infrastructure error, - # never evidence that an attacker cannot recover. - load_native(sources) - with tempfile.TemporaryDirectory(prefix='weave-public-private-') as directory: + return _hex(response['data'], MAX_WIRE) + + +def experiment(message, profile, corpus, sources, *, material=None): + """Execute the actual candidate and its public inverse attack, with scoped evidence.""" + public, private = keygen(profile, corpus, sources, material=material) + with tempfile.TemporaryDirectory(prefix='weave-native-key-') as directory: root = Path(directory) for name in RUNTIME: (root/name).write_bytes((ROOT/name).read_bytes()) @@ -255,49 +314,32 @@ def experiment(message: bytes, profile: Profile, corpus: bytes, sources: str | P target = root/'inputs'/'ucns'/name target.parent.mkdir(parents=True, exist_ok=True) target.write_bytes((Path(sources)/'ucns'/name).read_bytes()) - source_files = {str(p.relative_to(root)): sha256(p.read_bytes()).hexdigest() - for p in sorted(root.rglob('*')) if p.is_file()} - # Public evaluation is tried before the private recovery input is sent to - # any process. No profile fixture or private file exists in the bundle. - candidate = _run(root, 'send', public, message) - sent = _run(root, 'send', control, message) - if sent['status'] != 'COMPLETED': - raise RuntimeError('full-profile control did not produce a packet') - packet = _hex(sent['data'], LIMITS.round_bytes + 128) - recipient = _run(root, 'recover', public, packet, private) - projected_attack = _run(root, 'public-recover', public, packet) - control_attack = _run(root, 'public-recover', control, packet) - after = {str(p.relative_to(root)): sha256(p.read_bytes()).hexdigest() - for p in sorted(root.rglob('*')) if p.is_file()} - if source_files != after: - raise RuntimeError('worker bundle changed during the experiment') - recipient_exact = recipient['status'] == 'COMPLETED' and _hex(recipient['data'], LIMITS.input_bytes) == message - attack_exact = control_attack['status'] == 'COMPLETED' and _hex(control_attack['data'], LIMITS.input_bytes) == message - if not recipient_exact or not attack_exact or candidate['status'] != 'BLOCKED' or projected_attack['status'] != 'BLOCKED': - raise RuntimeError('frozen cycle control behavior changed; reassess the experiment') - return { - 'schema': 'weave.private-public-experiment/v1', 'execution': 'COMPLETED', - 'construction': 'INPUT_PARTITION_ONLY', 'distinction_established': False, - 'whole_plus_one': {'status': candidate['status'], 'reason': candidate['reason'], - 'packet_created_by_public_sender': False, - 'private_gonol_relation': 'UNIMPLEMENTED'}, - 'full_profile_control': {'status': 'FALSIFIED', 'public_sender_completed': True, - 'recipient_exact': recipient_exact, 'public_only_exact': attack_exact, - 'whole_plus_one_preserved': False}, - 'projected_public_recovery': {'status': projected_attack['status'], - 'scope': 'control packet; API refusal is not private necessity'}, - 'inputs': {'sender': ['message', 'public'], 'recipient': ['packet', 'public', 'private'], - 'attacker': ['packet', 'public'], 'shared': ['exact code', 'locked native sources'], - 'corpus': 'actual bytes inside public artifact'}, - 'accounting': {'message_bytes': len(message), 'packet_bytes': len(packet), - 'public_bytes': len(public), 'private_bytes': len(private), - 'full_profile_control_bytes': len(control)}, - 'source_files': source_files, - 'source_sha256': sha256(canonical(source_files)).hexdigest(), - 'native_lock_sha256': sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).hexdigest(), - 'packet_sha256': sha256(packet).hexdigest(), - 'nonclaim': 'No falsification of intended Weave, no trapdoor or cryptographic security claim.', - 'hmmm': 'Native private-gonol constructor, public evaluation and private recovery law remain undefined.'} + files = lambda: {str(p.relative_to(root)): sha256(p.read_bytes()).hexdigest() + for p in sorted(root.rglob('*')) if p.is_file()} + source_files = files() + packet = _run(root, 'send', public, message) + restored = _run(root, 'recover', public, packet, private) + attacked = _run(root, 'public-recover', public, packet) + if files() != source_files: + raise RuntimeError('worker bundle changed during execution') + if restored != message: + raise RuntimeError('candidate private recovery failed') + exact = attacked == message + return {'schema': 'weave.private-public-experiment/v2', 'law': LAW, + 'status': 'FALSIFIED' if exact else 'UNRESOLVED', 'distinction_established': False, + 'public_sender_completed': True, 'private_recovery_exact': True, + 'public_recovery_exact': exact, 'attack': 'exact public coefficient decomposition', + 'private_material_given_to_attacker': False, 'equivalent_inverse_recovered': exact, + 'inputs': {'sender': ['message', 'public'], 'recipient': ['packet', 'public', 'private'], + 'attacker': ['packet', 'public'], 'shared': ['exact code', 'locked native sources']}, + 'accounting': {'message_bytes': len(message), 'packet_bytes': len(packet), + 'public_bytes': len(public), 'private_bytes': len(private), + 'inner_cycle_bytes': _packet(packet, public)[0]}, + 'source_files': source_files, 'source_sha256': sha256(canonical(source_files)).hexdigest(), + 'public_sha256': sha256(public).hexdigest(), 'packet_sha256': sha256(packet).hexdigest(), + 'native_lock_sha256': sha256((ROOT/'CYCLE_NATIVE.json').read_bytes()).hexdigest(), + 'scope': 'This candidate only; no verdict on all native private/public relations.', + 'hmmm': 'A surviving asymmetric relation and cryptographic security remain unestablished.'} def main(): @@ -311,7 +353,8 @@ def main(): result = _worker() else: profile = Profile.read((ROOT/'profiles'/'cycle-v1.json').read_bytes(), LIMITS) - result = experiment(b'ABxABy\x00ABxABy\xff', profile, bytes(range(256)), args.sources) + result = experiment(b'ABxABy\x00ABxABy\xff', profile, bytes(range(256)), args.sources, + material=bytes((i*73+41)%256 for i in range(256))) print(canonical(result).decode()) return 1 if args.require_distinction and not result.get('distinction_established', False) else 0 except (Refused, OSError, RuntimeError) as exc: @@ -321,4 +364,4 @@ def main(): if __name__ == '__main__': raise SystemExit(main()) -# ratios: loc_comments=229:55 imports_exports=14:8 calls_definitions=121:16 +# ratios: loc_comments=271:50 imports_exports=16:7 calls_definitions=157:19 diff --git a/research/weave/test.py b/research/weave/test.py index 17deedec..6c399510 100644 --- a/research/weave/test.py +++ b/research/weave/test.py @@ -18,7 +18,7 @@ from assembly import Pipeline ROOT = Path(__file__).resolve().parent -EXPECTED_TESTS = 196 # 176 merged-cycle regressions + 20 private/public boundary experiments. +EXPECTED_TESTS = 196 # 176 merged-cycle regressions + 20 native public/private candidate tests. def snapshot(): @@ -67,12 +67,12 @@ def main(): 'review_closure_methods': 18, 'final_findings_methods': 9, 'terminal_record_methods': 9, 'header_admission_methods': 3, 'numeral_single_replay_methods': 4, 'cheap_admission_methods': 8, - 'private_public_boundary_methods': 20 + 'private_public_candidate_methods': 20 }, 'cycle': 'EXPLICIT_CANDIDATE: corpus normalization once; native sequence affixiation and prime-derived bit interleave; exact reverse', 'full_weave': Pipeline().plan(), 'asymmetric_cipher': 'NOT_IMPLEMENTED: cycle/profile recovery is not a public/private trapdoor', - 'private_public_experiment': 'BLOCKED: whole-plus-one sender law missing; full-profile control permits public recovery', + 'private_public_experiment': 'FALSIFIED_CANDIDATE: native-shift polynomial supports public sending and private recovery, but public coefficients reveal an equivalent inverse', 'native_language_corpus_replay': 'NOT_EXECUTED: binary construction does not use or replace a language corpus', 'security_observations': [ 'The older fixed transport map remains recoverable in its declared attack experiment.', diff --git a/research/weave/tests/test_private_public.py b/research/weave/tests/test_private_public.py index f9e71ddb..f71afb17 100644 --- a/research/weave/tests/test_private_public.py +++ b/research/weave/tests/test_private_public.py @@ -1,110 +1,110 @@ -# ratios: loc_comments=184:111 imports_exports=14:1 calls_definitions=122:22 +# ratios: loc_comments=187:106 imports_exports=14:1 calls_definitions=131:22 # === CHECKS === -# id: check_private_public_exact_whole_plus_one_export_for_each_circle -# proves: weave_public_artifact_boundary -# call: self::test_exact_whole_plus_one_export_for_each_circle +# id: check_native_candidate_private_native_states_preserve_full_frame +# proves: native_private_key_relation +# call: self::test_private_native_states_preserve_full_frame # mutates: none # cleanup: none -# id: check_private_public_sender_blocks_before_forward_or_native_loading -# proves: weave_sender_no_private_fallback -# call: self::test_sender_blocks_before_forward_or_native_loading +# id: check_native_candidate_public_artifact_exposes_two_states_and_evaluation_coefficients +# proves: native_private_key_relation +# call: self::test_public_artifact_exposes_two_states_and_evaluation_coefficients # mutates: none # cleanup: none -# id: check_private_public_projected_public_recovery_refusal_is_not_security_evidence -# proves: weave_sender_no_private_fallback -# call: self::test_projected_public_recovery_refusal_is_not_security_evidence +# id: check_native_candidate_keygen_default_uses_fresh_material +# proves: native_private_key_relation +# call: self::test_keygen_default_uses_fresh_material # mutates: none # cleanup: none -# id: check_private_public_private_recovery_exact_for_binary_edge_cases -# proves: weave_private_reconstruction_inputs -# call: self::test_private_recovery_exact_for_binary_edge_cases +# id: check_native_candidate_expanded_public_law_equals_private_composition +# proves: native_private_exact_recovery +# call: self::test_expanded_public_law_equals_private_composition # mutates: none # cleanup: none -# id: check_private_public_public_only_inverse_is_a_real_counterexample -# proves: weave_public_recovery_falsifier -# call: self::test_public_only_inverse_is_a_real_counterexample +# id: check_native_candidate_sender_never_calls_private_construction_or_inverse +# proves: public_sender_evaluation +# call: self::test_sender_never_calls_private_construction_or_inverse # mutates: none # cleanup: none -# id: check_private_public_control_matches_unmodified_cycle_bytes -# proves: weave_public_recovery_falsifier -# call: self::test_control_matches_unmodified_cycle_bytes +# id: check_native_candidate_private_exact_recovery_binary_edge_cases +# proves: native_private_exact_recovery +# call: self::test_private_exact_recovery_binary_edge_cases # mutates: none # cleanup: none -# id: check_private_public_private_input_is_required_by_recipient_interface -# proves: weave_private_reconstruction_inputs -# call: self::test_private_input_is_required_by_recipient_interface +# id: check_native_candidate_every_native_state_materially_changes_binding +# proves: native_private_key_relation +# call: self::test_every_native_state_materially_changes_binding # mutates: none # cleanup: none -# id: check_private_public_wrong_hidden_private_space_fails_even_with_matching_public_projection -# proves: weave_private_reconstruction_inputs -# call: self::test_wrong_hidden_private_space_fails_even_with_matching_public_projection +# id: check_native_candidate_public_only_attack_recovers_without_private_path +# proves: public_inverse_falsification +# call: self::test_public_only_attack_recovers_without_private_path # mutates: none # cleanup: none -# id: check_private_public_wrong_pair_and_changed_visible_space_refused -# proves: weave_private_reconstruction_inputs -# call: self::test_wrong_pair_and_changed_visible_space_refused +# id: check_native_candidate_public_decomposition_recovers_equivalent_inverse_not_material +# proves: public_inverse_falsification +# call: self::test_public_decomposition_recovers_equivalent_inverse_not_material # mutates: none # cleanup: none -# id: check_private_public_private_leak_and_incomplete_control_are_refused -# proves: weave_public_artifact_boundary -# call: self::test_private_leak_and_incomplete_control_are_refused +# id: check_native_candidate_private_api_rejects_missing_and_wrong_material +# proves: candidate_artifact_admission +# call: self::test_private_api_rejects_missing_and_wrong_material # mutates: none # cleanup: none -# id: check_private_public_strict_artifact_schema_and_types -# proves: weave_public_artifact_boundary -# call: self::test_strict_artifact_schema_and_types +# id: check_native_candidate_non_exact_root_is_refused_without_rounding +# proves: candidate_artifact_admission +# call: self::test_non_exact_root_is_refused_without_rounding # mutates: none # cleanup: none -# id: check_private_public_invalid_public_profile_rejected_before_cycle -# proves: weave_public_artifact_boundary -# call: self::test_invalid_public_profile_rejected_before_cycle +# id: check_native_candidate_malformed_public_artifacts_are_refused +# proves: candidate_artifact_admission +# call: self::test_malformed_public_artifacts_are_refused # mutates: none # cleanup: none -# id: check_private_public_artifact_sizes_and_corpus_limits -# proves: weave_public_artifact_boundary -# call: self::test_artifact_sizes_and_corpus_limits +# id: check_native_candidate_packet_identity_length_and_framing_are_checked +# proves: candidate_artifact_admission +# call: self::test_packet_identity_length_and_framing_are_checked # mutates: none # cleanup: none -# id: check_private_public_corrupted_and_truncated_packet_are_not_recovery -# proves: weave_private_reconstruction_inputs -# call: self::test_corrupted_and_truncated_packet_are_not_recovery +# id: check_native_candidate_budgets_and_private_material_admission +# proves: candidate_artifact_admission +# call: self::test_budgets_and_private_material_admission # mutates: none # cleanup: none -# id: check_private_public_three_round_fresh_process_input_manifests -# proves: weave_process_evidence_boundary -# call: self::test_three_round_fresh_process_input_manifests -# mutates: filesystem -# cleanup: tempdir_teardown -# id: check_private_public_report_does_not_include_private_artifact_or_plaintext -# proves: weave_process_evidence_boundary -# call: self::test_report_does_not_include_private_artifact_or_plaintext +# id: check_native_candidate_decomposition_refuses_non_candidate_polynomial +# proves: candidate_artifact_admission +# call: self::test_decomposition_refuses_non_candidate_polynomial +# mutates: none +# cleanup: none +# id: check_native_candidate_three_round_fresh_process_construction_and_attack +# proves: candidate_process_boundary +# call: self::test_three_round_fresh_process_construction_and_attack # mutates: filesystem # cleanup: tempdir_teardown -# id: check_private_public_missing_native_source_is_error_not_attacker_failure -# proves: weave_distinction_gate -# call: self::test_missing_native_source_is_error_not_attacker_failure +# id: check_native_candidate_source_failure_is_error_not_attack_resistance +# proves: candidate_process_boundary +# call: self::test_source_failure_is_error_not_attack_resistance # mutates: none # cleanup: none -# id: check_private_public_worker_crash_is_not_public_recovery_failure -# proves: weave_distinction_gate -# call: self::test_worker_crash_is_not_public_recovery_failure +# id: check_native_candidate_worker_failure_is_error_not_attack_resistance +# proves: candidate_process_boundary +# call: self::test_worker_failure_is_error_not_attack_resistance # mutates: filesystem # cleanup: tempdir_teardown -# id: check_private_public_acceptance_cli_exits_one_with_honest_report -# proves: weave_distinction_gate -# call: self::test_acceptance_cli_exits_one_with_honest_report +# id: check_native_candidate_acceptance_command_fails_on_actual_public_recovery +# proves: public_inverse_falsification +# call: self::test_acceptance_command_fails_on_actual_public_recovery # mutates: filesystem # cleanup: tempdir_teardown -# id: check_private_public_cli_infrastructure_error_exits_two -# proves: weave_distinction_gate +# id: check_native_candidate_cli_infrastructure_error_exits_two +# proves: candidate_process_boundary # call: self::test_cli_infrastructure_error_exits_two # mutates: none # cleanup: none # === END CHECKS === """Usage: WEAVE_SOURCES=/checkouts python -m unittest discover -s tests -p test_private_public.py -v. -These regressions protect honest experimental failure. They do not claim that -the intended asymmetric/private-gonol layer has been constructed. +This suite constructs the explicit native-shift polynomial candidate and records +its public coefficient attack. Green tests mean the falsification is reproduced. """ from copy import deepcopy from dataclasses import replace @@ -112,213 +112,211 @@ from pathlib import Path import json import os +import random import subprocess import sys import unittest from unittest.mock import patch -import cycle import private_public as pp from cycle import Profile, canonical -from numeral import Refused +from numeral import Refused, _blob, _uint ROOT = Path(__file__).resolve().parents[1] SOURCES = Path(os.environ.get('WEAVE_SOURCES', ROOT/'sources')) BASE = Profile.read((ROOT/'profiles'/'cycle-v1.json').read_bytes()) -# Hidden values deliberately differ from the checked-in example. The explicit -# private input has no public filename, path, fixture lookup or PRNG seed. -ROUND = replace(BASE.rounds[0], spaces=tuple(Fraction(13 + i * 11, 113) for i in range(8))) -PROFILE = replace(BASE, scope='private-public-test', rounds=(ROUND,)) -CORPUS = b'actual shared corpus\x00\xff' +PROFILE = replace(BASE, rounds=BASE.rounds[:1]) +CORPUS = b'actual corpus\x00\xff' +MATERIAL = bytes((i*73+41)%256 for i in range(256)) class PrivatePublicTests(unittest.TestCase): def setUp(self): - self.public, self.private = pp.partition(PROFILE, CORPUS) - self.control = pp.full_profile_control(self.public, self.private) + self.public, self.private = pp.keygen(PROFILE, CORPUS, SOURCES, material=MATERIAL) + + def test_private_native_states_preserve_full_frame(self): + origin, turns = pp._native(MATERIAL, SOURCES) + self.assertEqual(len(origin), 64) + self.assertEqual(len(turns), 8) + self.assertEqual(turns, tuple(Fraction(MATERIAL[i],256)+(MATERIAL[8+i]&1) for i in range(8))) + changed = bytearray(MATERIAL); changed[10] ^= 1 + _, other = pp._native(bytes(changed), SOURCES) + self.assertEqual(abs(other[2]-turns[2]), 1) + self.assertEqual(abs(pp._offsets(other)[2]-pp._offsets(turns)[2]), 256) - def test_exact_whole_plus_one_export_for_each_circle(self): - for circle in range(1, 8): - public, private = pp.partition(BASE, CORPUS, circle=circle) - obj = json.loads(public) - for original, row in zip(BASE.as_dict()['rounds'], obj['profile']['rounds']): - self.assertEqual([i for i, x in enumerate(row['spaces']) if x is not None], [0, circle]) - self.assertEqual(row['spaces'][0], original['spaces'][0]) - self.assertEqual(row['spaces'][circle], original['spaces'][circle]) - self.assertEqual({k: v for k, v in row.items() if k != 'spaces'}, - {k: v for k, v in original.items() if k != 'spaces'}) - self.assertEqual(json.loads(private)['profile'], BASE.as_dict()) - self.assertEqual(bytes.fromhex(obj['corpus_hex']), CORPUS) + def test_public_artifact_exposes_two_states_and_evaluation_coefficients(self): + obj = json.loads(self.public) + self.assertEqual(set(obj), {'law','key_origin','public_turns','coefficients','profile','corpus_hex','native_lock_sha256'}) + self.assertEqual(len(obj['public_turns']), 2) + self.assertEqual(len(obj['coefficients']), 65) + self.assertNotIn('material_hex', obj) + self.assertEqual(json.loads(self.private), {'law':pp.LAW,'material_hex':MATERIAL.hex()}) + self.assertEqual(obj['profile'], PROFILE.as_dict()) + self.assertEqual(bytes.fromhex(obj['corpus_hex']), CORPUS) + + def test_keygen_default_uses_fresh_material(self): + alternative = bytes((x+1)%256 for x in MATERIAL) + with patch.object(pp.secrets,'token_bytes',side_effect=[MATERIAL,alternative]) as entropy: + a = pp.keygen(PROFILE,CORPUS,SOURCES) + b = pp.keygen(PROFILE,CORPUS,SOURCES) + self.assertEqual(entropy.call_args_list[0].args,(256,)) + self.assertNotEqual(a,b) - def test_sender_blocks_before_forward_or_native_loading(self): - with patch.object(cycle, 'forward', side_effect=AssertionError('private-dependent forward called')): - with self.assertRaisesRegex(pp.MissingPublicRelation, 'native public evaluation law missing'): - pp.send(b'message', self.public, SOURCES) + def test_expanded_public_law_equals_private_composition(self): + rng = random.Random(20261006) + for _ in range(32): + shifts = tuple(rng.randint(1,512) for _ in range(6)) + coefficients = pp._expand(shifts) + for x in (0,1,255,65536): + value = x + for shift in shifts: value = (value+shift)**2 + self.assertEqual(pp._evaluate(coefficients,x),value) + self.assertEqual(pp._invert(value,shifts),x) + self.assertEqual(pp.decompose(coefficients),shifts) - def test_projected_public_recovery_refusal_is_not_security_evidence(self): - with patch.object(cycle, 'reverse', side_effect=AssertionError('guessed missing spaces')): - with self.assertRaises(pp.MissingPublicRelation): - pp.public_recover(b'not even a packet', self.public, SOURCES) + def test_sender_never_calls_private_construction_or_inverse(self): + with patch.object(pp,'keygen',side_effect=AssertionError('keygen in sender')), \ + patch.object(pp,'_native',side_effect=AssertionError('private state in sender')), \ + patch.object(pp,'decompose',side_effect=AssertionError('inverse in sender')), \ + patch.object(pp,'recover',side_effect=AssertionError('private recovery in sender')): + packet = pp.send(b'ABxABy',self.public,SOURCES) + self.assertEqual(pp.recover(packet,self.public,self.private,SOURCES),b'ABxABy') - def test_private_recovery_exact_for_binary_edge_cases(self): - for message in (b'', b'\x00\x00\x01', bytes(range(256)), b'ABxABy' * 12): + def test_private_exact_recovery_binary_edge_cases(self): + for message in (b'',b'\x00\x00\x01',bytes(range(256)),b'ABxABy'*8): with self.subTest(length=len(message)): - packet = pp.send(message, self.control, SOURCES) - self.assertEqual(pp.recover(packet, self.public, self.private, SOURCES), message) + packet = pp.send(message,self.public,SOURCES) + self.assertEqual(pp.recover(packet,self.public,self.private,SOURCES),message) - def test_public_only_inverse_is_a_real_counterexample(self): - message = b'ABxABy\x00ABxABy\xff' - packet = pp.send(message, self.control, SOURCES) - # The attacker bypasses the entire private-path API rather than making - # that API accept a missing key. No private parameter goes to this call. - with patch.object(pp, 'recover', side_effect=AssertionError('private API called')): - self.assertEqual(pp.public_recover(packet, self.control, SOURCES), message) + def test_every_native_state_materially_changes_binding(self): + packet = pp.send(b'ABxABy',self.public,SOURCES) + value = pp._packet(packet,self.public)[1] + for i in range(8): + changed = bytearray(MATERIAL); changed[i] = (changed[i]+1)%256 + public, private = pp.keygen(PROFILE,CORPUS,SOURCES,material=bytes(changed)) + other = pp.send(b'ABxABy',public,SOURCES) + self.assertNotEqual(pp._packet(other,public)[1],value) + self.assertEqual(pp.recover(other,public,private,SOURCES),b'ABxABy') - def test_control_matches_unmodified_cycle_bytes(self): - message = b'ABxABy' - expected, _ = cycle.forward(message, CORPUS, PROFILE, SOURCES, pp.LIMITS) - self.assertEqual(pp.send(message, self.control, SOURCES), expected) - control = json.loads(self.control) - self.assertEqual(control['disclosure'], pp.CONTROL) - self.assertEqual(control['profile'], PROFILE.as_dict()) + def test_public_only_attack_recovers_without_private_path(self): + message = b'ABxABy\x00ABxABy' + packet = pp.send(message,self.public,SOURCES) + with patch.object(pp,'keygen',side_effect=AssertionError('attacker keygen')), \ + patch.object(pp,'_native',side_effect=AssertionError('attacker private states')), \ + patch.object(pp,'recover',side_effect=AssertionError('attacker private API')): + self.assertEqual(pp.public_recover(packet,self.public,SOURCES),message) - def test_private_input_is_required_by_recipient_interface(self): - packet = pp.send(b'message', self.control, SOURCES) - for absent in (None, b'', self.public, self.control): - with self.assertRaises(Refused): - pp.recover(packet, self.public, absent, SOURCES) - # This interface refusal is deliberately paired with the public attack. - self.assertEqual(pp.public_recover(packet, self.control, SOURCES), b'message') + def test_public_decomposition_recovers_equivalent_inverse_not_material(self): + _, turns = pp._native(MATERIAL,SOURCES) + recovered = pp.decompose(json.loads(self.public)['coefficients']) + self.assertEqual(recovered,pp._offsets(turns)[2:]) + self.assertEqual(len(recovered),6) + self.assertNotEqual(recovered,tuple(MATERIAL)) - def test_wrong_hidden_private_space_fails_even_with_matching_public_projection(self): - packet = pp.send(b'ABxABy', self.control, SOURCES) - obj = json.loads(self.private) - obj['profile']['rounds'][0]['spaces'][2] = [1, 97] - with self.assertRaisesRegex(Refused, 'profile identity mismatch'): - pp.recover(packet, self.public, canonical(obj), SOURCES) + def test_private_api_rejects_missing_and_wrong_material(self): + packet = pp.send(b'message',self.public,SOURCES) + for private in (None,b'',self.public,canonical({'law':pp.LAW,'material_hex':'00'*256})): + with self.assertRaises(Refused): pp.recover(packet,self.public,private,SOURCES) + self.assertEqual(pp.public_recover(packet,self.public,SOURCES),b'message') - def test_wrong_pair_and_changed_visible_space_refused(self): - packet = pp.send(b'message', self.control, SOURCES) - other, _ = pp.partition(PROFILE, CORPUS + b'!') - with self.assertRaisesRegex(Refused, 'exact public bytes'): - pp.recover(packet, other, self.private, SOURCES) - obj = json.loads(self.private) - obj['profile']['rounds'][0]['spaces'][0] = [1, 97] - with self.assertRaisesRegex(Refused, 'public projection'): - pp.recover(packet, self.public, canonical(obj), SOURCES) + def test_non_exact_root_is_refused_without_rounding(self): + packet = pp.send(b'message',self.public,SOURCES) + length,y = pp._packet(packet,self.public) + encoded = (y+1).to_bytes(((y+1).bit_length()+7)//8,'big') + damaged = packet[:36]+_uint(length)+_blob(encoded) + with self.assertRaisesRegex(Refused,'non-exact square'): + pp.recover(damaged,self.public,self.private,SOURCES) + with self.assertRaises(Refused): pp.public_recover(damaged,self.public,SOURCES) - def test_private_leak_and_incomplete_control_are_refused(self): + def test_malformed_public_artifacts_are_refused(self): obj = json.loads(self.public) - obj['profile']['rounds'][0]['spaces'][2] = [0, 1] - with self.assertRaisesRegex(Refused, 'leaked'): - pp.send(b'hi', canonical(obj), SOURCES) - obj = json.loads(self.control) - obj['profile']['rounds'][0]['spaces'][2] = None - with self.assertRaisesRegex(Refused, 'public space absent'): - pp.send(b'hi', canonical(obj), SOURCES) + variants=[] + for key,value in [('law','other'),('material_hex',MATERIAL.hex()),('public_turns',[[0,1]]), + ('coefficients',[1]),('native_lock_sha256','0'*64),('corpus_hex','AA'), + ('key_origin','ab')]: + bad=deepcopy(obj); bad[key]=value; variants.append(canonical(bad)) + bad=deepcopy(obj); bad['public_turns'][0]=[2,4]; variants.append(canonical(bad)) + bad=deepcopy(obj); bad['coefficients'][0]=True; variants.append(canonical(bad)) + bad=deepcopy(obj); bad['coefficients'][0]=-1; variants.append(canonical(bad)) + variants += [b'{"law":1,"law":2}',b'[]',b'\xff',b'x'*65537,json.dumps(obj,indent=2).encode()] + for value in variants: + with self.assertRaises(Refused): pp.send(b'm',value,SOURCES) - def test_strict_artifact_schema_and_types(self): - base = json.loads(self.public) - cases = [] - for key, value in [('circle', True), ('circle', 0), ('circle', 8), ('schema', 'other'), - ('disclosure', 'asymmetric'), ('private', 'file://secret'), - ('corpus_hex', 'AA'), ('corpus_hex', '00 '), ('native_lock_sha256', '0' * 64)]: - obj = deepcopy(base); obj[key] = value; cases.append(canonical(obj)) - cases += [b'[]', b'{"schema":1,"schema":2}', b'{"x":NaN}', b'\xff', b'x' * 32769] - for data in cases: - with self.subTest(data=data[:80]), self.assertRaises(Refused): - pp.send(b'm', data, SOURCES) + def test_packet_identity_length_and_framing_are_checked(self): + packet=pp.send(b'message',self.public,SOURCES) + variants=[packet[:-1],packet+b'!',packet[:36]+_uint(pp.MAX_RECORD+1)+_blob(b'\x01'), + packet[:36]+_uint(1)+_blob(b'\x00\x01'),b'x'*(pp.MAX_WIRE+1)] + corrupted=bytearray(packet); corrupted[4]^=1; variants.append(bytes(corrupted)) + for value in variants: + with self.assertRaises(Refused): pp.public_recover(value,self.public,SOURCES) - def test_invalid_public_profile_rejected_before_cycle(self): - for field, value in [('spaces', []), ('end_order', 'sideways'), ('arities', [True]), - ('circle_order', [1] * 7), ('prime_path', {'seed': 1, 'steps': []})]: - obj = json.loads(self.public) - obj['profile']['rounds'][0][field] = value - with self.assertRaises(Refused): - pp.send(b'm', canonical(obj), SOURCES) - obj = json.loads(self.public) - obj['profile']['rounds'][0]['spaces'][0] = [2, 4] - with self.assertRaises(Refused): - pp.send(b'm', canonical(obj), SOURCES) + def test_budgets_and_private_material_admission(self): + for material in (b'',b'x'*255,b'x'*257,'text'): + with self.assertRaises(Refused): pp.keygen(PROFILE,CORPUS,SOURCES,material=material) + for corpus in (b'',b'x'*4097,'text'): + with self.assertRaises(Refused): pp.keygen(PROFILE,corpus,SOURCES,material=MATERIAL) + with self.assertRaises(Refused): pp.send(b'x'*257,self.public,SOURCES) + with self.assertRaises(Refused): pp.keygen(replace(BASE,rounds=BASE.rounds*2),CORPUS,SOURCES,material=MATERIAL) - def test_artifact_sizes_and_corpus_limits(self): - for corpus in (b'', b'x' * 4097, 'corpus'): - with self.assertRaises(Refused): pp.partition(PROFILE, corpus) - with self.assertRaises(Refused): - pp.partition(replace(BASE, rounds=BASE.rounds * 2), CORPUS) - with self.assertRaises(Refused): - pp.send(b'x' * 257, self.control, SOURCES) - with self.assertRaises(Refused): - pp.experiment(b'x' * 257, PROFILE, CORPUS, SOURCES) + def test_decomposition_refuses_non_candidate_polynomial(self): + coefficients=json.loads(self.public)['coefficients'] + for index in (0,1,63): + altered=coefficients.copy(); altered[index]+=1 + with self.assertRaises(Refused): pp.decompose(altered) - def test_corrupted_and_truncated_packet_are_not_recovery(self): - packet = pp.send(b'message', self.control, SOURCES) - damaged = bytearray(packet); damaged[36] ^= 1 - for bad in (packet[:-1], packet + b'!', bytes(damaged)): - with self.assertRaises(Refused): pp.public_recover(bad, self.control, SOURCES) - with self.assertRaises(Refused): pp.recover(bad, self.public, self.private, SOURCES) - - def test_three_round_fresh_process_input_manifests(self): - calls = [] - original = pp._run - def observe(root, operation, public, data, private=None): - files = {str(p.relative_to(root)) for p in root.rglob('*') if p.is_file()} + def test_three_round_fresh_process_construction_and_attack(self): + calls=[] + private_inputs=[] + original=pp._run + def observe(root,operation,public,data,private=None): + self.assertEqual(private is not None,operation=='recover') + files={str(p.relative_to(root)) for p in root.rglob('*') if p.is_file()} self.assertFalse(any('profile' in p or 'test_' in p for p in files)) - self.assertEqual(private is not None, operation == 'recover') - calls.append((operation, set(json.loads(public)), private is not None)) - return original(root, operation, public, data, private) - with patch.object(pp, '_run', side_effect=observe): - report = pp.experiment(b'ABxABy\x00ABxABy', BASE, CORPUS, SOURCES) - self.assertEqual([c[0] for c in calls], ['send', 'send', 'recover', 'public-recover', 'public-recover']) + if private is not None: + private_inputs.append(json.loads(private)['material_hex']) + calls.append(operation) + return original(root,operation,public,data,private) + with patch.object(pp,'_run',side_effect=observe): + report=pp.experiment(b'ABxABy\x00ABxABy',BASE,CORPUS,SOURCES) + self.assertEqual(calls,['send','recover','public-recover']) + self.assertEqual(report['status'],'FALSIFIED') + self.assertTrue(report['public_sender_completed']) + self.assertTrue(report['private_recovery_exact']) + self.assertTrue(report['public_recovery_exact']) + self.assertFalse(report['private_material_given_to_attacker']) self.assertFalse(report['distinction_established']) - self.assertEqual(report['whole_plus_one']['status'], 'BLOCKED') - self.assertEqual(report['full_profile_control']['status'], 'FALSIFIED') - self.assertTrue(report['full_profile_control']['recipient_exact']) - self.assertTrue(report['full_profile_control']['public_only_exact']) - self.assertNotIn('private', report['inputs']['sender']) - self.assertNotIn('private', report['inputs']['attacker']) - self.assertNotIn('message', report['inputs']['recipient']) - self.assertEqual(set(report['source_files']), set(pp.RUNTIME) | { - 'inputs/ucns/src/ucns/axis_circle.py', 'inputs/ucns/src/ucns/direct_mobius.py'}) - - def test_report_does_not_include_private_artifact_or_plaintext(self): - message = b'not-a-secret-but-must-not-enter-report' - report = pp.experiment(message, PROFILE, CORPUS, SOURCES) - rendered = canonical(report) - self.assertNotIn(message, rendered) - self.assertNotIn(message.hex().encode(), rendered) - self.assertNotIn(self.private, rendered) - self.assertNotIn(b'corpus_hex', rendered) - self.assertEqual(report['accounting']['message_bytes'], len(message)) + self.assertEqual(set(report['source_files']),set(pp.RUNTIME)|{ + 'inputs/ucns/src/ucns/axis_circle.py','inputs/ucns/src/ucns/direct_mobius.py'}) + self.assertEqual(len(private_inputs),1) + self.assertNotIn(private_inputs[0].encode(),canonical(report)) - def test_missing_native_source_is_error_not_attacker_failure(self): - with patch.object(pp, 'load_native', side_effect=Refused('missing native source')): - with self.assertRaisesRegex(Refused, 'missing native source'): - pp.experiment(b'm', PROFILE, CORPUS, SOURCES) + def test_source_failure_is_error_not_attack_resistance(self): + with patch.object(pp,'load_native',side_effect=Refused('missing native source')): + with self.assertRaisesRegex(Refused,'missing native source'): + pp.experiment(b'm',PROFILE,CORPUS,SOURCES,material=MATERIAL) - def test_worker_crash_is_not_public_recovery_failure(self): - result = subprocess.CompletedProcess([], 2, b'', b'infrastructure failure') - with patch.object(pp.subprocess, 'run', return_value=result): - with self.assertRaisesRegex(RuntimeError, 'worker failed'): - pp.experiment(b'm', PROFILE, CORPUS, SOURCES) + def test_worker_failure_is_error_not_attack_resistance(self): + result=subprocess.CompletedProcess([],2,b'',b'infrastructure failure') + with patch.object(pp.subprocess,'run',return_value=result): + with self.assertRaisesRegex(RuntimeError,'worker failed'): + pp.experiment(b'm',PROFILE,CORPUS,SOURCES,material=MATERIAL) - def test_acceptance_cli_exits_one_with_honest_report(self): - result = subprocess.run([sys.executable, str(ROOT/'private_public.py'), '--sources', str(SOURCES), - '--require-distinction'], capture_output=True) - self.assertEqual(result.returncode, 1, result.stderr.decode()) - report = json.loads(result.stdout) - self.assertEqual(report['execution'], 'COMPLETED') + def test_acceptance_command_fails_on_actual_public_recovery(self): + result=subprocess.run([sys.executable,str(ROOT/'private_public.py'),'--sources',str(SOURCES), + '--require-distinction'],capture_output=True) + self.assertEqual(result.returncode,1,result.stderr.decode()) + report=json.loads(result.stdout) + self.assertEqual(report['status'],'FALSIFIED') + self.assertTrue(report['public_recovery_exact']) self.assertFalse(report['distinction_established']) - self.assertEqual(report['full_profile_control']['status'], 'FALSIFIED') def test_cli_infrastructure_error_exits_two(self): - result = subprocess.run([sys.executable, str(ROOT/'private_public.py'), '--sources', '/nonexistent-weave-inputs'], - capture_output=True) - self.assertEqual(result.returncode, 2) - self.assertEqual(result.stdout, b'') - self.assertIn(b'experiment error', result.stderr) + result=subprocess.run([sys.executable,str(ROOT/'private_public.py'),'--sources','/missing-weave-sources'], + capture_output=True) + self.assertEqual(result.returncode,2) + self.assertEqual(result.stdout,b'') + self.assertIn(b'experiment error',result.stderr) -if __name__ == '__main__': - unittest.main() -# ratios: loc_comments=184:111 imports_exports=14:1 calls_definitions=122:22 +if __name__=='__main__': unittest.main() +# ratios: loc_comments=187:106 imports_exports=14:1 calls_definitions=131:22