coderifts-verifier 0.1.0__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.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CodeRifts
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,124 @@
1
+ Metadata-Version: 2.4
2
+ Name: coderifts-verifier
3
+ Version: 0.1.0
4
+ Summary: Offline verifier for CodeRifts chain receipts and DSSE/in-toto envelopes. Ed25519, no network.
5
+ License-Expression: MIT
6
+ Project-URL: Homepage, https://github.com/coderifts/receipt-verifier
7
+ Requires-Python: >=3.10
8
+ Description-Content-Type: text/markdown
9
+ License-File: LICENSE
10
+ Requires-Dist: cryptography>=41
11
+ Dynamic: license-file
12
+
13
+ # coderifts-verifier (Python)
14
+
15
+ Verify a CodeRifts chain receipt — or a DSSE / in-toto envelope carrying one —
16
+ offline, in Python, without calling CodeRifts.
17
+
18
+ ```python
19
+ from coderifts_verifier import unwrap, verify_receipt, keyring_from_document
20
+
21
+ keyring = keyring_from_document(registry_json, "my-registry.json")
22
+ token = unwrap(whatever_arrived) # compact token, or DSSE -> compact
23
+ result = verify_receipt(token, {"keyring": keyring})
24
+
25
+ if result["valid"]:
26
+ ...
27
+ else:
28
+ handle(result["status"]) # INVALID_SIGNATURE, UNKNOWN_KEY, ...
29
+ ```
30
+
31
+ Ed25519 via `cryptography`. No network on the verify path.
32
+
33
+ ## This is not the CodeRifts SDK
34
+
35
+ Two different packages, on purpose:
36
+
37
+ | | `coderifts` (SDK) | `coderifts-verifier` (this) |
38
+ | --- | --- | --- |
39
+ | does | calls the API to get a decision | verifies evidence you already hold |
40
+ | crypto | none — deliberately | Ed25519 |
41
+ | network | yes, that is the point | none on the verify path |
42
+
43
+ A dependency on `cryptography` is correct here: this package checks signatures.
44
+ The SDK stays crypto-free.
45
+
46
+ ## Unwrapping is not verification
47
+
48
+ `unwrap` and `from_dsse` check **no signature**. An envelope wrapping a receipt
49
+ with a bad signature unwraps cleanly and then fails `verify_receipt`, exactly as
50
+ that compact token would have — the signature is over the compact bytes.
51
+
52
+ The DSSE envelope proves **exactly what the compact receipt proves**. A standard
53
+ container does not strengthen a claim.
54
+
55
+ ```python
56
+ token = from_dsse(envelope) # succeeds — nothing checked yet
57
+ verify_receipt(token, {"keyring": keyring}) # this is the verdict
58
+ ```
59
+
60
+ ## Cross-language agreement
61
+
62
+ The signed-bytes reconstruction is byte-identical to the JavaScript verifier —
63
+ `verify.js` says so in its own source, and `tests/xlang-vectors.json` holds
64
+ receipts produced and verified by that implementation. The test suite asserts
65
+ Python reaches the **same verdict on the same bytes**, for a passing receipt and
66
+ for two distinct failure modes.
67
+
68
+ Two implementations disagreeing about whether a receipt is valid is the failure
69
+ a multi-language verifier exists to avoid, so the agreement is pinned rather than
70
+ assumed.
71
+
72
+ The verification core (`coderifts_verifier/_verify.py`) is **vendored** from the
73
+ reference verifier, not reimplemented. The only edit is the import path for the
74
+ vendored `arity` helper; the file is otherwise byte-identical, so there is one
75
+ definition of what a valid receipt is rather than two that can drift.
76
+
77
+ ## Refusals are named
78
+
79
+ `from_dsse` raises `DsseError` with a `code`:
80
+
81
+ | code | when |
82
+ | --- | --- |
83
+ | `MALFORMED` | not an envelope; unreadable payload; zero, two, or signature-less entries; missing `encoded_payload` |
84
+ | `UNSUPPORTED` | a `payloadType`, `predicateType`, or compact `form` this version does not implement |
85
+ | `PREDICATE_MISMATCH` | the decoded `predicate.fields` disagree with the preserved payload segment |
86
+
87
+ That last one matters: the predicate carries the payload twice, once as bytes and
88
+ once decoded for a reader. If they disagree, the readable half describes
89
+ something the signed half does not contain — refuse rather than hand back bytes
90
+ that do not match the envelope someone read.
91
+
92
+ ## What a valid verdict proves
93
+
94
+ * a holder of a key in **the keyring you supplied** signed this receipt;
95
+ * the receipt's signed fields are unmodified;
96
+ * per `status`: whether it is current, or was issued while a now-retired key was
97
+ still valid.
98
+
99
+ ## What it does NOT prove
100
+
101
+ * **That anything happened.** A receipt is a signed statement about a decision,
102
+ not an observation of an effect.
103
+ * **That the key is uncompromised.** Everything reduces to "a holder of this key
104
+ signed this". Custody is yours.
105
+ * **That the receipt is the right one for what you are about to do.** Scope
106
+ matching — does this receipt cover THIS operation on THIS target — is the
107
+ caller's check, against the decision envelope. This verifies the receipt.
108
+ * **That a human saw anything.**
109
+
110
+ ## Format
111
+
112
+ `RECEIPT_FORMAT.md` in the reference verifier is the specification; section 9
113
+ covers the DSSE / in-toto export. The constants here are byte-exact to it and
114
+ pinned by a test.
115
+
116
+ ## Tests
117
+
118
+ ```bash
119
+ python3 -m pytest tests/ -q
120
+ ```
121
+
122
+ ## License
123
+
124
+ MIT
@@ -0,0 +1,112 @@
1
+ # coderifts-verifier (Python)
2
+
3
+ Verify a CodeRifts chain receipt — or a DSSE / in-toto envelope carrying one —
4
+ offline, in Python, without calling CodeRifts.
5
+
6
+ ```python
7
+ from coderifts_verifier import unwrap, verify_receipt, keyring_from_document
8
+
9
+ keyring = keyring_from_document(registry_json, "my-registry.json")
10
+ token = unwrap(whatever_arrived) # compact token, or DSSE -> compact
11
+ result = verify_receipt(token, {"keyring": keyring})
12
+
13
+ if result["valid"]:
14
+ ...
15
+ else:
16
+ handle(result["status"]) # INVALID_SIGNATURE, UNKNOWN_KEY, ...
17
+ ```
18
+
19
+ Ed25519 via `cryptography`. No network on the verify path.
20
+
21
+ ## This is not the CodeRifts SDK
22
+
23
+ Two different packages, on purpose:
24
+
25
+ | | `coderifts` (SDK) | `coderifts-verifier` (this) |
26
+ | --- | --- | --- |
27
+ | does | calls the API to get a decision | verifies evidence you already hold |
28
+ | crypto | none — deliberately | Ed25519 |
29
+ | network | yes, that is the point | none on the verify path |
30
+
31
+ A dependency on `cryptography` is correct here: this package checks signatures.
32
+ The SDK stays crypto-free.
33
+
34
+ ## Unwrapping is not verification
35
+
36
+ `unwrap` and `from_dsse` check **no signature**. An envelope wrapping a receipt
37
+ with a bad signature unwraps cleanly and then fails `verify_receipt`, exactly as
38
+ that compact token would have — the signature is over the compact bytes.
39
+
40
+ The DSSE envelope proves **exactly what the compact receipt proves**. A standard
41
+ container does not strengthen a claim.
42
+
43
+ ```python
44
+ token = from_dsse(envelope) # succeeds — nothing checked yet
45
+ verify_receipt(token, {"keyring": keyring}) # this is the verdict
46
+ ```
47
+
48
+ ## Cross-language agreement
49
+
50
+ The signed-bytes reconstruction is byte-identical to the JavaScript verifier —
51
+ `verify.js` says so in its own source, and `tests/xlang-vectors.json` holds
52
+ receipts produced and verified by that implementation. The test suite asserts
53
+ Python reaches the **same verdict on the same bytes**, for a passing receipt and
54
+ for two distinct failure modes.
55
+
56
+ Two implementations disagreeing about whether a receipt is valid is the failure
57
+ a multi-language verifier exists to avoid, so the agreement is pinned rather than
58
+ assumed.
59
+
60
+ The verification core (`coderifts_verifier/_verify.py`) is **vendored** from the
61
+ reference verifier, not reimplemented. The only edit is the import path for the
62
+ vendored `arity` helper; the file is otherwise byte-identical, so there is one
63
+ definition of what a valid receipt is rather than two that can drift.
64
+
65
+ ## Refusals are named
66
+
67
+ `from_dsse` raises `DsseError` with a `code`:
68
+
69
+ | code | when |
70
+ | --- | --- |
71
+ | `MALFORMED` | not an envelope; unreadable payload; zero, two, or signature-less entries; missing `encoded_payload` |
72
+ | `UNSUPPORTED` | a `payloadType`, `predicateType`, or compact `form` this version does not implement |
73
+ | `PREDICATE_MISMATCH` | the decoded `predicate.fields` disagree with the preserved payload segment |
74
+
75
+ That last one matters: the predicate carries the payload twice, once as bytes and
76
+ once decoded for a reader. If they disagree, the readable half describes
77
+ something the signed half does not contain — refuse rather than hand back bytes
78
+ that do not match the envelope someone read.
79
+
80
+ ## What a valid verdict proves
81
+
82
+ * a holder of a key in **the keyring you supplied** signed this receipt;
83
+ * the receipt's signed fields are unmodified;
84
+ * per `status`: whether it is current, or was issued while a now-retired key was
85
+ still valid.
86
+
87
+ ## What it does NOT prove
88
+
89
+ * **That anything happened.** A receipt is a signed statement about a decision,
90
+ not an observation of an effect.
91
+ * **That the key is uncompromised.** Everything reduces to "a holder of this key
92
+ signed this". Custody is yours.
93
+ * **That the receipt is the right one for what you are about to do.** Scope
94
+ matching — does this receipt cover THIS operation on THIS target — is the
95
+ caller's check, against the decision envelope. This verifies the receipt.
96
+ * **That a human saw anything.**
97
+
98
+ ## Format
99
+
100
+ `RECEIPT_FORMAT.md` in the reference verifier is the specification; section 9
101
+ covers the DSSE / in-toto export. The constants here are byte-exact to it and
102
+ pinned by a test.
103
+
104
+ ## Tests
105
+
106
+ ```bash
107
+ python3 -m pytest tests/ -q
108
+ ```
109
+
110
+ ## License
111
+
112
+ MIT
@@ -0,0 +1,62 @@
1
+ """CodeRifts offline verifier for Python.
2
+
3
+ Verify a CodeRifts chain receipt, or a DSSE / in-toto envelope carrying one,
4
+ without calling CodeRifts. Offline Ed25519 against a keyring you pin.
5
+
6
+ from coderifts_verifier import unwrap, verify_receipt
7
+
8
+ token = unwrap(whatever_arrived) # compact token, or DSSE -> compact
9
+ result = verify_receipt(token, {"keyring": my_keyring})
10
+ if result["valid"]:
11
+ ...
12
+
13
+ UNWRAPPING IS NOT VERIFICATION. ``unwrap`` / ``from_dsse`` check no signature: a
14
+ DSSE envelope wrapping a bad receipt unwraps cleanly and then fails
15
+ ``verify_receipt``, exactly as that compact token would have. The DSSE proves
16
+ exactly what the compact receipt proves -- a standard container does not
17
+ strengthen a claim.
18
+
19
+ The verification core (``_verify.py``) is VENDORED from the reference JS/Python
20
+ verifier rather than reimplemented, so there is one definition of what a valid
21
+ receipt is. Its signed-bytes reconstruction is byte-identical to ``verify.js``;
22
+ the cross-language tests pin that against shared vectors.
23
+ """
24
+
25
+ from ._verify import ( # noqa: F401
26
+ canonical_json,
27
+ derive_status,
28
+ keyring_from_document,
29
+ reconstruct_signed_input,
30
+ verify_chain,
31
+ verify_receipt,
32
+ )
33
+ from .dsse import ( # noqa: F401
34
+ FORM_ATTESTATION,
35
+ FORM_RECEIPT,
36
+ PAYLOAD_TYPE,
37
+ PREDICATE_TYPE,
38
+ STATEMENT_TYPE,
39
+ DsseError,
40
+ from_dsse,
41
+ looks_like_dsse,
42
+ unwrap,
43
+ )
44
+
45
+ __all__ = [
46
+ "verify_receipt",
47
+ "verify_chain",
48
+ "from_dsse",
49
+ "unwrap",
50
+ "looks_like_dsse",
51
+ "DsseError",
52
+ "keyring_from_document",
53
+ "canonical_json",
54
+ "derive_status",
55
+ "reconstruct_signed_input",
56
+ "PAYLOAD_TYPE",
57
+ "STATEMENT_TYPE",
58
+ "PREDICATE_TYPE",
59
+ "FORM_RECEIPT",
60
+ "FORM_ATTESTATION",
61
+ ]
62
+ __version__ = "0.1.0"
@@ -0,0 +1,96 @@
1
+ """1961 TAG 1 -- finding the receipt that belongs to a commit (Python side).
2
+
3
+ Byte-for-byte the same convention as ``receipt-verifier/receipt-from-commit.js``: the
4
+ ``CodeRifts-Receipt`` git trailer, or ``.coderifts/receipts/<full-sha>.json``. See
5
+ ``receipt-verifier/docs/receipt-commit-binding.md`` for why both exist and why the trailer wins.
6
+
7
+ WARNING -- THE CARRIER PROVES NOTHING. Whichever one delivers the token, the token is then
8
+ verified exactly as if it had been pasted on the command line. A forged sidecar yields
9
+ INVALID_SIGNATURE, not a false pass. The sidecar is a POINTER; the signature is the authority.
10
+ """
11
+
12
+ import json
13
+ import os
14
+ import pathlib
15
+ import subprocess
16
+
17
+ TRAILER_KEY = "CodeRifts-Receipt"
18
+ SIDECAR_DIR = os.path.join(".coderifts", "receipts")
19
+
20
+
21
+ def _git(args, cwd):
22
+ return subprocess.run(
23
+ ["git", *args], cwd=cwd, capture_output=True, text=True, check=True,
24
+ ).stdout.strip()
25
+
26
+
27
+ def resolve_sha(ref, cwd):
28
+ """Abbreviations collide, and a verifier resolving one itself would be guessing which commit
29
+ the operator meant. Git knows; ask it."""
30
+ return _git(["rev-parse", ref + "^{commit}"], cwd)
31
+
32
+
33
+ def receipt_from_trailer(sha, cwd):
34
+ raw = _git(["log", "-1", "--format=%%(trailers:key=%s,valueonly)" % TRAILER_KEY, sha], cwd)
35
+ if not raw:
36
+ return None
37
+ # Unfold: git wraps long trailer values, and a receipt token is long. A token has no
38
+ # whitespace in it, so joining the trimmed lines is lossless.
39
+ token = "".join(line.strip() for line in raw.splitlines())
40
+ return token or None
41
+
42
+
43
+ def receipt_from_sidecar(sha, cwd):
44
+ """A MALFORMED SIDECAR IS NOT "NO SIDECAR".
45
+
46
+ Treating it as absent would let a corrupted pointer fall through to the trailer, or to
47
+ "nothing found" -- both of which read as an innocent absence rather than the broken file it is.
48
+ """
49
+ path = pathlib.Path(cwd) / SIDECAR_DIR / (sha + ".json")
50
+ if not path.exists():
51
+ return None
52
+ try:
53
+ doc = json.loads(path.read_text())
54
+ except Exception as e:
55
+ raise ValueError("sidecar %s is not valid JSON: %s" % (path, e))
56
+ token = (doc or {}).get("receipt") or (doc or {}).get("token")
57
+ if not token:
58
+ raise ValueError('sidecar %s has no "receipt" field' % path)
59
+ return {"token": str(token), "envelope": (doc or {}).get("envelope"), "file": str(path)}
60
+
61
+
62
+ def receipt_for_commit(ref, cwd=None):
63
+ """Returns {sha, token, envelope, carrier}. Raises on anything that is not a clean find.
64
+
65
+ WARNING -- TRAILER WINS, BUT A DISAGREEMENT IS REFUSED RATHER THAN RESOLVED. The trailer is
66
+ inside the commit object and the sidecar is a file anyone can edit, so where both exist the
67
+ immutable one is authoritative. When they DISAGREE, silently preferring the trailer would hide
68
+ precisely the tampering the precedence rule exists to make visible.
69
+ """
70
+ cwd = cwd or os.getcwd()
71
+ try:
72
+ sha = resolve_sha(ref, cwd)
73
+ except subprocess.CalledProcessError as e:
74
+ raise ValueError("cannot resolve %s in %s: %s" % (ref, cwd, (e.stderr or "").splitlines()[:1]))
75
+
76
+ trailer = receipt_from_trailer(sha, cwd)
77
+ sidecar = receipt_from_sidecar(sha, cwd)
78
+
79
+ if trailer and sidecar:
80
+ if trailer != sidecar["token"]:
81
+ raise ValueError(
82
+ "receipt conflict for %s: the commit trailer and %s carry DIFFERENT receipts. "
83
+ "The trailer is covered by the commit SHA and the sidecar is not, so they should "
84
+ "never disagree -- resolve it deliberately rather than letting one win silently."
85
+ % (sha, sidecar["file"])
86
+ )
87
+ return {"sha": sha, "token": trailer, "envelope": sidecar["envelope"], "carrier": "both"}
88
+ if trailer:
89
+ return {"sha": sha, "token": trailer, "envelope": None, "carrier": "trailer"}
90
+ if sidecar:
91
+ return {"sha": sha, "token": sidecar["token"], "envelope": sidecar["envelope"], "carrier": "sidecar"}
92
+
93
+ raise ValueError(
94
+ "no receipt attached to %s: no %s trailer and no %s/%s.json. That means no receipt was "
95
+ "ATTACHED -- it does not mean none exists." % (sha, TRAILER_KEY, SIDECAR_DIR, sha)
96
+ )