pnasyscnct 0.2.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.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.4
2
+ Name: pnasyscnct
3
+ Version: 0.2.0
4
+ Summary: PNASystems Connect — tailscale-style Pi remote: computer + Pi, SSH over queue, MCP
5
+ Author: PNASystems
6
+ Project-URL: Homepage, https://github.com/Powerentity303/PNASystems_CRP
7
+ Project-URL: Repository, https://github.com/Powerentity303/PNASystems_CRP
8
+ Requires-Python: >=3.10
9
+ Description-Content-Type: text/markdown
10
+ Requires-Dist: cryptography>=41
11
+ Requires-Dist: pnasys-encryption-service>=1.12
12
+ Requires-Dist: pnasys-ses>=0.1.0
13
+
14
+ # pnasyscnct — tailscale-style Pi remote (computer + Pi)
15
+
16
+ One package, both ends. Install on Windows 11 (TPM 2.0) and on Pi 4/5
17
+ (Raspberry Pi OS, headless or desktop) — via pip or via GitHub, identical:
18
+
19
+ ```bash
20
+ pip install pnasyscnct
21
+ # or:
22
+ pip install git+https://github.com/Powerentity303/PNASystems_CRP.git
23
+ # or the one-liner (venv-safe on Pi OS):
24
+ curl -fsSL https://raw.githubusercontent.com/Powerentity303/PNASystems_CRP/main/installer.sh | bash
25
+ ```
26
+
27
+ This gives you **both** commands: `pnasyscnct` (computer) and `pnasyscrp` (Pi).
28
+
29
+ ## Computer
30
+
31
+ ```bash
32
+ pnasyscnct setup # same questions + TPM-sealed vault, prints computer ID
33
+ pnasyscnct setup --ssh # scan for the Pi's pairing request, verify, rotate link key
34
+ pnasyscnct ssh # remote shell (reconnects on loss)
35
+ pnasyscnct mcp --enckey K # MCP server (OpenCode), SSH always active
36
+ ```
37
+
38
+ OpenCode (`opencode.json`):
39
+
40
+ ```json
41
+ {"mcp": {"pnasys-ssh": {"type": "local",
42
+ "command": ["pnasyscnct", "mcp", "--enckey", "LINK_KEY"],
43
+ "enabled": true}}}
44
+ ```
45
+
46
+ ## Pi
47
+
48
+ ```bash
49
+ pnasyscrp setup # identity + AES vault, prints access key
50
+ pnasyscrp enable # listener + fresh session key (10-min idle auto-off)
51
+ pnasyscrp ssh setup # pairing: key first, code issued, enter computer ID
52
+ pnasyscrp ssh enable # presence + listener (root-aware exec)
53
+ pnasyscrp update # reinstall latest (cache-busted)
54
+ ```
55
+
56
+ Linking in short: computer `setup --ssh` scans → Pi `ssh setup` sends an
57
+ encrypted pairing code to that computer ID → computer verifies with the
58
+ pairing key, both adopt a fresh link key → Pi verifies the answer the same
59
+ way. Vercel only relays opaque blobs. Pi exec runs root-aware (`sudo -n`
60
+ when not uid 0).
@@ -0,0 +1,47 @@
1
+ # pnasyscnct — tailscale-style Pi remote (computer + Pi)
2
+
3
+ One package, both ends. Install on Windows 11 (TPM 2.0) and on Pi 4/5
4
+ (Raspberry Pi OS, headless or desktop) — via pip or via GitHub, identical:
5
+
6
+ ```bash
7
+ pip install pnasyscnct
8
+ # or:
9
+ pip install git+https://github.com/Powerentity303/PNASystems_CRP.git
10
+ # or the one-liner (venv-safe on Pi OS):
11
+ curl -fsSL https://raw.githubusercontent.com/Powerentity303/PNASystems_CRP/main/installer.sh | bash
12
+ ```
13
+
14
+ This gives you **both** commands: `pnasyscnct` (computer) and `pnasyscrp` (Pi).
15
+
16
+ ## Computer
17
+
18
+ ```bash
19
+ pnasyscnct setup # same questions + TPM-sealed vault, prints computer ID
20
+ pnasyscnct setup --ssh # scan for the Pi's pairing request, verify, rotate link key
21
+ pnasyscnct ssh # remote shell (reconnects on loss)
22
+ pnasyscnct mcp --enckey K # MCP server (OpenCode), SSH always active
23
+ ```
24
+
25
+ OpenCode (`opencode.json`):
26
+
27
+ ```json
28
+ {"mcp": {"pnasys-ssh": {"type": "local",
29
+ "command": ["pnasyscnct", "mcp", "--enckey", "LINK_KEY"],
30
+ "enabled": true}}}
31
+ ```
32
+
33
+ ## Pi
34
+
35
+ ```bash
36
+ pnasyscrp setup # identity + AES vault, prints access key
37
+ pnasyscrp enable # listener + fresh session key (10-min idle auto-off)
38
+ pnasyscrp ssh setup # pairing: key first, code issued, enter computer ID
39
+ pnasyscrp ssh enable # presence + listener (root-aware exec)
40
+ pnasyscrp update # reinstall latest (cache-busted)
41
+ ```
42
+
43
+ Linking in short: computer `setup --ssh` scans → Pi `ssh setup` sends an
44
+ encrypted pairing code to that computer ID → computer verifies with the
45
+ pairing key, both adopt a fresh link key → Pi verifies the answer the same
46
+ way. Vercel only relays opaque blobs. Pi exec runs root-aware (`sudo -n`
47
+ when not uid 0).
@@ -0,0 +1,23 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "pnasyscnct"
7
+ version = "0.2.0"
8
+ description = "PNASystems Connect — tailscale-style Pi remote: computer + Pi, SSH over queue, MCP"
9
+ readme = "README.md"
10
+ requires-python = ">=3.10"
11
+ dependencies = ["cryptography>=41", "pnasys-encryption-service>=1.12", "pnasys-ses>=0.1.0"]
12
+ authors = [{name="PNASystems"}]
13
+
14
+ [project.urls]
15
+ Homepage = "https://github.com/Powerentity303/PNASystems_CRP"
16
+ Repository = "https://github.com/Powerentity303/PNASystems_CRP"
17
+
18
+ [project.scripts]
19
+ pnasyscnct = "pnasyscnct.cli:main"
20
+ pnasyscrp = "pnasyscnct.pi_cli:main"
21
+
22
+ [tool.setuptools.packages.find]
23
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,2 @@
1
+ """pnasyscnct — tailscale-style Pi remote (computer + Pi sides)."""
2
+ __version__ = "0.2.0"
@@ -0,0 +1,307 @@
1
+ """pnasyscnct — computer side (Windows 11, TPM 2.0).
2
+
3
+ pnasyscnct setup fav restaurant/animal/color + pc user/pass + local pw
4
+ pnasyscnct setup --ssh link a Pi (scan for its pairing request)
5
+ pnasyscnct ssh remote shell into the linked Pi (reconnects)
6
+ pnasyscnct mcp --enckey K stdio MCP server, SSH always active
7
+ pnasyscnct selftest offline crypto check
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import base64
12
+ import getpass
13
+ import json
14
+ import os
15
+ import sys
16
+ import time
17
+ import uuid
18
+ from pathlib import Path
19
+
20
+ from pnasyscnct import link as L
21
+ from pnasyscnct.common import (decrypt_local, derive_key_iv, encrypt_local,
22
+ generate_session_key, interleave3, make_access_sha1024,
23
+ make_access_variant, make_pi_blob, secure_pack,
24
+ secure_unpack, sha256_hex)
25
+
26
+ HOME = Path.home()
27
+ SAFE = HOME / ".pnasys_crp"
28
+ VAULT = SAFE / ".vault"
29
+ CREDS = VAULT / "creds.txt"
30
+ ACCESS = VAULT / "access.enc.json"
31
+
32
+
33
+ def _ensure() -> None:
34
+ SAFE.mkdir(mode=0o700, parents=True, exist_ok=True)
35
+ VAULT.mkdir(mode=0o700, parents=True, exist_ok=True)
36
+
37
+
38
+ def _save_state(s: dict) -> None:
39
+ _ensure()
40
+ cur = L.load_state()
41
+ cur.update(s)
42
+ (SAFE / "state.json").write_text(json.dumps(cur, indent=2))
43
+
44
+
45
+ def cmd_setup() -> int:
46
+ _ensure()
47
+ print("pnasyscnct setup (computer)")
48
+ fav_rest = input("Favorite restaurant: ").strip()
49
+ fav_animal = input("Favorite animal: ").strip()
50
+ fav_color = input("Favorite color: ").strip()
51
+ pc_user = input("Computer username: ").strip()
52
+ pc_pass = getpass.getpass("Computer password: ")
53
+ local_pw = getpass.getpass("Local encryption password (anything): ")
54
+ if not all([fav_rest, fav_animal, fav_color, pc_user, pc_pass, local_pw]):
55
+ print("All fields required.", file=sys.stderr)
56
+ return 2
57
+ _ = derive_key_iv(local_pw)
58
+ CREDS.write_text(json.dumps(encrypt_local(f"{pc_user}\n{pc_pass}\n".encode(), local_pw)))
59
+ computer_id = uuid.uuid4().hex
60
+ uuid1 = str(uuid.uuid4())
61
+ blob = make_pi_blob(uuid1, fav_color, fav_rest)
62
+ _, _u2, access = make_access_variant(uuid1, fav_color, fav_rest)
63
+ _ = fav_animal, make_access_sha1024(blob, uuid1, fav_color, fav_rest)
64
+ ACCESS.write_text(json.dumps(encrypt_local(access.encode(), local_pw)))
65
+ from pnasys_ses import SecureEncryptionService as SES
66
+
67
+ SES.CreateEncryptedFile(json.dumps({"computer_id": computer_id}), "cnct_self", local_pw)
68
+ _save_state({"setup_done": True, "computer_id": computer_id})
69
+ for f in (CREDS, ACCESS):
70
+ try:
71
+ os.chmod(f, 0o600)
72
+ except Exception:
73
+ pass
74
+ print(f"Computer ID: {computer_id}")
75
+ print("Setup complete. Vault sealed with TPM (pnasys-ses).")
76
+ return 0
77
+
78
+
79
+ def _unlock() -> tuple[str, dict]:
80
+ """Prompt local pw; return (pw, {"computer_id": ...}). Never prints secrets."""
81
+ from pnasys_ses import SecureEncryptionService as SES
82
+
83
+ pw = getpass.getpass("Local encryption password: ")
84
+ try:
85
+ link = json.loads(SES.DecryptEncryptedFile("cnct_link", pw))
86
+ print("Vault unlocked (linked).")
87
+ return pw, link
88
+ except Exception:
89
+ pass
90
+ try:
91
+ me = json.loads(SES.DecryptEncryptedFile("cnct_self", pw))
92
+ print("Vault unlocked.")
93
+ return pw, me
94
+ except Exception:
95
+ print("Wrong password or no setup. Run: pnasyscnct setup", file=sys.stderr)
96
+ sys.exit(2)
97
+
98
+
99
+ def cmd_setup_ssh() -> int:
100
+ """Scan for a Pi pairing request, verify, rotate to a fresh link key."""
101
+ pw, me = _unlock()
102
+ computer_id = me.get("computer_id") or L.load_state().get("computer_id", "")
103
+ if not computer_id:
104
+ print("No computer ID. Run: pnasyscnct setup", file=sys.stderr)
105
+ return 2
106
+ print(f"Scanning for Pi pairing requests (computer {computer_id[:8]}...). Ctrl+C to stop.")
107
+ req = None
108
+ while req is None:
109
+ try:
110
+ r = L.link_poll(computer_id)
111
+ except KeyboardInterrupt:
112
+ print("\nStopped.")
113
+ return 130
114
+ except Exception as e:
115
+ print(f"scan error, retrying: {e}")
116
+ time.sleep(3)
117
+ continue
118
+ if r.get("empty"):
119
+ continue
120
+ req = r
121
+ print("Pairing request received.")
122
+ pair_key = getpass.getpass("Pairing encryption key (the one entered on the Pi): ")
123
+ new_key = getpass.getpass("NEW link encryption key (choose now, Pi will adopt it): ")
124
+ if not new_key:
125
+ print("A new link key is required.", file=sys.stderr)
126
+ return 2
127
+ try:
128
+ inner = secure_unpack(pair_key, req["blob"])
129
+ pi_ident = inner["pi_ident"]
130
+ except Exception:
131
+ print("Decrypt failed — wrong pairing key?", file=sys.stderr)
132
+ return 1
133
+ ans = secure_pack(pair_key, {"computer_id": computer_id, "link_key": new_key,
134
+ "ok": True, "ts": int(time.time())})
135
+ r = L.link_answer(computer_id, ans, for_pi=pi_ident)
136
+ if not r.get("ok"):
137
+ print(f"Answer failed: {r}", file=sys.stderr)
138
+ return 1
139
+ L.pc_save_link(computer_id, pi_ident, new_key, pw)
140
+ print("Linked. Pi verified and both sides hold the fresh link key.")
141
+ return 0
142
+
143
+
144
+ def _shell(pi_ident: str, link_key: str) -> int:
145
+ print(f"Remote shell (link {pi_ident[:8]}...). :put local remote | :get remote local | :exit")
146
+ while True:
147
+ try:
148
+ line = input("pi# ").strip()
149
+ except (EOFError, KeyboardInterrupt):
150
+ print()
151
+ return 0
152
+ if not line:
153
+ continue
154
+ if line in (":exit", ":quit"):
155
+ return 0
156
+ try:
157
+ if line.startswith(":put "):
158
+ _, local, remote = line.split(None, 2)
159
+ raw = Path(local).expanduser().read_bytes()
160
+ import base64 as _b64
161
+
162
+ op = {"kind": "write", "path": remote, "data_b64": _b64.b64encode(raw).decode()}
163
+ if len(raw) > 4_000_000:
164
+ print("Large file: streaming in encrypted chunks...")
165
+ _stream_write(pi_ident, link_key, remote, raw)
166
+ continue
167
+ elif line.startswith(":get "):
168
+ _, remote, local = line.split(None, 2)
169
+ op = {"kind": "read", "path": remote}
170
+ else:
171
+ op = {"kind": "exec", "cmd": line[:4000]}
172
+ rid = f"sh{int(time.time() * 1000)}"
173
+ blob = secure_pack(link_key, op)
174
+ r = L.enqueue(_access_for(link_key, pi_ident), "secure", {"blob": blob}, rid)
175
+ if not r.get("ok"):
176
+ print(f"send failed: {r.get('error', r)} — retrying...")
177
+ time.sleep(3)
178
+ continue
179
+ res = L.wait_result(pi_ident, rid, 180)
180
+ if res.get("pending"):
181
+ print("(no reply yet — Pi may be offline; result kept, retry :status)")
182
+ continue
183
+ inner = secure_unpack(link_key, res["enc"])
184
+ if inner.get("out"):
185
+ print(inner["out"], end="" if inner["out"].endswith("\n") else "\n")
186
+ if inner.get("data_b64"):
187
+ out = input("save output to local path: ").strip()
188
+ if out:
189
+ import base64 as _b64
190
+
191
+ Path(out).expanduser().write_bytes(_b64.b64decode(inner["data_b64"]))
192
+ print(f"saved {out}")
193
+ if inner.get("rc") not in (None, 0):
194
+ print(f"[rc={inner.get('rc')}] {inner.get('err', '')}")
195
+ elif inner.get("error"):
196
+ print(f"Pi error: {inner['error']}")
197
+ except (EOFError, KeyboardInterrupt):
198
+ print()
199
+ return 0
200
+ except Exception as e:
201
+ print(f"link error ({e}) — reconnecting...")
202
+ time.sleep(3)
203
+
204
+
205
+ def _access_for(link_key: str, pi_ident: str) -> str:
206
+ """Registration check needs the access key — but SSH uses link identity.
207
+
208
+ The Pi registered under its access key; pairing stored pi_ident. For
209
+ enqueue auth we send pi_ident as the selector: the server accepts either
210
+ a registered access key or a paired pi_ident for kind=secure.
211
+ """
212
+ return f"ident:{pi_ident}"
213
+
214
+
215
+ def _stream_write(pi_ident: str, link_key: str, remote: str, raw: bytes) -> None:
216
+ from pnasyscnct.common import secure_pack as _sp
217
+
218
+ import base64 as _b64
219
+
220
+ rid = f"sw{int(time.time() * 1000)}"
221
+ n = 500_000
222
+ parts = [_sp(link_key, {"chunk": _b64.b64encode(raw[i:i + n]).decode()})
223
+ for i in range(0, len(raw), n)] or [_sp(link_key, {"chunk": ""})]
224
+ r = L.enqueue(f"ident:{pi_ident}", "secure",
225
+ {"blob": _sp(link_key, {"kind": "write-parts", "path": remote,
226
+ "parts": parts})}, rid)
227
+ if not r.get("ok"):
228
+ print(f"stream failed: {r.get('error', r)}")
229
+ return
230
+ res = L.wait_result(pi_ident, rid, 300)
231
+ ok = res.get("ok") if isinstance(res, dict) else False
232
+ print(f"streamed {len(raw)} bytes in {len(parts)} encrypted chunks (ok={ok})")
233
+
234
+
235
+ def cmd_ssh() -> int:
236
+ pw, link = _unlock()
237
+ if "link_key" not in link:
238
+ print("Not linked yet. Run: pnasyscnct setup --ssh", file=sys.stderr)
239
+ return 2
240
+ L.ssh_join(link["computer_id"], link["pi_ident"],
241
+ secure_pack(link["link_key"], {"hello": "pc", "ts": int(time.time())}))
242
+ print("Session open. Reconnects automatically on failure.")
243
+ while True:
244
+ rc = _shell(link["pi_ident"], link["link_key"])
245
+ if rc == 0:
246
+ return 0
247
+ print("Shell exited abnormally — rejoining in 5s (Ctrl+C to quit)...")
248
+ try:
249
+ time.sleep(5)
250
+ except KeyboardInterrupt:
251
+ return 130
252
+
253
+
254
+ def cmd_mcp(enckey: str) -> int:
255
+ from pnasyscnct.mcp_server import serve
256
+
257
+ st = L.load_state()
258
+ if not st.get("pi_ident") or not st.get("computer_id"):
259
+ print("Not linked yet. Run: pnasyscnct setup --ssh", file=sys.stderr)
260
+ return 2
261
+ if not enckey:
262
+ print("--enckey (the link encryption key) is required.", file=sys.stderr)
263
+ return 2
264
+ print("MCP server starting (SSH session active).", file=sys.stderr)
265
+ serve(link_key=enckey, pi_ident=st["pi_ident"], computer_id=st["computer_id"])
266
+ return 0
267
+
268
+
269
+ def cmd_selftest() -> int:
270
+ pw = "selftest-pw"
271
+ key, iv = derive_key_iv(pw)
272
+ assert len(key) == 32 and len(iv) == 12
273
+ b = encrypt_local(b"hello-pc", pw)
274
+ assert decrypt_local(b, pw) == b"hello-pc"
275
+ sk = generate_session_key()
276
+ assert len(sk.split("=")) == 6
277
+ blob = secure_pack("k", {"kind": "exec", "cmd": "x"})
278
+ assert secure_unpack("k", blob)["cmd"] == "x"
279
+ print("selftest OK: AES vault, session keygen, secure pack/unpack")
280
+ return 0
281
+
282
+
283
+ def main(argv: list[str] | None = None) -> int:
284
+ argv = sys.argv[1:] if argv is None else argv
285
+ if not argv or argv[0] in ("-h", "--help", "help"):
286
+ print("Usage: pnasyscnct {setup [--ssh]|ssh|mcp --enckey KEY|selftest}")
287
+ return 0
288
+ if argv[0] == "setup" and "--ssh" in argv[1:]:
289
+ return cmd_setup_ssh()
290
+ if argv[0] == "setup":
291
+ return cmd_setup()
292
+ if argv[0] == "ssh":
293
+ return cmd_ssh()
294
+ if argv[0] == "mcp":
295
+ key = ""
296
+ for i, a in enumerate(argv[1:]):
297
+ if a == "--enckey" and i + 1 < len(argv[1:]):
298
+ key = argv[1:][i + 1]
299
+ return cmd_mcp(key)
300
+ if argv[0] == "selftest":
301
+ return cmd_selftest()
302
+ print(f"Unknown command: {argv[0]}", file=sys.stderr)
303
+ return 2
304
+
305
+
306
+ if __name__ == "__main__":
307
+ raise SystemExit(main())
@@ -0,0 +1,151 @@
1
+ """Shared crypto: AES-256-GCM at-rest, pnasys transport, keygen, identity math.
2
+
3
+ - AES key = SHA256(password).digest() (32B)
4
+ - AES iv = SHA256(reverse(b64(reverse(password)))).digest()[:12]
5
+ - Transport (pnasys-encryption-service): session/link keys, exact ops.
6
+ - Session/link code generator: exact PNASystemsKeyGenerator.py logic.
7
+ - Pi identity: interleave blob + SHA1024 + uuid2 variant (unchanged spec).
8
+ """
9
+ from __future__ import annotations
10
+
11
+ import base64
12
+ import datetime
13
+ import hashlib
14
+ import os
15
+ import secrets
16
+ import uuid
17
+
18
+
19
+ def sha256_hex(s: str) -> str:
20
+ return hashlib.sha256(s.encode("utf-8")).hexdigest()
21
+
22
+
23
+ def derive_key_iv(password: str) -> tuple[bytes, bytes]:
24
+ key = hashlib.sha256(password.encode("utf-8")).digest()
25
+ rev_b64 = base64.b64encode(password[::-1].encode("utf-8")).decode("ascii")[::-1]
26
+ return key, hashlib.sha256(rev_b64.encode("utf-8")).digest()[:12]
27
+
28
+
29
+ def encrypt_local(plaintext: bytes, password: str) -> dict:
30
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
31
+
32
+ key, _ = derive_key_iv(password)
33
+ nonce = os.urandom(12)
34
+ return {"nonce_b64": base64.b64encode(nonce).decode("ascii"),
35
+ "ct_b64": base64.b64encode(AESGCM(key).encrypt(nonce, plaintext, None)).decode("ascii")}
36
+
37
+
38
+ def decrypt_local(bundle: dict, password: str) -> bytes:
39
+ from cryptography.hazmat.primitives.ciphers.aead import AESGCM
40
+
41
+ key, _ = derive_key_iv(password)
42
+ return AESGCM(key).decrypt(base64.b64decode(bundle["nonce_b64"]),
43
+ base64.b64decode(bundle["ct_b64"]), None)
44
+
45
+
46
+ def secure_pack(key: str, op: dict) -> str:
47
+ import json as _json
48
+
49
+ from pnasys_encryption_service import EncryptString
50
+
51
+ return EncryptString(_json.dumps(op, separators=(",", ":")), key)
52
+
53
+
54
+ def secure_unpack(key: str, token: str) -> dict:
55
+ import json as _json
56
+
57
+ from pnasys_encryption_service import DecryptString
58
+
59
+ return _json.loads(DecryptString(token, key))
60
+
61
+
62
+ # --- session/link code generator (exact PNASystemsKeyGenerator.py) ---
63
+
64
+ def RandomService(Minimum, Maximum) -> int:
65
+ return Minimum + secrets.randbelow(Maximum - Minimum + 1)
66
+
67
+
68
+ def ToHexSHA256(Input: str) -> str:
69
+ return hashlib.sha256(Input.encode("utf-8")).hexdigest()
70
+
71
+
72
+ def GetRandomNumber() -> int:
73
+ A = 0
74
+ B = RandomService(123821, 98169809)
75
+ C = RandomService(838390, 67867189)
76
+ if B == C:
77
+ B += RandomService(1, 100)
78
+ if B > C:
79
+ A = round(B / C)
80
+ else:
81
+ A = round(C / B)
82
+ return A
83
+
84
+
85
+ def ReverseString(String: str) -> str:
86
+ return String[::-1]
87
+
88
+
89
+ def generate_session_key() -> str:
90
+ from pnasys_encryption_service import GenerateEncryptionKey
91
+
92
+ EncryptionKeyBase = GenerateEncryptionKey()
93
+ try:
94
+ EncryptionKeyPart1 = str(uuid.uuid8()) # exact original call
95
+ except AttributeError:
96
+ EncryptionKeyPart1 = str(uuid.uuid4()) # stdlib has no uuid8
97
+ EncryptionKeyPart2 = (EncryptionKeyPart1
98
+ + datetime.datetime.now().strftime("%d/%m/%Y")
99
+ + os.getcwd() + str(GetRandomNumber()))
100
+ EncryptionKeyPart3 = ReverseString(EncryptionKeyPart2)
101
+ EncryptionKeyPart4 = (EncryptionKeyPart2 + "-" + EncryptionKeyBase + "-"
102
+ + ReverseString(EncryptionKeyBase) + "-" + EncryptionKeyPart3)
103
+ Hash1 = ToHexSHA256(EncryptionKeyPart4)
104
+ Hash2 = ToHexSHA256(EncryptionKeyBase)
105
+ Hash3 = ToHexSHA256(Hash1 + EncryptionKeyBase + ReverseString(EncryptionKeyBase)
106
+ + ReverseString(EncryptionKeyBase) + ReverseString(Hash1))
107
+ return (Hash1 + "=" + Hash2 + "=" + Hash3 + "=" + ReverseString(Hash3)
108
+ + "=" + ReverseString(Hash2) + "=" + ReverseString(Hash1))
109
+
110
+
111
+ # --- identity math (unchanged spec) ---
112
+
113
+ def interleave3(a: str, b: str, c: str) -> str:
114
+ out: list[str] = []
115
+ for i in range(max(len(a), len(b), len(c))):
116
+ if i < len(a):
117
+ out.append(a[i])
118
+ if i < len(b):
119
+ out.append(b[i])
120
+ if i < len(c):
121
+ out.append(c[i])
122
+ return "".join(out)
123
+
124
+
125
+ def make_pi_blob(uuid4str: str, favcolor: str, favrest: str) -> str:
126
+ return interleave3(sha256_hex(uuid4str), sha256_hex(favcolor), sha256_hex(favrest))
127
+
128
+
129
+ def make_access_sha1024(combined: str, uuid4str: str, favcolor: str, favrest: str) -> str:
130
+ p1 = sha256_hex(combined)
131
+ p2 = sha256_hex(f"{uuid4str}-{p1}-{p1[::-1]}-{uuid4str[::-1]}")
132
+ p3 = sha256_hex(f"{favcolor}-{p2}-{p2[::-1]}-{favcolor[::-1]}")
133
+ p4 = sha256_hex(f"{favrest}-{p3}-{p3[::-1]}-{favrest[::-1]}")
134
+ return f"{p1}-{p2}-{p3}-{p4}"
135
+
136
+
137
+ def make_access_variant(uuid1: str, favcolor: str, favrest: str, uuid2: str | None = None):
138
+ uuid2 = uuid2 or str(uuid.uuid4())
139
+ combined2 = interleave3(sha256_hex(f"{uuid1}-{uuid2}"),
140
+ sha256_hex(f"{favcolor}-{uuid2}"),
141
+ sha256_hex(f"{favrest}-{uuid2}"))
142
+ return combined2, uuid2, make_access_sha1024(combined2, uuid2, favcolor, favrest)
143
+
144
+
145
+ def derive_pi_encryption_key(accesskey: str) -> str:
146
+ akh = sha256_hex(accesskey)
147
+ h1 = sha256_hex(accesskey + akh)
148
+ h2 = sha256_hex(akh + h1 + accesskey)
149
+ h3 = sha256_hex(h2 + accesskey + h1)
150
+ h4 = sha256_hex(h1 + akh + h2)
151
+ return f"{h1}-{h3}-{h4}-{h2}"