nenrin-verify 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.
Files changed (33) hide show
  1. nenrin_verify-0.1.0/LICENSE +21 -0
  2. nenrin_verify-0.1.0/MANIFEST.in +3 -0
  3. nenrin_verify-0.1.0/PKG-INFO +110 -0
  4. nenrin_verify-0.1.0/README.md +90 -0
  5. nenrin_verify-0.1.0/pyproject.toml +36 -0
  6. nenrin_verify-0.1.0/setup.cfg +4 -0
  7. nenrin_verify-0.1.0/src/nenrin_verify/VENDORED.json +12 -0
  8. nenrin_verify-0.1.0/src/nenrin_verify/__init__.py +44 -0
  9. nenrin_verify-0.1.0/src/nenrin_verify/_js.py +505 -0
  10. nenrin_verify-0.1.0/src/nenrin_verify/agreement_verify.py +1243 -0
  11. nenrin_verify-0.1.0/src/nenrin_verify/cli.py +73 -0
  12. nenrin_verify-0.1.0/src/nenrin_verify/key_succession.py +140 -0
  13. nenrin_verify-0.1.0/src/nenrin_verify/provenance.py +977 -0
  14. nenrin_verify-0.1.0/src/nenrin_verify/py.typed +0 -0
  15. nenrin_verify-0.1.0/src/nenrin_verify/selftest.json +1 -0
  16. nenrin_verify-0.1.0/src/nenrin_verify.egg-info/PKG-INFO +110 -0
  17. nenrin_verify-0.1.0/src/nenrin_verify.egg-info/SOURCES.txt +31 -0
  18. nenrin_verify-0.1.0/src/nenrin_verify.egg-info/dependency_links.txt +1 -0
  19. nenrin_verify-0.1.0/src/nenrin_verify.egg-info/entry_points.txt +3 -0
  20. nenrin_verify-0.1.0/src/nenrin_verify.egg-info/requires.txt +1 -0
  21. nenrin_verify-0.1.0/src/nenrin_verify.egg-info/top_level.txt +1 -0
  22. nenrin_verify-0.1.0/tests/fixtures/bundles.json +1946 -0
  23. nenrin_verify-0.1.0/tests/fixtures/js_reports.json +285 -0
  24. nenrin_verify-0.1.0/tests/fixtures/make_fixtures.mjs +132 -0
  25. nenrin_verify-0.1.0/tests/parity/js_canon.mjs +11 -0
  26. nenrin_verify-0.1.0/tests/parity/js_runner.mjs +24 -0
  27. nenrin_verify-0.1.0/tests/parity/mutate.py +71 -0
  28. nenrin_verify-0.1.0/tests/test_agreement_frozen.py +37 -0
  29. nenrin_verify-0.1.0/tests/test_js_semantics_live.py +54 -0
  30. nenrin_verify-0.1.0/tests/test_parity_frozen.py +58 -0
  31. nenrin_verify-0.1.0/tests/test_parity_live.py +72 -0
  32. nenrin_verify-0.1.0/tests/test_vendored.py +24 -0
  33. nenrin_verify-0.1.0/tools/vendor.py +67 -0
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The HORIZONs Co., Ltd.
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,3 @@
1
+ include README.md LICENSE
2
+ recursive-include tests *.py *.mjs *.json
3
+ include tools/vendor.py
@@ -0,0 +1,110 @@
1
+ Metadata-Version: 2.4
2
+ Name: nenrin-verify
3
+ Version: 0.1.0
4
+ Summary: Recompute NENRIN evidence yourself, offline: A2A task provenance and two-party agreement records. Same reports as the npm nenrin-verify.
5
+ Author: The HORIZONs Co., Ltd.
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/ogasurfproject-jpg/horizon-shield/tree/main/workers/hs-ledger/nenrin/sdk-python
8
+ Project-URL: Source, https://github.com/ogasurfproject-jpg/horizon-shield
9
+ Project-URL: JavaScript twin, https://www.npmjs.com/package/nenrin-verify
10
+ Keywords: a2a,provenance,verification,did-key,ed25519,nenrin,agent,evidence,horizon-shield
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Topic :: Security :: Cryptography
14
+ Classifier: Intended Audience :: Developers
15
+ Requires-Python: >=3.9
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: cryptography>=41
19
+ Dynamic: license-file
20
+
21
+ # nenrin-verify (Python)
22
+
23
+ Recompute NENRIN evidence yourself, offline, in your own Python process. The Python twin of the npm package
24
+ [`nenrin-verify`](https://www.npmjs.com/package/nenrin-verify): same input, same report (the two limits are listed
25
+ under "Where the claim stops").
26
+
27
+ pip install nenrin-verify
28
+ nenrin-verify --selftest # 31/31 frozen bundles give the same report as npm nenrin-verify
29
+ nenrin-verify bundle.json # the provenance report; exit 0 accepted, 1 refused
30
+
31
+ ```python
32
+ import json, nenrin_verify as nv
33
+
34
+ bundle = nv.js_loads(open("bundle.json", "rb").read()) # parsed the way JSON.parse parses it
35
+ report = nv.verify_bundle(bundle) # did:key resolution, no network, no clock
36
+ print(report["verdict"], report["does_not_establish"])
37
+ print(nv.report_json(report)) # JSON text as the JavaScript CLI prints it (not json.dumps)
38
+ print(nv.report_sha256(report)) # compare with the JavaScript run, byte for byte
39
+ ```
40
+
41
+ One dependency, `cryptography`, for Ed25519. Nothing here opens a socket.
42
+
43
+ ## What it verifies
44
+
45
+ | module | what | held to |
46
+ |---|---|---|
47
+ | `verify_provenance`, `consume_evidence`, `posture_line`, `candidate_evidence_set`, `preflight_report` | one A2A task's provenance graph: the delegation chain observed by third-party witnesses (R1 to R4, witness and edge signatures), the caller's grant and the provider's execution receipt (E1 to E3, caller and provider signatures), the provider's pre-execution intent, the outcome's evidence pointer, and the digest link between the layers | npm nenrin-verify 0.2.3 (`nenrin_verify.mjs`, verifier 0.1.3): the same report, key for key |
48
+ | `agreement_verify.verify`, `nenrin-agreement-verify` | a two-party agreement record (`a2a-agreement-v1`, `v1.1`), including key succession across a rotation | the repository's own Python verifier, unchanged but for one import line and a header comment; it and the JavaScript verifier return the same report on 5,286 frozen cases |
49
+
50
+ ## How "same report" is checked
51
+
52
+ The JavaScript file is the reference. The port reads it line for line, and every place where Python and
53
+ JavaScript disagree by default (`x or y` on `{}`, `==` on `True` and `1`, `Date.parse` accepting February 31st,
54
+ Node's lenient base64, an own `__proto__` key that Object.assign turns into a prototype, the order V8's sort
55
+ gives values a numeric comparator cannot order) goes through one file,
56
+ `src/nenrin_verify/_js.py`, so the differences can be read in one place.
57
+
58
+ - **Frozen.** 31 bundles signed with keys derived from a public phrase, covering accepted graphs, refusals of every
59
+ layer, witness disagreement, provider equivocation, action bindings, non-ASCII and escapes, and the date edge
60
+ cases. For each, the report, the `consume_evidence` projection and the CLI output are byte-identical to the
61
+ JavaScript's. They ship in the package; `nenrin-verify --selftest` re-runs them on your machine.
62
+ - **Live.** Every path of every frozen bundle is broken in about twenty ways (deleted, nulled, retyped, shortened,
63
+ reordered, timestamps and sequence numbers bent, own `__proto__` keys added, the whole bundle replaced by an
64
+ array or null), 27,062 inputs in all. Each is run through the JavaScript verifier and through this port. Pass
65
+ means both threw, or both returned the same report and projection. 0 differ.
66
+ - **Semantics.** The helpers in `_js.py` against Node directly: number text, Date.parse, Buffer base64, string
67
+ escaping and mixed-type sorting, several thousand random values each.
68
+ - **Agreement.** The packaged agreement verifier returns every one of the 5,286 frozen reports in
69
+ `agreement-v0/agreement_vectors_v1.json`, the file the JavaScript verifier is scored against.
70
+ - **Unchanged.** `VENDORED.json` pins the sha256 of the agreement files and their sources; a copy that drifts fails.
71
+
72
+ `report_sha256(report)` is the sha256 of the report with keys sorted by UTF-16 code unit at every depth, no
73
+ whitespace, strings and numbers as JSON.stringify writes them. The JavaScript side of the same hash is
74
+ `tests/parity/js_canon.mjs` (twelve lines).
75
+
76
+ ## Where the claim stops
77
+
78
+ - **Stack depth.** A document nested deep enough to exhaust a runtime's stack has no report in that runtime, and
79
+ Node and Python run out at different depths (Node threw at 3,000 nested arrays inside a signed field, Python's
80
+ default limit is 1,000). Real bundles are a few levels deep.
81
+ - **Sixty-four or more non-numeric `hop.seq` values in one bundle.** The order of those keys is V8's merge sort over
82
+ a comparator that cannot order them; the port reproduces V8 for up to 63 keys and raises above that instead of
83
+ guessing.
84
+ - Where the JavaScript throws, the port raises; the CLI then prints no report and exits 2 (Node exits 1 on an
85
+ uncaught throw).
86
+
87
+ ## What it does not establish
88
+
89
+ What every report says itself, accepted or refused: a signature proves who asserted, not that the assertion is
90
+ true; E1 compares a provider's signed claim to a caller's signed authorization and has no side-effect oracle;
91
+ R1 proves a witness is structurally distinct from the parties, not unaffiliated with them. There is no score and
92
+ no allow or deny anywhere in this package. The decision belongs to whoever reads the evidence.
93
+
94
+ Not in this release: the TSUGI recovery-chain verifier (`tsugi_verify.mjs`) and the MUSUBI contract spine. Each
95
+ comes in when it can carry the same guarantee as the two verifiers above: the same report as its JavaScript twin,
96
+ checked case by case.
97
+
98
+ ## Reproduce
99
+
100
+ cd workers/hs-ledger/nenrin/sdk-python
101
+ pip install -e . pytest
102
+ node tests/fixtures/make_fixtures.mjs --check # the frozen fixtures re-create byte for byte
103
+ python tools/vendor.py --check # the agreement copies are their sources
104
+ pytest tests -q -s # frozen, live differential, agreement
105
+
106
+ Published from GitHub Actions with PyPI Trusted Publishing and attestations
107
+ (`.github/workflows/pypi-publish-nenrin-verify.yml`); the parity suite runs on every change to this directory,
108
+ to the JavaScript SDK and to the agreement verifier (`.github/workflows/nenrin-verify-py.yml`).
109
+
110
+ MIT. The HORIZONs Co., Ltd.
@@ -0,0 +1,90 @@
1
+ # nenrin-verify (Python)
2
+
3
+ Recompute NENRIN evidence yourself, offline, in your own Python process. The Python twin of the npm package
4
+ [`nenrin-verify`](https://www.npmjs.com/package/nenrin-verify): same input, same report (the two limits are listed
5
+ under "Where the claim stops").
6
+
7
+ pip install nenrin-verify
8
+ nenrin-verify --selftest # 31/31 frozen bundles give the same report as npm nenrin-verify
9
+ nenrin-verify bundle.json # the provenance report; exit 0 accepted, 1 refused
10
+
11
+ ```python
12
+ import json, nenrin_verify as nv
13
+
14
+ bundle = nv.js_loads(open("bundle.json", "rb").read()) # parsed the way JSON.parse parses it
15
+ report = nv.verify_bundle(bundle) # did:key resolution, no network, no clock
16
+ print(report["verdict"], report["does_not_establish"])
17
+ print(nv.report_json(report)) # JSON text as the JavaScript CLI prints it (not json.dumps)
18
+ print(nv.report_sha256(report)) # compare with the JavaScript run, byte for byte
19
+ ```
20
+
21
+ One dependency, `cryptography`, for Ed25519. Nothing here opens a socket.
22
+
23
+ ## What it verifies
24
+
25
+ | module | what | held to |
26
+ |---|---|---|
27
+ | `verify_provenance`, `consume_evidence`, `posture_line`, `candidate_evidence_set`, `preflight_report` | one A2A task's provenance graph: the delegation chain observed by third-party witnesses (R1 to R4, witness and edge signatures), the caller's grant and the provider's execution receipt (E1 to E3, caller and provider signatures), the provider's pre-execution intent, the outcome's evidence pointer, and the digest link between the layers | npm nenrin-verify 0.2.3 (`nenrin_verify.mjs`, verifier 0.1.3): the same report, key for key |
28
+ | `agreement_verify.verify`, `nenrin-agreement-verify` | a two-party agreement record (`a2a-agreement-v1`, `v1.1`), including key succession across a rotation | the repository's own Python verifier, unchanged but for one import line and a header comment; it and the JavaScript verifier return the same report on 5,286 frozen cases |
29
+
30
+ ## How "same report" is checked
31
+
32
+ The JavaScript file is the reference. The port reads it line for line, and every place where Python and
33
+ JavaScript disagree by default (`x or y` on `{}`, `==` on `True` and `1`, `Date.parse` accepting February 31st,
34
+ Node's lenient base64, an own `__proto__` key that Object.assign turns into a prototype, the order V8's sort
35
+ gives values a numeric comparator cannot order) goes through one file,
36
+ `src/nenrin_verify/_js.py`, so the differences can be read in one place.
37
+
38
+ - **Frozen.** 31 bundles signed with keys derived from a public phrase, covering accepted graphs, refusals of every
39
+ layer, witness disagreement, provider equivocation, action bindings, non-ASCII and escapes, and the date edge
40
+ cases. For each, the report, the `consume_evidence` projection and the CLI output are byte-identical to the
41
+ JavaScript's. They ship in the package; `nenrin-verify --selftest` re-runs them on your machine.
42
+ - **Live.** Every path of every frozen bundle is broken in about twenty ways (deleted, nulled, retyped, shortened,
43
+ reordered, timestamps and sequence numbers bent, own `__proto__` keys added, the whole bundle replaced by an
44
+ array or null), 27,062 inputs in all. Each is run through the JavaScript verifier and through this port. Pass
45
+ means both threw, or both returned the same report and projection. 0 differ.
46
+ - **Semantics.** The helpers in `_js.py` against Node directly: number text, Date.parse, Buffer base64, string
47
+ escaping and mixed-type sorting, several thousand random values each.
48
+ - **Agreement.** The packaged agreement verifier returns every one of the 5,286 frozen reports in
49
+ `agreement-v0/agreement_vectors_v1.json`, the file the JavaScript verifier is scored against.
50
+ - **Unchanged.** `VENDORED.json` pins the sha256 of the agreement files and their sources; a copy that drifts fails.
51
+
52
+ `report_sha256(report)` is the sha256 of the report with keys sorted by UTF-16 code unit at every depth, no
53
+ whitespace, strings and numbers as JSON.stringify writes them. The JavaScript side of the same hash is
54
+ `tests/parity/js_canon.mjs` (twelve lines).
55
+
56
+ ## Where the claim stops
57
+
58
+ - **Stack depth.** A document nested deep enough to exhaust a runtime's stack has no report in that runtime, and
59
+ Node and Python run out at different depths (Node threw at 3,000 nested arrays inside a signed field, Python's
60
+ default limit is 1,000). Real bundles are a few levels deep.
61
+ - **Sixty-four or more non-numeric `hop.seq` values in one bundle.** The order of those keys is V8's merge sort over
62
+ a comparator that cannot order them; the port reproduces V8 for up to 63 keys and raises above that instead of
63
+ guessing.
64
+ - Where the JavaScript throws, the port raises; the CLI then prints no report and exits 2 (Node exits 1 on an
65
+ uncaught throw).
66
+
67
+ ## What it does not establish
68
+
69
+ What every report says itself, accepted or refused: a signature proves who asserted, not that the assertion is
70
+ true; E1 compares a provider's signed claim to a caller's signed authorization and has no side-effect oracle;
71
+ R1 proves a witness is structurally distinct from the parties, not unaffiliated with them. There is no score and
72
+ no allow or deny anywhere in this package. The decision belongs to whoever reads the evidence.
73
+
74
+ Not in this release: the TSUGI recovery-chain verifier (`tsugi_verify.mjs`) and the MUSUBI contract spine. Each
75
+ comes in when it can carry the same guarantee as the two verifiers above: the same report as its JavaScript twin,
76
+ checked case by case.
77
+
78
+ ## Reproduce
79
+
80
+ cd workers/hs-ledger/nenrin/sdk-python
81
+ pip install -e . pytest
82
+ node tests/fixtures/make_fixtures.mjs --check # the frozen fixtures re-create byte for byte
83
+ python tools/vendor.py --check # the agreement copies are their sources
84
+ pytest tests -q -s # frozen, live differential, agreement
85
+
86
+ Published from GitHub Actions with PyPI Trusted Publishing and attestations
87
+ (`.github/workflows/pypi-publish-nenrin-verify.yml`); the parity suite runs on every change to this directory,
88
+ to the JavaScript SDK and to the agreement verifier (`.github/workflows/nenrin-verify-py.yml`).
89
+
90
+ MIT. The HORIZONs Co., Ltd.
@@ -0,0 +1,36 @@
1
+ [build-system]
2
+ requires = ["setuptools>=77"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "nenrin-verify"
7
+ version = "0.1.0"
8
+ description = "Recompute NENRIN evidence yourself, offline: A2A task provenance and two-party agreement records. Same reports as the npm nenrin-verify."
9
+ readme = "README.md"
10
+ license = "MIT"
11
+ license-files = ["LICENSE"]
12
+ requires-python = ">=3.9"
13
+ authors = [{ name = "The HORIZONs Co., Ltd." }]
14
+ dependencies = ["cryptography>=41"]
15
+ keywords = ["a2a", "provenance", "verification", "did-key", "ed25519", "nenrin", "agent", "evidence", "horizon-shield"]
16
+ classifiers = [
17
+ "Programming Language :: Python :: 3",
18
+ "Operating System :: OS Independent",
19
+ "Topic :: Security :: Cryptography",
20
+ "Intended Audience :: Developers",
21
+ ]
22
+
23
+ [project.urls]
24
+ Homepage = "https://github.com/ogasurfproject-jpg/horizon-shield/tree/main/workers/hs-ledger/nenrin/sdk-python"
25
+ Source = "https://github.com/ogasurfproject-jpg/horizon-shield"
26
+ "JavaScript twin" = "https://www.npmjs.com/package/nenrin-verify"
27
+
28
+ [project.scripts]
29
+ nenrin-verify = "nenrin_verify.cli:main"
30
+ nenrin-agreement-verify = "nenrin_verify.cli:agreement_main"
31
+
32
+ [tool.setuptools.packages.find]
33
+ where = ["src"]
34
+
35
+ [tool.setuptools.package-data]
36
+ nenrin_verify = ["VENDORED.json", "py.typed", "selftest.json"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,12 @@
1
+ {
2
+ "agreement_verify.py": {
3
+ "source": "workers/hs-ledger/nenrin/agreement-v0/agreement_verify.py",
4
+ "source_sha256": "563887cf0b5c111a37f1ad676c946ecedfe27a42058befd1e819795689f9f691",
5
+ "vendored_sha256": "5a143e177ee055d4e0da9c83ea5ad3901bcf9fec0a5c4cbdb7d83901247697e3"
6
+ },
7
+ "key_succession.py": {
8
+ "source": "workers/hs-ledger/nenrin/agreement-v0/key_succession.py",
9
+ "source_sha256": "0d0209b318f654d0995ac6ca40f5194064cd6e8b49459d1bbbcfdb957b61452d",
10
+ "vendored_sha256": "fd3601681733cc3a5a9b30a2a459f5c6e49ada7167055775283493084c48178a"
11
+ }
12
+ }
@@ -0,0 +1,44 @@
1
+ """nenrin-verify: recompute NENRIN evidence yourself, offline, in your own Python process.
2
+
3
+ The Python twin of the npm package nenrin-verify. Two verifiers, each held to its JavaScript counterpart:
4
+
5
+ provenance verify_provenance / consume_evidence: one A2A task's delegation chain, execution receipt,
6
+ pre-execution intent and outcome evidence (port of nenrin_verify.mjs; same report, key for key)
7
+ agreement agreement_verify.verify: a two-party agreement record (a2a-agreement-v1 / v1.1), the repository's
8
+ own Python verifier, which returns the same report as the JavaScript one on 5,286 frozen cases
9
+
10
+ No network, no clock, no score. A signature proves who asserted, not that the assertion is true; every report
11
+ says what it does not establish.
12
+ """
13
+ import hashlib
14
+
15
+ from ._js import assign as _assign, stringify as _stringify, loads as js_loads
16
+ from .provenance import (VERIFIER_VERSION, candidate_evidence_set, consume_evidence, did_key_resolver,
17
+ evidence_id, grant_ref, intent_id, posture_line, preflight_report,
18
+ public_key_from_did_key, receipt_id, verify_provenance)
19
+ from . import agreement_verify
20
+
21
+ __version__ = "0.1.0"
22
+
23
+ __all__ = ["verify_provenance", "consume_evidence", "posture_line", "candidate_evidence_set", "preflight_report",
24
+ "public_key_from_did_key", "did_key_resolver", "evidence_id", "grant_ref", "receipt_id", "intent_id",
25
+ "agreement_verify", "report_sha256", "report_json", "verify_bundle", "js_loads", "VERIFIER_VERSION", "__version__"]
26
+
27
+
28
+ def report_sha256(report):
29
+ """sha256 of the report's sorted-key JSON, the same bytes the JavaScript side hashes for the parity check:
30
+ keys ordered by UTF-16 code unit at every depth, no whitespace, numbers as JavaScript writes them."""
31
+ return hashlib.sha256(_stringify(report, sort_keys=True).encode("utf-8")).hexdigest()
32
+
33
+
34
+ def verify_bundle(bundle):
35
+ """What `nenrin-verify bundle.json` does: Object.assign({}, bundle, {resolve: didKeyResolver}), then verify,
36
+ offline. Any JSON value is accepted the way the JavaScript CLI accepts it (an array or null verifies as {})."""
37
+ return verify_provenance(_assign(bundle, {"resolve": did_key_resolver}))
38
+
39
+
40
+ def report_json(report, indent=2):
41
+ """The report as JSON text, written the way JSON.stringify(report, null, indent) writes it. Use this rather
42
+ than json.dumps: a report can carry the port's stand-in for JavaScript undefined (a key the JavaScript leaves
43
+ out of its output), which json.dumps cannot write."""
44
+ return _stringify(report, indent=indent)