xoonya 3.1.16 → 3.2.0

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 (240) hide show
  1. package/README.md +97 -74
  2. package/RELEASE_NOTES.md +7 -1
  3. package/START_HERE.md +38 -134
  4. package/bin/xoonya-first-run.js +22 -10
  5. package/bin/xoonya.js +61 -29
  6. package/lib/native_verify.js +1918 -0
  7. package/package.json +19 -42
  8. package/scripts/verify_release_candidate.js +243 -116
  9. package/scripts/verify_vectors.js +351 -104
  10. package/verify.js +95 -0
  11. package/.env.example +0 -10
  12. package/CARRIED_HANDOFF_DEMO.md +0 -60
  13. package/COMPATIBILITY_NOTE.md +0 -27
  14. package/CROSS_RUNTIME_POSTURE.md +0 -77
  15. package/EXAMPLE_PATHS.md +0 -53
  16. package/FINAL_CONTRACT.md +0 -445
  17. package/PARTNER_READY_PATH.md +0 -67
  18. package/PUBLIC_DEMO_SCRIPT.md +0 -79
  19. package/SERVICE_CONTRACT.md +0 -207
  20. package/TERMINOLOGY.md +0 -55
  21. package/WHY_XYTARA_XOONYA.md +0 -43
  22. package/XNYA_CONFORMANCE_MARK.md +0 -78
  23. package/XNYA_REFERENCE_IMPLEMENTATIONS.md +0 -157
  24. package/XOONYA_DEFAULT_PATH_MODEL.md +0 -63
  25. package/XOONYA_FOLLOWTHROUGH_TRUTH_MODEL.md +0 -63
  26. package/XOONYA_NATIVE_ATOM_SPEC.md +0 -184
  27. package/XOONYA_NATIVE_PROOF_FORMAT.md +0 -570
  28. package/XOONYA_NATIVE_STANDARD_GOVERNANCE.md +0 -119
  29. package/XOONYA_PROOF_TRUTH_MODEL.md +0 -58
  30. package/bin/xoonya-release.js +0 -1367
  31. package/bin/xoonya-review.js +0 -70
  32. package/examples/integration_boundary_walkthrough.js +0 -37
  33. package/examples/package_quickstart.js +0 -1007
  34. package/examples/partner_proof_walkthrough.js +0 -68
  35. package/examples/proof_review_walkthrough.js +0 -66
  36. package/examples/review_carried_handoff.js +0 -72
  37. package/index.js +0 -31
  38. package/integrations/PROOF_INTEGRATION_PROCESS.md +0 -45
  39. package/integrations/examples/example-third-party-verifier-registration.record.json +0 -22
  40. package/integrations/registration-record.schema.json +0 -99
  41. package/integrations/registry-snapshot.schema.json +0 -36
  42. package/integrations/registry.js +0 -636
  43. package/integrations/views.js +0 -36
  44. package/lib/adapter_certification_contract.js +0 -90
  45. package/lib/announcement_pack.js +0 -61
  46. package/lib/artifact_distribution_lane_contract.js +0 -54
  47. package/lib/asyncapi_contract.js +0 -135
  48. package/lib/auth_interop_contract.js +0 -69
  49. package/lib/authority_binding_lifecycle_contract.js +0 -182
  50. package/lib/bridge_intelligence.js +0 -102
  51. package/lib/cloudevents_contract.js +0 -79
  52. package/lib/crypto_agility_matrix.js +0 -131
  53. package/lib/default_path_contract.js +0 -380
  54. package/lib/ecosystem_entry.js +0 -64
  55. package/lib/erc8004_proof_lane_contract.js +0 -64
  56. package/lib/event_system_lane_contract.js +0 -76
  57. package/lib/external_proof_import_lane_contract.js +0 -87
  58. package/lib/feature_control_lane_contract.js +0 -54
  59. package/lib/followthrough_truth_contract.js +0 -214
  60. package/lib/framework_lane_contract.js +0 -76
  61. package/lib/governance_disputes_summary.js +0 -36
  62. package/lib/governance_intelligence.js +0 -108
  63. package/lib/identity_interop_contract.js +0 -83
  64. package/lib/integration_matrix_contract.js +0 -81
  65. package/lib/launch_narrative.js +0 -53
  66. package/lib/machine_identity_registry_contract.js +0 -199
  67. package/lib/major_rails_proof_lane_contract.js +0 -87
  68. package/lib/offline_verifiability_recovery_contract.js +0 -155
  69. package/lib/openapi_contract.js +0 -227
  70. package/lib/operational_receipt_contract.js +0 -165
  71. package/lib/outreach_proof.js +0 -71
  72. package/lib/phase_10_closeout_pack.js +0 -45
  73. package/lib/phase_10_completion_pack.js +0 -76
  74. package/lib/phase_10_decision_record_pack.js +0 -54
  75. package/lib/phase_10_decision_resolution_pack.js +0 -53
  76. package/lib/phase_10_demand_signal_adapters_proof_pack.js +0 -74
  77. package/lib/phase_10_ecosystem_backlog_triage_proof_pack.js +0 -74
  78. package/lib/phase_10_freeze_baseline_pack.js +0 -67
  79. package/lib/phase_10_freeze_review_pack.js +0 -64
  80. package/lib/phase_10_gate_pack.js +0 -57
  81. package/lib/phase_10_long_tail_continuity_pack.js +0 -80
  82. package/lib/phase_10_long_tail_ecosystem_pack.js +0 -68
  83. package/lib/phase_10_niche_extension_registry_proof_pack.js +0 -74
  84. package/lib/phase_1_claude_mcp_pack.js +0 -60
  85. package/lib/phase_1_claude_mcp_proof_pack.js +0 -57
  86. package/lib/phase_1_ecosystem_pack.js +0 -51
  87. package/lib/phase_1_openai_codex_pack.js +0 -60
  88. package/lib/phase_1_openai_codex_proof_pack.js +0 -57
  89. package/lib/phase_2_base_proof_pack.js +0 -71
  90. package/lib/phase_2_base_usdc_proof_pack.js +0 -72
  91. package/lib/phase_2_closeout_pack.js +0 -65
  92. package/lib/phase_2_completion_pack.js +0 -113
  93. package/lib/phase_2_decision_record_pack.js +0 -66
  94. package/lib/phase_2_decision_resolution_pack.js +0 -69
  95. package/lib/phase_2_eth_proof_pack.js +0 -70
  96. package/lib/phase_2_evm_base_proof_pack.js +0 -73
  97. package/lib/phase_2_evm_proof_pack.js +0 -71
  98. package/lib/phase_2_flip_preview_pack.js +0 -60
  99. package/lib/phase_2_freeze_review_pack.js +0 -99
  100. package/lib/phase_2_gate_pack.js +0 -77
  101. package/lib/phase_2_payment_rails_pack.js +0 -71
  102. package/lib/phase_2_usdc_proof_pack.js +0 -71
  103. package/lib/phase_3_brc_proof_pack.js +0 -81
  104. package/lib/phase_3_bsv_teranode_proof_pack.js +0 -81
  105. package/lib/phase_3_closeout_pack.js +0 -62
  106. package/lib/phase_3_completion_pack.js +0 -92
  107. package/lib/phase_3_decision_record_pack.js +0 -64
  108. package/lib/phase_3_decision_resolution_pack.js +0 -69
  109. package/lib/phase_3_freeze_baseline_pack.js +0 -67
  110. package/lib/phase_3_freeze_review_pack.js +0 -80
  111. package/lib/phase_3_gate_pack.js +0 -76
  112. package/lib/phase_3_metanet_proof_pack.js +0 -81
  113. package/lib/phase_3_native_bsv_pack.js +0 -89
  114. package/lib/phase_3_native_continuity_pack.js +0 -82
  115. package/lib/phase_4_attestation_proof_pack.js +0 -75
  116. package/lib/phase_4_closeout_pack.js +0 -45
  117. package/lib/phase_4_completion_pack.js +0 -82
  118. package/lib/phase_4_decision_record_pack.js +0 -54
  119. package/lib/phase_4_decision_resolution_pack.js +0 -45
  120. package/lib/phase_4_erc8004_proof_pack.js +0 -76
  121. package/lib/phase_4_freeze_baseline_pack.js +0 -67
  122. package/lib/phase_4_freeze_review_pack.js +0 -68
  123. package/lib/phase_4_gate_pack.js +0 -58
  124. package/lib/phase_4_identity_registry_proof_pack.js +0 -75
  125. package/lib/phase_4_identity_trust_pack.js +0 -73
  126. package/lib/phase_4_trust_continuity_pack.js +0 -86
  127. package/lib/phase_5_antelope_proof_pack.js +0 -76
  128. package/lib/phase_5_closeout_pack.js +0 -44
  129. package/lib/phase_5_completion_pack.js +0 -82
  130. package/lib/phase_5_decision_record_pack.js +0 -54
  131. package/lib/phase_5_decision_resolution_pack.js +0 -45
  132. package/lib/phase_5_freeze_baseline_pack.js +0 -67
  133. package/lib/phase_5_freeze_review_pack.js +0 -68
  134. package/lib/phase_5_gate_pack.js +0 -58
  135. package/lib/phase_5_major_rails_continuity_pack.js +0 -86
  136. package/lib/phase_5_major_rails_pack.js +0 -74
  137. package/lib/phase_5_proton_xpr_proof_pack.js +0 -76
  138. package/lib/phase_5_solana_proof_pack.js +0 -76
  139. package/lib/phase_6_autogen_proof_pack.js +0 -74
  140. package/lib/phase_6_closeout_pack.js +0 -46
  141. package/lib/phase_6_completion_pack.js +0 -77
  142. package/lib/phase_6_decision_record_pack.js +0 -54
  143. package/lib/phase_6_decision_resolution_pack.js +0 -53
  144. package/lib/phase_6_framework_continuity_pack.js +0 -81
  145. package/lib/phase_6_framework_runtime_pack.js +0 -68
  146. package/lib/phase_6_freeze_baseline_pack.js +0 -68
  147. package/lib/phase_6_freeze_review_pack.js +0 -65
  148. package/lib/phase_6_gate_pack.js +0 -59
  149. package/lib/phase_6_langchain_proof_pack.js +0 -74
  150. package/lib/phase_6_langgraph_proof_pack.js +0 -74
  151. package/lib/phase_6_semantic_kernel_proof_pack.js +0 -74
  152. package/lib/phase_7_closeout_pack.js +0 -45
  153. package/lib/phase_7_completion_pack.js +0 -85
  154. package/lib/phase_7_decision_record_pack.js +0 -53
  155. package/lib/phase_7_decision_resolution_pack.js +0 -53
  156. package/lib/phase_7_event_system_continuity_pack.js +0 -89
  157. package/lib/phase_7_event_system_pack.js +0 -76
  158. package/lib/phase_7_freeze_baseline_pack.js +0 -74
  159. package/lib/phase_7_freeze_review_pack.js +0 -65
  160. package/lib/phase_7_gate_pack.js +0 -58
  161. package/lib/phase_7_kafka_proof_pack.js +0 -76
  162. package/lib/phase_7_mqtt_proof_pack.js +0 -76
  163. package/lib/phase_7_nats_proof_pack.js +0 -76
  164. package/lib/phase_7_webhook_event_bus_proof_pack.js +0 -76
  165. package/lib/phase_8_closeout_pack.js +0 -46
  166. package/lib/phase_8_completion_pack.js +0 -82
  167. package/lib/phase_8_decision_record_pack.js +0 -54
  168. package/lib/phase_8_decision_resolution_pack.js +0 -53
  169. package/lib/phase_8_external_receipt_import_proof_pack.js +0 -74
  170. package/lib/phase_8_external_result_import_proof_pack.js +0 -74
  171. package/lib/phase_8_freeze_baseline_pack.js +0 -71
  172. package/lib/phase_8_freeze_review_pack.js +0 -64
  173. package/lib/phase_8_gate_pack.js +0 -58
  174. package/lib/phase_8_proof_bridge_continuity_pack.js +0 -86
  175. package/lib/phase_8_proof_bridges_pack.js +0 -72
  176. package/lib/phase_8_proof_bundle_normalization_proof_pack.js +0 -74
  177. package/lib/phase_9_closeout_pack.js +0 -46
  178. package/lib/phase_9_completion_pack.js +0 -82
  179. package/lib/phase_9_custody_refs_proof_pack.js +0 -74
  180. package/lib/phase_9_decision_record_pack.js +0 -54
  181. package/lib/phase_9_decision_resolution_pack.js +0 -53
  182. package/lib/phase_9_freeze_baseline_pack.js +0 -71
  183. package/lib/phase_9_freeze_review_pack.js +0 -64
  184. package/lib/phase_9_gate_pack.js +0 -58
  185. package/lib/phase_9_operator_accounting_bridges_proof_pack.js +0 -74
  186. package/lib/phase_9_treasury_connectivity_pack.js +0 -72
  187. package/lib/phase_9_treasury_continuity_pack.js +0 -86
  188. package/lib/phase_9_treasury_destinations_proof_pack.js +0 -74
  189. package/lib/phase_program_pack.js +0 -47
  190. package/lib/proof_bridge_lane_contract.js +0 -82
  191. package/lib/proof_truth_contract.js +0 -224
  192. package/lib/provenance_lane_contract.js +0 -54
  193. package/lib/provider_lane_contract.js +0 -67
  194. package/lib/public_surface_policy_contract.js +0 -111
  195. package/lib/publish_plan.js +0 -51
  196. package/lib/release_candidate.js +0 -50
  197. package/lib/release_center.js +0 -1037
  198. package/lib/release_history.js +0 -206
  199. package/lib/release_manifest.js +0 -114
  200. package/lib/release_pack.js +0 -391
  201. package/lib/result_package_trust_lane_contract.js +0 -74
  202. package/lib/review_intelligence.js +0 -100
  203. package/lib/role_freeze.js +0 -436
  204. package/lib/shared_signals_lane_contract.js +0 -54
  205. package/lib/signed_audit_provenance_contract.js +0 -93
  206. package/lib/telemetry_lane_contract.js +0 -54
  207. package/lib/treasury_proof_lane_contract.js +0 -83
  208. package/lib/trust_intelligence.js +0 -106
  209. package/lib/trust_portability_boundary_contract.js +0 -66
  210. package/lib/trust_portability_pack.js +0 -224
  211. package/lib/verification_service_pack.js +0 -149
  212. package/proof_sdk.js +0 -6692
  213. package/reference/python/README.md +0 -35
  214. package/reference/python/requirements-optional.txt +0 -1
  215. package/reference/python/verify_vectors.py +0 -223
  216. package/reference/python/xnya_reference.py +0 -810
  217. package/reference/rust/Cargo.toml +0 -11
  218. package/reference/rust/README.md +0 -28
  219. package/reference/rust/src/lib.rs +0 -87
  220. package/scripts/registry_cli.js +0 -157
  221. package/scripts/verify_all.js +0 -603
  222. package/scripts/verify_control_plane_contracts.js +0 -34
  223. package/scripts/verify_control_plane_routes.js +0 -70
  224. package/scripts/verify_examples.js +0 -19
  225. package/scripts/verify_external_proof_import_gate.js +0 -84
  226. package/scripts/verify_external_proof_signature.js +0 -142
  227. package/scripts/verify_forgery_resistance.js +0 -281
  228. package/scripts/verify_integrations.js +0 -170
  229. package/scripts/verify_native_hybrid_design_gate.js +0 -114
  230. package/scripts/verify_paid_execute.js +0 -133
  231. package/scripts/verify_paywall_flag.js +0 -86
  232. package/scripts/verify_reference_promotion_gate.js +0 -74
  233. package/scripts/verify_reference_python.js +0 -49
  234. package/scripts/verify_reference_python_crypto.js +0 -87
  235. package/scripts/verify_reference_rust_scaffold.js +0 -50
  236. package/scripts/verify_service.js +0 -3038
  237. package/scripts/verify_tooling.js +0 -1251
  238. package/scripts/verify_trust_portability.js +0 -56
  239. package/scripts/verify_verification_services.js +0 -56
  240. package/server.js +0 -4430
@@ -1,810 +0,0 @@
1
- """Independent Python candidate reference helpers for XNYA atom/payload vectors.
2
-
3
- This module intentionally does not import or shell out to the JavaScript
4
- implementation. It is a vector-parity scaffold, not yet a full
5
- XNYA-compatible reference implementation.
6
- """
7
-
8
- from __future__ import annotations
9
-
10
- import hashlib
11
- import json
12
- import base64
13
- from dataclasses import dataclass
14
- from typing import Any
15
-
16
-
17
- XNYA_MAGIC = bytes.fromhex("584e5941")
18
- VERSION_V1 = 0x01
19
- VERSION_V2 = 0x02
20
- VALID_RECORD_TYPES = {0x01, 0x02, 0x03}
21
- ZERO_HASH32 = bytes(32)
22
- IDENTITY_PREFIXES = {"01", "02", "03", "04", "05", "06", "07"}
23
-
24
- XNYA_DS_AGENT_V1 = b"xoonya:agent:v1:"
25
- XNYA_DS_AGENT_V2 = b"xoonya:agent:v2:"
26
- XNYA_DS_PAYLOAD_V1 = b"xoonya:payload:v1:"
27
- XNYA_DS_PAYLOAD_V2 = b"xoonya:payload:v2:"
28
- XNYA_DS_PAYLOAD_PROFILE_V1 = b"xoonya:payload-profile:v1:"
29
- XNYA_DS_LINE_RATE_FRAME_V1 = b"xoonya:line-rate-frame:v1:"
30
- CANONICAL_EVENT_BUNDLE_VERSION = "canonical-event-bundle/v1"
31
- CANONICAL_RECEIPT_VERSION = "receipt/v1"
32
- PROOF_BUNDLE_VERSION = "proof-bundle/v1"
33
- XOONYA_NATIVE_SHA256_METHOD = "xoonya-native-sha256-v1"
34
- XOONYA_NATIVE_SIGNED_METHOD = "xoonya-native-signed-v1"
35
- XOONYA_NATIVE_ANCHORED_METHOD = "xoonya-native-anchored-v1"
36
- XOONYA_NATIVE_HYBRID_METHOD = "xoonya-native-hybrid-v1"
37
- XOONYA_NATIVE_PROOF_FORMAT_VERSION = "xoonya-native-proof/v1"
38
- LEGACY_OPRETURN_METHOD = "legacy-opreturn-v1"
39
- DETACHED_ASSERTION_METHOD = "detached-assertion-v1"
40
- EXTERNAL_ASSERTION_METHODS = {
41
- "detached-assertion-v1",
42
- "jwt-assertion-v1",
43
- "did-vc-v1",
44
- "sd-jwt-vc-v1",
45
- "signed-envelope-v1",
46
- "partner-proof-envelope-v1",
47
- }
48
-
49
- FIELD_TYPES = {
50
- "bytes32": (1, 32),
51
- "bytes20": (2, 20),
52
- "uint64": (3, 8),
53
- "utf8": (4, None),
54
- "bool": (5, 1),
55
- }
56
- FIELD_TYPES_BY_ID = {spec[0]: (name, spec[1]) for name, spec in FIELD_TYPES.items()}
57
-
58
-
59
- class XnyaReferenceError(ValueError):
60
- def __init__(self, code: str, message: str):
61
- super().__init__(message)
62
- self.code = code
63
-
64
-
65
- @dataclass(frozen=True)
66
- class ParsedAtom:
67
- atom_version: str
68
- spec_version: str
69
- byte_length: int
70
- record_type_hex: str
71
- agent_id: str
72
- payload_hash_sha256: str
73
- atom_hash_sha256: str
74
- atom_hex: str
75
-
76
-
77
- def sha256(data: bytes) -> bytes:
78
- return hashlib.sha256(data).digest()
79
-
80
-
81
- def sha256_hex(data: bytes) -> str:
82
- return sha256(data).hex()
83
-
84
-
85
- def hash160_hex(data: bytes) -> str:
86
- return hashlib.new("ripemd160", sha256(data)).hexdigest()
87
-
88
-
89
- def canonicalize(value: Any) -> str:
90
- if isinstance(value, list):
91
- return "[" + ",".join(canonicalize(item) for item in value) + "]"
92
- if isinstance(value, dict):
93
- return "{" + ",".join(
94
- json.dumps(str(key), separators=(",", ":"), ensure_ascii=False) + ":" + canonicalize(value[key])
95
- for key in sorted(value.keys())
96
- ) + "}"
97
- return json.dumps(value, separators=(",", ":"), ensure_ascii=False)
98
-
99
-
100
- def canonical_json_sha256_hex(value: Any) -> str:
101
- return sha256_hex(canonicalize(value).encode("utf-8"))
102
-
103
-
104
- def normalize_string(value: Any) -> str:
105
- return str("" if value is None else value).strip()
106
-
107
-
108
- def normalize_string_array(values: Any, label: str) -> list[str] | None:
109
- if values is None:
110
- return None
111
- if not isinstance(values, list):
112
- raise ValueError(f"{label} must be an array")
113
- normalized = [normalize_string(value) for value in values]
114
- normalized = [value for value in normalized if value]
115
- return normalized or None
116
-
117
-
118
- def canonicalize_event(event: dict[str, Any]) -> dict[str, Any]:
119
- if not isinstance(event, dict):
120
- raise ValueError("event must be an object")
121
- normalized = {
122
- "version": normalize_string(event.get("version") or CANONICAL_EVENT_BUNDLE_VERSION),
123
- "domain_id": normalize_string(event.get("domain_id")),
124
- "sequence": normalize_string(event.get("sequence")),
125
- "actor_id": normalize_string(event.get("actor_id")),
126
- "adapter_source": normalize_string(event.get("adapter_source")),
127
- "event_type": normalize_string(event.get("event_type")),
128
- "event_hash": normalize_string(event.get("event_hash")).lower(),
129
- }
130
- for key, value in normalized.items():
131
- if not value:
132
- raise ValueError(f"event.{key} required")
133
- if normalized["version"] != CANONICAL_EVENT_BUNDLE_VERSION:
134
- raise ValueError(f"event.version must be {CANONICAL_EVENT_BUNDLE_VERSION}")
135
- assert_hex(normalized["event_hash"], "event_hash_invalid", "event.event_hash must be 32 byte hex")
136
-
137
- timestamp_ns = normalize_string(event.get("timestamp_ns"))
138
- if timestamp_ns:
139
- normalized["timestamp_ns"] = timestamp_ns
140
- parent_refs = normalize_string_array(event.get("parent_refs"), "event.parent_refs")
141
- if parent_refs:
142
- normalized["parent_refs"] = parent_refs
143
- capability_refs = normalize_string_array(event.get("capability_refs"), "event.capability_refs")
144
- if capability_refs:
145
- normalized["capability_refs"] = capability_refs
146
- attestation_ref = normalize_string(event.get("attestation_ref"))
147
- if attestation_ref:
148
- normalized["attestation_ref"] = attestation_ref
149
- semantic_slots = event.get("semantic_slots")
150
- if semantic_slots is not None:
151
- if not isinstance(semantic_slots, dict):
152
- raise ValueError("event.semantic_slots must be an object")
153
- normalized["semantic_slots"] = semantic_slots
154
- return normalized
155
-
156
-
157
- def hash_event(event: dict[str, Any]) -> str:
158
- return canonical_json_sha256_hex(canonicalize_event(event))
159
-
160
-
161
- def build_canonical_receipt_signing_material(receipt: dict[str, Any]) -> dict[str, Any]:
162
- return {
163
- "agent_identity": receipt.get("agent_identity"),
164
- "domain_id": receipt.get("domain_id"),
165
- "event_commitment": receipt.get("event_commitment"),
166
- "receipt_id": receipt.get("receipt_id"),
167
- "sequence": receipt.get("sequence"),
168
- "version": receipt.get("version"),
169
- }
170
-
171
-
172
- def create_legacy_receipt(identity_material_hex: str, payload: bytes) -> dict[str, Any]:
173
- material = bytes.fromhex(assert_hex(identity_material_hex, "non_hex", "identityMaterialHex must be hex"))
174
- agent_id = hash160_hex(material)
175
- payload_hash = sha256_hex(payload) if payload else ZERO_HASH32.hex()
176
- opreturn_hex = "50524654" + "01" + "01" + agent_id + payload_hash
177
- return {
178
- "agent_id": agent_id,
179
- "opreturn_hex": opreturn_hex,
180
- "payload_hash": payload_hash,
181
- "spec_version": "v1",
182
- }
183
-
184
-
185
- def verify_legacy_receipt(opreturn_hex: str, payload: bytes) -> dict[str, Any]:
186
- normalized = str(opreturn_hex or "").strip().lower()
187
- try:
188
- bytes.fromhex(normalized)
189
- except ValueError:
190
- return {"valid": False, "reason": "non_hex", "agent_id": None}
191
- if len(normalized) != 116:
192
- return {"valid": False, "reason": "invalid_length", "agent_id": None}
193
- if normalized[:8] != "50524654":
194
- return {"valid": False, "reason": "bad_magic", "agent_id": None}
195
- if normalized[8:10] != "01":
196
- return {"valid": False, "reason": "bad_version", "agent_id": None}
197
- if normalized[10:12] not in {"01", "02", "03"}:
198
- return {"valid": False, "reason": "bad_type", "agent_id": None}
199
- expected_payload = sha256_hex(payload) if payload else ZERO_HASH32.hex()
200
- if normalized[52:116] != expected_payload:
201
- return {"valid": False, "reason": "payload_mismatch", "agent_id": normalized[12:52]}
202
- return {"valid": True, "reason": None, "agent_id": normalized[12:52]}
203
-
204
-
205
- def select_receipt_algorithm(options: dict[str, Any], compatibility: dict[str, Any] | None) -> str:
206
- requested = normalize_string(options.get("algorithm"))
207
- if requested:
208
- return requested
209
- if compatibility:
210
- return LEGACY_OPRETURN_METHOD
211
- if options.get("signature") is not None:
212
- return DETACHED_ASSERTION_METHOD
213
- return XOONYA_NATIVE_SHA256_METHOD
214
-
215
-
216
- def build_native_receipt_signature(receipt: dict[str, Any], canonical_event: dict[str, Any]) -> str:
217
- material = {
218
- "method": XOONYA_NATIVE_SHA256_METHOD,
219
- "receipt_signing_material": build_canonical_receipt_signing_material(receipt),
220
- "event_commitment": hash_event(canonical_event),
221
- }
222
- return XOONYA_NATIVE_SHA256_METHOD + ":" + sha256_hex(canonicalize(material).encode("utf-8"))
223
-
224
-
225
- def _base64url_decode(value: str) -> bytes:
226
- normalized = normalize_string(value)
227
- padded = normalized + ("=" * ((4 - (len(normalized) % 4)) % 4))
228
- return base64.urlsafe_b64decode(padded.encode("ascii"))
229
-
230
-
231
- def _spki_der_from_pem(public_key_pem: str) -> bytes:
232
- pem = str(public_key_pem or "").replace("\r\n", "\n").strip()
233
- begin = "-----BEGIN PUBLIC KEY-----"
234
- end = "-----END PUBLIC KEY-----"
235
- if begin not in pem or end not in pem:
236
- raise ValueError("native public key must be SPKI PEM")
237
- body = pem.split(begin, 1)[1].split(end, 1)[0]
238
- return base64.b64decode("".join(body.split()))
239
-
240
-
241
- def _ed25519_crypto_available() -> bool:
242
- try:
243
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey # noqa: F401
244
- from cryptography.hazmat.primitives.serialization import load_pem_public_key # noqa: F401
245
- except Exception:
246
- return False
247
- return True
248
-
249
-
250
- def build_native_signed_proof_material(receipt: dict[str, Any], canonical_event: dict[str, Any], proof_descriptor: dict[str, Any]) -> dict[str, Any]:
251
- return {
252
- "identity_ref": proof_descriptor.get("identity_ref"),
253
- "method": XOONYA_NATIVE_SIGNED_METHOD,
254
- "public_key_ref": proof_descriptor.get("public_key_ref"),
255
- "receipt_signing_material": build_canonical_receipt_signing_material(receipt),
256
- "event_commitment": hash_event(canonical_event),
257
- "signature_suite": proof_descriptor.get("signature_suite"),
258
- "standard_version": XOONYA_NATIVE_PROOF_FORMAT_VERSION,
259
- }
260
-
261
-
262
- def verify_native_signed_receipt_proof(receipt: dict[str, Any], ceb: dict[str, Any]) -> dict[str, Any]:
263
- proof = receipt.get("native_proof")
264
- if not isinstance(proof, dict):
265
- return {"valid": False, "reason": "native_proof_missing"}
266
- if normalize_string(proof.get("method")) != XOONYA_NATIVE_SIGNED_METHOD:
267
- return {"valid": False, "reason": "native_proof_method_mismatch"}
268
- signature_suite = normalize_string(proof.get("signature_suite"))
269
- if signature_suite != "ed25519":
270
- return {"valid": False, "reason": "native_signature_suite_crypto_unavailable_in_python_candidate"}
271
- identity_ref = normalize_string(proof.get("identity_ref"))
272
- if not identity_ref:
273
- return {"valid": False, "reason": "native_identity_ref_missing"}
274
- public_key_material = proof.get("public_key_material")
275
- if not isinstance(public_key_material, dict):
276
- return {"valid": False, "reason": "native_public_key_material_missing"}
277
- if normalize_string(public_key_material.get("format")) != "spki-pem":
278
- return {"valid": False, "reason": "native_public_key_format_mismatch"}
279
- try:
280
- der = _spki_der_from_pem(public_key_material.get("value", ""))
281
- except Exception as error:
282
- return {"valid": False, "reason": str(error)}
283
- expected_public_key_ref = "ed25519-spki-sha256:" + sha256_hex(der)
284
- if normalize_string(proof.get("public_key_ref")) != expected_public_key_ref:
285
- return {"valid": False, "reason": "native_public_key_ref_mismatch"}
286
- proof_material = proof.get("proof_material")
287
- if not isinstance(proof_material, dict):
288
- return {"valid": False, "reason": "native_proof_material_missing"}
289
- signature = normalize_string(proof_material.get("signature"))
290
- if not signature:
291
- return {"valid": False, "reason": "native_signature_missing"}
292
- if normalize_string(receipt.get("signature")) != XOONYA_NATIVE_SIGNED_METHOD + ":ed25519:" + signature:
293
- return {"valid": False, "reason": "signature_native_signed_mismatch"}
294
- signing_material = build_native_signed_proof_material(receipt, ceb, {
295
- "identity_ref": identity_ref,
296
- "public_key_ref": expected_public_key_ref,
297
- "signature_suite": "ed25519",
298
- })
299
- signing_bytes = canonicalize(signing_material).encode("utf-8")
300
- if normalize_string(proof_material.get("signing_material_sha256")).lower() != sha256_hex(signing_bytes):
301
- return {"valid": False, "reason": "native_signing_material_hash_mismatch"}
302
- if not _ed25519_crypto_available():
303
- return {
304
- "valid": False,
305
- "reason": "native_signature_crypto_dependency_missing",
306
- "dependency": "cryptography",
307
- }
308
- try:
309
- from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PublicKey
310
- from cryptography.hazmat.primitives.serialization import load_pem_public_key
311
-
312
- public_key = load_pem_public_key(public_key_material["value"].encode("utf-8"))
313
- if not isinstance(public_key, Ed25519PublicKey):
314
- return {"valid": False, "reason": "native_public_key_suite_mismatch"}
315
- public_key.verify(_base64url_decode(signature), signing_bytes)
316
- except Exception:
317
- return {"valid": False, "reason": "native_signature_invalid"}
318
- return {
319
- "valid": True,
320
- "verification_state": "verified_native_signature",
321
- "signature_suite": "ed25519",
322
- }
323
-
324
-
325
- def assert_hex(value: str, code: str, message: str) -> str:
326
- normalized = str(value or "").strip().lower()
327
- if not normalized or len(normalized) % 2 != 0:
328
- raise XnyaReferenceError(code, message)
329
- try:
330
- bytes.fromhex(normalized)
331
- except ValueError as exc:
332
- raise XnyaReferenceError(code, message) from exc
333
- return normalized
334
-
335
-
336
- def normalize_identity_material(identity_material_hex: str) -> bytes:
337
- material_hex = assert_hex(
338
- identity_material_hex,
339
- "identity_unsupported_type_prefix",
340
- "identityMaterialHex has unsupported identity type prefix",
341
- )
342
- prefix = material_hex[:2]
343
- if prefix not in IDENTITY_PREFIXES:
344
- raise XnyaReferenceError(
345
- "identity_unsupported_type_prefix",
346
- "identityMaterialHex has unsupported identity type prefix",
347
- )
348
- if len(material_hex) == 2:
349
- raise XnyaReferenceError(
350
- "identity_missing_raw_bytes",
351
- "identityMaterialHex must include raw identity bytes",
352
- )
353
- return bytes.fromhex(material_hex)
354
-
355
-
356
- def hash_payload(payload: bytes | None, domain: bytes) -> bytes:
357
- if not payload:
358
- return ZERO_HASH32
359
- digest = sha256(domain + payload)
360
- if digest == ZERO_HASH32:
361
- raise XnyaReferenceError("payload_hash_mismatch", "payload hash cannot equal absent sentinel")
362
- return digest
363
-
364
-
365
- def create_atom_v1(identity_material_hex: str, payload: bytes | None, record_type_hex: str = "01") -> dict[str, Any]:
366
- material = normalize_identity_material(identity_material_hex)
367
- record_type = int(record_type_hex, 16)
368
- if record_type not in VALID_RECORD_TYPES:
369
- raise XnyaReferenceError("atom_unsupported_record_type", "atomHex has unsupported record type")
370
- agent_id = bytes.fromhex(hash160_hex(XNYA_DS_AGENT_V1 + material))
371
- payload_hash = hash_payload(payload, XNYA_DS_PAYLOAD_V1)
372
- atom = XNYA_MAGIC + bytes([VERSION_V1, record_type]) + agent_id + payload_hash
373
- return {
374
- "atom_hex": atom.hex(),
375
- "atom_version": "xoonya-native-atom/v1",
376
- "spec_version": "v1",
377
- "byte_length": len(atom),
378
- "record_type_hex": f"{record_type:02x}",
379
- "agent_id": agent_id.hex(),
380
- "payload_hash_sha256": payload_hash.hex(),
381
- "atom_hash_sha256": sha256_hex(atom),
382
- }
383
-
384
-
385
- def create_atom_v2(identity_material_hex: str, payload: bytes | None, record_type_hex: str = "01") -> dict[str, Any]:
386
- material = normalize_identity_material(identity_material_hex)
387
- record_type = int(record_type_hex, 16)
388
- if record_type not in VALID_RECORD_TYPES:
389
- raise XnyaReferenceError("atom_unsupported_record_type", "atomHex has unsupported record type")
390
- agent_id = sha256(XNYA_DS_AGENT_V2 + material)
391
- payload_hash = hash_payload(payload, XNYA_DS_PAYLOAD_V2)
392
- atom = XNYA_MAGIC + bytes([VERSION_V2, record_type]) + agent_id + payload_hash
393
- return {
394
- "atom_hex": atom.hex(),
395
- "atom_version": "xoonya-native-atom/v2",
396
- "spec_version": "v2",
397
- "byte_length": len(atom),
398
- "record_type_hex": f"{record_type:02x}",
399
- "agent_id": agent_id.hex(),
400
- "payload_hash_sha256": payload_hash.hex(),
401
- "atom_hash_sha256": sha256_hex(atom),
402
- }
403
-
404
-
405
- def parse_atom(atom_hex: str) -> ParsedAtom:
406
- hex_value = str(atom_hex or "").strip().lower()
407
- try:
408
- atom = bytes.fromhex(hex_value)
409
- except ValueError as exc:
410
- raise XnyaReferenceError("atom_hex_not_hex", "atomHex must be hex") from exc
411
- if len(atom) not in (58, 70):
412
- raise XnyaReferenceError("atom_unsupported_length", "atomHex must be a supported xoonya native atom")
413
- if atom[:4] != XNYA_MAGIC:
414
- raise XnyaReferenceError("atom_bad_magic", "atomHex has bad xoonya native atom magic")
415
- record_type = atom[5]
416
- if record_type not in VALID_RECORD_TYPES:
417
- raise XnyaReferenceError("atom_unsupported_record_type", "atomHex has unsupported record type")
418
- version = atom[4]
419
- if version == VERSION_V1 and len(atom) == 58:
420
- return ParsedAtom(
421
- atom_version="xoonya-native-atom/v1",
422
- spec_version="v1",
423
- byte_length=58,
424
- record_type_hex=f"{record_type:02x}",
425
- agent_id=atom[6:26].hex(),
426
- payload_hash_sha256=atom[26:58].hex(),
427
- atom_hash_sha256=sha256_hex(atom),
428
- atom_hex=hex_value,
429
- )
430
- if version == VERSION_V2 and len(atom) == 70:
431
- return ParsedAtom(
432
- atom_version="xoonya-native-atom/v2",
433
- spec_version="v2",
434
- byte_length=70,
435
- record_type_hex=f"{record_type:02x}",
436
- agent_id=atom[6:38].hex(),
437
- payload_hash_sha256=atom[38:70].hex(),
438
- atom_hash_sha256=sha256_hex(atom),
439
- atom_hex=hex_value,
440
- )
441
- raise XnyaReferenceError("atom_version_length_mismatch", "atomHex/version length mismatch")
442
-
443
-
444
- def verify_atom(atom_hex: str, *, identity_material_hex: str | None = None, payload: bytes | None = None) -> dict[str, Any]:
445
- parsed = parse_atom(atom_hex)
446
- if identity_material_hex is not None:
447
- material = normalize_identity_material(identity_material_hex)
448
- expected_agent_id = (
449
- hash160_hex(XNYA_DS_AGENT_V1 + material)
450
- if parsed.spec_version == "v1"
451
- else sha256_hex(XNYA_DS_AGENT_V2 + material)
452
- )
453
- if parsed.agent_id != expected_agent_id:
454
- return {"valid": False, "reason": "agent_id_mismatch", "error_code": "agent_id_mismatch"}
455
- if payload is not None:
456
- expected_payload_hash = (
457
- hash_payload(payload, XNYA_DS_PAYLOAD_V1)
458
- if parsed.spec_version == "v1"
459
- else hash_payload(payload, XNYA_DS_PAYLOAD_V2)
460
- ).hex()
461
- if parsed.payload_hash_sha256 != expected_payload_hash:
462
- return {"valid": False, "reason": "payload_hash_mismatch", "error_code": "payload_hash_mismatch"}
463
- return {"valid": True, "reason": None, "error_code": None, "parsed": parsed}
464
-
465
-
466
- def _encode_field(field: dict[str, Any]) -> tuple[int, int, bytes]:
467
- field_id = int(field.get("field_id", field.get("id")))
468
- field_type = str(field.get("field_type", field.get("type"))).strip()
469
- if field_id < 1 or field_id > 0xFFFF:
470
- raise ValueError("payload profile field_id must be uint16")
471
- if field_type not in FIELD_TYPES:
472
- raise ValueError("payload profile field_type unsupported")
473
- field_type_id, fixed_len = FIELD_TYPES[field_type]
474
- if field_type in ("bytes32", "bytes20"):
475
- value = bytes.fromhex(str(field.get("value_hex", field.get("value"))).strip().lower())
476
- if len(value) != fixed_len:
477
- raise ValueError("payload profile fixed field length mismatch")
478
- elif field_type == "uint64":
479
- n = int(field.get("value"))
480
- if n < 0 or n > 0xFFFFFFFFFFFFFFFF:
481
- raise ValueError("payload profile uint64 out of range")
482
- value = n.to_bytes(8, "big")
483
- elif field_type == "utf8":
484
- value = str(field.get("value", "")).encode("utf-8")
485
- if not value or len(value) > 1024:
486
- raise ValueError("payload profile utf8 length unsupported")
487
- elif field_type == "bool":
488
- raw = field.get("value")
489
- if not isinstance(raw, bool):
490
- raise ValueError("payload profile bool must be boolean")
491
- value = b"\x01" if raw else b"\x00"
492
- return field_id, field_type_id, value
493
-
494
-
495
- def encode_payload_profile(input_value: dict[str, Any]) -> dict[str, Any]:
496
- schema_ref = str(input_value.get("schema_ref", input_value.get("schemaRef", "xoonya.payload.schema.generic.v1"))).strip()
497
- schema_hash = sha256_hex(f"xoonya:payload-schema:v1:{schema_ref}".encode("utf-8"))
498
- encoded_fields = sorted((_encode_field(field) for field in input_value["fields"]), key=lambda item: (item[0], item[1]))
499
- canonical = bytearray()
500
- canonical.extend(b"XNYP")
501
- canonical.append(1)
502
- canonical.extend(bytes.fromhex(schema_hash))
503
- canonical.append(len(encoded_fields))
504
- for field_id, field_type_id, value in encoded_fields:
505
- canonical.extend(field_id.to_bytes(2, "big"))
506
- canonical.append(field_type_id)
507
- canonical.extend(len(value).to_bytes(2, "big"))
508
- canonical.extend(value)
509
- canonical_bytes = bytes(canonical)
510
- return {
511
- "profile": "xoonya-native-payload-profile/v1",
512
- "schema_ref": schema_ref,
513
- "schema_hash_sha256": schema_hash,
514
- "field_count": len(encoded_fields),
515
- "canonical_bytes_hex": canonical_bytes.hex(),
516
- "payload_hash_sha256": sha256_hex(XNYA_DS_PAYLOAD_PROFILE_V1 + canonical_bytes),
517
- "field_order": [field_id for field_id, _, _ in encoded_fields],
518
- }
519
-
520
-
521
- def parse_payload_profile(canonical_bytes_hex: str) -> dict[str, Any]:
522
- canonical = bytes.fromhex(canonical_bytes_hex)
523
- if len(canonical) < 38:
524
- raise ValueError("payload profile bytes too short")
525
- if canonical[:4] != b"XNYP":
526
- raise ValueError("payload profile magic mismatch")
527
- if canonical[4] != 1:
528
- raise ValueError("payload profile version unsupported")
529
- field_count = canonical[37]
530
- offset = 38
531
- fields = []
532
- for _ in range(field_count):
533
- if offset + 5 > len(canonical):
534
- raise ValueError("payload profile truncated field header")
535
- field_id = int.from_bytes(canonical[offset : offset + 2], "big")
536
- field_type_id = canonical[offset + 2]
537
- length = int.from_bytes(canonical[offset + 3 : offset + 5], "big")
538
- offset += 5
539
- value = canonical[offset : offset + length]
540
- offset += length
541
- if field_type_id not in FIELD_TYPES_BY_ID:
542
- raise ValueError("payload profile field type unsupported")
543
- field_type, fixed_len = FIELD_TYPES_BY_ID[field_type_id]
544
- if fixed_len is not None and len(value) != fixed_len:
545
- raise ValueError("payload profile fixed field length mismatch")
546
- fields.append({"field_id": field_id, "field_type": field_type, "field_type_id": field_type_id, "value_hex": value.hex()})
547
- if offset != len(canonical):
548
- raise ValueError("payload profile trailing bytes")
549
- sorted_fields = sorted(fields, key=lambda item: (item["field_id"], item["field_type_id"]))
550
- if fields != sorted_fields:
551
- raise ValueError("payload profile fields not canonical order")
552
- return {
553
- "profile": "xoonya-native-payload-profile/v1",
554
- "schema_hash_sha256": canonical[5:37].hex(),
555
- "field_count": field_count,
556
- "fields": fields,
557
- "canonical_bytes_hex": canonical.hex(),
558
- "payload_hash_sha256": sha256_hex(XNYA_DS_PAYLOAD_PROFILE_V1 + canonical),
559
- }
560
-
561
-
562
- def line_rate_frame_checksum(atom: bytes) -> str:
563
- return sha256_hex(XNYA_DS_LINE_RATE_FRAME_V1 + atom)[:4]
564
-
565
-
566
- def normalize_anchor_refs(anchor_refs: list[dict[str, Any]]) -> list[dict[str, Any]]:
567
- if not isinstance(anchor_refs, list) or not anchor_refs:
568
- raise ValueError("native anchors must be a non-empty array")
569
- normalized = []
570
- for index, anchor in enumerate(anchor_refs):
571
- if not isinstance(anchor, dict):
572
- raise ValueError(f"native anchors[{index}] must be an object")
573
- entry = {
574
- "anchor_id": normalize_string(anchor.get("anchor_id") or anchor.get("id")),
575
- "anchor_kind": normalize_string(anchor.get("anchor_kind") or anchor.get("kind")),
576
- "anchor_ref": normalize_string(anchor.get("anchor_ref") or anchor.get("ref")),
577
- "anchor_hash_sha256": normalize_string(anchor.get("anchor_hash_sha256") or anchor.get("hash_sha256") or anchor.get("anchor_hash")).lower(),
578
- }
579
- if not entry["anchor_id"]:
580
- raise ValueError(f"native anchors[{index}].anchor_id required")
581
- if not entry["anchor_kind"]:
582
- raise ValueError(f"native anchors[{index}].anchor_kind required")
583
- if not entry["anchor_ref"]:
584
- raise ValueError(f"native anchors[{index}].anchor_ref required")
585
- assert_hex(entry["anchor_hash_sha256"], "anchor_hash_invalid", f"native anchors[{index}].anchor_hash_sha256 must be 32 byte hex")
586
- for key in ("network", "finality_state", "observed_at_iso", "provider_ref", "inclusion_proof_ref"):
587
- value = normalize_string(anchor.get(key))
588
- if value:
589
- entry[key] = value
590
- normalized.append(entry)
591
- return sorted(normalized, key=canonicalize)
592
-
593
-
594
- def build_native_anchor_binding_material(receipt: dict[str, Any], canonical_event: dict[str, Any], anchor_refs: list[dict[str, Any]]) -> dict[str, Any]:
595
- return {
596
- "anchor_refs": normalize_anchor_refs(anchor_refs),
597
- "base_method": XOONYA_NATIVE_SHA256_METHOD,
598
- "event_commitment": hash_event(canonical_event),
599
- "method": XOONYA_NATIVE_ANCHORED_METHOD,
600
- "receipt_signing_material": build_canonical_receipt_signing_material(receipt),
601
- "standard_version": "xoonya-native-proof/v1",
602
- }
603
-
604
-
605
- def build_native_anchored_receipt_proof(receipt: dict[str, Any], canonical_event: dict[str, Any], anchors: list[dict[str, Any]]) -> dict[str, Any]:
606
- anchor_refs = normalize_anchor_refs(anchors)
607
- binding_material = build_native_anchor_binding_material(receipt, canonical_event, anchor_refs)
608
- binding_hash = sha256_hex(canonicalize(binding_material).encode("utf-8"))
609
- anchor_root_hash = canonical_json_sha256_hex(anchor_refs)
610
- return {
611
- "method": XOONYA_NATIVE_ANCHORED_METHOD,
612
- "base_method": XOONYA_NATIVE_SHA256_METHOD,
613
- "anchor_refs": anchor_refs,
614
- "proof_material": {
615
- "anchor_root_sha256": anchor_root_hash,
616
- "binding_material_sha256": binding_hash,
617
- },
618
- "compact_signature": XOONYA_NATIVE_ANCHORED_METHOD + ":" + binding_hash,
619
- }
620
-
621
-
622
- def emit_receipt(event: dict[str, Any], options: dict[str, Any] | None = None) -> dict[str, Any]:
623
- options = options or {}
624
- canonical_event = canonicalize_event(event)
625
- event_commitment = hash_event(canonical_event)
626
- agent_identity = normalize_string(options.get("agent_identity") or options.get("agentIdentity") or canonical_event["actor_id"])
627
- if not agent_identity:
628
- raise ValueError("agent_identity required")
629
- compatibility = None
630
- legacy_identity = options.get("legacy_identity_material_hex")
631
- if legacy_identity:
632
- legacy_receipt = create_legacy_receipt(str(legacy_identity).strip().lower(), canonicalize(canonical_event).encode("utf-8"))
633
- compatibility = {"legacy_receipt": legacy_receipt}
634
- receipt_id = sha256_hex(canonicalize({
635
- "agent_identity": agent_identity,
636
- "domain_id": canonical_event["domain_id"],
637
- "event_commitment": event_commitment,
638
- "sequence": canonical_event["sequence"],
639
- "version": CANONICAL_RECEIPT_VERSION,
640
- }).encode("utf-8"))
641
- receipt = {
642
- "version": CANONICAL_RECEIPT_VERSION,
643
- "receipt_id": receipt_id,
644
- "agent_identity": agent_identity,
645
- "domain_id": canonical_event["domain_id"],
646
- "sequence": canonical_event["sequence"],
647
- "event_commitment": event_commitment,
648
- "algorithm": select_receipt_algorithm(options, compatibility),
649
- }
650
- if compatibility:
651
- receipt["compatibility"] = compatibility
652
- signature = normalize_string(options.get("signature"))
653
- native_proof = options.get("native_proof") if isinstance(options.get("native_proof"), dict) else None
654
- if not signature and receipt["algorithm"] == XOONYA_NATIVE_ANCHORED_METHOD:
655
- native_proof = build_native_anchored_receipt_proof(receipt, canonical_event, options.get("anchors") or options.get("anchor_refs") or [])
656
- signature = native_proof.pop("compact_signature")
657
- if not signature and receipt["algorithm"] == XOONYA_NATIVE_SHA256_METHOD:
658
- signature = build_native_receipt_signature(receipt, canonical_event)
659
- if not signature and compatibility:
660
- signature = compatibility["legacy_receipt"]["opreturn_hex"]
661
- if not signature:
662
- raise ValueError("signature or legacy_identity_material_hex required")
663
- if native_proof:
664
- receipt["native_proof"] = native_proof
665
- receipt["signature"] = signature
666
- return receipt
667
-
668
-
669
- def build_proof_bundle(input_value: dict[str, Any]) -> dict[str, Any]:
670
- ceb = canonicalize_event(input_value.get("ceb") or input_value.get("event"))
671
- anchors = input_value.get("anchors") if isinstance(input_value.get("anchors"), list) else None
672
- receipt_options = input_value.get("receipt_options") if isinstance(input_value.get("receipt_options"), dict) else {}
673
- if anchors and not any(key in receipt_options for key in ("anchors", "native_anchor_refs", "anchor_refs")):
674
- receipt_options = {**receipt_options, "anchors": anchors}
675
- receipt = input_value.get("receipt") if isinstance(input_value.get("receipt"), dict) else emit_receipt(ceb, receipt_options)
676
- bundle = {
677
- "version": PROOF_BUNDLE_VERSION,
678
- "ceb": ceb,
679
- "receipt": receipt,
680
- }
681
- if anchors is not None:
682
- bundle["anchors"] = anchors
683
- for key in ("attestations", "evidence_refs", "batch_refs"):
684
- if isinstance(input_value.get(key), list):
685
- bundle[key] = input_value[key]
686
- if isinstance(input_value.get("metadata"), dict):
687
- bundle["metadata"] = input_value["metadata"]
688
- return bundle
689
-
690
-
691
- def _verify_native_anchored(receipt: dict[str, Any], ceb: dict[str, Any], bundle: dict[str, Any]) -> dict[str, Any]:
692
- proof = receipt.get("native_proof")
693
- if not isinstance(proof, dict):
694
- return {"valid": False, "reason": "native_proof_missing"}
695
- if normalize_string(proof.get("method")) != XOONYA_NATIVE_ANCHORED_METHOD:
696
- return {"valid": False, "reason": "native_proof_method_mismatch"}
697
- if normalize_string(proof.get("base_method")) != XOONYA_NATIVE_SHA256_METHOD:
698
- return {"valid": False, "reason": "native_anchor_base_method_unsupported"}
699
- if not isinstance(bundle.get("anchors"), list):
700
- return {"valid": False, "reason": "native_anchor_bundle_missing"}
701
- proof_anchors = normalize_anchor_refs(proof.get("anchor_refs"))
702
- bundle_anchors = normalize_anchor_refs(bundle.get("anchors"))
703
- if canonicalize(proof_anchors) != canonicalize(bundle_anchors):
704
- return {"valid": False, "reason": "native_anchor_bundle_mismatch"}
705
- proof_material = proof.get("proof_material")
706
- if not isinstance(proof_material, dict):
707
- return {"valid": False, "reason": "native_anchor_proof_material_missing"}
708
- expected_anchor_root = canonical_json_sha256_hex(proof_anchors)
709
- if normalize_string(proof_material.get("anchor_root_sha256")).lower() != expected_anchor_root:
710
- return {"valid": False, "reason": "native_anchor_root_mismatch"}
711
- binding_material = build_native_anchor_binding_material(receipt, ceb, proof_anchors)
712
- binding_hash = sha256_hex(canonicalize(binding_material).encode("utf-8"))
713
- if normalize_string(proof_material.get("binding_material_sha256")).lower() != binding_hash:
714
- return {"valid": False, "reason": "native_anchor_binding_hash_mismatch"}
715
- if normalize_string(receipt.get("signature")) != XOONYA_NATIVE_ANCHORED_METHOD + ":" + binding_hash:
716
- return {"valid": False, "reason": "signature_native_anchored_mismatch"}
717
- return {"valid": True, "verification_state": "verified_native_anchor_binding", "anchor_count": len(proof_anchors), "anchor_root_sha256": expected_anchor_root}
718
-
719
-
720
- def verify_proof_bundle(bundle: dict[str, Any]) -> dict[str, Any]:
721
- try:
722
- if not isinstance(bundle, dict):
723
- return {"valid": False, "reason": "bundle_not_object"}
724
- if normalize_string(bundle.get("version")) != PROOF_BUNDLE_VERSION:
725
- return {"valid": False, "reason": "bad_bundle_version"}
726
- ceb = canonicalize_event(bundle.get("ceb"))
727
- receipt = bundle.get("receipt")
728
- if not isinstance(receipt, dict):
729
- return {"valid": False, "reason": "receipt_not_object"}
730
- if normalize_string(receipt.get("version")) != CANONICAL_RECEIPT_VERSION:
731
- return {"valid": False, "reason": "bad_receipt_version"}
732
- if normalize_string(receipt.get("domain_id")) != ceb["domain_id"]:
733
- return {"valid": False, "reason": "receipt_domain_mismatch"}
734
- if normalize_string(receipt.get("sequence")) != ceb["sequence"]:
735
- return {"valid": False, "reason": "receipt_sequence_mismatch"}
736
- expected_commitment = hash_event(ceb)
737
- if normalize_string(receipt.get("event_commitment")).lower() != expected_commitment:
738
- return {"valid": False, "reason": "event_commitment_mismatch"}
739
- expected_receipt_id = sha256_hex(canonicalize({
740
- "agent_identity": normalize_string(receipt.get("agent_identity")),
741
- "domain_id": ceb["domain_id"],
742
- "event_commitment": expected_commitment,
743
- "sequence": ceb["sequence"],
744
- "version": CANONICAL_RECEIPT_VERSION,
745
- }).encode("utf-8"))
746
- if normalize_string(receipt.get("receipt_id")).lower() != expected_receipt_id:
747
- return {"valid": False, "reason": "receipt_id_mismatch"}
748
- if not normalize_string(receipt.get("agent_identity")):
749
- return {"valid": False, "reason": "agent_identity_missing"}
750
- if not normalize_string(receipt.get("signature")):
751
- return {"valid": False, "reason": "signature_missing"}
752
- algorithm = normalize_string(receipt.get("algorithm"))
753
- if not algorithm:
754
- return {"valid": False, "reason": "verification_method_missing"}
755
- if algorithm == "none":
756
- return {"valid": False, "reason": "verification_method_none"}
757
- if algorithm == XOONYA_NATIVE_HYBRID_METHOD:
758
- return {"valid": False, "reason": "native_hybrid_runtime_unavailable_in_python_candidate"}
759
- if algorithm == LEGACY_OPRETURN_METHOD:
760
- legacy_receipt = (receipt.get("compatibility") or {}).get("legacy_receipt")
761
- if not isinstance(legacy_receipt, dict):
762
- return {"valid": False, "reason": "legacy_receipt_missing"}
763
- legacy_verification = verify_legacy_receipt(legacy_receipt.get("opreturn_hex"), canonicalize(ceb).encode("utf-8"))
764
- if not legacy_verification["valid"]:
765
- return {"valid": False, "reason": "legacy_receipt_" + legacy_verification["reason"]}
766
- if normalize_string(receipt.get("signature")).lower() != normalize_string(legacy_receipt.get("opreturn_hex")).lower():
767
- return {"valid": False, "reason": "signature_legacy_mismatch"}
768
- state = "verified_legacy_opreturn"
769
- elif algorithm == XOONYA_NATIVE_SHA256_METHOD:
770
- expected_signature = build_native_receipt_signature(receipt, ceb)
771
- if normalize_string(receipt.get("signature")) != expected_signature:
772
- return {"valid": False, "reason": "signature_native_mismatch"}
773
- state = "verified_native_digest"
774
- elif algorithm == XOONYA_NATIVE_ANCHORED_METHOD:
775
- anchored = _verify_native_anchored(receipt, ceb, bundle)
776
- if not anchored["valid"]:
777
- return {"valid": False, "reason": anchored["reason"]}
778
- state = anchored["verification_state"]
779
- elif algorithm == XOONYA_NATIVE_SIGNED_METHOD:
780
- signed = verify_native_signed_receipt_proof(receipt, ceb)
781
- if not signed["valid"]:
782
- return {
783
- "valid": False,
784
- "reason": signed["reason"],
785
- **({"dependency": signed["dependency"]} if signed.get("dependency") else {}),
786
- }
787
- state = signed["verification_state"]
788
- elif algorithm in EXTERNAL_ASSERTION_METHODS:
789
- signature = normalize_string(receipt.get("signature"))
790
- if signature.startswith((
791
- XOONYA_NATIVE_SHA256_METHOD + ":",
792
- XOONYA_NATIVE_SIGNED_METHOD + ":",
793
- XOONYA_NATIVE_ANCHORED_METHOD + ":",
794
- XOONYA_NATIVE_HYBRID_METHOD + ":",
795
- )):
796
- return {"valid": False, "reason": "signature_method_mismatch"}
797
- state = "accepted_detached_assertion" if algorithm == DETACHED_ASSERTION_METHOD else "accepted_external_proof_envelope"
798
- else:
799
- return {"valid": False, "reason": "verification_method_unsupported"}
800
- return {
801
- "valid": True,
802
- "reason": None,
803
- "version": PROOF_BUNDLE_VERSION,
804
- "event_commitment": expected_commitment,
805
- "receipt_id": expected_receipt_id,
806
- "verification_method": algorithm,
807
- "verification_state": state,
808
- }
809
- except Exception as error:
810
- return {"valid": False, "reason": str(error)}