NN Decryption - Softmax - Very able.

Paste ID: 5704f8ce

Created at: 2026-09-17 08:13:24

decryption encryption python

Content

First:
"
"""
rune_aes.py
===========
AES-128 (CBC) + SHA-256 keyed cipher + neural network that recovers
a plaintext from a fuzzy ~51% guess.

Produces network_build.gif: one frame every N iterations showing
the network graph evolving and the decrypted message resolving.

Run:    python rune_aes.py
Output: network_build.gif

Deps: numpy, pillow, pycryptodome
"""

import os
import math
import json
import time
import hashlib
import secrets
import numpy as np
from PIL import Image, ImageDraw, ImageFont

from Crypto.Cipher import AES
from Crypto.Util.Padding import pad, unpad


# ============================================================
# CONFIG
# ============================================================
MESSAGE = "The Mandelbrot set hides every secret we could ever need."
FUZZY_ERROR_RATE = 0.49
SEED_STRING = "mandelbrot-2025"

ITERATIONS = 3000
VISUALIZE_EVERY = 25
GIF_PATH = "network_build.gif"
FRAME_DURATION_MS = 120

HIDDEN = [24, 16]
N_FEATURES = 16

FRAME_W = 900
FRAME_H = 620
GRAPH_H = 380
TEXT_H = FRAME_H - GRAPH_H


# ============================================================
# AES-128-SHA CIPHER
# ============================================================
class AES128SHACipher:
    """
    Key derivation: SHA-256 of seed string -> first 16 bytes = AES-128 key.
    Encryption: AES-128-CBC with random IV (stored in the ciphertext package).
    Output: hex-encoded ciphertext per character position is not meaningful
    here, so we produce one encrypted block per chunk of plaintext and use
    the resulting bytes as the raw material for the neural network.
    """

    def __init__(self, seed_string: str):
        self.seed_string = seed_string
        digest = hashlib.sha256(seed_string.encode("utf-8")).digest()
        self.key = digest[:16]           # AES-128 key
        self.full_digest = digest

    def encrypt(self, plaintext: str):
        # Pad and encrypt with a random IV
        iv = secrets.token_bytes(16)
        cipher = AES.new(self.key, AES.MODE_CBC, iv=iv)
        ct = cipher.encrypt(pad(plaintext.encode("utf-8"), 16))

        # Also produce a per-character SHA hash for the network features.
        # Each character's hash is deterministic given (seed, position, char),
        # exactly like the Mandelbrot version, but the hash is now SHA-256.
        index_card = []
        for i, ch in enumerate(plaintext):
            h = hashlib.sha256(
                "{}|{}|{}".format(self.seed_string, i, ch).encode("utf-8")
            ).hexdigest()
            index_card.append({
                "position": i,
                "hash": h,
                "char": ord(ch),
            })

        return {
            "key_hex": self.key.hex(),
            "iv_hex": iv.hex(),
            "ciphertext_hex": ct.hex(),
            "index_card": index_card,
            "plaintext_len": len(plaintext),
        }

    def decrypt(self, package):
        iv = bytes.fromhex(package["iv_hex"])
        ct = bytes.fromhex(package["ciphertext_hex"])
        cipher = AES.new(self.key, AES.MODE_CBC, iv=iv)
        pt = unpad(cipher.decrypt(ct), 16)
        return pt.decode("utf-8")


# ============================================================
# FEATURES + ONE-HOT
# ============================================================
def hash_to_features(hash_hex, n_features=16):
    raw = bytes.fromhex(hash_hex)
    return np.array([b / 255.0 for b in raw[:n_features]], dtype=np.float64)


def char_to_index(ch):
    o = ord(ch)
    if 97 <= o <= 122: return o - 97
    if 65 <= o <= 90:  return 26 + (o - 65)
    if o == 32:        return 52
    if 48 <= o <= 57:  return 53 + (o - 48)
    return 63


def index_to_char(idx):
    if 0 <= idx <= 25:   return chr(97 + idx)
    if 26 <= idx <= 51:  return chr(65 + (idx - 26))
    if idx == 52:        return " "
    if 53 <= idx <= 62:  return chr(48 + (idx - 53))
    return "?"


def char_to_onehot(ch, vocab_size=64):
    vec = np.zeros(vocab_size, dtype=np.float64)
    vec[char_to_index(ch)] = 1.0
    return vec


# ============================================================
# FUZZY GUESS
# ============================================================
def make_fuzzy_guess(message, error_rate, seed=99):
    rng = np.random.default_rng(seed)
    chars = list(message)
    n_errors = int(len(chars) * error_rate)
    positions = rng.choice(len(chars), size=n_errors, replace=False)
    alphabet = "abcdefghijklmnopqrstuvwxyz .,"
    for p in positions:
        orig = chars[p]
        choices = [c for c in alphabet if c != orig]
        chars[p] = choices[rng.integers(0, len(choices))]
    return "".join(chars)


# ============================================================
# NETWORK
# ============================================================
class NeuralNetwork:
    def __init__(self, layer_sizes, seed=42):
        self.layer_sizes = [int(s) for s in layer_sizes]
        rng = np.random.default_rng(seed)
        self.learning_rate = 0.1
        self.momentum = 0.1

        self.W, self.b, self.vW, self.vb = [], [], [], []
        for i in range(len(self.layer_sizes) - 1):
            fan_in = self.layer_sizes[i]
            fan_out = self.layer_sizes[i + 1]
            scale = math.sqrt(1.0 / max(1, fan_in))
            W = rng.normal(0, scale, size=(fan_in, fan_out))
            b = np.zeros(fan_out)
            self.W.append(W)
            self.b.append(b)
            self.vW.append(np.zeros_like(W))
            self.vb.append(np.zeros_like(b))

        self.activations = [np.zeros(s) for s in self.layer_sizes]
        self.deltas = [np.zeros(s) for s in self.layer_sizes]

    def forward(self, x):
        cur = np.asarray(x, dtype=np.float64)
        self.activations[0] = cur
        for i in range(len(self.W)):
            z = cur @ self.W[i] + self.b[i]
            if i == len(self.W) - 1:
                z_shift = z - np.max(z)
                e = np.exp(z_shift)
                a = e / np.sum(e)
            else:
                z = np.clip(z, -15.0, 15.0)
                a = 1.0 / (1.0 + np.exp(-z))
            self.activations[i + 1] = a
            cur = a
        return cur

    def train_pattern(self, x, target):
        x = np.asarray(x, dtype=np.float64)
        target = np.asarray(target, dtype=np.float64)
        out = self.forward(x)

        # Correct gradient descent sign
        delta = target - out
        self.deltas[-1] = delta

        for li in range(len(self.W) - 2, -1, -1):
            delta = ((delta @ self.W[li + 1].T)
                     * self.activations[li + 1]
                     * (1.0 - self.activations[li + 1]))
            self.deltas[li + 1] = delta

        for i in range(len(self.W)):
            pre = self.activations[i]
            d = self.deltas[i + 1]
            grad_W = np.outer(pre, d)
            self.vW[i] = self.momentum * self.vW[i] + self.learning_rate * grad_W
            self.W[i] += self.vW[i]
            self.vb[i] = self.momentum * self.vb[i] + self.learning_rate * d
            self.b[i] += self.vb[i]

        loss = -float(np.sum(target * np.log(out + 1e-12)))
        return loss

    def predict(self, x):
        return self.forward(x)


# ============================================================
# FONTS
# ============================================================
def get_font(size):
    candidates = [
        "C:/Windows/Fonts/consola.ttf",
        "C:/Windows/Fonts/cour.ttf",
        "/usr/share/fonts/truetype/dejavu/DejaVuSansMono.ttf",
        "/System/Library/Fonts/Menlo.ttc",
    ]
    for p in candidates:
        if os.path.exists(p):
            try:
                return ImageFont.truetype(p, size)
            except Exception:
                continue
    return ImageFont.load_default()


FONT_12 = get_font(12)
FONT_14 = get_font(14)
FONT_16 = get_font(16)
FONT_18 = get_font(18)


# ============================================================
# FRAME RENDERER
# ============================================================
class FrameRenderer:
    def __init__(self, layer_sizes):
        self.layer_sizes = layer_sizes
        self.n_layers = len(layer_sizes)
        self.layer_xs = np.linspace(70, FRAME_W - 70, self.n_layers)
        self.positions = []
        for li, size in enumerate(layer_sizes):
            xs = self.layer_xs[li]
            ys = np.linspace(50, GRAPH_H - 40, max(size, 1))
            self.positions.append([(float(xs), float(y)) for y in ys])

    def render(self, net, iteration, loss, accuracy, message,
               wrong_positions, known_positions, burst_flag):
        img = Image.new("RGB", (FRAME_W, FRAME_H), (11, 18, 32))
        draw = ImageDraw.Draw(img)

        for y in range(GRAPH_H, FRAME_H):
            shade = 18
            draw.line([(0, y), (FRAME_W, y)], fill=(shade, shade + 5, shade + 12))

        # Edges
        for li in range(len(net.W)):
            W = net.W[li]
            max_w = max(1e-6, float(np.abs(W).max()))
            for i in range(W.shape[0]):
                for j in range(W.shape[1]):
                    w = float(W[i, j])
                    norm = abs(w) / max_w
                    if norm < 0.22:
                        continue
                    x1, y1 = self.positions[li][i]
                    x2, y2 = self.positions[li + 1][j]
                    if w > 0:
                        a = min(0.85, 0.15 + norm)
                        color = (int(20 + 54 * a), int(80 + 142 * a), int(40 + 88 * a))
                    else:
                        a = min(0.85, 0.15 + norm)
                        color = (int(160 + 88 * a), int(40 + 33 * a), int(40 + 33 * a))
                    width = max(1, int(0.4 + 1.8 * norm))
                    draw.line([(x1, y1), (x2, y2)], fill=color, width=width)

        # Neurons
        for li, layer_pos in enumerate(self.positions):
            acts = net.activations[li]
            for ni, (x, y) in enumerate(layer_pos):
                a = float(acts[ni]) if ni < len(acts) else 0.0
                if li == 0:
                    base = (56, 189, 248)
                elif li == self.n_layers - 1:
                    base = (245, 158, 11)
                else:
                    base = (167, 139, 250)
                glow = 0.20 + 0.80 * min(1.0, a)
                r = int(base[0] * glow + 40 * (1 - glow))
                g = int(base[1] * glow + 40 * (1 - glow))
                b = int(base[2] * glow + 40 * (1 - glow))
                radius = int(4 + 5 * min(1.0, a))
                draw.ellipse([x - radius, y - radius, x + radius, y + radius],
                             fill=(r, g, b), outline=(11, 18, 32))

        # Header
        draw.text((10, 8), "AES-128-SHA Network - Building",
                  font=FONT_18, fill=(226, 232, 240))
        draw.text((10, 30),
                  "iter {:5d}   loss={:.4f}   acc={:.1f}%".format(
                      iteration, loss, accuracy * 100),
                  font=FONT_14, fill=(148, 163, 184))

        # Decryption panel
        panel_y = GRAPH_H + 12
        draw.text((10, panel_y), "Recovered message:", font=FONT_14,
                  fill=(148, 163, 184))
        panel_y += 22

        char_w = 11
        line_h = 22
        max_chars_per_line = (FRAME_W - 20) // char_w

        for idx, ch in enumerate(message):
            col = idx % max_chars_per_line
            row = idx // max_chars_per_line
            x = 10 + col * char_w
            y = panel_y + row * line_h
            if idx in known_positions:
                color = (74, 222, 128)
            elif idx in wrong_positions:
                color = (248, 113, 113)
            else:
                color = (96, 165, 250)
            draw.text((x, y), ch if ch else " ", font=FONT_16, fill=color)

        total_rows = (len(message) // max_chars_per_line) + 1
        legend_y = panel_y + total_rows * line_h + 8
        draw.text((10, legend_y), "green = known   blue = correct   red = wrong",
                  font=FONT_12, fill=(100, 116, 139))

        if burst_flag:
            draw.text((FRAME_W - 180, legend_y), "[ TARGETED BURST ]",
                      font=FONT_14, fill=(251, 191, 36))

        draw.text((10, FRAME_H - 18),
                  "AES-128-CBC  |  SHA-256 key derivation  |  seed='{}'".format(
                      SEED_STRING),
                  font=FONT_12, fill=(71, 85, 105))

        return img


# ============================================================
# MAIN
# ============================================================
def build():
    message = MESSAGE

    print("=" * 70)
    print("AES-128-SHA + neural recovery - building {}".format(GIF_PATH))
    print("=" * 70)
    print("Message:      {}".format(message))
    print("Seed:         {}".format(SEED_STRING))

    # --- Encrypt ---
    cipher = AES128SHACipher(SEED_STRING)
    package = cipher.encrypt(message)

    print("\n[Encryption]")
    print("  Key (AES-128): {}".format(package["key_hex"]))
    print("  IV:            {}".format(package["iv_hex"]))
    print("  Ciphertext:    {} bytes".format(len(package["ciphertext_hex"]) // 2))
    print("  Round-trip OK: {}".format(cipher.decrypt(package) == message))

    # --- Fuzzy guess ---
    fuzzy = make_fuzzy_guess(message, FUZZY_ERROR_RATE)
    matches = sum(1 for a, b in zip(message, fuzzy) if a == b)
    print("\n[Fuzzy guess]")
    print("  Given:  {!r}".format(fuzzy))
    print("  Match:  {}/{} ({:.1f}%)".format(
        matches, len(message), matches / len(message) * 100))

    # Known positions = wherever the fuzzy guess matches
    known_positions = set()
    for i, (a, b) in enumerate(zip(message, fuzzy)):
        if a == b:
            known_positions.add(i)
    print("  Known positions: {}/{}".format(len(known_positions), len(message)))

    # --- Samples ---
    samples = []
    for entry in package["index_card"]:
        i = entry["position"]
        feats = hash_to_features(entry["hash"], n_features=N_FEATURES)
        target = char_to_onehot(chr(entry["char"]))
        is_known = i in known_positions
        samples.append({
            "position": i,
            "features": feats,
            "target": target,
            "known": is_known,
            "true_char": chr(entry["char"]) if is_known else None,
        })

    unknown_samples = [s for s in samples if not s["known"]]

    # --- Network ---
    layer_sizes = [N_FEATURES] + list(HIDDEN) + [64]
    net = NeuralNetwork(layer_sizes, seed=42)
    renderer = FrameRenderer(layer_sizes)
    frames = []

    acc_history = []
    nudge_active_until = -1

    print("\n[Training] {} iterations, frame every {}".format(
        ITERATIONS, VISUALIZE_EVERY))

    for it in range(ITERATIONS):
        order = np.random.permutation(len(samples))
        epoch_loss = 0.0
        for idx in order:
            s = samples[idx]
            epoch_loss += net.train_pattern(s["features"], s["target"])
        epoch_loss /= max(1, len(samples))

        num_correct = 0
        wrong_positions = set()
        for s in unknown_samples:
            pred = net.predict(s["features"])
            pred_idx = int(np.argmax(pred))
            true_idx = int(np.argmax(s["target"]))
            if pred_idx == true_idx:
                num_correct += 1
            else:
                wrong_positions.add(s["position"])
        accuracy = num_correct / max(1, len(unknown_samples))

        current_chars = []
        for s in samples:
            if s["known"]:
                current_chars.append(s["true_char"])
            else:
                pred = net.predict(s["features"])
                current_chars.append(index_to_char(int(np.argmax(pred))))
        current_message = "".join(current_chars)

        acc_history.append(accuracy)
        burst_flag = False
        if len(acc_history) >= 200 and it >= 2000:
            prev_best = max(acc_history[-200:-100])
            curr_best = max(acc_history[-100:])
            if curr_best - prev_best < 0.002 and it > nudge_active_until:
                wrong_set = set(wrong_positions)
                oversample = list(range(len(samples)))
                for _ in range(20):
                    for i, s in enumerate(samples):
                        if s["position"] in wrong_set:
                            oversample.append(i)
                for _ in range(80):
                    np.random.shuffle(oversample)
                    for idx in oversample:
                        s = samples[idx]
                        net.train_pattern(s["features"], s["target"])
                nudge_active_until = it + 80
                burst_flag = True

        if (it + 1) % VISUALIZE_EVERY == 0 or it == ITERATIONS - 1:
            img = renderer.render(
                net=net, iteration=it + 1, loss=epoch_loss, accuracy=accuracy,
                message=current_message, wrong_positions=wrong_positions,
                known_positions=known_positions, burst_flag=burst_flag,
            )
            frames.append(img)
            print("  iter {:5d} | loss={:.4f} | acc={:5.1f}% | {!r}".format(
                it + 1, epoch_loss, accuracy * 100, current_message))

    if not frames:
        print("No frames captured.")
        return

    print("\n[Saving] {} frames to {}".format(len(frames), GIF_PATH))
    frames[0].save(
        GIF_PATH,
        save_all=True,
        append_images=frames[1:],
        duration=FRAME_DURATION_MS,
        loop=0,
        optimize=False,
    )
    size_kb = os.path.getsize(GIF_PATH) / 1024
    print("[Done] {} ({:.1f} KB)".format(os.path.abspath(GIF_PATH), size_kb))


if __name__ == "__main__":
    build()
"

second page:
"
"""
deeper3.py
==========
Mandelbrot blockchain cipher + brain.js-style neural network that learns
to recover the missing half of an encrypted message, with a live HTML
visualization of the network firing in real time.

Run:    python deeper3.py
Output: mandelbrot_live.html (auto-refreshing live view + final report)
        A browser tab auto-opens when training finishes.

Dependencies: numpy only (plus stdlib).
"""

import os
import sys
import json
import time
import math
import zlib
import random
import hashlib
import hmac
import webbrowser
import difflib
from typing import Optional, List, Dict, Any, Tuple

import numpy as np


# ============================================================
# CONSTANTS
# ============================================================
LIVE_HTML = "mandelbrot_live.html"

STUCK_WINDOW = 100
STUCK_IMPROVEMENT = 0.002
OVERSAMPLE_FACTOR = 20
BURST_DURATION = 80
BURST_LR_SCALE = 1.0
BURST_MIN_ITERATION = 2000


# ============================================================
# FUZZY PREDICTION
# ============================================================
REFERENCE_SENTENCES = [
    "A fractal is a shape that contains itself at every scale.",
    "Complex numbers have a real part and an imaginary part.",
    "Chaos theory studies how small changes lead to big effects.",
    "The butterfly effect is a famous example of sensitive dependence.",
    "Iteration is the process of repeating a calculation many times.",
    "Boundaries between order and chaos are often fractal in nature.",
    "Mathematics reveals patterns that nature hides in plain sight.",
]


def fuzzy_prediction(raw_text):
    best_ratio = 0.0
    best_match = raw_text
    for cand in REFERENCE_SENTENCES:
        ratio = difflib.SequenceMatcher(None, raw_text.lower(), cand.lower()).ratio()
        if ratio > best_ratio:
            best_ratio = ratio
            best_match = cand
    return best_match, best_ratio


# ============================================================
# MANDEBROT CIPHER
# ============================================================
class MandelbrotSVGScaler:
    MAX_ALLOWED_DEPTH = 300

    def __init__(self):
        self.max_depth = 300
        self.precision = 1_000_000

    @staticmethod
    def _crc32(data: bytes) -> int:
        return zlib.crc32(data) & 0xFFFFFFFF

    @staticmethod
    def _make_rng(seed_value: int):
        return random.Random(seed_value)

    def generate_scaled_location(self, seed, char, iteration_limit=1000):
        depth = int(getattr(self, "max_depth", 30))
        if depth < 1 or depth > self.MAX_ALLOWED_DEPTH:
            depth = self.MAX_ALLOWED_DEPTH
        depth = max(1, min(self.MAX_ALLOWED_DEPTH, depth))

        seed_bytes = (str(seed) + char).encode("utf-8")
        rng = self._make_rng(self._crc32(seed_bytes))

        x0 = rng.randint(-2000, 1000) / 1000
        y0 = rng.randint(-1500, 1500) / 1000

        zoom_level = int(rng.randint(1, depth))
        zoom_level = max(1, min(self.MAX_ALLOWED_DEPTH, zoom_level))
        zoom = float(2 ** zoom_level)

        pixel_x = rng.randint(0, 800)
        pixel_y = rng.randint(0, 600)

        width = 3.0 / zoom
        height = 3.0 / zoom

        real = x0 + (pixel_x / 800 * width - (width / 2))
        imag = y0 + (pixel_y / 600 * height - (height / 2))
        real_scaled = round(real * self.precision)
        imag_scaled = round(imag * self.precision)

        zr, zi = 0.0, 0.0
        iterations = 0
        while zr * zr + zi * zi < 4 and iterations < iteration_limit:
            temp = zr * zr - zi * zi + real
            zi = 2 * zr * zi + imag
            zr = temp
            iterations += 1

        return {
            "real": real, "imag": imag,
            "realScaled": real_scaled, "imagScaled": imag_scaled,
            "zoom": zoom_level, "iterations": iterations,
            "pixelX": pixel_x, "pixelY": pixel_y,
        }

    def generate_unique_hash(self, location, char):
        unique_data = "|".join([
            str(location["realScaled"]), str(location["imagScaled"]),
            str(location["zoom"]), str(location["iterations"]),
            str(ord(char)),
        ])
        return hashlib.sha512(unique_data.encode("utf-8")).hexdigest()

    def visualize_mandelbrot_point(self, location, size=300):
        real = location["real"]
        imag = location["imag"]
        return (
            f'<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 {size} {size}" '
            f'width="{size}" height="{size}">'
            f'<rect width="100%" height="100%" fill="#000" />'
            f'<circle cx="{size/2}" cy="{size/2}" r="3" fill="red" />'
            f'<text x="5" y="15" fill="white" font-size="10">Real: {real:.8f}</text>'
            f'<text x="5" y="30" fill="white" font-size="10">Imag: {imag:.8f}</text>'
            f'<text x="5" y="45" fill="white" font-size="10">Zoom: {location["zoom"]}x</text>'
            f'</svg>'
        )


class MandelbrotBlockchainCipher:
    def __init__(self, seed: Optional[int] = None, difficulty: int = 2):
        self.max_iterations = 10000
        self.difficulty = max(1, min(4, int(difficulty)))
        self.seed = seed if seed is not None else int(time.time())
        self.svg_scaler = MandelbrotSVGScaler()

    def encrypt(self, plaintext):
        index_card = []
        hashes = []
        blockchain = []
        prev_hash = "0" * 64

        for index, char in enumerate(plaintext):
            location = self.svg_scaler.generate_scaled_location(
                "{}{}".format(self.seed, index), char)
            h = self.svg_scaler.generate_unique_hash(location, char)
            hashes.append(h)
            index_card.append({
                "location": location, "hash": h,
                "char": ord(char),
                "svg": self.svg_scaler.visualize_mandelbrot_point(location),
            })

        mega_hash = self._create_mega_hash(hashes)
        block_data = []
        for index, mapping in enumerate(index_card):
            mac = self._create_hmac(mapping["hash"], mega_hash)
            index_card[index]["hmac"] = mac
            block_data.append({
                "char": mapping["char"], "hash": mapping["hash"], "hmac": mac,
                "location": {
                    "real": mapping["location"]["real"],
                    "imag": mapping["location"]["imag"],
                    "zoom": mapping["location"]["zoom"],
                    "iterations": mapping["location"]["iterations"],
                },
            })
            if len(block_data) == 5 or index == len(index_card) - 1:
                block = self._mine_block(prev_hash, block_data, self.difficulty)
                blockchain.append(block)
                prev_hash = block["hash"]
                block_data = []

        return {
            "seed": self.seed, "indexCard": index_card,
            "megaHash": mega_hash, "blockchain": blockchain,
            "timestamp": int(time.time()),
        }

    def _create_mega_hash(self, hashes):
        return hashlib.sha512("".join(hashes).encode("utf-8")).hexdigest()

    def _create_hmac(self, h, mega_hash):
        return hmac.new(
            mega_hash.encode("utf-8"), h.encode("utf-8"), hashlib.md5
        ).hexdigest()

    def _mine_block(self, prev_hash, data, difficulty):
        nonce = 0
        prefix = "0" * difficulty
        start_time = time.time()
        while True:
            block_data = prev_hash + json.dumps(data, separators=(",", ":")) + str(nonce)
            h = hashlib.sha256(block_data.encode("utf-8")).hexdigest()
            if h.startswith(prefix):
                return {"hash": h, "prevHash": prev_hash, "data": data,
                        "nonce": nonce, "timestamp": int(time.time())}
            nonce += 1
            if nonce % 20000 == 0 and (time.time() - start_time) > 2:
                difficulty -= 1
                prefix = "0" * max(1, difficulty)
                if difficulty < 1:
                    return {"hash": h, "prevHash": prev_hash, "data": data,
                            "nonce": nonce, "timestamp": int(time.time()),
                            "note": "Mining timeout - difficulty reduced"}

    def decrypt(self, encrypted_package):
        if not self._verify_blockchain(encrypted_package["blockchain"]):
            return "ERROR: Blockchain integrity check failed"
        all_hashes = [m["hash"] for m in encrypted_package["indexCard"]]
        if self._create_mega_hash(all_hashes) != encrypted_package["megaHash"]:
            return "ERROR: MegaHash verification failed"
        out = []
        for mapping in encrypted_package["indexCard"]:
            calc = self._create_hmac(mapping["hash"], encrypted_package["megaHash"])
            if calc != mapping["hmac"]:
                return "ERROR: HMAC verification failed for character"
            out.append(chr(mapping["char"]))
        return "".join(out)

    def _verify_blockchain(self, blockchain):
        prev_hash = "0" * 64
        for block in blockchain:
            if block["prevHash"] != prev_hash:
                return False
            bd = (block["prevHash"]
                  + json.dumps(block["data"], separators=(",", ":"))
                  + str(block["nonce"]))
            if hashlib.sha256(bd.encode("utf-8")).hexdigest() != block["hash"]:
                return False
            prev_hash = block["hash"]
        return True


# ============================================================
# HASH -> FEATURES, CHAR <-> ONE-HOT
# ============================================================
def hash_to_features(hash_hex: str, n_features: int = 16) -> np.ndarray:
    raw = bytes.fromhex(hash_hex)
    return np.array([b / 255.0 for b in raw[:n_features]], dtype=np.float64)


def char_to_index(ch: str) -> int:
    o = ord(ch)
    if 97 <= o <= 122:
        return o - 97
    if 65 <= o <= 90:
        return 26 + (o - 65)
    if o == 32:
        return 52
    if 48 <= o <= 57:
        return 53 + (o - 48)
    return 63


def index_to_char(idx: int) -> str:
    if 0 <= idx <= 25:
        return chr(97 + idx)
    if 26 <= idx <= 51:
        return chr(65 + (idx - 26))
    if idx == 52:
        return " "
    if 53 <= idx <= 62:
        return chr(48 + (idx - 53))
    return "?"


def char_to_onehot(ch: str, vocab_size: int = 64) -> np.ndarray:
    idx = char_to_index(ch)
    vec = np.zeros(vocab_size, dtype=np.float64)
    vec[idx] = 1.0
    return vec


# ============================================================
# LIVE NEURAL NETWORK
# ============================================================
class LiveNeuralNetwork:
    def __init__(self, layer_sizes, seed=42):
        self.layer_sizes = [int(s) for s in layer_sizes]
        self.rng = np.random.default_rng(seed)

        self.learning_rate = 0.1
        self.momentum = 0.1
        self.error_thresh = 0.005

        self.W = []
        self.b = []
        self.vW = []
        self.vb = []

        for i in range(len(self.layer_sizes) - 1):
            fan_in = self.layer_sizes[i]
            fan_out = self.layer_sizes[i + 1]
            scale = math.sqrt(1.0 / max(1, fan_in))
            W = self.rng.normal(0, scale, size=(fan_in, fan_out))
            b = np.zeros(fan_out)
            self.W.append(W)
            self.b.append(b)
            self.vW.append(np.zeros_like(W))
            self.vb.append(np.zeros_like(b))

        self.activations = [np.zeros(s) for s in self.layer_sizes]
        self.deltas = [np.zeros(s) for s in self.layer_sizes]
        print("[fwd] acts:", [round(float(np.mean(a)),4) for a in self.activations])
    def forward(self, x):
        cur = np.asarray(x, dtype=np.float64)
        self.activations[0] = cur
        for i in range(len(self.W)):
            z = cur @ self.W[i] + self.b[i]
            if i == len(self.W) - 1:
                z_shift = z - np.max(z)
                e = np.exp(z_shift)
                a = e / np.sum(e)
            else:
                z = np.clip(z, -15.0, 15.0)
                a = 1.0 / (1.0 + np.exp(-z))
            self.activations[i + 1] = a
            cur = a
        return cur

    def train_pattern(self, x, target):
        x = np.asarray(x, dtype=np.float64)
        target = np.asarray(target, dtype=np.float64)

        out = self.forward(x)
        if not hasattr(self, "_dbg"):
            true_idx = int(np.argmax(target))
            print("[dbg] true class:", true_idx, "predicted prob for it:", float(out[true_idx]))
            print("[dbg] max prob class:", int(np.argmax(out)), "value:", float(out.max()))
            print("[dbg] out[:5]:", ["{:.4f}".format(float(v)) for v in out[:5]])
            print("[dbg] target argmax:", true_idx)
            print("[dbg] W0 first row:", ["{:.4f}".format(float(v)) for v in self.W[0][0][:5]])
            self._dbg = True
        delta = target - out
        self.deltas[-1] = delta

        for li in range(len(self.W) - 2, -1, -1):
            delta = ((delta @ self.W[li + 1].T)
                     * self.activations[li + 1]
                     * (1.0 - self.activations[li + 1]))
            self.deltas[li + 1] = delta

        for i in range(len(self.W)):
            pre = self.activations[i]
            d = self.deltas[i + 1]

            grad_W = np.outer(pre, d)
            self.vW[i] = self.momentum * self.vW[i] + self.learning_rate * grad_W
            self.W[i] += self.vW[i]

            self.vb[i] = self.momentum * self.vb[i] + self.learning_rate * d
            self.b[i] += self.vb[i]

        loss = -float(np.sum(target * np.log(out + 1e-12)))
        return loss

    def predict(self, x):
        return self.forward(x)


# ============================================================
# LIVE VISUALIZER
# ============================================================
def _esc(s):
    return (str(s).replace("&", "&amp;")
                   .replace("<", "&lt;")
                   .replace(">", "&gt;")
                   .replace('"', "&quot;"))


LIVE_TEMPLATE = """<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>Mandelbrot Network - Live</title>
<meta http-equiv="refresh" content="{refresh_ms}">
<style>
  :root {{ --panel:#121a2b; --text:#e2e8f0; --muted:#94a3b8; }}
  * {{ box-sizing: border-box; }}
  body {{
    margin:0; padding: 24px;
    background: radial-gradient(1200px 600px at 50% -10%, #17223a, #0b1220 60%);
    color: var(--text);
    font-family: ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
    line-height: 1.5;
  }}
  h1 {{ font-size: 22px; margin: 0 0 4px; }}
  h2 {{ font-size: 13px; margin: 20px 0 8px; color: var(--muted);
       text-transform: uppercase; letter-spacing: 0.08em; }}
  .sub {{ color: var(--muted); font-size: 13px; margin-bottom: 20px; }}
  .grid {{ display: grid; grid-template-columns: repeat(auto-fit, minmax(180px,1fr));
           gap: 12px; margin-bottom: 16px; }}
  .metric {{ background: var(--panel); border: 1px solid #22304a;
             border-radius: 10px; padding: 12px 14px; }}
  .metric .label {{ color: var(--muted); font-size: 11px;
                    text-transform: uppercase; letter-spacing: 0.06em; }}
  .metric .value {{ font-size: 22px; font-weight: 600; margin-top: 4px; }}
  .card {{ background: var(--panel); border: 1px solid #22304a;
           border-radius: 12px; padding: 16px; margin-top: 12px; }}
  .pred-row {{ display: flex; justify-content: space-between;
               padding: 6px 0; border-bottom: 1px solid #1e2a3f; font-size: 13px; }}
  .pred-row:last-child {{ border-bottom: none; }}
  .pred-row .label {{ color: var(--muted); }}
  .pred-row .val {{ font-family: monospace; }}
  code {{ background: #0e1526; padding: 2px 6px; border-radius: 4px; font-size: 12px; }}
  .legend {{ display: flex; gap: 14px; font-size: 12px; color: var(--muted); margin-top: 8px; }}
  .dot {{ display: inline-block; width: 9px; height: 9px; border-radius: 50%;
          margin-right: 6px; vertical-align: middle; }}
</style>
</head>
<body>
  <h1>Mandelbrot Network - Live Training</h1>
  <div class="sub">Page auto-refreshes every {refresh_ms} ms. Neurons glow as they fire;
    edges thicken as weights strengthen.</div>

  <div class="grid">
    <div class="metric"><div class="label">Trial</div><div class="value">{trial}</div></div>
    <div class="metric"><div class="label">Loss</div><div class="value">{loss:.4f}</div></div>
    <div class="metric"><div class="label">Accuracy</div><div class="value">{accuracy:.1f}%</div></div>
    <div class="metric"><div class="label">Neurons</div><div class="value">{n_neurons}</div></div>
    <div class="metric"><div class="label">Synapses</div><div class="value">{n_syn}</div></div>
  </div>

  <div class="card">
    <h2 style="margin-top:0">Network</h2>
    <svg viewBox="0 0 {svg_w} {svg_h}" style="width:100%;height:auto;display:block;
      background:#0b1220;border-radius:8px">{edges}{neurons}</svg>
    <div class="legend">
      <span><span class="dot" style="background:#38bdf8"></span>Input</span>
      <span><span class="dot" style="background:#a78bfa"></span>Hidden</span>
      <span><span class="dot" style="background:#f59e0b"></span>Output</span>
      <span>green edges = positive weight, red = negative</span>
    </div>
  </div>

  <div class="card">
    <h2 style="margin-top:0">Last prediction</h2>
    {pred_html}
  </div>

  <div class="card">
    <h2 style="margin-top:0">Loss</h2>
    {loss_svg}
  </div>
  <div class="card">
    <h2 style="margin-top:0">Accuracy</h2>
    {acc_svg}
  </div>
</body>
</html>
"""


class LiveVisualizer:
    def __init__(self, net, path=LIVE_HTML):
        self.net = net
        self.path = path
        self.trial_history = []
        self.loss_history = []
        self.accuracy_history = []
        self.last_prediction = ""
        self.last_expected = ""

    def record(self, trial, loss, accuracy, prediction, expected):
        self.trial_history.append(int(trial))
        self.loss_history.append(float(loss))
        self.accuracy_history.append(float(accuracy))
        self.last_prediction = prediction
        self.last_expected = expected

    def _sparkline(self, values, width=800, height=100, color="#4ade80"):
        if not values:
            return "<svg></svg>"
        vmin, vmax = min(values), max(values)
        if vmax == vmin:
            vmax = vmin + 1e-9
        n = len(values)
        pts = []
        for i, v in enumerate(values):
            x = (i / max(1, n - 1)) * width
            y = height - ((v - vmin) / (vmax - vmin)) * (height - 20) - 10
            pts.append("{:.1f},{:.1f}".format(x, y))
        poly = " ".join(pts)
        return (
            '<svg viewBox="0 0 {w} {h}" preserveAspectRatio="none" '
            'style="width:100%;height:{h}px;display:block">'
            '<polyline points="{p}" fill="none" stroke="{c}" stroke-width="2"/>'
            '</svg>'
        ).format(w=width, h=height, p=poly, c=color)

    def render(self):
        net = self.net
        layers = net.layer_sizes
        n_layers = len(layers)

        svg_w, svg_h = 900, 400
        layer_xs = np.linspace(80, svg_w - 80, n_layers)
        positions = []
        for li, size in enumerate(layers):
            xs = layer_xs[li]
            ys = np.linspace(60, svg_h - 40, max(size, 1))
            positions.append([(float(xs), float(y)) for y in ys])

        edges = []
        for li in range(len(net.W)):
            W = net.W[li]
            max_w = max(1e-6, float(np.abs(W).max()))
            for i in range(W.shape[0]):
                for j in range(W.shape[1]):
                    w = float(W[i, j])
                    norm = abs(w) / max_w
                    if norm < 0.15:
                        continue
                    x1, y1 = positions[li][i]
                    x2, y2 = positions[li + 1][j]
                    if w > 0:
                        color = "rgba(74,222,128,{:.2f})".format(min(0.9, norm))
                    else:
                        color = "rgba(248,113,113,{:.2f})".format(min(0.9, norm))
                    sw = 0.3 + 2.0 * norm
                    edges.append(
                        '<line x1="{:.1f}" y1="{:.1f}" x2="{:.1f}" y2="{:.1f}" '
                        'stroke="{}" stroke-width="{:.2f}"/>'.format(
                            x1, y1, x2, y2, color, sw)
                    )

        neurons = []
        for li, layer_pos in enumerate(positions):
            activations = net.activations[li]
            for ni, (x, y) in enumerate(layer_pos):
                a = float(activations[ni]) if ni < len(activations) else 0.0
                if li == 0:
                    base = (56, 189, 248)
                elif li == n_layers - 1:
                    base = (245, 158, 11)
                else:
                    base = (167, 139, 250)
                glow = 0.25 + 0.75 * min(1.0, a)
                r = int(base[0] * glow + 40 * (1 - glow))
                g = int(base[1] * glow + 40 * (1 - glow))
                b = int(base[2] * glow + 40 * (1 - glow))
                radius = 5 + 6 * min(1.0, a)
                neurons.append(
                    '<circle cx="{:.1f}" cy="{:.1f}" r="{:.1f}" '
                    'fill="rgb({},{},{})" stroke="#0b1220" stroke-width="1"/>'.format(
                        x, y, radius, r, g, b)
                )

        loss_svg = self._sparkline(self.loss_history, color="#4ade80")
        acc_svg = self._sparkline(self.accuracy_history, color="#60a5fa")

        pred_html = (
            '<div class="pred-row">'
            '<span class="label">expected</span>'
            '<span class="val">' + _esc(self.last_expected) + '</span>'
            '</div>'
            '<div class="pred-row">'
            '<span class="label">predicted</span>'
            '<span class="val">' + _esc(self.last_prediction) + '</span>'
            '</div>'
        )

        last_loss = self.loss_history[-1] if self.loss_history else 0.0
        last_acc = self.accuracy_history[-1] if self.accuracy_history else 0.0
        trial = self.trial_history[-1] if self.trial_history else 0

        html_out = LIVE_TEMPLATE.format(
            svg_w=svg_w, svg_h=svg_h,
            edges="".join(edges),
            neurons="".join(neurons),
            loss_svg=loss_svg,
            acc_svg=acc_svg,
            pred_html=pred_html,
            trial=trial,
            loss=last_loss,
            accuracy=last_acc * 100.0,
            n_syn=int(sum(W.size for W in net.W)),
            n_neurons=int(sum(layers)),
            refresh_ms=2000,
        )

        tmp = self.path + ".tmp"
        with open(tmp, "w", encoding="utf-8") as f:
            f.write(html_out)
        os.replace(tmp, self.path)


# ============================================================
# TRAINING
# ============================================================
def train_on_ciphertext(message: str,
                        known_half: str,
                        assume_prefix: bool = True,
                        n_features: int = 16,
                        hidden: List[int] = None,
                        iterations: int = 1500,
                        seed: int = 7,
                        difficulty: int = 2,
                        visualize_every: int = 25,
                        verbose: bool = True):
    if hidden is None:
        hidden = [24, 16]

    print("=" * 70)
    print("Mandelbrot Network - cipher + solver + live visualization")
    print("=" * 70)

    cipher = MandelbrotBlockchainCipher(seed=seed, difficulty=difficulty)
    print("\n[1/4] Encrypting message ({} chars) with seed={} difficulty={}...".format(
        len(message), seed, cipher.difficulty))
    t0 = time.time()
    encrypted = cipher.encrypt(message)
    print("      Done in {:.3f}s. {} blocks mined.".format(
        time.time() - t0, len(encrypted["blockchain"])))

    total_len = len(message)
    known_len = len(known_half)
    if assume_prefix:
        known_positions = set(range(known_len))
    else:
        known_positions = set(range(total_len - known_len, total_len))

    print("\n[2/4] Building training data...")
    print("      Known positions: {}/{}".format(len(known_positions), total_len))

    samples = []
    for i, entry in enumerate(encrypted["indexCard"]):
        feats = hash_to_features(entry["hash"], n_features=n_features)
        target = char_to_onehot(chr(entry["char"]))
        is_known = i in known_positions
        samples.append({
            "position": i,
            "features": feats,
            "target": target,
            "known": is_known,
            "true_char": chr(entry["char"]) if is_known else None,
        })

    unknown_samples = [s for s in samples if not s["known"]]
    print("      Total samples: {}, unknown: {}".format(
        len(samples), len(unknown_samples)))

    layer_sizes = [n_features] + list(hidden) + [64]
    print("\n[3/4] Building network: {}".format(layer_sizes))
    net = LiveNeuralNetwork(layer_sizes, seed=42)
    visualizer = LiveVisualizer(net, path=LIVE_HTML)

    print("\n[4/4] Training ({} iterations, visualizing every {})...".format(
        iterations, visualize_every))
    print("      Live view:  {}".format(os.path.abspath(LIVE_HTML)))

    acc_history = []
    nudge_active_until = -1
    nudge_count = 0

    for it in range(iterations):
        order = np.random.permutation(len(samples))
        epoch_loss = 0.0
        for idx in order:
            s = samples[idx]
            epoch_loss += net.train_pattern(s["features"], s["target"])
        epoch_loss /= max(1, len(samples))

        num_correct = 0
        last_pred_char = ""
        last_true_char = ""
        wrong_positions = []
        for s in unknown_samples:
            pred = net.predict(s["features"])
            pred_idx = int(np.argmax(pred))
            true_idx = int(np.argmax(s["target"]))
            if pred_idx == true_idx:
                num_correct += 1
            else:
                wrong_positions.append(s["position"])
            last_pred_char = index_to_char(pred_idx)
            last_true_char = index_to_char(true_idx)
        accuracy = num_correct / max(1, len(unknown_samples))

        acc_history.append(accuracy)

        if len(acc_history) >= 2 * STUCK_WINDOW:
            prev_best = max(acc_history[:STUCK_WINDOW])
            curr_best = max(acc_history[STUCK_WINDOW:])
            improvement = curr_best - prev_best

            if it < BURST_MIN_ITERATION:
                acc_history[:] = acc_history[STUCK_WINDOW:]
            elif improvement < STUCK_IMPROVEMENT and it > nudge_active_until and wrong_positions:
                wrong_set = set(wrong_positions)
                oversample_indices = list(range(len(samples)))
                for _ in range(OVERSAMPLE_FACTOR):
                    for i, s in enumerate(samples):
                        if s["position"] in wrong_set:
                            oversample_indices.append(i)

                old_lr = net.learning_rate
                net.learning_rate = old_lr * BURST_LR_SCALE

                for _ in range(BURST_DURATION):
                    np.random.shuffle(oversample_indices)
                    for idx in oversample_indices:
                        s = samples[idx]
                        net.train_pattern(s["features"], s["target"])

                net.learning_rate = old_lr
                nudge_active_until = it + BURST_DURATION
                nudge_count += 1

                if verbose:
                    print("  [{:5d}] STUCK #{} (best {:.3f}->{:.3f}) | "
                          "wrong at {} | oversample {}x for {} iters".format(
                              it + 1, nudge_count, prev_best, curr_best,
                              wrong_positions, OVERSAMPLE_FACTOR,
                              BURST_DURATION))

                acc_history[:] = acc_history[STUCK_WINDOW:]

        visualizer.record(it + 1, epoch_loss, accuracy,
                          last_pred_char, last_true_char)

        if (it + 1) % visualize_every == 0:
            visualizer.render()
            if verbose:
                print("  iter {:5d} | loss={:.4f} | unknown_acc={:5.1f}% | ({}/{})".format(
                    it + 1, epoch_loss, accuracy * 100,
                    num_correct, len(unknown_samples)))

    visualizer.render()
    print("\nTraining complete. Live view: {}".format(os.path.abspath(LIVE_HTML)))

    predicted_message = []
    for s in samples:
        if s["known"]:
            predicted_message.append(s["true_char"])
        else:
            pred = net.predict(s["features"])
            predicted_message.append(index_to_char(int(np.argmax(pred))))
    predicted_message = "".join(predicted_message)

    print("\nTrue message:      {!r}".format(message))
    print("Predicted message: {!r}".format(predicted_message))
    matches = sum(1 for a, b in zip(message, predicted_message) if a == b)
    print("Match: {}/{} ({:.1f}%)".format(
        matches, len(message), matches / len(message) * 100))

    fuzzy, ratio = fuzzy_prediction(predicted_message)
    print("\nFuzzy prediction:  {!r}".format(fuzzy))
    print("Similarity:        {:.1%}".format(ratio))

    return net, encrypted, predicted_message, visualizer


# ============================================================
# MAIN
# ============================================================
if __name__ == "__main__":
    message = "The Mandelbrot set hides every secret we could ever need. And nomes fly, if you walk through them."
    known = message[:len(message) // 2]

    print("Full message:  {!r}".format(message))
    print("Known prefix:  {!r}".format(known))
    print()

    net, encrypted, predicted, viz = train_on_ciphertext(
        message=message,
        known_half=known,
        assume_prefix=True,
        n_features=16,
        hidden=[24, 16],
        iterations=1500,
        seed=7,
        difficulty=2,
        visualize_every=25,
        verbose=True,
    )

    report_path = os.path.abspath(LIVE_HTML)
    url = "file:///" + report_path.replace("\\", "/")
    print("\nOpening report: {}".format(url))
    try:
        webbrowser.open(url)
    except Exception as e:
        print("Could not auto-open browser: {}".format(e))
        print("Open this file manually: {}".format(report_path))
"
End of File Transmission. GL

Share this Paste