How Geometric Proof Actually Works — Two Concrete Examples¶
What does it bring to Sawabona?¶
Geometric Proof (GP) is Sawabona's license validation protocol. Instead of just sending a license key to the server and getting back "yes/no", the client must prove it has the real algorithm and the real key by solving a geometric puzzle.
What it prevents:
- Simple key sharing — A stolen license key alone is useless. You also need the exact GP algorithm (embedded in the SDK) running on the registered device.
- Replay attacks — Each challenge is single-use and expires after the configured TTL (default 300 seconds).
- Man-in-the-middle — The proof is HMAC-signed; intercepting the challenge doesn't help without the secret derived from the license key.
- Reverse engineering — Even if someone decompiles the client, the proof is
bound to
device_hash + license_key + challenge_id, so a clone won't work on a different machine.
In short: GP turns license validation into a cryptographic handshake that binds together who you are (license key), where you are (device), and what you're running (code fingerprint).
The Full Flow (Simplified)¶
CLIENT SERVER
| |
| 1. POST /challenge {license_key, product} |
| -------------------------------------------> |
| | Generate random seed
| | Pick random operations
| | Store challenge (single-use)
| 2. {challenge_id, seed, ops, expires_at} |
| <------------------------------------------- |
| |
| 3. Client builds proof: |
| secret = HKDF(license_key, |
| device:code:challenge_id) |
| for each op: |
| sig = HMAC(secret, op_json) |
| proof = HMAC(secret, seed+id+all_sigs) |
| |
| 4. POST /prove {challenge_id, proof} |
| -------------------------------------------> |
| | Server rebuilds same proof
| | Constant-time compare
| | Delete challenge (no replay)
| 5. {valid: true, token: "eyJ..."} |
| <------------------------------------------- |
| |
| Client now has a JWT for API access |
Example 1 — SIMPLE: The Square¶
Setup¶
The server generates this challenge:
{
"challenge_id": "chal_a1b2c3d4e5f6g7h8",
"seed": 42,
"ops": [{ "op_type": "rotate", "params": { "angle": 90 } }],
"expires_at": 1709400120
}
What the client does internally¶
Step A: Build the figure (Square)
A Square centered at origin with side=2 has 4 corners:
4 segments:
S0: (-1,-1) → ( 1,-1) bottom edge → angle = 0°
S1: ( 1,-1) → ( 1, 1) right edge → angle = 90°
S2: ( 1, 1) → (-1, 1) top edge → angle = 180°
S3: (-1, 1) → (-1,-1) left edge → angle = 270°
Step B: Apply operation — Rotate 90°
The rotation matrix for 90° (π/2 radians):
Each point (x, y) becomes (-y, x):
Result after rotation:
(-1, 1) ●─────────● (1, 1)
| |
| ● | Same shape! (square is
| (0,0) | symmetric under 90° rotation)
| |
(-1,-1) ●─────────● (1,-1)
The square looks the same, but the segment order and direction changed:
S0: ( 1,-1) → ( 1, 1) was bottom, now right → angle = 90°
S1: ( 1, 1) → (-1, 1) was right, now top → angle = 180°
S2: (-1, 1) → (-1,-1) was top, now left → angle = 270°
S3: (-1,-1) → ( 1,-1) was left, now bottom → angle = 0°
Step C: Extract invariants (GPV — optional second proof factor)
When a product has require_gpv = true and the global config gpv.enabled = true, the GPV is computed and verified as a second proof factor alongside HMAC. Here's what the GPV looks like:
center_of_mass = (0.0, 0.0) — unchanged by rotation
perimeter = 8.0 — unchanged (4 sides × 2.0)
area = 4.0 — unchanged
angle_histogram = [0.25, 0, 0, 0.25, 0, 0.50]
0° 30° 60° 90° 120° 150-180°
stroke_parity = 0.0 — 4 segments, even
segment_norm = ln(4) + 1 ≈ 2.386
Step D: Compute the actual proof (what really gets sent)
license_key = "saw_default-tenant_myapp_live_a1b2c3d4..."
device_hash = "sha256_of_machine_fingerprint"
code_hash = SHA256("geometric_proof_core") = "7f3a..."
1. Derive secret:
info = "{device_hash}:{code_hash}:chal_a1b2c3d4e5f6g7h8"
client_secret = HKDF-SHA256(key=license_key, info=info) → 32 bytes
2. Build proof payload:
proof_data = 42_u64.to_le_bytes() → 8 bytes
proof_data += "chal_a1b2c3d4e5f6g7h8".as_bytes() → 24 bytes
3. Sign the operation:
op_json = '{"op_type":"rotate","params":{"angle":90}}'
op_sig = HMAC-SHA256(client_secret, op_json) → 32 bytes
proof_data += op_sig
4. Final proof:
proof = HMAC-SHA256(client_secret, proof_data) → 32 bytes
send = base64(proof) → 44 chars
The server does the exact same computation. If the 32-byte HMAC matches (constant-time comparison), the license is valid.
Example 2 — HARDEST: The 5-Pointed Star with 3 Operations¶
Setup¶
The server generates a complex challenge:
{
"challenge_id": "chal_z9y8x7w6v5u4t3s2",
"seed": 789012,
"ops": [
{ "op_type": "rotate", "params": { "angle": 135 } },
{ "op_type": "mirror_h", "params": {} },
{ "op_type": "translate", "params": { "dx": 1, "dy": -1, "dz": 0 } }
],
"expires_at": 1709400120
}
What the client does internally¶
Step A: Build the figure (5-pointed Star)
A Star-5 has 10 vertices alternating between outer radius (R=2) and inner radius (r=1), starting at the top (-90° = pointing up):
Vertex layout (10 points, every 36°):
V0 (outer, top): ( 0.00, 2.00) ← tip
V1 (inner): ( 0.59, 0.81)
V2 (outer, right): ( 1.90, 0.62) ← tip
V3 (inner): ( 0.95, -0.31)
V4 (outer, lower-R): ( 1.18, -1.62) ← tip
V5 (inner): ( 0.00, -1.00)
V6 (outer, lower-L): (-1.18, -1.62) ← tip
V7 (inner): (-0.95, -0.31)
V8 (outer, left): (-1.90, 0.62) ← tip
V9 (inner): (-0.59, 0.81)
Approximate shape (the * are the 5 outer tips, · are inner valleys):
10 segments connecting V0→V1→V2→V3→...→V9→V0.
Each segment has a different angle and length. Outer→inner segments are shorter (≈1.18) than inner→outer segments (≈1.18) — they alternate between the two radii. The segments zigzag in and out.
Step B: Apply 3 operations sequentially
Operation 1: Rotate 135°
Matrix for 135° (3π/4 rad):
Every point (x,y) → (-0.707x - 0.707y, 0.707x - 0.707y):
V0 ( 0.00, 2.00) → (-1.414, -1.414)
V1 ( 0.59, 0.81) → (-0.990, 0.001)
V2 ( 1.90, 0.62) → (-1.782, 0.907)
V3 ( 0.95, -0.31) → (-0.452, 0.891)
V4 ( 1.18, -1.62) → ( 0.311, 1.979)
V5 ( 0.00, -1.00) → ( 0.707, 0.707)
V6 (-1.18, -1.62) → ( 1.979, 0.311)
V7 (-0.95, -0.31) → ( 0.891, -0.452)
V8 (-1.90, 0.62) → ( 0.907, -1.782)
V9 (-0.59, 0.81) → (-0.155, -1.000)
The star is now rotated 135° clockwise — the top tip now points to the lower-left.
Operation 2: Mirror Horizontal (mirror_y — flip X)
mirror_y matrix: negate all X coordinates.
Every point (x, y) → (-x, y):
V0 (-1.414, -1.414) → ( 1.414, -1.414)
V1 (-0.990, 0.001) → ( 0.990, 0.001)
V2 (-1.782, 0.907) → ( 1.782, 0.907)
V3 (-0.452, 0.891) → ( 0.452, 0.891)
V4 ( 0.311, 1.979) → (-0.311, 1.979)
V5 ( 0.707, 0.707) → (-0.707, 0.707)
V6 ( 1.979, 0.311) → (-1.979, 0.311)
V7 ( 0.891, -0.452) → (-0.891, -0.452)
V8 ( 0.907, -1.782) → (-0.907, -1.782)
V9 (-0.155, -1.000) → ( 0.155, -1.000)
The star is now a mirror image — like seeing it in a mirror placed along the Y axis.
Operation 3: Translate (+1, -1)
Shift everything right by 1, down by 1:
V0 ( 1.414, -1.414) → ( 2.414, -2.414)
V1 ( 0.990, 0.001) → ( 1.990, -0.999)
V2 ( 1.782, 0.907) → ( 2.782, -0.093)
V3 ( 0.452, 0.891) → ( 1.452, -0.109)
V4 (-0.311, 1.979) → ( 0.689, 0.979)
V5 (-0.707, 0.707) → ( 0.293, -0.293)
V6 (-1.979, 0.311) → (-0.979, -0.689)
V7 (-0.891, -0.452) → ( 0.109, -1.452)
V8 (-0.907, -1.782) → ( 0.093, -2.782)
V9 ( 0.155, -1.000) → ( 1.155, -2.000)
The whole star has shifted to the right and down:
* V4
/ \
/ \
V5 · · V3
\ /
V6 *─\ /─* V2
· V1
|
V7 · · V9
\ /
V8 *─\ /─* V0
·
(centered around ~1.0, -1.0)
Step C: Compute the proof
Even though 3 operations produced complex geometry, the proof computation is elegantly simple — it doesn't care about the actual geometry at all!
1. Derive secret (same as Example 1, different challenge_id):
info = "{device_hash}:{code_hash}:chal_z9y8x7w6v5u4t3s2"
client_secret = HKDF-SHA256(key=license_key, info=info) → 32 bytes
2. Build proof payload:
proof_data = 789012_u64.to_le_bytes() → 8 bytes
proof_data += "chal_z9y8x7w6v5u4t3s2".as_bytes() → 24 bytes
3. Sign EACH operation:
op1_json = '{"op_type":"rotate","params":{"angle":135}}'
op1_sig = HMAC-SHA256(secret, op1_json) → 32 bytes
op2_json = '{"op_type":"mirror_h","params":{}}'
op2_sig = HMAC-SHA256(secret, op2_json) → 32 bytes
op3_json = '{"op_type":"translate","params":{"dx":1,"dy":-1,"dz":0}}'
op3_sig = HMAC-SHA256(secret, op3_json) → 32 bytes
proof_data += op1_sig + op2_sig + op3_sig
4. Final proof:
proof = HMAC-SHA256(secret, proof_data) → 32 bytes
send = base64(proof)
Total payload signed: 8 + 24 + 32 + 32 + 32 = 128 bytes → one final 32-byte HMAC.
Why the Geometry Matters (Even Though the Proof is HMAC-Based)¶
You might wonder: if the proof is just HMAC over the operation JSON, why bother with geometry at all?
Three reasons:
-
Anti-reverse-engineering layer. The
build_tile+extract_invariantscode exists in the client SDK. An attacker decompiling the SDK sees complex geometric math (affine transforms, angle histograms, shoelace formula) and must understand all of it to know what matters and what doesn't. This makes the attack surface harder to map. -
Dual-proof factor (GPV). The GPV (Geometric Proof Vector) is now wired as an optional second proof mechanism. When a product has
require_gpv = trueand the server config hasgpv.enabled = true, the client must send both the HMAC proof AND the GPV proof. The server independently rebuilds the tile, computes the GPV, and verifies the client's GPV proof via constant-time comparison. Both proofs must pass for validation to succeed. -
Tile-based challenge binding. The
build_tilefunction is deterministic (ChaCha8 RNG from seed). The server can independently rebuild the exact same tile and verify geometric properties if needed — e.g., "the transformed star's center of mass is at (1.0, -1.0)" — as an additional server-side check layered on top of HMAC.
Summary Table¶
| Aspect | Example 1: Square | Example 2: Star-5 |
|---|---|---|
| Figure | 4 corners, 4 segments | 10 vertices, 10 segments |
| Operations | 1 (rotate 90°) | 3 (rotate 135° + mirror_h + translate) |
| Geometric complexity | Low — square is symmetric | High — zigzag edges, asymmetric result |
| Proof computation | Identical mechanism | Identical mechanism |
| Proof size | 44 chars (base64 of 32 bytes) | 44 chars (same) |
| Security | HMAC binds license+device+challenge | Same |
The geometric complexity of the target and operations does NOT affect proof security — that comes entirely from HMAC-SHA256 + HKDF key derivation. The geometry adds obfuscation depth and future extensibility.