provenex-verifier 1.0.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.
Files changed (37) hide show
  1. provenex_verifier-1.0.0/CHANGELOG.md +26 -0
  2. provenex_verifier-1.0.0/CODEOWNERS +4 -0
  3. provenex_verifier-1.0.0/CODE_OF_CONDUCT.md +65 -0
  4. provenex_verifier-1.0.0/CONTRIBUTING.md +39 -0
  5. provenex_verifier-1.0.0/LICENSE +21 -0
  6. provenex_verifier-1.0.0/MANIFEST.in +15 -0
  7. provenex_verifier-1.0.0/Makefile +17 -0
  8. provenex_verifier-1.0.0/PKG-INFO +130 -0
  9. provenex_verifier-1.0.0/README.md +98 -0
  10. provenex_verifier-1.0.0/SECURITY.md +49 -0
  11. provenex_verifier-1.0.0/provenex_verifier/__init__.py +33 -0
  12. provenex_verifier-1.0.0/provenex_verifier/canonical.py +57 -0
  13. provenex_verifier-1.0.0/provenex_verifier/cli.py +109 -0
  14. provenex_verifier-1.0.0/provenex_verifier/merkle.py +108 -0
  15. provenex_verifier-1.0.0/provenex_verifier/py.typed +0 -0
  16. provenex_verifier-1.0.0/provenex_verifier/verifier.py +240 -0
  17. provenex_verifier-1.0.0/provenex_verifier.egg-info/PKG-INFO +130 -0
  18. provenex_verifier-1.0.0/provenex_verifier.egg-info/SOURCES.txt +35 -0
  19. provenex_verifier-1.0.0/provenex_verifier.egg-info/dependency_links.txt +1 -0
  20. provenex_verifier-1.0.0/provenex_verifier.egg-info/entry_points.txt +2 -0
  21. provenex_verifier-1.0.0/provenex_verifier.egg-info/requires.txt +5 -0
  22. provenex_verifier-1.0.0/provenex_verifier.egg-info/top_level.txt +1 -0
  23. provenex_verifier-1.0.0/pyproject.toml +81 -0
  24. provenex_verifier-1.0.0/setup.cfg +4 -0
  25. provenex_verifier-1.0.0/test-vectors/README.md +25 -0
  26. provenex_verifier-1.0.0/test-vectors/ed25519_public_key.pem +3 -0
  27. provenex_verifier-1.0.0/test-vectors/generate.py +226 -0
  28. provenex_verifier-1.0.0/test-vectors/hmac_receipt.json +36 -0
  29. provenex_verifier-1.0.0/test-vectors/tampered_field_receipt.json +78 -0
  30. provenex_verifier-1.0.0/test-vectors/valid_full_receipt.json +78 -0
  31. provenex_verifier-1.0.0/test-vectors/valid_minimal_receipt.json +36 -0
  32. provenex_verifier-1.0.0/tests/__init__.py +0 -0
  33. provenex_verifier-1.0.0/tests/conftest.py +137 -0
  34. provenex_verifier-1.0.0/tests/test_canonical.py +83 -0
  35. provenex_verifier-1.0.0/tests/test_cli.py +123 -0
  36. provenex_verifier-1.0.0/tests/test_merkle.py +238 -0
  37. provenex_verifier-1.0.0/tests/test_verifier.py +319 -0
@@ -0,0 +1,26 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project are documented here. Format follows [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
4
+
5
+ ## [1.0.0] - 2026-05-25
6
+
7
+ Initial release.
8
+
9
+ ### Added
10
+
11
+ * `verify_receipt(receipt, public_key)` returning `VerificationResult`.
12
+ * `canonicalize(receipt)` exposed publicly so callers can audit the canonical-payload step.
13
+ * `verify_inclusion_proof(leaf_hash, proof, tree_root, leaf_index, tree_size)` for RFC 6962 Merkle inclusion proofs.
14
+ * `provenex-verify` CLI with stdin support and standard exit codes (0/1/2).
15
+ * Static test vectors under `test-vectors/` plus a generator script.
16
+ * 100% line and branch coverage on the verifier modules.
17
+ * CI matrix across Python 3.9, 3.10, 3.11, 3.12, and 3.13.
18
+
19
+ ### Design notes
20
+
21
+ * Ed25519 only. HMAC receipts are rejected with a clear error rather than silently accepted, because HMAC is symmetric and out of scope for third-party verification.
22
+ * No network calls. Safe to run in air-gapped environments.
23
+ * No dependency on `provenex-core`. The library implements the receipt-format spec directly.
24
+ * Single runtime dependency: `cryptography` (for Ed25519 primitives).
25
+
26
+ [1.0.0]: https://github.com/aoptimystic/provenex-verifier-python/releases/tag/v1.0.0
@@ -0,0 +1,4 @@
1
+ # Default owner for everything in this repo.
2
+ # Replace with actual GitHub handles before publishing.
3
+
4
+ * @saurabh-provenex
@@ -0,0 +1,65 @@
1
+ # Contributor Covenant Code of Conduct
2
+
3
+ ## Our Pledge
4
+
5
+ We as members, contributors, and leaders pledge to make participation in our
6
+ community a harassment-free experience for everyone, regardless of age, body
7
+ size, visible or invisible disability, ethnicity, sex characteristics, gender
8
+ identity and expression, level of experience, education, socio-economic status,
9
+ nationality, personal appearance, race, religion, or sexual identity
10
+ and orientation.
11
+
12
+ We pledge to act and interact in ways that contribute to an open, welcoming,
13
+ diverse, inclusive, and healthy community.
14
+
15
+ ## Our Standards
16
+
17
+ Examples of behavior that contributes to a positive environment for our
18
+ community include:
19
+
20
+ * Demonstrating empathy and kindness toward other people
21
+ * Being respectful of differing opinions, viewpoints, and experiences
22
+ * Giving and gracefully accepting constructive feedback
23
+ * Accepting responsibility and apologizing to those affected by our mistakes,
24
+ and learning from the experience
25
+ * Focusing on what is best not just for us as individuals, but for the overall
26
+ community
27
+
28
+ Examples of unacceptable behavior include:
29
+
30
+ * The use of sexualized language or imagery, and sexual attention or advances of
31
+ any kind
32
+ * Trolling, insulting or derogatory comments, and personal or political attacks
33
+ * Public or private harassment
34
+ * Publishing others' private information, such as a physical or email address,
35
+ without their explicit permission
36
+ * Other conduct which could reasonably be considered inappropriate in a
37
+ professional setting
38
+
39
+ ## Enforcement Responsibilities
40
+
41
+ Community leaders are responsible for clarifying and enforcing our standards of
42
+ acceptable behavior and will take appropriate and fair corrective action in
43
+ response to any behavior that they deem inappropriate, threatening, offensive,
44
+ or harmful.
45
+
46
+ ## Scope
47
+
48
+ This Code of Conduct applies within all community spaces, and also applies when
49
+ an individual is officially representing the community in public spaces.
50
+
51
+ ## Enforcement
52
+
53
+ Instances of abusive, harassing, or otherwise unacceptable behavior may be
54
+ reported to the community leaders responsible for enforcement at
55
+ **contact@provenex.ai**. All complaints will be reviewed and investigated
56
+ promptly and fairly.
57
+
58
+ ## Attribution
59
+
60
+ This Code of Conduct is adapted from the [Contributor Covenant][homepage],
61
+ version 2.1, available at
62
+ [https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
63
+
64
+ [homepage]: https://www.contributor-covenant.org
65
+ [v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
@@ -0,0 +1,39 @@
1
+ # Contributing
2
+
3
+ Thank you for your interest in contributing. The verifier is intentionally small: scope discipline is the project's defining property.
4
+
5
+ ## Ground rules
6
+
7
+ 1. **No new runtime dependencies.** The library depends on `cryptography` and nothing else. Proposals to add dependencies require a clear rationale and will be considered carefully.
8
+ 2. **No network access at runtime.** The library must run unmodified in an air-gapped environment.
9
+ 3. **No bidirectional dependency on `provenex-core`.** The verifier implements the receipt-format spec directly. If the spec changes, this library changes; if `provenex-core` adds an unrelated feature, this library does not.
10
+ 4. **Spec changes are major versions.** Any change that breaks a previously valid receipt requires a major version bump and a corresponding CHANGELOG entry.
11
+
12
+ ## Development
13
+
14
+ ```
15
+ git clone https://github.com/aoptimystic/provenex-verifier-python
16
+ cd provenex-verifier-python
17
+ python -m pip install -e ".[test]"
18
+ make test
19
+ ```
20
+
21
+ Tests must:
22
+
23
+ * Maintain 100% line and branch coverage on the `provenex_verifier` package.
24
+ * Complete in under 5 seconds (`pytest -xvs`).
25
+ * Pass on Python 3.9 through 3.13.
26
+
27
+ ## Adding a test
28
+
29
+ Negative cases are first-class. Every new validation path should ship with a test for the failure mode it guards.
30
+
31
+ ## Pull requests
32
+
33
+ * Open a PR against `main`.
34
+ * Reference the issue or use case in the description.
35
+ * The CI matrix must pass on all supported Python versions before merge.
36
+
37
+ ## Releases
38
+
39
+ Releases are tagged `vX.Y.Z`, signed with Sigstore cosign, and published to PyPI with an SBOM. The first stable release is v1.0.0. See [CHANGELOG.md](CHANGELOG.md) for version history.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Saurabh Kulkarni
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,15 @@
1
+ include LICENSE
2
+ include README.md
3
+ include CHANGELOG.md
4
+ include SECURITY.md
5
+ include CONTRIBUTING.md
6
+ include CODE_OF_CONDUCT.md
7
+ include CODEOWNERS
8
+ include Makefile
9
+ include pyproject.toml
10
+ recursive-include provenex_verifier *.py py.typed
11
+ recursive-include tests *.py
12
+ recursive-include test-vectors *.py *.json *.pem *.md
13
+ global-exclude __pycache__
14
+ global-exclude *.py[cod]
15
+ global-exclude .DS_Store
@@ -0,0 +1,17 @@
1
+ .PHONY: install test cov lint clean build
2
+
3
+ install:
4
+ python -m pip install -e ".[test]"
5
+
6
+ test:
7
+ pytest -xvs
8
+
9
+ cov:
10
+ pytest --cov=provenex_verifier --cov-report=term-missing --cov-report=html
11
+
12
+ clean:
13
+ rm -rf build dist *.egg-info .coverage htmlcov .pytest_cache
14
+
15
+ build:
16
+ python -m pip install --upgrade build
17
+ python -m build
@@ -0,0 +1,130 @@
1
+ Metadata-Version: 2.4
2
+ Name: provenex-verifier
3
+ Version: 1.0.0
4
+ Summary: Standalone, offline verifier for Provenex receipts. Implements the receipt-format spec; no network, no telemetry, no dependencies on provenex-core.
5
+ Author-email: Provenex <contact@provenex.ai>
6
+ License: MIT
7
+ Project-URL: Homepage, https://provenex.ai
8
+ Project-URL: Source, https://github.com/aoptimystic/provenex-verifier-python
9
+ Project-URL: Issues, https://github.com/aoptimystic/provenex-verifier-python/issues
10
+ Project-URL: Documentation, https://github.com/aoptimystic/provenex-verifier-python#readme
11
+ Keywords: provenex,receipt,verifier,ed25519,merkle,rfc6962,ai-compliance,rag,llm-security
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: Information Technology
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Operating System :: OS Independent
17
+ Classifier: Programming Language :: Python :: 3
18
+ Classifier: Programming Language :: Python :: 3.9
19
+ Classifier: Programming Language :: Python :: 3.10
20
+ Classifier: Programming Language :: Python :: 3.11
21
+ Classifier: Programming Language :: Python :: 3.12
22
+ Classifier: Programming Language :: Python :: 3.13
23
+ Classifier: Topic :: Security :: Cryptography
24
+ Requires-Python: >=3.9
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: cryptography>=41.0.0
28
+ Provides-Extra: test
29
+ Requires-Dist: pytest>=7.0; extra == "test"
30
+ Requires-Dist: pytest-cov>=4.0; extra == "test"
31
+ Dynamic: license-file
32
+
33
+ # provenex-verifier
34
+
35
+ [![CI](https://github.com/aoptimystic/provenex-verifier-python/actions/workflows/ci.yml/badge.svg)](https://github.com/aoptimystic/provenex-verifier-python/actions/workflows/ci.yml)
36
+ [![PyPI](https://img.shields.io/pypi/v/provenex-verifier.svg)](https://pypi.org/project/provenex-verifier/)
37
+ [![Python](https://img.shields.io/pypi/pyversions/provenex-verifier.svg)](https://pypi.org/project/provenex-verifier/)
38
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
39
+
40
+ Standalone, offline verifier for [Provenex](https://provenex.ai) receipts.
41
+
42
+ A Provenex receipt is a signed JSON artifact that a regulator or third party can validate without contacting the issuing organization. This library is the reference verifier: it takes a receipt and an Ed25519 public key, and tells you whether the signature is valid over the canonical payload.
43
+
44
+ * **No network.** Runs in air-gapped environments. No telemetry, no analytics, no phone-home.
45
+ * **No `provenex-core` dependency.** Implements the receipt-format spec directly.
46
+ * **One runtime dependency** (`cryptography`) for Ed25519. Everything else is stdlib.
47
+ * **Tiny.** Under 500 lines of actual code.
48
+
49
+ ## Install
50
+
51
+ ```
52
+ pip install provenex-verifier
53
+ ```
54
+
55
+ ## 30-second example
56
+
57
+ ```python
58
+ import json
59
+ from provenex_verifier import verify_receipt
60
+
61
+ with open("receipt.json") as fh:
62
+ receipt = json.load(fh)
63
+ with open("issuer_public_key.pem", "rb") as fh:
64
+ public_key = fh.read()
65
+
66
+ result = verify_receipt(receipt, public_key)
67
+
68
+ if result.valid:
69
+ print(f"Receipt OK. Signed by {result.signer} at {result.signed_at}.")
70
+ else:
71
+ print("Receipt INVALID:")
72
+ for err in result.errors:
73
+ print(f" - {err}")
74
+ ```
75
+
76
+ ## Command line
77
+
78
+ ```
79
+ provenex-verify receipt.json --public-key issuer_public_key.pem
80
+ ```
81
+
82
+ Exit codes: `0` valid, `1` invalid, `2` usage error. Reads from stdin if the receipt argument is `-`.
83
+
84
+ ## API
85
+
86
+ | Symbol | Purpose |
87
+ | ------ | ------- |
88
+ | `verify_receipt(receipt, public_key) -> VerificationResult` | Verify an Ed25519 receipt signature. |
89
+ | `VerificationResult` | NamedTuple: `valid`, `signer`, `signed_at`, `errors`, `warnings`. |
90
+ | `canonicalize(receipt) -> bytes` | Reproduce the canonical signed-payload bytes. Exposed so callers can audit the canonicalization step independently of signature verification. |
91
+ | `verify_inclusion_proof(leaf_hash, proof, tree_root, leaf_index, tree_size) -> bool` | Verify an RFC 6962 Merkle inclusion proof. |
92
+
93
+ Every public function carries an inline docstring. See the source for parameter shapes and edge-case behavior.
94
+
95
+ ## What this library does *not* do
96
+
97
+ The verifier is deliberately narrow:
98
+
99
+ * No fingerprinting, normalization, indexing, or retrieval logic.
100
+ * No policy evaluation. Receipts record decisions; this library does not re-interpret them.
101
+ * No HTTP client, no key-fetching, no TSA lookups.
102
+ * No automatic Merkle inclusion check, no trajectory walking. Those are layered on top by callers.
103
+
104
+ If you need to issue receipts, use `provenex-core`. This library is purely the verification half.
105
+
106
+ ## Algorithm support
107
+
108
+ * **Ed25519** -- yes. The verifier accepts public keys in PEM (`SubjectPublicKeyInfo`), DER, or 32-byte raw form.
109
+ * **HMAC-SHA256** -- explicitly rejected. HMAC is symmetric: anyone able to verify can forge. A third-party verifier is by definition not the producer, so HMAC receipts are out of scope. Receipts using HMAC fail with a clear `unsupported signature algorithm` error.
110
+
111
+ ## Spec compatibility
112
+
113
+ This library implements:
114
+
115
+ * Receipt schema **2.5.0** canonicalization rules (`docs/receipt_format.md` in `provenex-core`).
116
+ * RFC 6962 Merkle inclusion proofs.
117
+
118
+ Test vectors live in [`test-vectors/`](test-vectors/) and are versioned alongside the library. The cross-compatibility test in `tests/test_merkle.py` pins a proof generated by `provenex-core` and asserts this verifier accepts it.
119
+
120
+ ## Stability promise
121
+
122
+ This library is at **v1.0.0** because the receipt format is. Any change that breaks a previously valid receipt is a breaking change and bumps the major version.
123
+
124
+ ## Security
125
+
126
+ Report vulnerabilities to **contact@provenex.ai** (see [SECURITY.md](SECURITY.md)). The library has no network surface area, but a verifier that mis-validates a tampered receipt is a security issue.
127
+
128
+ ## License
129
+
130
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,98 @@
1
+ # provenex-verifier
2
+
3
+ [![CI](https://github.com/aoptimystic/provenex-verifier-python/actions/workflows/ci.yml/badge.svg)](https://github.com/aoptimystic/provenex-verifier-python/actions/workflows/ci.yml)
4
+ [![PyPI](https://img.shields.io/pypi/v/provenex-verifier.svg)](https://pypi.org/project/provenex-verifier/)
5
+ [![Python](https://img.shields.io/pypi/pyversions/provenex-verifier.svg)](https://pypi.org/project/provenex-verifier/)
6
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE)
7
+
8
+ Standalone, offline verifier for [Provenex](https://provenex.ai) receipts.
9
+
10
+ A Provenex receipt is a signed JSON artifact that a regulator or third party can validate without contacting the issuing organization. This library is the reference verifier: it takes a receipt and an Ed25519 public key, and tells you whether the signature is valid over the canonical payload.
11
+
12
+ * **No network.** Runs in air-gapped environments. No telemetry, no analytics, no phone-home.
13
+ * **No `provenex-core` dependency.** Implements the receipt-format spec directly.
14
+ * **One runtime dependency** (`cryptography`) for Ed25519. Everything else is stdlib.
15
+ * **Tiny.** Under 500 lines of actual code.
16
+
17
+ ## Install
18
+
19
+ ```
20
+ pip install provenex-verifier
21
+ ```
22
+
23
+ ## 30-second example
24
+
25
+ ```python
26
+ import json
27
+ from provenex_verifier import verify_receipt
28
+
29
+ with open("receipt.json") as fh:
30
+ receipt = json.load(fh)
31
+ with open("issuer_public_key.pem", "rb") as fh:
32
+ public_key = fh.read()
33
+
34
+ result = verify_receipt(receipt, public_key)
35
+
36
+ if result.valid:
37
+ print(f"Receipt OK. Signed by {result.signer} at {result.signed_at}.")
38
+ else:
39
+ print("Receipt INVALID:")
40
+ for err in result.errors:
41
+ print(f" - {err}")
42
+ ```
43
+
44
+ ## Command line
45
+
46
+ ```
47
+ provenex-verify receipt.json --public-key issuer_public_key.pem
48
+ ```
49
+
50
+ Exit codes: `0` valid, `1` invalid, `2` usage error. Reads from stdin if the receipt argument is `-`.
51
+
52
+ ## API
53
+
54
+ | Symbol | Purpose |
55
+ | ------ | ------- |
56
+ | `verify_receipt(receipt, public_key) -> VerificationResult` | Verify an Ed25519 receipt signature. |
57
+ | `VerificationResult` | NamedTuple: `valid`, `signer`, `signed_at`, `errors`, `warnings`. |
58
+ | `canonicalize(receipt) -> bytes` | Reproduce the canonical signed-payload bytes. Exposed so callers can audit the canonicalization step independently of signature verification. |
59
+ | `verify_inclusion_proof(leaf_hash, proof, tree_root, leaf_index, tree_size) -> bool` | Verify an RFC 6962 Merkle inclusion proof. |
60
+
61
+ Every public function carries an inline docstring. See the source for parameter shapes and edge-case behavior.
62
+
63
+ ## What this library does *not* do
64
+
65
+ The verifier is deliberately narrow:
66
+
67
+ * No fingerprinting, normalization, indexing, or retrieval logic.
68
+ * No policy evaluation. Receipts record decisions; this library does not re-interpret them.
69
+ * No HTTP client, no key-fetching, no TSA lookups.
70
+ * No automatic Merkle inclusion check, no trajectory walking. Those are layered on top by callers.
71
+
72
+ If you need to issue receipts, use `provenex-core`. This library is purely the verification half.
73
+
74
+ ## Algorithm support
75
+
76
+ * **Ed25519** -- yes. The verifier accepts public keys in PEM (`SubjectPublicKeyInfo`), DER, or 32-byte raw form.
77
+ * **HMAC-SHA256** -- explicitly rejected. HMAC is symmetric: anyone able to verify can forge. A third-party verifier is by definition not the producer, so HMAC receipts are out of scope. Receipts using HMAC fail with a clear `unsupported signature algorithm` error.
78
+
79
+ ## Spec compatibility
80
+
81
+ This library implements:
82
+
83
+ * Receipt schema **2.5.0** canonicalization rules (`docs/receipt_format.md` in `provenex-core`).
84
+ * RFC 6962 Merkle inclusion proofs.
85
+
86
+ Test vectors live in [`test-vectors/`](test-vectors/) and are versioned alongside the library. The cross-compatibility test in `tests/test_merkle.py` pins a proof generated by `provenex-core` and asserts this verifier accepts it.
87
+
88
+ ## Stability promise
89
+
90
+ This library is at **v1.0.0** because the receipt format is. Any change that breaks a previously valid receipt is a breaking change and bumps the major version.
91
+
92
+ ## Security
93
+
94
+ Report vulnerabilities to **contact@provenex.ai** (see [SECURITY.md](SECURITY.md)). The library has no network surface area, but a verifier that mis-validates a tampered receipt is a security issue.
95
+
96
+ ## License
97
+
98
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,49 @@
1
+ # Security policy
2
+
3
+ ## Reporting a vulnerability
4
+
5
+ Email **contact@provenex.ai** with the subject line `[verifier] security`.
6
+
7
+ Please include:
8
+
9
+ * A description of the issue and its impact.
10
+ * Steps to reproduce or a proof-of-concept.
11
+ * Affected versions.
12
+ * Your name or handle if you would like credit; otherwise we will publish the disclosure anonymously.
13
+
14
+ Do **not** open public GitHub issues for security reports.
15
+
16
+ ## Response targets
17
+
18
+ | Step | Target |
19
+ | ---- | ------ |
20
+ | Acknowledgement of report | 2 business days |
21
+ | Initial triage and severity assessment | 5 business days |
22
+ | Fix or mitigation in a tagged release | Critical: 7 days. High: 30 days. Lower: best-effort. |
23
+ | Public disclosure | Coordinated with reporter; default 90 days after fix is shipped. |
24
+
25
+ ## Scope
26
+
27
+ In scope:
28
+
29
+ * Bugs that cause `verify_receipt` to return `valid=True` for a receipt whose signature does not match its canonical payload.
30
+ * Bugs that cause `verify_inclusion_proof` to return `True` for an invalid proof.
31
+ * Canonicalization mismatches that would let a producer and verifier disagree on the signed bytes.
32
+ * Resource-exhaustion attacks reachable through public APIs (CPU, memory, file descriptors) on adversarial input.
33
+
34
+ Out of scope:
35
+
36
+ * Issues that require an attacker already holding the issuer's private key.
37
+ * Policy interpretation, trajectory validation, or anything beyond cryptographic envelope verification: this library deliberately does not perform those checks.
38
+ * Bugs in upstream `cryptography` (please report to PyCA).
39
+
40
+ ## Supported versions
41
+
42
+ We support the latest minor release of the `1.x` line. Security fixes for older minor releases are best-effort.
43
+
44
+ ## Cryptographic primitives
45
+
46
+ * Ed25519 via [pyca/cryptography](https://github.com/pyca/cryptography). Constant-time signature verification at the primitive level.
47
+ * SHA-256 (stdlib `hashlib`) for RFC 6962 Merkle hashing.
48
+
49
+ There is no random-number generation, no key derivation, and no symmetric encryption in this library. Public-key loading is offline (no key-fetching).
@@ -0,0 +1,33 @@
1
+ """Standalone verifier for Provenex receipts.
2
+
3
+ Public API:
4
+
5
+ verify_receipt(receipt, public_key) -> VerificationResult
6
+ Verify the Ed25519 signature on a receipt.
7
+
8
+ canonicalize(receipt) -> bytes
9
+ Produce the canonical signed-payload bytes for a receipt.
10
+
11
+ verify_inclusion_proof(leaf_hash, proof, tree_root, leaf_index, tree_size) -> bool
12
+ Verify an RFC 6962 Merkle inclusion proof.
13
+
14
+ VerificationResult
15
+ NamedTuple returned by verify_receipt.
16
+
17
+ The library has a single runtime dependency (``cryptography``) and makes no
18
+ network calls. It is safe to run in an air-gapped environment.
19
+ """
20
+
21
+ from .canonical import canonicalize
22
+ from .merkle import verify_inclusion_proof
23
+ from .verifier import VerificationResult, verify_receipt
24
+
25
+ __version__ = "1.0.0"
26
+
27
+ __all__ = [
28
+ "VerificationResult",
29
+ "__version__",
30
+ "canonicalize",
31
+ "verify_inclusion_proof",
32
+ "verify_receipt",
33
+ ]
@@ -0,0 +1,57 @@
1
+ """Canonical JSON serialization for Provenex receipts.
2
+
3
+ The canonical form is what gets signed. Any verifier that wants to reproduce
4
+ the signed bytes must follow the same rules. The serialization is exposed
5
+ publicly so callers can audit the canonicalization step independently of
6
+ signature verification.
7
+
8
+ Rules:
9
+
10
+ * The ``signature`` block is stripped before serialization. Signing a receipt
11
+ is signing every byte of its canonical form *except* the signature itself.
12
+ * Keys are sorted lexicographically at every level of nesting.
13
+ * Separators are ``","`` and ``":"`` (no whitespace).
14
+ * Non-ASCII characters (smart quotes, accented letters, CJK) are emitted as
15
+ raw UTF-8, not as ``\\uXXXX`` escapes. This matches the producer side and
16
+ keeps the form portable to non-Python verifiers.
17
+ * ``NaN``, ``Infinity``, and ``-Infinity`` are rejected.
18
+
19
+ Matches provenex-core ``ProvenanceReceipt.canonical_payload`` as of receipt
20
+ schema 2.5.0.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ import json
26
+ from typing import Any, Mapping
27
+
28
+
29
+ def canonicalize(receipt: Mapping[str, Any]) -> bytes:
30
+ """Return the canonical signed-payload bytes for a receipt.
31
+
32
+ Args:
33
+ receipt: The receipt dict (typically the output of ``json.loads`` on a
34
+ receipt JSON file). The input is not mutated.
35
+
36
+ Returns:
37
+ UTF-8 encoded canonical JSON bytes, with any ``signature`` block
38
+ omitted.
39
+
40
+ Raises:
41
+ TypeError: If ``receipt`` is not a mapping, or contains values JSON
42
+ cannot serialize.
43
+ ValueError: If the receipt contains ``NaN``, ``Infinity``, or
44
+ ``-Infinity``.
45
+ """
46
+ if not isinstance(receipt, Mapping):
47
+ raise TypeError(
48
+ f"receipt must be a mapping (dict), got {type(receipt).__name__}"
49
+ )
50
+ payload = {k: v for k, v in receipt.items() if k != "signature"}
51
+ return json.dumps(
52
+ payload,
53
+ sort_keys=True,
54
+ separators=(",", ":"),
55
+ ensure_ascii=False,
56
+ allow_nan=False,
57
+ ).encode("utf-8")
@@ -0,0 +1,109 @@
1
+ """``provenex-verify`` command-line entry point.
2
+
3
+ Usage::
4
+
5
+ provenex-verify <receipt.json> --public-key <key.pem>
6
+ cat receipt.json | provenex-verify - --public-key key.pem
7
+
8
+ Exit codes:
9
+
10
+ * ``0`` -- signature verified.
11
+ * ``1`` -- signature did not verify (tampered receipt, wrong key, etc.).
12
+ * ``2`` -- usage error (missing file, malformed JSON, bad arguments).
13
+ """
14
+
15
+ from __future__ import annotations
16
+
17
+ import argparse
18
+ import json
19
+ import sys
20
+ from typing import List, Optional, TextIO
21
+
22
+ from . import __version__
23
+ from .verifier import verify_receipt
24
+
25
+
26
+ def _build_parser() -> argparse.ArgumentParser:
27
+ parser = argparse.ArgumentParser(
28
+ prog="provenex-verify",
29
+ description=(
30
+ "Verify the Ed25519 signature on a Provenex receipt. "
31
+ "Reads receipt JSON from a file (or '-' for stdin) and an "
32
+ "Ed25519 public key (PEM or DER) from disk."
33
+ ),
34
+ )
35
+ parser.add_argument(
36
+ "receipt",
37
+ help="Path to receipt JSON file, or '-' to read from stdin.",
38
+ )
39
+ parser.add_argument(
40
+ "--public-key",
41
+ required=True,
42
+ help="Path to Ed25519 public key (PEM SubjectPublicKeyInfo or DER).",
43
+ )
44
+ parser.add_argument(
45
+ "--quiet",
46
+ action="store_true",
47
+ help="Suppress per-field output; rely on exit code only.",
48
+ )
49
+ parser.add_argument(
50
+ "--version",
51
+ action="version",
52
+ version=f"provenex-verify {__version__}",
53
+ )
54
+ return parser
55
+
56
+
57
+ def main(
58
+ argv: Optional[List[str]] = None,
59
+ stdin: Optional[TextIO] = None,
60
+ stdout: Optional[TextIO] = None,
61
+ stderr: Optional[TextIO] = None,
62
+ ) -> int:
63
+ """Entry point. Returns an exit code; never raises on normal failures."""
64
+ stdin = stdin or sys.stdin
65
+ stdout = stdout or sys.stdout
66
+ stderr = stderr or sys.stderr
67
+
68
+ parser = _build_parser()
69
+ args = parser.parse_args(argv)
70
+
71
+ try:
72
+ if args.receipt == "-":
73
+ receipt = json.load(stdin)
74
+ else:
75
+ with open(args.receipt, "r", encoding="utf-8") as fh:
76
+ receipt = json.load(fh)
77
+ except OSError as exc:
78
+ print(f"error: could not read receipt: {exc}", file=stderr)
79
+ return 2
80
+ except json.JSONDecodeError as exc:
81
+ print(f"error: receipt is not valid JSON: {exc}", file=stderr)
82
+ return 2
83
+
84
+ try:
85
+ with open(args.public_key, "rb") as fh:
86
+ key_bytes = fh.read()
87
+ except OSError as exc:
88
+ print(f"error: could not read public key: {exc}", file=stderr)
89
+ return 2
90
+
91
+ result = verify_receipt(receipt, key_bytes)
92
+
93
+ if not args.quiet:
94
+ print(f"valid: {result.valid}", file=stdout)
95
+ print(f"signer: {result.signer or '(missing)'}", file=stdout)
96
+ print(
97
+ f"signed_at: {result.signed_at.isoformat() if result.signed_at else '(unknown)'}",
98
+ file=stdout,
99
+ )
100
+ for err in result.errors:
101
+ print(f"error: {err}", file=stderr)
102
+ for warn in result.warnings:
103
+ print(f"warning: {warn}", file=stderr)
104
+
105
+ return 0 if result.valid else 1
106
+
107
+
108
+ if __name__ == "__main__": # pragma: no cover
109
+ sys.exit(main())