Files
ietf-draft-analyzer/workspace/packages/interop/tests/test_algorithm_matrix.py
Christian Nennemann 37859beef6 feat: interop test package + session handoff doc
Cross-spec interop validation between ietf-act and ietf-ect:
- new packages/interop/ sibling package (ietf-act-ect-interop)
- 32 tests pass: shared claims, algorithm matrix, DAG structure,
  divergence handling, anti-goals
- documents ES256 raw signature wire-compatibility
- documents airtight typ separation (act+jwt vs exec+jwt)

Hazards surfaced:
- ACTLedger.append() silently accepts ECT Payload via duck-typing
  (both have .jti) — documented in interop README as a production
  hazard requiring external isinstance checks

Session handoff:
- SESSION-2026-04-12.md — snapshot of decisions, artifacts, open
  actions, and next-session starting points

Also: session-end commit of hash-format fix propagation to
packages/ect/ (the fix was applied to the old refimpl location
but did not propagate through the parallel package-move agent).
2026-04-12 07:39:41 +02:00

106 lines
3.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""Algorithm compatibility matrix between ACT and ECT.
Facts pinned here:
- Both specs share the JWS/ES256 signing primitive.
- ECT is ES256-only; ACT also supports EdDSA.
- ECT's typ gate rejects cross-type compact tokens even when the
signature algorithm matches — that separation is deliberate.
"""
from __future__ import annotations
import pytest
from act.errors import ACTValidationError
from ect.verify import verify as ect_verify, VerifyOptions
class TestAlgorithmMatrix:
def test_es256_act_record_signature_primitive_verifies(
self, act_record_builder, dual_resolver
):
"""ACT Record signed ES256 feeds through ECT verify up to the typ gate.
Signature primitive is wire-compatible; typ=act+jwt causes
ECT to refuse. Documents "sig-compatible, typ-rejected".
"""
_, ect_resolver = dual_resolver
compact, _ = act_record_builder(alg="ES256")
opts = VerifyOptions(resolve_key=ect_resolver)
with pytest.raises(ValueError, match="invalid typ parameter"):
ect_verify(compact, opts)
def test_eddsa_act_record_rejected_by_ect(
self, act_record_builder, ed25519_keypair
):
"""ACT Record signed EdDSA — ECT parser rejects at alg gate."""
priv, _ = ed25519_keypair
compact, _ = act_record_builder(alg="EdDSA", priv=priv)
# ect.parse (used indirectly via verify) rejects non-ES256.
# verify() checks typ first, but parse() would reject alg first;
# either way the token is rejected — record which gate fires.
opts = VerifyOptions(resolve_key=lambda kid: None)
with pytest.raises(ValueError) as exc:
ect_verify(compact, opts)
# The verify() path checks typ before alg. We accept either
# error message as valid "rejection by ECT" evidence.
assert (
"invalid typ parameter" in str(exc.value)
or "expected ES256" in str(exc.value)
)
def test_ect_compact_rejected_by_act_decoder(self, ect_payload_builder):
"""Mirror direction: ECT compact → ACT decoder rejects on typ."""
from act.token import decode_jws
compact, _ = ect_payload_builder()
with pytest.raises(ACTValidationError, match="typ"):
decode_jws(compact)
def test_es256_primitive_is_wire_compatible_at_raw_sig_level(
self, ect_payload_builder, es256_keypair
):
"""The raw ES256 signature over an ECT compact verifies with
act.crypto.verify using the ECT public key.
This is the layer that is actually portable: both refimpls
sign/verify over the same RFC 7518 §3.4 raw r||s format.
"""
from act.crypto import verify as act_verify
from act.token import _b64url_decode
priv, pub = es256_keypair
compact, _ = ect_payload_builder(priv=priv)
parts = compact.split(".")
signing_input = f"{parts[0]}.{parts[1]}".encode("ascii")
signature = _b64url_decode(parts[2])
# Should not raise.
act_verify(pub, signature, signing_input)
def test_compatibility_matrix_assertion(
act_record_builder, ed25519_keypair, dual_resolver
):
"""Pin the whole matrix in one assertion block.
Matrix entries:
(ACT/ES256) × ECT-verify -> sig-compatible-but-typ-rejected
(ACT/EdDSA) × ECT-verify -> rejected (alg or typ)
"""
_, ect_resolver = dual_resolver
opts = VerifyOptions(resolve_key=ect_resolver)
# ACT/ES256 × ECT-verify
act_es256, _ = act_record_builder(alg="ES256")
with pytest.raises(ValueError, match="invalid typ parameter"):
ect_verify(act_es256, opts)
# ACT/EdDSA × ECT-verify
priv, _ = ed25519_keypair
act_eddsa, _ = act_record_builder(alg="EdDSA", priv=priv)
with pytest.raises(ValueError):
ect_verify(act_eddsa, opts)