Path: blob/main/scripts/generate_readme_images.py
483 views
unlisted
#!/usr/bin/env python31"""Generate README showcase images using matplotlib (no SageMath needed)."""23import numpy as np4import matplotlib5matplotlib.use('Agg')6import matplotlib.pyplot as plt7from matplotlib.patches import FancyArrowPatch, FancyBboxPatch8from pathlib import Path910OUT = Path(__file__).resolve().parent.parent / "docs" / "images"11OUT.mkdir(parents=True, exist_ok=True)1213# Consistent style14plt.rcParams.update({15'figure.facecolor': '#0d1117',16'axes.facecolor': '#0d1117',17'text.color': '#e6edf3',18'axes.labelcolor': '#e6edf3',19'xtick.color': '#8b949e',20'ytick.color': '#8b949e',21'axes.edgecolor': '#30363d',22'grid.color': '#21262d',23'font.family': 'monospace',24})2526CYAN = '#58a6ff'27GREEN = '#3fb950'28ORANGE = '#d29922'29RED = '#f85149'30PURPLE = '#bc8cff'31PINK = '#f778ba'323334def generate_elliptic_curve():35"""Elliptic curve y^2 = x^3 - x + 1 with point addition."""36fig, ax = plt.subplots(figsize=(8, 6))3738# Curve39x = np.linspace(-1.5, 2.5, 2000)40rhs = x**3 - x + 141mask = rhs >= 042y = np.sqrt(np.maximum(rhs, 0))43ax.plot(x[mask], y[mask], color=CYAN, linewidth=2.5)44ax.plot(x[mask], -y[mask], color=CYAN, linewidth=2.5)4546# Two points P and Q on the curve47px, py = 0.0, 1.0 # P: y^2 = 0 - 0 + 1 = 148qx, qy = 1.0, 1.0 # Q: y^2 = 1 - 1 + 1 = 14950# Line through P and Q (slope = 0 since py == qy)51m = (qy - py) / (qx - px) if qx != px else None52if m is not None:53# Third intersection: x^3 - x + 1 - (m*(x - px) + py)^2 = 054# For m=0, b=1: x^3 - x + 1 - 1 = x^3 - x = x(x-1)(x+1)55# Roots: x = -1, 0, 1. Third root is x = -156rx = -1.057ry = m * (rx - px) + py # = 1.058sx, sy = rx, -ry # Reflect: P + Q = (rx, -ry) = (-1, -1)5960# Draw the secant line61x_line = np.linspace(-1.8, 1.8, 100)62y_line = m * (x_line - px) + py63ax.plot(x_line, y_line, '--', color=ORANGE, linewidth=1.2, alpha=0.7)6465# Vertical reflection line66ax.plot([rx, rx], [ry, sy], ':', color='#8b949e', linewidth=1.2, alpha=0.6)6768# Points69ax.plot(px, py, 'o', color=GREEN, markersize=12, zorder=5)70ax.annotate('P', (px, py), textcoords='offset points', xytext=(-15, 10),71fontsize=14, fontweight='bold', color=GREEN)7273ax.plot(qx, qy, 'o', color=GREEN, markersize=12, zorder=5)74ax.annotate('Q', (qx, qy), textcoords='offset points', xytext=(10, 10),75fontsize=14, fontweight='bold', color=GREEN)7677ax.plot(rx, ry, 's', color=PURPLE, markersize=10, zorder=5, alpha=0.6)78ax.annotate('R', (rx, ry), textcoords='offset points', xytext=(-18, 8),79fontsize=12, color=PURPLE, alpha=0.7)8081ax.plot(sx, sy, '*', color=PINK, markersize=18, zorder=5)82ax.annotate('P + Q', (sx, sy), textcoords='offset points', xytext=(10, -15),83fontsize=14, fontweight='bold', color=PINK)8485ax.set_xlim(-2, 2.8)86ax.set_ylim(-3, 3)87ax.set_aspect('equal')88ax.grid(True, alpha=0.3)89ax.set_title('Elliptic Curve Point Addition', fontsize=16, pad=15, color='#e6edf3')90ax.set_xlabel('$x$', fontsize=13)91ax.set_ylabel('$y$', fontsize=13)9293# Equation label94ax.text(1.8, -2.4, r'$y^2 = x^3 - x + 1$', fontsize=13, color=CYAN,95style='italic', ha='center')9697fig.tight_layout()98fig.savefig(OUT / 'elliptic-curve.png', dpi=150, bbox_inches='tight',99facecolor=fig.get_facecolor())100plt.close(fig)101print(f" Saved {OUT / 'elliptic-curve.png'}")102103104def generate_lattice():105"""2D lattice with good vs bad basis."""106fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(12, 5.5))107108# Good basis (short, nearly orthogonal)109b1_good = np.array([2, 1])110b2_good = np.array([0, 2])111112# Bad basis (long, nearly parallel)113b1_bad = np.array([2, 5])114b2_bad = np.array([4, 7])115116for ax, b1, b2, title, color in [117(ax1, b1_good, b2_good, '"Good" Basis (Short, Orthogonal)', GREEN),118(ax2, b1_bad, b2_bad, '"Bad" Basis (Long, Nearly Parallel)', RED),119]:120# Generate lattice points121pts = []122for i in range(-4, 5):123for j in range(-4, 5):124p = i * b1 + j * b2125if -10 <= p[0] <= 10 and -10 <= p[1] <= 10:126pts.append(p)127128pts = np.array(pts)129ax.scatter(pts[:, 0], pts[:, 1], color=CYAN, s=30, zorder=4, alpha=0.8)130131# Basis vectors as arrows132origin = np.array([0, 0])133ax.annotate('', xy=b1, xytext=origin,134arrowprops=dict(arrowstyle='->', color=color, lw=2.5))135ax.annotate('', xy=b2, xytext=origin,136arrowprops=dict(arrowstyle='->', color=ORANGE, lw=2.5))137138ax.annotate(r'$\mathbf{b}_1$', xy=b1 / 2, textcoords='offset points',139xytext=(8, -12), fontsize=13, fontweight='bold', color=color)140ax.annotate(r'$\mathbf{b}_2$', xy=b2 / 2, textcoords='offset points',141xytext=(-20, 5), fontsize=13, fontweight='bold', color=ORANGE)142143# Origin144ax.plot(0, 0, 'o', color='white', markersize=8, zorder=5)145146ax.set_xlim(-8, 8)147ax.set_ylim(-8, 8)148ax.set_aspect('equal')149ax.grid(True, alpha=0.2)150ax.set_title(title, fontsize=12, pad=10, color='#e6edf3')151152fig.suptitle('Lattice Basis Reduction', fontsize=16, y=1.02, color='#e6edf3')153fig.tight_layout()154fig.savefig(OUT / 'lattice-basis.png', dpi=150, bbox_inches='tight',155facecolor=fig.get_facecolor())156plt.close(fig)157print(f" Saved {OUT / 'lattice-basis.png'}")158159160def generate_module_flow():161"""Visual diagram of the 4-phase learning flow."""162fig, ax = plt.subplots(figsize=(12, 3.5))163ax.set_xlim(0, 12)164ax.set_ylim(0, 3.5)165ax.axis('off')166167phases = [168('Explore', 'SageMath\nNotebooks', CYAN, 1.5),169('Implement', 'Rust\nExercises', GREEN, 4.5),170('Break', 'Attack Weak\nPrimitives', ORANGE, 7.5),171('Connect', 'Real-World\nProtocols', PURPLE, 10.5),172]173174box_w, box_h = 2.2, 2.2175176for name, subtitle, color, cx in phases:177# Box178rect = FancyBboxPatch((cx - box_w/2, 0.6), box_w, box_h,179boxstyle="round,pad=0.15",180facecolor=color + '18', edgecolor=color,181linewidth=2)182ax.add_patch(rect)183184# Phase name185ax.text(cx, 2.15, name, ha='center', va='center',186fontsize=16, fontweight='bold', color=color)187188# Subtitle189ax.text(cx, 1.35, subtitle, ha='center', va='center',190fontsize=11, color='#8b949e')191192# Arrows between phases193for i in range(3):194x_start = phases[i][3] + box_w/2 + 0.05195x_end = phases[i+1][3] - box_w/2 - 0.05196ax.annotate('', xy=(x_end, 1.7), xytext=(x_start, 1.7),197arrowprops=dict(arrowstyle='->', color='#8b949e',198lw=2, connectionstyle='arc3,rad=0'))199200# Stats bar at bottom201ax.text(6, 0.15, '123 notebooks | 57 Rust exercises | 12 modules | BSc to postgrad',202ha='center', va='center', fontsize=11, color='#8b949e', style='italic')203204fig.tight_layout()205fig.savefig(OUT / 'module-flow.png', dpi=150, bbox_inches='tight',206facecolor=fig.get_facecolor())207plt.close(fig)208print(f" Saved {OUT / 'module-flow.png'}")209210211def generate_social_preview():212"""1280x640 social preview for GitHub link sharing."""213fig, ax = plt.subplots(figsize=(12.8, 6.4))214ax.set_xlim(0, 12.8)215ax.set_ylim(0, 6.4)216ax.axis('off')217218# Small elliptic curve in the bottom-right corner219x = np.linspace(-1.2, 2.2, 1500)220rhs = x**3 - x + 1221mask = rhs >= 0222y = np.sqrt(np.maximum(rhs, 0))223# Scale and shift to bottom-right224sx, sy_offset, scale = 9.5, 1.8, 0.9225ax.plot(x[mask] * scale + sx, y[mask] * scale + sy_offset, color=CYAN, linewidth=1.8, alpha=0.35)226ax.plot(x[mask] * scale + sx, -y[mask] * scale + sy_offset, color=CYAN, linewidth=1.8, alpha=0.35)227228# Title229ax.text(6.4, 4.6, 'Crypto From First Principles', ha='center', va='center',230fontsize=36, fontweight='bold', color='#e6edf3')231232# Stats line233ax.text(6.4, 3.6, '123 notebooks | 57 Rust exercises | 12 modules',234ha='center', va='center', fontsize=18, color=CYAN)235236# Tagline237ax.text(6.4, 2.6, 'Learn the math. Build it in Rust. Break it. See it in the wild.',238ha='center', va='center', fontsize=16, color='#8b949e', style='italic')239240# Phase chips at the bottom241phases = [242('Explore', CYAN, 2.5),243('Implement', GREEN, 5.0),244('Break', ORANGE, 7.5),245('Connect', PURPLE, 10.0),246]247for label, color, cx in phases:248rect = FancyBboxPatch((cx - 1.0, 0.8), 2.0, 0.9,249boxstyle="round,pad=0.12",250facecolor=color + '20', edgecolor=color,251linewidth=1.5)252ax.add_patch(rect)253ax.text(cx, 1.25, label, ha='center', va='center',254fontsize=14, fontweight='bold', color=color)255256fig.tight_layout(pad=0)257fig.savefig(OUT / 'social-preview.png', dpi=100, bbox_inches='tight',258facecolor=fig.get_facecolor(), pad_inches=0.2)259plt.close(fig)260print(f" Saved {OUT / 'social-preview.png'}")261262263if __name__ == '__main__':264print("Generating README images...")265generate_elliptic_curve()266generate_lattice()267generate_module_flow()268generate_social_preview()269print("Done!")270271272