agmem 0.1.3__py3-none-any.whl → 0.1.5__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.
memvcs/core/zk_proofs.py CHANGED
@@ -1,26 +1,160 @@
1
1
  """
2
- Zero-knowledge proof system for agmem (stub).
2
+ Zero-knowledge proof system for agmem.
3
3
 
4
- Planned: zk-SNARKs (Groth16) for keyword containment, memory freshness, competence verification.
5
- Requires optional zk extra (circuit lib, proving system). Trusted setup: public ceremony or small multi-party.
4
+ Hash/signature-based proofs: keyword containment (Merkle set membership),
5
+ memory freshness (signed timestamp). Full zk-SNARK backend can be added later.
6
6
  """
7
7
 
8
+ import base64
9
+ import hashlib
10
+ import json
11
+ import os
8
12
  from pathlib import Path
9
- from typing import Optional, Tuple
13
+ from typing import Optional, List, Tuple, Any, Dict
14
+
15
+ from .crypto_verify import (
16
+ build_merkle_tree,
17
+ merkle_proof,
18
+ verify_merkle_proof,
19
+ load_public_key,
20
+ load_private_key_from_env,
21
+ sign_merkle_root,
22
+ verify_signature,
23
+ ED25519_AVAILABLE,
24
+ )
25
+
26
+
27
+ def _word_hashes(content: str) -> List[str]:
28
+ """Extract words and return sorted list of SHA-256 hashes (hex)."""
29
+ words = set()
30
+ for word in content.split():
31
+ w = word.strip().lower()
32
+ if len(w) >= 1:
33
+ words.add(w)
34
+ return sorted(hashlib.sha256(w.encode()).hexdigest() for w in words)
10
35
 
11
36
 
12
37
  def prove_keyword_containment(memory_path: Path, keyword: str, output_proof_path: Path) -> bool:
13
- """Prove memory file contains keyword without revealing content. Stub: returns False until zk backend added."""
14
- return False
38
+ """
39
+ Prove memory file contains keyword without revealing content.
40
+ Proof: Merkle set membership of H(keyword) over word hashes in file.
41
+ """
42
+ if not memory_path.exists() or not memory_path.is_file():
43
+ return False
44
+ try:
45
+ content = memory_path.read_text(encoding="utf-8", errors="replace")
46
+ except Exception:
47
+ return False
48
+ word_hashes_list = _word_hashes(content)
49
+ keyword_hash = hashlib.sha256(keyword.strip().lower().encode()).hexdigest()
50
+ if keyword_hash not in word_hashes_list:
51
+ return False
52
+ root = build_merkle_tree(word_hashes_list)
53
+ proof_path_list = merkle_proof(word_hashes_list, keyword_hash)
54
+ if proof_path_list is None:
55
+ return False
56
+ proof_data = {
57
+ "statement_type": "keyword",
58
+ "keyword_hash": keyword_hash,
59
+ "root": root,
60
+ "path": proof_path_list,
61
+ }
62
+ output_proof_path.parent.mkdir(parents=True, exist_ok=True)
63
+ output_proof_path.write_text(json.dumps(proof_data, indent=2))
64
+ return True
15
65
 
16
66
 
17
67
  def prove_memory_freshness(
18
- memory_path: Path, after_timestamp: str, output_proof_path: Path
68
+ memory_path: Path, after_timestamp: str, output_proof_path: Path, mem_dir: Optional[Path] = None
19
69
  ) -> bool:
20
- """Prove memory was updated after date without revealing content. Stub: returns False until zk backend added."""
21
- return False
70
+ """
71
+ Prove memory was updated after date without revealing content.
72
+ Proof: signed file mtime (or current time) and optional public key.
73
+ """
74
+ if not memory_path.exists() or not memory_path.is_file():
75
+ return False
76
+ if not ED25519_AVAILABLE:
77
+ return False
78
+ try:
79
+ stat = memory_path.stat()
80
+ ts = stat.st_mtime
81
+ from datetime import datetime, timezone
82
+
83
+ iso_ts = datetime.fromtimestamp(ts, tz=timezone.utc).isoformat()
84
+ except Exception:
85
+ return False
86
+ private_pem = load_private_key_from_env() if mem_dir is not None else None
87
+ if private_pem is None:
88
+ return False
89
+ try:
90
+ sig_hex = sign_merkle_root(iso_ts, private_pem)
91
+ except Exception:
92
+ return False
93
+ proof_data = {"statement_type": "freshness", "timestamp": iso_ts, "signature": sig_hex}
94
+ if mem_dir is not None:
95
+ pub_pem = load_public_key(mem_dir)
96
+ if pub_pem is not None:
97
+ proof_data["public_key_pem_b64"] = base64.b64encode(pub_pem).decode()
98
+ output_proof_path.parent.mkdir(parents=True, exist_ok=True)
99
+ output_proof_path.write_text(json.dumps(proof_data, indent=2))
100
+ return True
101
+
22
102
 
103
+ def verify_proof(proof_path: Path, statement_type: str, **kwargs: Any) -> bool:
104
+ """
105
+ Verify a proof. statement_type in ("keyword", "freshness").
106
+ For keyword: pass keyword=... (the keyword string).
107
+ For freshness: pass after_timestamp=... (ISO date string). Optional mem_dir=... for public key.
108
+ """
109
+ if not proof_path.exists() or not proof_path.is_file():
110
+ return False
111
+ try:
112
+ data = json.loads(proof_path.read_text())
113
+ except Exception:
114
+ return False
115
+ if data.get("statement_type") != statement_type:
116
+ return False
117
+ if statement_type == "keyword":
118
+ keyword = kwargs.get("keyword")
119
+ if keyword is None:
120
+ return False
121
+ keyword_hash = hashlib.sha256(keyword.strip().lower().encode()).hexdigest()
122
+ if data.get("keyword_hash") != keyword_hash:
123
+ return False
124
+ root = data.get("root")
125
+ path_list = data.get("path")
126
+ if not root or path_list is None:
127
+ return False
128
+ return verify_merkle_proof(keyword_hash, path_list, root)
129
+ if statement_type == "freshness":
130
+ after_ts = kwargs.get("after_timestamp")
131
+ if after_ts is None:
132
+ return False
133
+ ts_str = data.get("timestamp")
134
+ sig_hex = data.get("signature")
135
+ if not ts_str or not sig_hex:
136
+ return False
137
+ pub_pem_b64 = data.get("public_key_pem_b64")
138
+ if pub_pem_b64:
139
+ try:
140
+ pub_pem = base64.b64decode(pub_pem_b64)
141
+ except Exception:
142
+ return False
143
+ else:
144
+ mem_dir = kwargs.get("mem_dir")
145
+ if mem_dir is None:
146
+ return False
147
+ pub_pem = load_public_key(Path(mem_dir))
148
+ if pub_pem is None:
149
+ return False
150
+ if not verify_signature(ts_str, sig_hex, pub_pem):
151
+ return False
152
+ try:
153
+ from datetime import datetime
23
154
 
24
- def verify_proof(proof_path: Path, statement_type: str, **kwargs) -> bool:
25
- """Verify a zk proof. Stub: returns False until zk backend added."""
155
+ after_dt = datetime.fromisoformat(after_ts.replace("Z", "+00:00"))
156
+ ts_dt = datetime.fromisoformat(ts_str.replace("Z", "+00:00"))
157
+ return ts_dt >= after_dt
158
+ except Exception:
159
+ return False
26
160
  return False
File without changes