"""Shared-claim consistency between ACT and ECT refimpls. Covers the claims both specs declare (jti, wid, iat, exp, aud, exec_act, pred, inp_hash, out_hash): verifies that identical wire values are accepted and round-trip cleanly. """ from __future__ import annotations import base64 import hashlib import uuid import pytest from act.crypto import b64url_sha256 from act.token import ACTRecord from ect.types import Payload from ect.validate import validate_hash_format # --- pred array semantics ---------------------------------------------------- class TestPredArray: @pytest.mark.parametrize( "pred", [ [], [str(uuid.uuid4())], [str(uuid.uuid4()), str(uuid.uuid4())], ], ) def test_pred_roundtrips_on_both(self, act_record_builder, ect_payload_builder, pred): """Same pred array shape is serialised identically by both refimpls.""" act_compact, act_rec = act_record_builder(pred=list(pred)) ect_compact, ect_pl = ect_payload_builder(pred=list(pred)) assert act_rec.pred == pred assert ect_pl.pred == pred # Decode and compare the on-wire pred arrays. from act.token import decode_jws _, act_claims, _, _ = decode_jws(act_compact) assert act_claims["pred"] == pred import jwt ect_claims = jwt.decode( ect_compact, options={"verify_signature": False, "verify_exp": False} ) assert ect_claims["pred"] == pred # --- jti / wid scoping ------------------------------------------------------- class TestJtiWidScoping: def test_same_uuid_accepted_independently(self, act_record_builder, ect_payload_builder): """The same jti UUID is accepted by both refimpls independently. jti uniqueness is scoped per-store in each refimpl; there is no shared namespace. This test documents that reusing a jti across token types is not inherently rejected. """ shared_jti = str(uuid.uuid4()) act_compact, _ = act_record_builder(jti=shared_jti) ect_compact, _ = ect_payload_builder(jti=shared_jti) assert act_compact and ect_compact def test_same_wid_accepted_by_both(self, act_record_builder, ect_payload_builder): """Identical wid value is preserved on both sides.""" shared_wid = str(uuid.uuid4()) _, act_rec = act_record_builder(wid=shared_wid) _, ect_pl = ect_payload_builder(wid=shared_wid) assert act_rec.wid == shared_wid == ect_pl.wid # --- inp_hash / out_hash format (both now plain base64url) ------------------- class TestHashFormat: def test_act_b64url_sha256_is_valid_for_ect(self): """ACT's b64url_sha256() output MUST pass ECT's validate_hash_format. The ECT validator was aligned to plain base64url to match ACT — this test would break if either side ever drifts. """ h = b64url_sha256(b"interop-payload") # Should not raise. validate_hash_format(h) def test_ect_rejects_prefixed_hash(self): """Prefixed form (sha-256:...) is explicitly rejected by ECT now.""" digest = b64url_sha256(b"interop-payload") with pytest.raises(ValueError, match="plain base64url"): validate_hash_format(f"sha-256:{digest}") def test_act_and_ect_agree_on_hash_shape(self): """Identical raw bytes produce the same base64url hash on both sides.""" raw = b"same bytes hashed on both sides" act_hash = b64url_sha256(raw) # Recompute independently. expected = ( base64.urlsafe_b64encode(hashlib.sha256(raw).digest()) .rstrip(b"=") .decode("ascii") ) assert act_hash == expected validate_hash_format(act_hash) # ECT accepts. # --- exec_act ---------------------------------------------------------------- class TestExecAct: @pytest.mark.parametrize("value", ["read", "read.data", "write.result"]) def test_exec_act_string_shared(self, act_record_builder, ect_payload_builder, value): """ACT-grammar-legal exec_act values are accepted unchanged by ECT.""" _, act_rec = act_record_builder(exec_act=value, cap_actions=[value]) _, ect_pl = ect_payload_builder(exec_act=value) assert act_rec.exec_act == value == ect_pl.exec_act