proofbundle 1.9.0__tar.gz → 1.9.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (82) hide show
  1. {proofbundle-1.9.0/src/proofbundle.egg-info → proofbundle-1.9.1}/PKG-INFO +4 -4
  2. {proofbundle-1.9.0 → proofbundle-1.9.1}/README.md +3 -3
  3. {proofbundle-1.9.0 → proofbundle-1.9.1}/pyproject.toml +1 -1
  4. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/__init__.py +1 -1
  5. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/statuslist.py +27 -4
  6. {proofbundle-1.9.0 → proofbundle-1.9.1/src/proofbundle.egg-info}/PKG-INFO +4 -4
  7. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_fuzz_parsers.py +9 -0
  8. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_statuslist.py +32 -0
  9. {proofbundle-1.9.0 → proofbundle-1.9.1}/LICENSE +0 -0
  10. {proofbundle-1.9.0 → proofbundle-1.9.1}/setup.cfg +0 -0
  11. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/_inspect_registry.py +0 -0
  12. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/_integration.py +0 -0
  13. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/adapters/__init__.py +0 -0
  14. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/adapters/_provenance.py +0 -0
  15. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/adapters/eee.py +0 -0
  16. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/adapters/inspect_ai.py +0 -0
  17. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/adapters/lm_eval.py +0 -0
  18. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/adapters/promptfoo.py +0 -0
  19. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/adapters/samples.py +0 -0
  20. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/beacon.py +0 -0
  21. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/bundle.py +0 -0
  22. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/checkpoint.py +0 -0
  23. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/cli.py +0 -0
  24. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/demo.py +0 -0
  25. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/dsse.py +0 -0
  26. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/eee_eval_schema.json +0 -0
  27. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/emit.py +0 -0
  28. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/errors.py +0 -0
  29. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/evalclaim.py +0 -0
  30. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/hf_evals.py +0 -0
  31. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/inspect_hook.py +0 -0
  32. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/intoto.py +0 -0
  33. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/kbjwt.py +0 -0
  34. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/merkle.py +0 -0
  35. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/persample.py +0 -0
  36. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/prereg.py +0 -0
  37. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/py.typed +0 -0
  38. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/pytest_plugin.py +0 -0
  39. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/sdjwt.py +0 -0
  40. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/sdjwt_issue.py +0 -0
  41. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/signature.py +0 -0
  42. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle/tlogproof.py +0 -0
  43. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle.egg-info/SOURCES.txt +0 -0
  44. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle.egg-info/dependency_links.txt +0 -0
  45. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle.egg-info/entry_points.txt +0 -0
  46. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle.egg-info/requires.txt +0 -0
  47. {proofbundle-1.9.0 → proofbundle-1.9.1}/src/proofbundle.egg-info/top_level.txt +0 -0
  48. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_adapters.py +0 -0
  49. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_adversarial.py +0 -0
  50. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_beacon.py +0 -0
  51. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_bundle.py +0 -0
  52. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_bundle_robustness.py +0 -0
  53. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_checkpoint.py +0 -0
  54. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_cli.py +0 -0
  55. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_cli_eval.py +0 -0
  56. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_cosignature.py +0 -0
  57. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_cosignature_mldsa.py +0 -0
  58. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_demo.py +0 -0
  59. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_eee.py +0 -0
  60. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_emit.py +0 -0
  61. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_eval_claim_schema.py +0 -0
  62. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_evalclaim.py +0 -0
  63. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_examples.py +0 -0
  64. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_hf_evals.py +0 -0
  65. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_inspect_hook.py +0 -0
  66. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_intoto.py +0 -0
  67. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_intoto_dsse.py +0 -0
  68. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_kbjwt.py +0 -0
  69. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_merkle.py +0 -0
  70. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_merkle_property.py +0 -0
  71. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_persample.py +0 -0
  72. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_prereg.py +0 -0
  73. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_promptfoo.py +0 -0
  74. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_provenance.py +0 -0
  75. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_pytest_plugin.py +0 -0
  76. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_rekor_interop.py +0 -0
  77. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_rfc6962_external_vectors.py +0 -0
  78. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_schema.py +0 -0
  79. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_sdjwt_issue.py +0 -0
  80. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_sdjwt_reference.py +0 -0
  81. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_signature.py +0 -0
  82. {proofbundle-1.9.0 → proofbundle-1.9.1}/tests/test_tlogproof.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proofbundle
3
- Version: 1.9.0
3
+ Version: 1.9.1
4
4
  Summary: Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT.
5
5
  Author: Konrad Gruszka
6
6
  License: MIT
@@ -145,6 +145,7 @@ flowchart LR
145
145
  | For… | Read |
146
146
  |---|---|
147
147
  | Skeptics (why not SHA-256 / Sigstore / trust the issuer) | [docs/FAQ.md](docs/FAQ.md) |
148
+ | New to this? plain-terms glossary | [docs/GLOSSARY.md](docs/GLOSSARY.md) |
148
149
  | Reviewers (30-minute adversarial audit path) | [docs/REVIEWERS.md](docs/REVIEWERS.md) |
149
150
  | Where every trust anchor comes from | [docs/TRUST_ANCHORS.md](docs/TRUST_ANCHORS.md) |
150
151
  | The demos, tier by tier | [docs/DEMO.md](docs/DEMO.md) |
@@ -157,8 +158,7 @@ flowchart LR
157
158
 
158
159
  ```bash
159
160
  pip install proofbundle # core: offline verify + plain emit (dependency-free)
160
- pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds rfc8785 JCS)
161
- pip install "proofbundle[eval]" # emit eval receipts (adds an RFC 8785 canonicalizer)
161
+ pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds an RFC 8785 JCS canonicalizer)
162
162
  pip install "proofbundle[inspect]" # inspect_ai adapter + hook
163
163
  pip install "proofbundle[pq]" # verify ML-DSA-44 (post-quantum) witness cosignatures
164
164
  ```
@@ -168,7 +168,7 @@ Requires Python 3.10+. The verify path never rolls its own crypto — Ed25519 co
168
168
 
169
169
  ## Status & scope
170
170
 
171
- Beta, SemVer-committed, 299 tests + a CI mutation gate + property-based parser fuzzing. Correctness
171
+ Beta, SemVer-committed, 303 tests + a CI mutation gate + property-based parser fuzzing. Correctness
172
172
  is anchored to external RFC 6962 vectors and a real Rekor proof, not just its own bundles. It is
173
173
  **not** a log service, a full in-toto client, a TEE, a consensus network, or a compliance product
174
174
  by itself — it is the small, offline, standards-native receipt layer between them. Security policy:
@@ -97,6 +97,7 @@ flowchart LR
97
97
  | For… | Read |
98
98
  |---|---|
99
99
  | Skeptics (why not SHA-256 / Sigstore / trust the issuer) | [docs/FAQ.md](docs/FAQ.md) |
100
+ | New to this? plain-terms glossary | [docs/GLOSSARY.md](docs/GLOSSARY.md) |
100
101
  | Reviewers (30-minute adversarial audit path) | [docs/REVIEWERS.md](docs/REVIEWERS.md) |
101
102
  | Where every trust anchor comes from | [docs/TRUST_ANCHORS.md](docs/TRUST_ANCHORS.md) |
102
103
  | The demos, tier by tier | [docs/DEMO.md](docs/DEMO.md) |
@@ -109,8 +110,7 @@ flowchart LR
109
110
 
110
111
  ```bash
111
112
  pip install proofbundle # core: offline verify + plain emit (dependency-free)
112
- pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds rfc8785 JCS)
113
- pip install "proofbundle[eval]" # emit eval receipts (adds an RFC 8785 canonicalizer)
113
+ pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds an RFC 8785 JCS canonicalizer)
114
114
  pip install "proofbundle[inspect]" # inspect_ai adapter + hook
115
115
  pip install "proofbundle[pq]" # verify ML-DSA-44 (post-quantum) witness cosignatures
116
116
  ```
@@ -120,7 +120,7 @@ Requires Python 3.10+. The verify path never rolls its own crypto — Ed25519 co
120
120
 
121
121
  ## Status & scope
122
122
 
123
- Beta, SemVer-committed, 299 tests + a CI mutation gate + property-based parser fuzzing. Correctness
123
+ Beta, SemVer-committed, 303 tests + a CI mutation gate + property-based parser fuzzing. Correctness
124
124
  is anchored to external RFC 6962 vectors and a real Rekor proof, not just its own bundles. It is
125
125
  **not** a log service, a full in-toto client, a TEE, a consensus network, or a compliance product
126
126
  by itself — it is the small, offline, standards-native receipt layer between them. Security policy:
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "proofbundle"
7
- version = "1.9.0"
7
+ version = "1.9.1"
8
8
  description = "Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -13,7 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  from typing import TYPE_CHECKING
15
15
 
16
- __version__ = "1.9.0"
16
+ __version__ = "1.9.1"
17
17
 
18
18
  __all__ = [
19
19
  "__version__",
@@ -70,7 +70,8 @@ def _status_at(bit_array: bytes, bits: int, idx: int) -> int:
70
70
 
71
71
 
72
72
  def verify_status_snapshot(status_list_token: str, *, expected_uri: str, index: int,
73
- issuer_pubkey: bytes, now: Optional[int] = None) -> dict:
73
+ issuer_pubkey: bytes, now: Optional[int] = None,
74
+ receipt_issuer_pubkey: Optional[bytes] = None) -> dict:
74
75
  """Verify a Status List Token snapshot and read one token's status, fully offline.
75
76
 
76
77
  Checks, fail-closed: compact-JWS shape, `typ` == ``statuslist+jwt``, EdDSA signature under
@@ -78,11 +79,33 @@ def verify_status_snapshot(status_list_token: str, *, expected_uri: str, index:
78
79
  `bits` ∈ {1,2,4,8}, zlib decode, index in range. Freshness: `iat`/`exp`/`ttl` are returned;
79
80
  `fresh` is None unless ``now`` (POSIX seconds) is supplied, then it is
80
81
  ``iat <= now`` AND ``now < exp`` (if exp) AND ``now <= iat + ttl`` (if ttl).
81
- Returns ``{ok, status, status_label, fresh, iat, exp, ttl, detail}`` — ``ok`` covers
82
- signature + structure + lookup; combining ``ok`` with ``fresh`` is the caller's policy.
82
+
83
+ **Trust-anchor separation (v1.9.1, external review #8/#12):** a status list signed by the
84
+ SAME key that signed the receipt carries no *independent* revocation assurance — the issuer
85
+ simply attests its own "still valid" state, and can flip it at will. Pass
86
+ ``receipt_issuer_pubkey`` (the bundle's signing key) and the result reports
87
+ ``self_issued=True`` when the status issuer key equals it. This is REPORTED, not fatal — the
88
+ relying party decides whether self-issued revocation is acceptable for its threat model (it
89
+ often is not; a distinct, independently-operated status authority is the stronger anchor).
90
+
91
+ Returns ``{ok, status, status_label, fresh, self_issued, iat, exp, ttl, detail}`` — ``ok``
92
+ covers signature + structure + lookup; combining ``ok`` with ``fresh``/``self_issued`` is the
93
+ caller's policy.
83
94
  """
84
95
  result = {"ok": False, "status": None, "status_label": None, "fresh": None,
85
- "iat": None, "exp": None, "ttl": None, "detail": ""}
96
+ "self_issued": None, "iat": None, "exp": None, "ttl": None, "detail": ""}
97
+ if receipt_issuer_pubkey is not None:
98
+ # hmac.compare_digest for a constant-time compare of the two public keys (defensive; the
99
+ # values are public, but consistent with the codebase's compare discipline).
100
+ import hmac as _hmac # noqa: PLC0415
101
+ # SYMMETRISCHER Typ-Guard: beide MUESSEN bytes/bytearray sein, sonst crasht bytes(str) mit TypeError
102
+ # statt fail-closed (verify_status_snapshot deklariert 'never crashes'). Non-bytes receipt_issuer_pubkey
103
+ # (str/int/list) → self_issued bleibt False (kein Crash, kein Fake-True).
104
+ result["self_issued"] = (isinstance(issuer_pubkey, (bytes, bytearray))
105
+ and isinstance(receipt_issuer_pubkey, (bytes, bytearray))
106
+ and len(issuer_pubkey) == len(receipt_issuer_pubkey)
107
+ and _hmac.compare_digest(bytes(issuer_pubkey),
108
+ bytes(receipt_issuer_pubkey)))
86
109
  if status_list_token.count(".") != 2:
87
110
  result["detail"] = "not a compact JWS"
88
111
  return result
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: proofbundle
3
- Version: 1.9.0
3
+ Version: 1.9.1
4
4
  Summary: Emit and verify portable cryptographic evidence bundles, offline: Ed25519 + RFC 6962 Merkle + optional SD-JWT.
5
5
  Author: Konrad Gruszka
6
6
  License: MIT
@@ -145,6 +145,7 @@ flowchart LR
145
145
  | For… | Read |
146
146
  |---|---|
147
147
  | Skeptics (why not SHA-256 / Sigstore / trust the issuer) | [docs/FAQ.md](docs/FAQ.md) |
148
+ | New to this? plain-terms glossary | [docs/GLOSSARY.md](docs/GLOSSARY.md) |
148
149
  | Reviewers (30-minute adversarial audit path) | [docs/REVIEWERS.md](docs/REVIEWERS.md) |
149
150
  | Where every trust anchor comes from | [docs/TRUST_ANCHORS.md](docs/TRUST_ANCHORS.md) |
150
151
  | The demos, tier by tier | [docs/DEMO.md](docs/DEMO.md) |
@@ -157,8 +158,7 @@ flowchart LR
157
158
 
158
159
  ```bash
159
160
  pip install proofbundle # core: offline verify + plain emit (dependency-free)
160
- pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds rfc8785 JCS)
161
- pip install "proofbundle[eval]" # emit eval receipts (adds an RFC 8785 canonicalizer)
161
+ pip install "proofbundle[eval]" # + eval receipts, prereg, and the demo (adds an RFC 8785 JCS canonicalizer)
162
162
  pip install "proofbundle[inspect]" # inspect_ai adapter + hook
163
163
  pip install "proofbundle[pq]" # verify ML-DSA-44 (post-quantum) witness cosignatures
164
164
  ```
@@ -168,7 +168,7 @@ Requires Python 3.10+. The verify path never rolls its own crypto — Ed25519 co
168
168
 
169
169
  ## Status & scope
170
170
 
171
- Beta, SemVer-committed, 299 tests + a CI mutation gate + property-based parser fuzzing. Correctness
171
+ Beta, SemVer-committed, 303 tests + a CI mutation gate + property-based parser fuzzing. Correctness
172
172
  is anchored to external RFC 6962 vectors and a real Rekor proof, not just its own bundles. It is
173
173
  **not** a log service, a full in-toto client, a TEE, a consensus network, or a compliance product
174
174
  by itself — it is the small, offline, standards-native receipt layer between them. Security policy:
@@ -83,6 +83,15 @@ if given is not None:
83
83
  _must_not_crash(verify_status_snapshot, token, expected_uri="u", index=0,
84
84
  issuer_pubkey=b"\x00" * 32)
85
85
 
86
+ @settings(max_examples=200, deadline=None)
87
+ @given(_texts, st.one_of(st.text(min_size=32, max_size=32), st.integers(),
88
+ st.lists(st.integers()), st.none(), st.binary(max_size=64)))
89
+ def test_verify_status_snapshot_never_crashes_on_malformed_receipt_key(self, token, rk):
90
+ # v1.9.1 self_issued: receipt_issuer_pubkey darf JEDEN Typ tragen ohne die 'never crashes'-Zusage
91
+ # zu brechen (der symmetrische Typ-Guard fängt non-bytes → self_issued=False statt TypeError).
92
+ _must_not_crash(verify_status_snapshot, token, expected_uri="u", index=0,
93
+ issuer_pubkey=b"\x00" * 32, receipt_issuer_pubkey=rk)
94
+
86
95
 
87
96
  if __name__ == "__main__":
88
97
  unittest.main()
@@ -177,3 +177,35 @@ class TestFreshnessAndStrictTypes(unittest.TestCase):
177
177
  res = verify_status_snapshot(f"{h}.{p}.{sig}", expected_uri=URI, index=0, issuer_pubkey=self.pub)
178
178
  self.assertFalse(res["ok"])
179
179
  self.assertIn("exp", res["detail"])
180
+
181
+
182
+ class TestSelfIssuedSeparation(unittest.TestCase):
183
+ """v1.9.1 (#8/#12): a status list signed by the receipt key has no independent revocation
184
+ assurance — verify_status_snapshot reports self_issued so the relying party can refuse it."""
185
+
186
+ def setUp(self):
187
+ self.status_signer = generate_signer()
188
+ self.status_pub = _raw(self.status_signer)
189
+ self.token = issue_status_list_token([0, 1], uri=URI, signer=self.status_signer, iat=IAT)
190
+
191
+ def test_self_issued_none_when_not_asked(self):
192
+ res = verify_status_snapshot(self.token, expected_uri=URI, index=0,
193
+ issuer_pubkey=self.status_pub)
194
+ self.assertTrue(res["ok"])
195
+ self.assertIsNone(res["self_issued"]) # not requested → not judged
196
+
197
+ def test_self_issued_true_when_same_key(self):
198
+ res = verify_status_snapshot(self.token, expected_uri=URI, index=0,
199
+ issuer_pubkey=self.status_pub,
200
+ receipt_issuer_pubkey=self.status_pub)
201
+ self.assertTrue(res["ok"]) # still valid crypto
202
+ self.assertTrue(res["self_issued"]) # but flagged as self-issued
203
+
204
+ def test_self_issued_false_when_distinct_key(self):
205
+ receipt_pub = _raw(generate_signer()) # an independent receipt issuer
206
+ res = verify_status_snapshot(self.token, expected_uri=URI, index=1,
207
+ issuer_pubkey=self.status_pub,
208
+ receipt_issuer_pubkey=receipt_pub)
209
+ self.assertTrue(res["ok"])
210
+ self.assertFalse(res["self_issued"]) # distinct anchor → not self-issued
211
+ self.assertEqual(res["status_label"], "INVALID")
File without changes
File without changes