ciris-verify 7.2.0__tar.gz → 7.4.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 (32) hide show
  1. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/PKG-INFO +1 -1
  2. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/__init__.py +5 -1
  3. ciris_verify-7.4.0/ciris_verify/_rns_dest_hash.py +150 -0
  4. ciris_verify-7.4.0/ciris_verify/_scope_privacy.py +186 -0
  5. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify.egg-info/PKG-INFO +1 -1
  6. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify.egg-info/SOURCES.txt +2 -0
  7. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/pyproject.toml +1 -1
  8. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/README.md +0 -0
  9. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_accord_custody.py +0 -0
  10. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_bin_sign.py +0 -0
  11. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_bin_verify.py +0 -0
  12. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_federation_identity.py +0 -0
  13. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_jcs.py +0 -0
  14. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_manifest_contribution.py +0 -0
  15. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_operational_admit.py +0 -0
  16. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_wheel_hybrid_kex.py +0 -0
  17. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_wheel_key_grant.py +0 -0
  18. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_wheel_locale_merkle.py +0 -0
  19. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_wheel_reconsider_dos.py +0 -0
  20. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/_wheel_skill_import.py +0 -0
  21. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/client.py +0 -0
  22. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/exceptions.py +0 -0
  23. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/py.typed +0 -0
  24. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify/types.py +0 -0
  25. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify.egg-info/dependency_links.txt +0 -0
  26. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify.egg-info/entry_points.txt +0 -0
  27. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify.egg-info/requires.txt +0 -0
  28. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/ciris_verify.egg-info/top_level.txt +0 -0
  29. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/setup.cfg +0 -0
  30. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/tests/test_client.py +0 -0
  31. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/tests/test_memory_baseline.py +0 -0
  32. {ciris_verify-7.2.0 → ciris_verify-7.4.0}/tests/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ciris-verify
3
- Version: 7.2.0
3
+ Version: 7.4.0
4
4
  Summary: Python bindings for CIRISVerify hardware-rooted license verification
5
5
  Author-email: CIRIS Engineering <engineering@ciris.ai>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -30,6 +30,8 @@ import logging as _logging
30
30
 
31
31
  from .client import CIRISVerify, MockCIRISVerify, verify_tree, DEFAULT_REGISTRY_URL
32
32
  from ._jcs import jcs_canonicalize
33
+ from ._rns_dest_hash import rns_destination_hash
34
+ from . import _scope_privacy as scope_privacy
33
35
  from ._federation_identity import create_federation_identity
34
36
  from ._manifest_contribution import verify_build_manifest_contribution
35
37
  from ._accord_custody import verify_accord_custody_attestation
@@ -127,12 +129,14 @@ def get_library_version() -> str:
127
129
  return __version__
128
130
 
129
131
 
130
- __version__ = "7.2.0"
132
+ __version__ = "7.4.0"
131
133
  __all__ = [
132
134
  "CIRISVerify",
133
135
  "MockCIRISVerify",
134
136
  "verify_tree",
135
137
  "jcs_canonicalize",
138
+ "rns_destination_hash",
139
+ "scope_privacy",
136
140
  "create_federation_identity",
137
141
  "verify_build_manifest_contribution",
138
142
  "verify_accord_custody_attestation",
@@ -0,0 +1,150 @@
1
+ """RNS ``destination_hash`` recompute — Python binding (CIRISVerify#28).
2
+
3
+ Exposes :func:`rns_destination_hash`, a thin wrapper over the FFI symbol
4
+ ``ciris_verify_rns_destination_hash`` which calls
5
+ ``ciris_verify_core::transport_binding::compute_destination_hash`` — the one
6
+ blessed two-stage RNS destination-hash construction (CEG 1.0-RC6 §5.6.8.8.1.1).
7
+
8
+ This lifts the last verify-side remainder of the #28 transport-binding
9
+ waterfall: the recompute shipped Rust-side in v5.6.0 (``DestinationHashCheck``
10
+ lifted off ``Unsupported``) but was never on the wheel, so a Python consumer —
11
+ and CIRISConformance's ``test_150_rns_dest_hash.py`` cross-check — could not
12
+ verify a peer's ``destination_hash`` against the pinned algorithm. With this it
13
+ can, byte-for-byte, with **no** Reticulum vendoring and no second
14
+ implementation: the canonical bytes come from the same Rust path the verifiers
15
+ use.
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import ctypes
21
+ import json as _json
22
+ import platform as _platform
23
+ import threading as _threading
24
+ from pathlib import Path
25
+ from typing import Optional, Sequence
26
+
27
+ __all__ = ["rns_destination_hash"]
28
+
29
+ _lib: Optional[ctypes.CDLL] = None
30
+ _lib_lock = _threading.Lock()
31
+ _SUCCESS = 0
32
+
33
+
34
+ def _candidate_paths() -> list[str]:
35
+ here = Path(__file__).resolve().parent
36
+ system = _platform.system()
37
+ names = {
38
+ "Linux": ["libciris_verify_ffi.so", "libciris_verify.so"],
39
+ "Darwin": ["libciris_verify_ffi.dylib", "libciris_verify.dylib"],
40
+ "Windows": ["ciris_verify_ffi.dll", "ciris_verify.dll"],
41
+ }.get(system, ["libciris_verify_ffi.so"])
42
+
43
+ paths: list[str] = []
44
+ for n in names:
45
+ paths.append(str(here / n))
46
+ try:
47
+ from .client import DEFAULT_BINARY_PATHS # type: ignore
48
+
49
+ paths.extend(DEFAULT_BINARY_PATHS.get(system, []))
50
+ except Exception: # pragma: no cover - defensive
51
+ pass
52
+ return paths
53
+
54
+
55
+ def _load_lib() -> ctypes.CDLL:
56
+ global _lib
57
+ if _lib is not None:
58
+ return _lib
59
+ with _lib_lock:
60
+ if _lib is not None:
61
+ return _lib
62
+ last_err: Optional[Exception] = None
63
+ for path in _candidate_paths():
64
+ if not Path(path).exists():
65
+ continue
66
+ try:
67
+ lib = ctypes.CDLL(path)
68
+ fn = lib.ciris_verify_rns_destination_hash
69
+ except (OSError, AttributeError) as exc:
70
+ last_err = exc
71
+ continue
72
+ fn.argtypes = [
73
+ ctypes.c_char_p, # input_json (UTF-8 bytes)
74
+ ctypes.c_size_t, # input_len
75
+ ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)), # result_out
76
+ ctypes.POINTER(ctypes.c_size_t), # result_len_out
77
+ ]
78
+ fn.restype = ctypes.c_int
79
+ lib.ciris_verify_free.argtypes = [ctypes.c_void_p]
80
+ lib.ciris_verify_free.restype = None
81
+ _lib = lib
82
+ return _lib
83
+ raise RuntimeError(
84
+ "ciris_verify_rns_destination_hash not available — could not load "
85
+ f"the CIRISVerify shared library (last error: {last_err}). "
86
+ "The library must be built with the wheel (>= v7.3.0)."
87
+ )
88
+
89
+
90
+ def rns_destination_hash(
91
+ app_name: str,
92
+ aspects: Sequence[str],
93
+ x25519_pub: bytes,
94
+ ed25519_pub: bytes,
95
+ ) -> bytes:
96
+ """Recompute the RNS ``destination_hash`` per CEG §5.6.8.8.1.1.
97
+
98
+ The two-stage construction (NOT a flat single SHA-256):
99
+
100
+ name_hash = SHA256(app_name + "." + ".".join(aspects))[:10]
101
+ identity_hash = SHA256(x25519_pub ‖ ed25519_pub)[:16]
102
+ destination_hash = SHA256(name_hash ‖ identity_hash)[:16]
103
+
104
+ Args:
105
+ app_name: the RNS app name (e.g. ``"ciris.federation"``).
106
+ aspects: the dot-joined aspects (each MUST NOT contain ``"."``).
107
+ x25519_pub: the peer's X25519 public key bytes.
108
+ ed25519_pub: the peer's Ed25519 public key bytes (key order is
109
+ x25519 THEN ed25519, per RNS ``get_public_key``).
110
+
111
+ Returns:
112
+ The 16-byte destination hash — byte-identical to
113
+ ``ciris_verify_core::transport_binding::compute_destination_hash`` and
114
+ therefore to the pinned §5.6.8.8.1.1 algorithm.
115
+
116
+ Raises:
117
+ ValueError: an aspect contains ``"."`` (illegal — it would alter the
118
+ name preimage split), or the inputs cannot be encoded.
119
+ RuntimeError: the shared library / FFI symbol is unavailable.
120
+ """
121
+ req = {
122
+ "app_name": app_name,
123
+ "aspects": list(aspects),
124
+ "x25519_pubkey": list(x25519_pub),
125
+ "ed25519_pubkey": list(ed25519_pub),
126
+ }
127
+ input_bytes = _json.dumps(req).encode("utf-8")
128
+
129
+ lib = _load_lib()
130
+ out_ptr = ctypes.POINTER(ctypes.c_ubyte)()
131
+ out_len = ctypes.c_size_t(0)
132
+ rc = lib.ciris_verify_rns_destination_hash(
133
+ input_bytes,
134
+ len(input_bytes),
135
+ ctypes.byref(out_ptr),
136
+ ctypes.byref(out_len),
137
+ )
138
+ if rc != _SUCCESS:
139
+ # rc 2 == SerializationError (bad request / dotted aspect).
140
+ raise ValueError(
141
+ f"rns_destination_hash: invalid request (e.g. an aspect containing "
142
+ f"'.', or non-encodable input) (FFI code {rc})"
143
+ )
144
+ n = out_len.value
145
+ try:
146
+ result = ctypes.string_at(out_ptr, n)
147
+ finally:
148
+ if n != 0:
149
+ lib.ciris_verify_free(ctypes.cast(out_ptr, ctypes.c_void_p))
150
+ return result
@@ -0,0 +1,186 @@
1
+ """Scope-native privacy derivations — Python binding (CIRISVerify#82).
2
+
3
+ Exposes the ``ciris_crypto::scope_privacy`` FSD §2.2/§2.4/§3.4 derivation
4
+ helpers (CEWP ``SCOPE_PRIVACY.md`` — CC 1.13.3 anonymous tier) on the wheel,
5
+ over the FFI symbol ``ciris_verify_scope_privacy_derive``. A Python consumer can
6
+ reproduce a ``record_id`` / ``symbol_key`` / witness cover-leaf **byte-identically**
7
+ to the Rust verifiers — the canonical bytes (incl. the RFC 8949 §4.2.1
8
+ deterministic CBOR ``record_id`` preimage and the pinned ``RecordType`` integers)
9
+ come from the one Rust impl, so there is no second implementation to drift.
10
+
11
+ Imported as the ``ciris_verify.scope_privacy`` namespace::
12
+
13
+ from ciris_verify import scope_privacy
14
+ krid = scope_privacy.k_record_id(exporter_secret)
15
+ rid = scope_privacy.derive_record_id(krid, internal_id, "community", epoch)
16
+ """
17
+
18
+ from __future__ import annotations
19
+
20
+ import ctypes
21
+ import json as _json
22
+ import platform as _platform
23
+ import threading as _threading
24
+ from pathlib import Path
25
+ from typing import Optional
26
+
27
+ __all__ = [
28
+ "k_record_id",
29
+ "k_symbol",
30
+ "derive_record_id",
31
+ "derive_symbol_key",
32
+ "witness_cover_leaf",
33
+ ]
34
+
35
+ _lib: Optional[ctypes.CDLL] = None
36
+ _lib_lock = _threading.Lock()
37
+ _SUCCESS = 0
38
+
39
+ # The record_type strings the FFI accepts (mirrors ciris_crypto RecordType).
40
+ _RECORD_TYPES = ("self", "family", "community", "federation")
41
+
42
+
43
+ def _candidate_paths() -> list[str]:
44
+ here = Path(__file__).resolve().parent
45
+ system = _platform.system()
46
+ names = {
47
+ "Linux": ["libciris_verify_ffi.so", "libciris_verify.so"],
48
+ "Darwin": ["libciris_verify_ffi.dylib", "libciris_verify.dylib"],
49
+ "Windows": ["ciris_verify_ffi.dll", "ciris_verify.dll"],
50
+ }.get(system, ["libciris_verify_ffi.so"])
51
+
52
+ paths: list[str] = []
53
+ for n in names:
54
+ paths.append(str(here / n))
55
+ try:
56
+ from .client import DEFAULT_BINARY_PATHS # type: ignore
57
+
58
+ paths.extend(DEFAULT_BINARY_PATHS.get(system, []))
59
+ except Exception: # pragma: no cover - defensive
60
+ pass
61
+ return paths
62
+
63
+
64
+ def _load_lib() -> ctypes.CDLL:
65
+ global _lib
66
+ if _lib is not None:
67
+ return _lib
68
+ with _lib_lock:
69
+ if _lib is not None:
70
+ return _lib
71
+ last_err: Optional[Exception] = None
72
+ for path in _candidate_paths():
73
+ if not Path(path).exists():
74
+ continue
75
+ try:
76
+ lib = ctypes.CDLL(path)
77
+ fn = lib.ciris_verify_scope_privacy_derive
78
+ except (OSError, AttributeError) as exc:
79
+ last_err = exc
80
+ continue
81
+ fn.argtypes = [
82
+ ctypes.c_char_p, # input_json (UTF-8 bytes)
83
+ ctypes.c_size_t, # input_len
84
+ ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)), # result_out
85
+ ctypes.POINTER(ctypes.c_size_t), # result_len_out
86
+ ]
87
+ fn.restype = ctypes.c_int
88
+ lib.ciris_verify_free.argtypes = [ctypes.c_void_p]
89
+ lib.ciris_verify_free.restype = None
90
+ _lib = lib
91
+ return _lib
92
+ raise RuntimeError(
93
+ "ciris_verify_scope_privacy_derive not available — could not load "
94
+ f"the CIRISVerify shared library (last error: {last_err}). "
95
+ "The library must be built with the wheel (>= v7.4.0)."
96
+ )
97
+
98
+
99
+ def _derive(req: dict) -> bytes:
100
+ input_bytes = _json.dumps(req).encode("utf-8")
101
+ lib = _load_lib()
102
+ out_ptr = ctypes.POINTER(ctypes.c_ubyte)()
103
+ out_len = ctypes.c_size_t(0)
104
+ rc = lib.ciris_verify_scope_privacy_derive(
105
+ input_bytes,
106
+ len(input_bytes),
107
+ ctypes.byref(out_ptr),
108
+ ctypes.byref(out_len),
109
+ )
110
+ if rc != _SUCCESS:
111
+ # rc 2 == SerializationError (bad request / wrong-length key / unknown type).
112
+ raise ValueError(
113
+ f"scope_privacy: invalid request — a 32-byte key has the wrong length, "
114
+ f"an unknown record_type, or non-encodable input (FFI code {rc})"
115
+ )
116
+ n = out_len.value
117
+ try:
118
+ result = ctypes.string_at(out_ptr, n)
119
+ finally:
120
+ if n != 0:
121
+ lib.ciris_verify_free(ctypes.cast(out_ptr, ctypes.c_void_p))
122
+ return result
123
+
124
+
125
+ def k_record_id(exporter_secret: bytes) -> bytes:
126
+ """§2.2 — derive ``K_record_id`` from the group's 32-byte MLS exporter secret."""
127
+ return _derive({"op": "k_record_id", "exporter_secret": list(exporter_secret)})
128
+
129
+
130
+ def k_symbol(exporter_secret: bytes) -> bytes:
131
+ """§2.2 — derive ``K_symbol`` from the group's 32-byte MLS exporter secret."""
132
+ return _derive({"op": "k_symbol", "exporter_secret": list(exporter_secret)})
133
+
134
+
135
+ def derive_record_id(
136
+ k_record_id: bytes,
137
+ internal_id: bytes,
138
+ record_type: str,
139
+ mls_group_epoch: int,
140
+ ) -> bytes:
141
+ """§2.4 — ``record_id = HMAC-SHA3-256(K_record_id, CBOR_dCE({v,iid,typ,epc}))``.
142
+
143
+ ``record_type`` is one of ``"self"``, ``"family"``, ``"community"``,
144
+ ``"federation"`` (the pinned ``RecordType`` mapping). The CBOR preimage is
145
+ RFC 8949 §4.2.1 core-deterministic — encoded Rust-side, so this is
146
+ byte-identical to the verifier's ``record_id``.
147
+ """
148
+ if record_type not in _RECORD_TYPES:
149
+ raise ValueError(f"record_type must be one of {_RECORD_TYPES}, got {record_type!r}")
150
+ return _derive(
151
+ {
152
+ "op": "record_id",
153
+ "k_record_id": list(k_record_id),
154
+ "internal_id": list(internal_id),
155
+ "record_type": record_type,
156
+ "mls_group_epoch": int(mls_group_epoch),
157
+ }
158
+ )
159
+
160
+
161
+ def derive_symbol_key(k_symbol: bytes, record_id: bytes, symbol_index: int) -> bytes:
162
+ """§2.4 — ``symbol_key = HKDF-SHA3-256(salt=record_id, ikm=K_symbol, info=label‖u16(idx))``."""
163
+ return _derive(
164
+ {
165
+ "op": "symbol_key",
166
+ "k_symbol": list(k_symbol),
167
+ "record_id": list(record_id),
168
+ "symbol_index": int(symbol_index),
169
+ }
170
+ )
171
+
172
+
173
+ def witness_cover_leaf(
174
+ witness_signing_key: bytes,
175
+ leaf_position: int,
176
+ federation_epoch_id: int,
177
+ ) -> bytes:
178
+ """§3.4 — witness cover-leaf ``HMAC-SHA3-256(key, u32_be(pos) ‖ u64_be(epoch))``."""
179
+ return _derive(
180
+ {
181
+ "op": "witness_cover_leaf",
182
+ "witness_signing_key": list(witness_signing_key),
183
+ "leaf_position": int(leaf_position),
184
+ "federation_epoch_id": int(federation_epoch_id),
185
+ }
186
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ciris-verify
3
- Version: 7.2.0
3
+ Version: 7.4.0
4
4
  Summary: Python bindings for CIRISVerify hardware-rooted license verification
5
5
  Author-email: CIRIS Engineering <engineering@ciris.ai>
6
6
  License-Expression: AGPL-3.0-or-later
@@ -8,6 +8,8 @@ ciris_verify/_federation_identity.py
8
8
  ciris_verify/_jcs.py
9
9
  ciris_verify/_manifest_contribution.py
10
10
  ciris_verify/_operational_admit.py
11
+ ciris_verify/_rns_dest_hash.py
12
+ ciris_verify/_scope_privacy.py
11
13
  ciris_verify/_wheel_hybrid_kex.py
12
14
  ciris_verify/_wheel_key_grant.py
13
15
  ciris_verify/_wheel_locale_merkle.py
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "ciris-verify"
7
- version = "7.2.0"
7
+ version = "7.4.0"
8
8
  description = "Python bindings for CIRISVerify hardware-rooted license verification"
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-or-later"
File without changes
File without changes