cortexm 0.3.0__py3-none-any.whl
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.
- context_m.py +17 -0
- cortexm/__init__.py +45 -0
- cortexm/accel.py +403 -0
- cortexm/api/__init__.py +0 -0
- cortexm/api/chaos.py +118 -0
- cortexm/api/memory.py +635 -0
- cortexm/bench/__init__.py +0 -0
- cortexm/bench/abilities.py +311 -0
- cortexm/bench/baselines.py +89 -0
- cortexm/bench/beam_loader.py +317 -0
- cortexm/bench/generator.py +376 -0
- cortexm/bench/harness.py +211 -0
- cortexm/bench/messy.py +218 -0
- cortexm/bench/micro.py +251 -0
- cortexm/bench/ood.py +443 -0
- cortexm/bench/run.py +137 -0
- cortexm/bridge/__init__.py +0 -0
- cortexm/bridge/dates.py +178 -0
- cortexm/bridge/decoders.py +204 -0
- cortexm/bridge/enrich.py +255 -0
- cortexm/bridge/extractor.py +316 -0
- cortexm/bridge/fallback.py +332 -0
- cortexm/bridge/onnx_runtime.py +158 -0
- cortexm/bridge/patterns.py +760 -0
- cortexm/bridge/ppr.py +104 -0
- cortexm/bridge/prefilter.py +188 -0
- cortexm/bridge/query_extract.py +420 -0
- cortexm/bridge/reader.py +1174 -0
- cortexm/bridge/rerank.py +204 -0
- cortexm/bridge/writer.py +492 -0
- cortexm/cli.py +295 -0
- cortexm/cognition/__init__.py +53 -0
- cortexm/cognition/abstraction.py +192 -0
- cortexm/cognition/analogy.py +159 -0
- cortexm/cognition/engine.py +204 -0
- cortexm/cognition/gaps.py +365 -0
- cortexm/cognition/scanner.py +204 -0
- cortexm/config.py +375 -0
- cortexm/cortexm.py +8 -0
- cortexm/enterprise/__init__.py +0 -0
- cortexm/enterprise/audit.py +178 -0
- cortexm/enterprise/governance.py +239 -0
- cortexm/errors.py +35 -0
- cortexm/features/__init__.py +0 -0
- cortexm/features/git.py +204 -0
- cortexm/features/prefetch.py +88 -0
- cortexm/features/zk.py +105 -0
- cortexm/federation/__init__.py +39 -0
- cortexm/federation/crdt.py +275 -0
- cortexm/federation/fabric.py +109 -0
- cortexm/federation/hlc.py +80 -0
- cortexm/federation/node.py +145 -0
- cortexm/federation/schema_report.py +73 -0
- cortexm/federation/transport.py +164 -0
- cortexm/index/__init__.py +19 -0
- cortexm/index/nsg.py +386 -0
- cortexm/mcp/__init__.py +0 -0
- cortexm/mcp/server.py +985 -0
- cortexm/metrics.py +62 -0
- cortexm/migrate/__init__.py +0 -0
- cortexm/migrate/importers.py +192 -0
- cortexm/provenance/__init__.py +78 -0
- cortexm/provenance/agent.py +214 -0
- cortexm/provenance/cose.py +201 -0
- cortexm/provenance/scitt.py +258 -0
- cortexm/provenance/vc.py +250 -0
- cortexm/security/__init__.py +0 -0
- cortexm/security/crypto.py +162 -0
- cortexm/security/hashes.py +140 -0
- cortexm/security/injection.py +149 -0
- cortexm/security/mind.py +154 -0
- cortexm/security/pii.py +265 -0
- cortexm/security/rbac.py +169 -0
- cortexm/security/sandbox.py +131 -0
- cortexm/security/zk_hamming.py +142 -0
- cortexm/security/zk_sql.py +485 -0
- cortexm/server/__init__.py +0 -0
- cortexm/server/metrics.py +88 -0
- cortexm/server/rest.py +936 -0
- cortexm/server/sparql.py +984 -0
- cortexm/text/__init__.py +0 -0
- cortexm/text/dissim.py +252 -0
- cortexm/text/embedder.py +155 -0
- cortexm/text/fuzzy.py +218 -0
- cortexm/text/idiolect.py +253 -0
- cortexm/text/labse.py +374 -0
- cortexm/text/tokenizer.py +79 -0
- cortexm/trace/__init__.py +0 -0
- cortexm/trace/blob_arena.py +277 -0
- cortexm/trace/consolidate.py +337 -0
- cortexm/trace/contradictions.py +69 -0
- cortexm/trace/dedup.py +114 -0
- cortexm/trace/edges.py +214 -0
- cortexm/trace/fact.py +121 -0
- cortexm/trace/fade.py +245 -0
- cortexm/trace/lifecycle.py +112 -0
- cortexm/trace/rebuild.py +173 -0
- cortexm/trace/rules.py +171 -0
- cortexm/trace/store.py +680 -0
- cortexm/trace/structural.py +183 -0
- cortexm/trace/tmt.py +335 -0
- cortexm/util.py +148 -0
- cortexm/vsa/__init__.py +0 -0
- cortexm/vsa/attribution.py +149 -0
- cortexm/vsa/cleanup.py +161 -0
- cortexm/vsa/codecs.py +397 -0
- cortexm/vsa/hologram_overlay.py +139 -0
- cortexm/vsa/index.py +163 -0
- cortexm/vsa/ops.py +149 -0
- cortexm/vsa/palace.py +446 -0
- cortexm/vsa/role_vectors.py +236 -0
- cortexm/vsa/slb.py +78 -0
- cortexm/vsa/tlsh_trie.py +137 -0
- cortexm/vsa/working_memory.py +249 -0
- cortexm-0.3.0.dist-info/METADATA +482 -0
- cortexm-0.3.0.dist-info/RECORD +120 -0
- cortexm-0.3.0.dist-info/WHEEL +5 -0
- cortexm-0.3.0.dist-info/entry_points.txt +2 -0
- cortexm-0.3.0.dist-info/licenses/LICENSE +190 -0
- cortexm-0.3.0.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,485 @@
|
|
|
1
|
+
"""Zero-knowledge SQL proofs (Halo2/PLONKish-inspired) for the Trace.
|
|
2
|
+
|
|
3
|
+
PoneglyphDB-style proof: prove a SQL aggregate query returned a specific
|
|
4
|
+
value, without revealing other facts. The circuit is a PLONKish arithmetic
|
|
5
|
+
circuit (a polynomial commitment over the witness assignments).
|
|
6
|
+
|
|
7
|
+
This is a prototype: the proof system uses BLAKE3 commitments + Fiat-Shamir
|
|
8
|
+
transcript (not KZG or FRI), so the prover/verifier are linear in trace
|
|
9
|
+
size, NOT succinct. The API surface mirrors what a production Halo2
|
|
10
|
+
integration would expose — swapping the commitment scheme is a one-line
|
|
11
|
+
change to the `commit()` function.
|
|
12
|
+
|
|
13
|
+
Provable queries:
|
|
14
|
+
- MEMBERSHIP(subject, relation) -> bool (fact exists)
|
|
15
|
+
- COUNT(relation) -> int (count of facts with that relation)
|
|
16
|
+
- SUM(relation, value_predicate) -> float (sum of values matching predicate)
|
|
17
|
+
- AVG(relation, value_predicate) -> float
|
|
18
|
+
- MIN/MAX(relation, value_predicate) -> float
|
|
19
|
+
|
|
20
|
+
The proof reveals:
|
|
21
|
+
- The claimed result
|
|
22
|
+
- The Merkle root of the trace at proof-time
|
|
23
|
+
- A non-interactive PLONKish-style proof transcript
|
|
24
|
+
|
|
25
|
+
The proof does NOT reveal:
|
|
26
|
+
- Other facts in the trace
|
|
27
|
+
- The exact matching facts (only the count / sum / etc.)
|
|
28
|
+
|
|
29
|
+
Honest scope (do not ship this to adversarial environments):
|
|
30
|
+
* The commitment is BLAKE3 (a hash, not a homomorphic commitment). The
|
|
31
|
+
verifier cannot independently re-evaluate the witness polynomial — they
|
|
32
|
+
trust the prover's HMAC attestation (the prover holds ZK_SQL_KEY). A
|
|
33
|
+
malicious prover WITH the key could forge any proof; external attackers
|
|
34
|
+
(without the key) cannot. This is documented as a known limitation.
|
|
35
|
+
* The verifier's CHECK path is O(1) in trace size (commitment is just
|
|
36
|
+
hash equality, eval_at_1 is one int compare, HMAC is constant-time).
|
|
37
|
+
The full PROVER protocol is O(N) — proving requires touching every
|
|
38
|
+
fact once to build the witness set and compute the polynomial eval.
|
|
39
|
+
A production Halo2 backend would compress that to O(log N) via KZG
|
|
40
|
+
commitments; this prototype demonstrates the API surface.
|
|
41
|
+
"""
|
|
42
|
+
from __future__ import annotations
|
|
43
|
+
|
|
44
|
+
import json
|
|
45
|
+
import secrets
|
|
46
|
+
from dataclasses import dataclass, field, asdict
|
|
47
|
+
from typing import Any
|
|
48
|
+
|
|
49
|
+
from cortexm.security.hashes import (HashProvider, attest, merkle_root,
|
|
50
|
+
verify_attest)
|
|
51
|
+
from cortexm.trace.fact import Fact
|
|
52
|
+
from cortexm.util import new_id
|
|
53
|
+
|
|
54
|
+
# PLONKish gate types (used by the circuit description helper).
|
|
55
|
+
GATE_TYPES = ("CONST", "VAR", "ADD", "MUL", "SELECTOR")
|
|
56
|
+
|
|
57
|
+
# Scalar field for the witness polynomial. We use the Mersenne prime
|
|
58
|
+
# 2^61 - 1 — small enough that int arithmetic in Python is exact, large
|
|
59
|
+
# enough that collisions on real traces (n_facts < 2^40) are negligible.
|
|
60
|
+
# Production Halo2 would use the BN254 scalar field (~2^254); the swap
|
|
61
|
+
# is one line below.
|
|
62
|
+
_FIELD_MOD = (1 << 61) - 1
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
@dataclass
|
|
66
|
+
class CircuitGate:
|
|
67
|
+
"""One PLONKish gate: q_L * w_L + q_R * w_R + q_O * w_O + q_M * w_L*w_R + q_C = 0
|
|
68
|
+
|
|
69
|
+
The prototype's only gate is a SUM-of-witnesses: q_L = 1, q_O = -1
|
|
70
|
+
for the running accumulator, plus n_facts SELECTOR gates that pick
|
|
71
|
+
out the witness bit for each fact.
|
|
72
|
+
"""
|
|
73
|
+
q_L: float = 0.0
|
|
74
|
+
q_R: float = 0.0
|
|
75
|
+
q_O: float = 1.0
|
|
76
|
+
q_M: float = 0.0
|
|
77
|
+
q_C: float = 0.0
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class ZkSqlProof:
|
|
82
|
+
"""A ZK-SQL proof: claim + commitment + transcript."""
|
|
83
|
+
query: str # "COUNT(works_at)"
|
|
84
|
+
claimed_result: float
|
|
85
|
+
merkle_root: str # BLAKE3 root of the trace at proof time
|
|
86
|
+
n_facts_committed: int
|
|
87
|
+
transcript: dict # Fiat-Shamir challenges + prover responses
|
|
88
|
+
circuit_gates: int
|
|
89
|
+
proof_id: str = ""
|
|
90
|
+
|
|
91
|
+
# ----- serialization for storage / audit -----------------------------
|
|
92
|
+
def to_dict(self) -> dict:
|
|
93
|
+
return {
|
|
94
|
+
"query": self.query,
|
|
95
|
+
"claimed_result": self.claimed_result,
|
|
96
|
+
"merkle_root": self.merkle_root,
|
|
97
|
+
"n_facts_committed": self.n_facts_committed,
|
|
98
|
+
"transcript": self.transcript,
|
|
99
|
+
"circuit_gates": self.circuit_gates,
|
|
100
|
+
"proof_id": self.proof_id,
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
def serialize(self) -> str:
|
|
104
|
+
"""Compact canonical JSON — for storage / transmission / audit.
|
|
105
|
+
|
|
106
|
+
The serialized form NEVER contains the underlying fact values
|
|
107
|
+
(only the aggregate), by construction. The transcript exposes
|
|
108
|
+
only: FS challenge `r`, polynomial evaluation `eval_y`,
|
|
109
|
+
evaluation at 1 (== sum of witnesses, the same as the aggregate
|
|
110
|
+
for COUNT / SUM), the witness commitment, and an HMAC tag.
|
|
111
|
+
"""
|
|
112
|
+
return json.dumps(self.to_dict(), sort_keys=True,
|
|
113
|
+
separators=(",", ":"), default=str)
|
|
114
|
+
|
|
115
|
+
|
|
116
|
+
class ZkSqlProver:
|
|
117
|
+
"""Prove SQL aggregate queries over the Trace without revealing it."""
|
|
118
|
+
|
|
119
|
+
# -------------------------------------------------------------------
|
|
120
|
+
def __init__(self, store, hash_provider: HashProvider | None = None):
|
|
121
|
+
self.store = store
|
|
122
|
+
self.hasher = hash_provider or store.hasher
|
|
123
|
+
key_hex = self.store.kv_get("ZK_SQL_KEY")
|
|
124
|
+
if not key_hex:
|
|
125
|
+
key_hex = secrets.token_hex(32)
|
|
126
|
+
self.store.kv_set("ZK_SQL_KEY", key_hex)
|
|
127
|
+
self._key = bytes.fromhex(key_hex)
|
|
128
|
+
|
|
129
|
+
# ----- helpers -------------------------------------------------------
|
|
130
|
+
def _active_facts(self, user_id: str | None = None) -> list[Fact]:
|
|
131
|
+
return self.store.query_facts(user_id=user_id, active=True,
|
|
132
|
+
include_quarantined=False)
|
|
133
|
+
|
|
134
|
+
def _fact_leaf(self, f: Fact) -> str:
|
|
135
|
+
"""Content-free Merkle leaf: H(fact_id || source_hash).
|
|
136
|
+
|
|
137
|
+
Publishing the root of this leaf set proves the witness set was
|
|
138
|
+
drawn from a committed trace — without revealing which facts.
|
|
139
|
+
"""
|
|
140
|
+
return self.hasher.hash_text(f"{f.id}:{f.source_hash}")
|
|
141
|
+
|
|
142
|
+
def _trace_root(self, facts: list[Fact]) -> str:
|
|
143
|
+
leaves = [self._fact_leaf(f) for f in facts]
|
|
144
|
+
return merkle_root(self.hasher, leaves)
|
|
145
|
+
|
|
146
|
+
def _witness_commitment(self, witnesses: list[float], pad: bytes) -> str:
|
|
147
|
+
"""H(permuted_witnesses || pad).
|
|
148
|
+
|
|
149
|
+
The witnesses are packed as a canonical JSON array (preserves
|
|
150
|
+
ordering for the polynomial evaluation); a 32-byte random pad is
|
|
151
|
+
appended so the commitment is non-invertible: an attacker with
|
|
152
|
+
the commitment cannot recover the witness list. The pad is
|
|
153
|
+
generated fresh per-proof and never published.
|
|
154
|
+
"""
|
|
155
|
+
blob = (json.dumps([float(w) for w in witnesses],
|
|
156
|
+
separators=(",", ":")).encode("utf-8")
|
|
157
|
+
+ pad)
|
|
158
|
+
return self.hasher.hash_bytes(blob)
|
|
159
|
+
|
|
160
|
+
def _fs_challenge(self, *parts: str) -> int:
|
|
161
|
+
"""Fiat-Shamir: H(parts) reduced to a non-zero scalar in F_p.
|
|
162
|
+
|
|
163
|
+
Non-zero because we need r^0 = 1 and don't want P(r) = 0
|
|
164
|
+
degenerately. The challenge is derived from the public statement
|
|
165
|
+
(query, root, n_facts, commitment), so it cannot be precomputed
|
|
166
|
+
by the prover before committing.
|
|
167
|
+
"""
|
|
168
|
+
joined = "|".join(parts)
|
|
169
|
+
digest = self.hasher.hash_text(joined)
|
|
170
|
+
# 64-bit slice mod (p-1), +1 to dodge zero
|
|
171
|
+
return (int(digest[:16], 16) % (_FIELD_MOD - 1)) + 1
|
|
172
|
+
|
|
173
|
+
def _eval_poly(self, witnesses: list[float], r: int) -> int:
|
|
174
|
+
"""P(r) = sum(w_i * r^i) mod p over the witness list."""
|
|
175
|
+
acc = 0
|
|
176
|
+
pow_r = 1
|
|
177
|
+
for w in witnesses:
|
|
178
|
+
# field-reduce only the integer part of w — for the COUNT/MEMBERSHIP
|
|
179
|
+
# case w in {0,1}, for SUM w is a value (we take int(w) mod p).
|
|
180
|
+
wi = int(round(w)) % _FIELD_MOD
|
|
181
|
+
acc = (acc + (wi * pow_r) % _FIELD_MOD) % _FIELD_MOD
|
|
182
|
+
pow_r = (pow_r * r) % _FIELD_MOD
|
|
183
|
+
return acc
|
|
184
|
+
|
|
185
|
+
def _sign(self, msg: str) -> str:
|
|
186
|
+
return attest(self.hasher, self._key, msg)
|
|
187
|
+
|
|
188
|
+
def _verify_sig(self, msg: str, tag: str) -> bool:
|
|
189
|
+
return verify_attest(self.hasher, self._key, msg, tag)
|
|
190
|
+
|
|
191
|
+
# ----- proof construction -------------------------------------------
|
|
192
|
+
def _build_proof(self, *, query: str, witnesses: list[float],
|
|
193
|
+
claimed_result: float, merkle_root: str,
|
|
194
|
+
n_matching: int = 0) -> ZkSqlProof:
|
|
195
|
+
"""Build a PLONKish-style proof for the given witness assignment.
|
|
196
|
+
|
|
197
|
+
witnesses: list of witness values (in {0,1} for COUNT/MEMBERSHIP,
|
|
198
|
+
the fact's numeric value for SUM/AVG/MIN/MAX; 0 if the
|
|
199
|
+
fact does not match the predicate).
|
|
200
|
+
claimed_result: the public SQL aggregate (COUNT / SUM / AVG / etc.).
|
|
201
|
+
"""
|
|
202
|
+
n_facts = len(witnesses)
|
|
203
|
+
pad = secrets.token_bytes(32)
|
|
204
|
+
commitment = self._witness_commitment(witnesses, pad)
|
|
205
|
+
r = self._fs_challenge(query, merkle_root, str(n_facts), commitment)
|
|
206
|
+
eval_y = self._eval_poly(witnesses, r)
|
|
207
|
+
# P(1) = sum(w_i) — this is the COUNT/SUM identity. For AVG it is
|
|
208
|
+
# the SUM, and claimed_result = sum / n_matching (verified below).
|
|
209
|
+
# For MIN/MAX it has no clean relationship to claimed; we still
|
|
210
|
+
# publish it (it's the sum of all matching values) so the verifier
|
|
211
|
+
# can run the AVG check on the AVG proof type.
|
|
212
|
+
eval_at_1 = self._eval_poly(witnesses, 1)
|
|
213
|
+
msg = "|".join([
|
|
214
|
+
query, repr(claimed_result), merkle_root, str(n_facts),
|
|
215
|
+
commitment, str(r), str(eval_y), str(eval_at_1),
|
|
216
|
+
str(n_matching),
|
|
217
|
+
])
|
|
218
|
+
attestation = self._sign(msg)
|
|
219
|
+
transcript = {
|
|
220
|
+
"r": r,
|
|
221
|
+
"eval_y": eval_y,
|
|
222
|
+
"eval_at_1": eval_at_1,
|
|
223
|
+
"commitment": commitment,
|
|
224
|
+
"attestation": attestation,
|
|
225
|
+
"n_matching": n_matching,
|
|
226
|
+
}
|
|
227
|
+
# circuit_gates: 1 SUM accumulator + 1 SELECTOR per witness fact
|
|
228
|
+
return ZkSqlProof(
|
|
229
|
+
query=query,
|
|
230
|
+
claimed_result=claimed_result,
|
|
231
|
+
merkle_root=merkle_root,
|
|
232
|
+
n_facts_committed=n_facts,
|
|
233
|
+
transcript=transcript,
|
|
234
|
+
circuit_gates=1 + n_facts,
|
|
235
|
+
proof_id=new_id(),
|
|
236
|
+
)
|
|
237
|
+
|
|
238
|
+
# -------------------------------------------------------------------
|
|
239
|
+
def membership_proof(self, subject: str, relation: str,
|
|
240
|
+
value: str | None = None) -> ZkSqlProof:
|
|
241
|
+
"""Prove (subject, relation[, value]) exists in the trace.
|
|
242
|
+
|
|
243
|
+
Reveal: just the boolean exists. Not the fact_id, not the
|
|
244
|
+
chunk_id, not the timestamp, not the user_id.
|
|
245
|
+
|
|
246
|
+
Raises ``VerificationError`` if no such fact exists — the prover
|
|
247
|
+
cannot honestly prove existence of a fact that isn't there. To
|
|
248
|
+
prove non-existence, query ``count_proof`` (which will return 0).
|
|
249
|
+
"""
|
|
250
|
+
from cortexm.errors import VerificationError
|
|
251
|
+
|
|
252
|
+
facts = self._active_facts()
|
|
253
|
+
witnesses = [
|
|
254
|
+
1.0 if (f.subject == subject and f.relation == relation
|
|
255
|
+
and (value is None or f.value == value)) else 0.0
|
|
256
|
+
for f in facts
|
|
257
|
+
]
|
|
258
|
+
if not any(int(w) == 1 for w in witnesses):
|
|
259
|
+
vs = f" = {value!r}" if value else ""
|
|
260
|
+
raise VerificationError(
|
|
261
|
+
f"no active fact ({subject}, {relation}{vs}) in trace "
|
|
262
|
+
f"of {len(facts)} facts — cannot prove existence")
|
|
263
|
+
root = self._trace_root(facts)
|
|
264
|
+
query_str = (f"MEMBERSHIP({subject},{relation}"
|
|
265
|
+
+ (f",{value}" if value else "") + ")")
|
|
266
|
+
return self._build_proof(
|
|
267
|
+
query=query_str, witnesses=witnesses,
|
|
268
|
+
claimed_result=1.0, merkle_root=root, n_matching=1)
|
|
269
|
+
|
|
270
|
+
# -------------------------------------------------------------------
|
|
271
|
+
def count_proof(self, relation: str, user_id: str | None = None) -> ZkSqlProof:
|
|
272
|
+
"""Prove COUNT(relation) -> N. Reveals N but not which facts."""
|
|
273
|
+
facts = self._active_facts(user_id=user_id)
|
|
274
|
+
witnesses = [1.0 if f.relation == relation else 0.0 for f in facts]
|
|
275
|
+
n_match = sum(1 for w in witnesses if int(w) == 1)
|
|
276
|
+
root = self._trace_root(facts)
|
|
277
|
+
scope = f" FOR USER {user_id}" if user_id else ""
|
|
278
|
+
query_str = f"COUNT({relation}{scope})"
|
|
279
|
+
return self._build_proof(
|
|
280
|
+
query=query_str, witnesses=witnesses,
|
|
281
|
+
claimed_result=float(n_match), merkle_root=root,
|
|
282
|
+
n_matching=n_match)
|
|
283
|
+
|
|
284
|
+
# -------------------------------------------------------------------
|
|
285
|
+
def sum_proof(self, relation: str, value_filter: str | None = None,
|
|
286
|
+
user_id: str | None = None) -> ZkSqlProof:
|
|
287
|
+
"""Prove SUM(relation.value WHERE value_filter matches) -> S.
|
|
288
|
+
|
|
289
|
+
value_filter: a substring that must be in the value (e.g. 'Google'
|
|
290
|
+
for SUM over values containing 'Google'). If None, all values
|
|
291
|
+
for that relation are summed.
|
|
292
|
+
|
|
293
|
+
The value strings are interpreted as numbers via float(); facts
|
|
294
|
+
whose value cannot be parsed as a number contribute 0 to the
|
|
295
|
+
sum (i.e. are excluded). This is the prototype's SQL semantics;
|
|
296
|
+
a production version would type-check the value column.
|
|
297
|
+
"""
|
|
298
|
+
facts = self._active_facts(user_id=user_id)
|
|
299
|
+
witnesses: list[float] = []
|
|
300
|
+
n_match = 0
|
|
301
|
+
for f in facts:
|
|
302
|
+
if f.relation != relation:
|
|
303
|
+
witnesses.append(0.0)
|
|
304
|
+
continue
|
|
305
|
+
if value_filter is not None and value_filter not in f.value:
|
|
306
|
+
witnesses.append(0.0)
|
|
307
|
+
continue
|
|
308
|
+
try:
|
|
309
|
+
v = float(f.value)
|
|
310
|
+
except (TypeError, ValueError):
|
|
311
|
+
witnesses.append(0.0)
|
|
312
|
+
continue
|
|
313
|
+
witnesses.append(v)
|
|
314
|
+
n_match += 1
|
|
315
|
+
total = sum(witnesses)
|
|
316
|
+
root = self._trace_root(facts)
|
|
317
|
+
scope = f" FOR USER {user_id}" if user_id else ""
|
|
318
|
+
flt = f" WHERE value LIKE %{value_filter}%" if value_filter else ""
|
|
319
|
+
query_str = f"SUM({relation}.value{flt}{scope})"
|
|
320
|
+
return self._build_proof(
|
|
321
|
+
query=query_str, witnesses=witnesses,
|
|
322
|
+
claimed_result=float(total), merkle_root=root,
|
|
323
|
+
n_matching=n_match)
|
|
324
|
+
|
|
325
|
+
# -------------------------------------------------------------------
|
|
326
|
+
def avg_proof(self, relation: str, value_filter: str | None = None,
|
|
327
|
+
user_id: str | None = None) -> ZkSqlProof:
|
|
328
|
+
"""Prove AVG(relation.value) -> mean. Reveals mean + matching count."""
|
|
329
|
+
facts = self._active_facts(user_id=user_id)
|
|
330
|
+
witnesses: list[float] = []
|
|
331
|
+
n_match = 0
|
|
332
|
+
for f in facts:
|
|
333
|
+
if f.relation != relation:
|
|
334
|
+
witnesses.append(0.0)
|
|
335
|
+
continue
|
|
336
|
+
if value_filter is not None and value_filter not in f.value:
|
|
337
|
+
witnesses.append(0.0)
|
|
338
|
+
continue
|
|
339
|
+
try:
|
|
340
|
+
v = float(f.value)
|
|
341
|
+
witnesses.append(v)
|
|
342
|
+
n_match += 1
|
|
343
|
+
except (TypeError, ValueError):
|
|
344
|
+
witnesses.append(0.0)
|
|
345
|
+
total = sum(witnesses)
|
|
346
|
+
avg = total / n_match if n_match else 0.0
|
|
347
|
+
root = self._trace_root(facts)
|
|
348
|
+
scope = f" FOR USER {user_id}" if user_id else ""
|
|
349
|
+
flt = f" WHERE value LIKE %{value_filter}%" if value_filter else ""
|
|
350
|
+
query_str = f"AVG({relation}.value{flt}{scope})"
|
|
351
|
+
return self._build_proof(
|
|
352
|
+
query=query_str, witnesses=witnesses,
|
|
353
|
+
claimed_result=float(avg), merkle_root=root,
|
|
354
|
+
n_matching=n_match)
|
|
355
|
+
|
|
356
|
+
# -------------------------------------------------------------------
|
|
357
|
+
def minmax_proof(self, relation: str, op: str,
|
|
358
|
+
value_filter: str | None = None,
|
|
359
|
+
user_id: str | None = None) -> ZkSqlProof:
|
|
360
|
+
"""Prove MIN/MAX(relation.value) -> extreme."""
|
|
361
|
+
if op not in ("MIN", "MAX"):
|
|
362
|
+
raise ValueError(f"op must be MIN or MAX, got {op!r}")
|
|
363
|
+
facts = self._active_facts(user_id=user_id)
|
|
364
|
+
witnesses: list[float] = []
|
|
365
|
+
n_match = 0
|
|
366
|
+
values: list[float] = []
|
|
367
|
+
for f in facts:
|
|
368
|
+
if f.relation != relation:
|
|
369
|
+
witnesses.append(0.0)
|
|
370
|
+
continue
|
|
371
|
+
if value_filter is not None and value_filter not in f.value:
|
|
372
|
+
witnesses.append(0.0)
|
|
373
|
+
continue
|
|
374
|
+
try:
|
|
375
|
+
v = float(f.value)
|
|
376
|
+
witnesses.append(v)
|
|
377
|
+
values.append(v)
|
|
378
|
+
n_match += 1
|
|
379
|
+
except (TypeError, ValueError):
|
|
380
|
+
witnesses.append(0.0)
|
|
381
|
+
if not values:
|
|
382
|
+
result = 0.0
|
|
383
|
+
else:
|
|
384
|
+
result = min(values) if op == "MIN" else max(values)
|
|
385
|
+
root = self._trace_root(facts)
|
|
386
|
+
scope = f" FOR USER {user_id}" if user_id else ""
|
|
387
|
+
flt = f" WHERE value LIKE %{value_filter}%" if value_filter else ""
|
|
388
|
+
query_str = f"{op}({relation}.value{flt}{scope})"
|
|
389
|
+
return self._build_proof(
|
|
390
|
+
query=query_str, witnesses=witnesses,
|
|
391
|
+
claimed_result=float(result), merkle_root=root,
|
|
392
|
+
n_matching=n_match)
|
|
393
|
+
|
|
394
|
+
# -------------------------------------------------------------------
|
|
395
|
+
def verify(self, proof: ZkSqlProof) -> bool:
|
|
396
|
+
"""Verify a ZK-SQL proof. Returns True if the proof is valid.
|
|
397
|
+
|
|
398
|
+
Verifier path (all O(1) in trace size — sublinear):
|
|
399
|
+
1. Recompute the Fiat-Shamir challenge r from the public
|
|
400
|
+
statement (query, merkle_root, n_facts, commitment).
|
|
401
|
+
2. Run the polynomial-identity check appropriate to the query
|
|
402
|
+
type (COUNT/SUM/MEMBERSHIP: eval_at_1 == claimed_result
|
|
403
|
+
mod field; AVG: claimed_result * n_matching ≈ eval_at_1;
|
|
404
|
+
MIN/MAX: trust HMAC).
|
|
405
|
+
3. Verify the HMAC attestation (prover bound to the claim).
|
|
406
|
+
"""
|
|
407
|
+
try:
|
|
408
|
+
t = proof.transcript
|
|
409
|
+
# 1. FS challenge — must match what the prover derived
|
|
410
|
+
r_expected = self._fs_challenge(
|
|
411
|
+
proof.query, proof.merkle_root,
|
|
412
|
+
str(proof.n_facts_committed), t["commitment"])
|
|
413
|
+
if r_expected != t["r"]:
|
|
414
|
+
return False
|
|
415
|
+
# 2. polynomial-identity check, by query type
|
|
416
|
+
cr = proof.claimed_result
|
|
417
|
+
q = proof.query.upper()
|
|
418
|
+
if q.startswith("COUNT(") or q.startswith("MEMBERSHIP("):
|
|
419
|
+
# eval_at_1 = sum of {0,1} witnesses = claimed count
|
|
420
|
+
if t["eval_at_1"] != int(cr) % _FIELD_MOD:
|
|
421
|
+
return False
|
|
422
|
+
elif q.startswith("SUM("):
|
|
423
|
+
if abs(float(t["eval_at_1"]) - float(cr)) > 1e-6:
|
|
424
|
+
# eval_at_1 is reduced mod p; lift back: the witness
|
|
425
|
+
# sum should fit in < 2^61 for any real trace.
|
|
426
|
+
ea1 = t["eval_at_1"]
|
|
427
|
+
# if wrapped mod p, this check fails — but for any
|
|
428
|
+
# trace size < 2^40 with values < 2^20, no wrap.
|
|
429
|
+
if not (ea1 == int(cr) % _FIELD_MOD
|
|
430
|
+
or abs(float(ea1) - float(cr)) <= 1e-6):
|
|
431
|
+
return False
|
|
432
|
+
elif q.startswith("AVG("):
|
|
433
|
+
# claimed = sum / n_matching → claimed * n_matching == sum
|
|
434
|
+
n_m = int(t.get("n_matching", 0))
|
|
435
|
+
if n_m > 0:
|
|
436
|
+
if abs(cr * n_m - float(t["eval_at_1"])) > 1e-3:
|
|
437
|
+
return False
|
|
438
|
+
elif cr != 0.0:
|
|
439
|
+
return False
|
|
440
|
+
# MIN/MAX: no cheap identity check — fall through to HMAC
|
|
441
|
+
# 3. HMAC attestation
|
|
442
|
+
msg = "|".join([
|
|
443
|
+
proof.query, repr(proof.claimed_result), proof.merkle_root,
|
|
444
|
+
str(proof.n_facts_committed), t["commitment"],
|
|
445
|
+
str(t["r"]), str(t["eval_y"]), str(t["eval_at_1"]),
|
|
446
|
+
str(t.get("n_matching", 0)),
|
|
447
|
+
])
|
|
448
|
+
if not self._verify_sig(msg, t["attestation"]):
|
|
449
|
+
return False
|
|
450
|
+
return True
|
|
451
|
+
except (KeyError, AttributeError, TypeError):
|
|
452
|
+
return False
|
|
453
|
+
|
|
454
|
+
# -------------------------------------------------------------------
|
|
455
|
+
def proof_report(self, proof: ZkSqlProof) -> str:
|
|
456
|
+
"""Human-readable proof report (for audit logs)."""
|
|
457
|
+
lines = [
|
|
458
|
+
"=" * 68,
|
|
459
|
+
"ZK-SQL Proof Report (PoneglyphDB-style PLONKish)",
|
|
460
|
+
"=" * 68,
|
|
461
|
+
f" Proof ID : {proof.proof_id}",
|
|
462
|
+
f" Query : {proof.query}",
|
|
463
|
+
f" Claimed result : {proof.claimed_result}",
|
|
464
|
+
f" Trace Merkle root: {proof.merkle_root[:16]}…"
|
|
465
|
+
f" ({proof.n_facts_committed} facts committed)",
|
|
466
|
+
f" Circuit gates : {proof.circuit_gates}",
|
|
467
|
+
"-" * 68,
|
|
468
|
+
" PLONKish transcript:",
|
|
469
|
+
f" Fiat-Shamir challenge r : {proof.transcript.get('r')}",
|
|
470
|
+
f" Polynomial eval P(r) : {proof.transcript.get('eval_y')}",
|
|
471
|
+
f" Polynomial eval P(1)=Σwᵢ : {proof.transcript.get('eval_at_1')}",
|
|
472
|
+
f" Witness commitment (BLAKE3): "
|
|
473
|
+
f"{str(proof.transcript.get('commitment'))[:32]}…",
|
|
474
|
+
f" Matching facts (public) : "
|
|
475
|
+
f"{proof.transcript.get('n_matching', '?')}",
|
|
476
|
+
f" HMAC attestation : "
|
|
477
|
+
f"{str(proof.transcript.get('attestation'))[:32]}…",
|
|
478
|
+
"-" * 68,
|
|
479
|
+
f" Verification: {'PASS' if self.verify(proof) else 'FAIL'}",
|
|
480
|
+
"=" * 68,
|
|
481
|
+
]
|
|
482
|
+
return "\n".join(lines)
|
|
483
|
+
|
|
484
|
+
|
|
485
|
+
__all__ = ["ZkSqlProver", "ZkSqlProof", "CircuitGate", "GATE_TYPES"]
|
|
File without changes
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
"""Prometheus-format metrics + structured operation counters.
|
|
2
|
+
|
|
3
|
+
Renders the process-wide counters from ``cortexm.metrics`` plus
|
|
4
|
+
server-side request metrics (status codes, latency histogram, rate-limit
|
|
5
|
+
rejections) in Prometheus text exposition format 0.0.4 — no client
|
|
6
|
+
libraries, no dependencies, scrape-ready.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
from __future__ import annotations
|
|
10
|
+
|
|
11
|
+
import threading
|
|
12
|
+
import time
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PromRegistry:
|
|
16
|
+
def __init__(self) -> None:
|
|
17
|
+
self._lock = threading.Lock()
|
|
18
|
+
self._counters: dict[str, dict[str, float]] = {}
|
|
19
|
+
self._histograms: dict[str, list[float]] = {}
|
|
20
|
+
self._gauges: dict[str, float] = {}
|
|
21
|
+
|
|
22
|
+
def inc(self, name: str, labels: dict | None = None, value: float = 1) -> None:
|
|
23
|
+
key = _label_key(labels)
|
|
24
|
+
with self._lock:
|
|
25
|
+
bucket = self._counters.setdefault(name, {})
|
|
26
|
+
bucket[key] = bucket.get(key, 0.0) + value
|
|
27
|
+
|
|
28
|
+
def observe(self, name: str, value: float) -> None:
|
|
29
|
+
with self._lock:
|
|
30
|
+
self._histograms.setdefault(name, []).append(value)
|
|
31
|
+
|
|
32
|
+
def gauge(self, name: str, value: float) -> None:
|
|
33
|
+
with self._lock:
|
|
34
|
+
self._gauges[name] = value
|
|
35
|
+
|
|
36
|
+
def render(self) -> str:
|
|
37
|
+
from cortexm import metrics as core
|
|
38
|
+
lines: list[str] = []
|
|
39
|
+
c = core.counters()
|
|
40
|
+
lines.append("# HELP contextm_ingested_tokens_total Total tokens ingested (mu=0 path).")
|
|
41
|
+
lines.append("# TYPE contextm_ingested_tokens_total counter")
|
|
42
|
+
lines.append(f"contextm_ingested_tokens_total {c['ingested_tokens']}")
|
|
43
|
+
lines.append("# HELP contextm_ingested_messages_total Total messages ingested.")
|
|
44
|
+
lines.append("# TYPE contextm_ingested_messages_total counter")
|
|
45
|
+
lines.append(f"contextm_ingested_messages_total {c['ingested_messages']}")
|
|
46
|
+
lines.append("# HELP contextm_extracted_facts_total Facts extracted.")
|
|
47
|
+
lines.append("# TYPE contextm_extracted_facts_total counter")
|
|
48
|
+
lines.append(f"contextm_extracted_facts_total {c['extracted_facts']}")
|
|
49
|
+
lines.append("# HELP contextm_retrievals_total Search operations served.")
|
|
50
|
+
lines.append("# TYPE contextm_retrievals_total counter")
|
|
51
|
+
lines.append(f"contextm_retrievals_total {c['retrievals']}")
|
|
52
|
+
lines.append("# HELP contextm_llm_calls_total LLM calls (must be 0 under mu=0).")
|
|
53
|
+
lines.append("# TYPE contextm_llm_calls_total counter")
|
|
54
|
+
lines.append(f"contextm_llm_calls_total {c['llm_calls']}")
|
|
55
|
+
with self._lock:
|
|
56
|
+
for name, bucket in sorted(self._counters.items()):
|
|
57
|
+
lines.append(f"# TYPE {name} counter")
|
|
58
|
+
for key, val in sorted(bucket.items()):
|
|
59
|
+
lines.append(f"{name}{key} {val}")
|
|
60
|
+
for name, val in sorted(self._gauges.items()):
|
|
61
|
+
lines.append(f"# TYPE {name} gauge")
|
|
62
|
+
lines.append(f"{name} {val}")
|
|
63
|
+
for name, obs in sorted(self._histograms.items()):
|
|
64
|
+
lines.append(f"# TYPE {name} histogram")
|
|
65
|
+
bounds = [0.001, 0.0025, 0.005, 0.01, 0.025, 0.05, 0.1,
|
|
66
|
+
0.25, 0.5, 1.0, 2.5, 5.0]
|
|
67
|
+
for b in bounds:
|
|
68
|
+
cnt = sum(1 for o in obs if o <= b)
|
|
69
|
+
lines.append(f"{name}_bucket{{le=\"{b}\"}} {cnt}")
|
|
70
|
+
lines.append(f"{name}_bucket{{le=\"+Inf\"}} {len(obs)}")
|
|
71
|
+
if obs:
|
|
72
|
+
lines.append(f"{name}_sum {sum(obs):.6f}")
|
|
73
|
+
lines.append(f"{name}_count {len(obs)}")
|
|
74
|
+
return "\n".join(lines) + "\n"
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def _label_key(labels: dict | None) -> str:
|
|
78
|
+
if not labels:
|
|
79
|
+
return ""
|
|
80
|
+
inner = ",".join(f'{k}="{_esc(str(v))}"' for k, v in sorted(labels.items()))
|
|
81
|
+
return "{" + inner + "}"
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
def _esc(s: str) -> str:
|
|
85
|
+
return s.replace("\\", "\\\\").replace('"', '\\"').replace("\n", "\\n")
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
REGISTRY = PromRegistry()
|