Visual key verification using nonogram puzzles.
Turn any cryptographic public key into a picture. Verify keys by solving a puzzle.
Alice's Key Fingerprint Bob's Key Fingerprint
┌────────────────────┐ ┌────────────────────┐
│██ ██ ████ ██ │ │██ ██ ████ ██ │
│ ████████ ████ │ │ ████████ ████ │
│██████ ██ ██ │ │██████ ██ ██ │
│██ ██ ██████████ │ │██ ██ ██████████ │
│ ██████ ██ │ │ ██████ ██ │
└────────────────────┘ └────────────────────┘
MATCH (100% identical) -- key verified!
Nonograms are picture logic puzzles where you fill cells based on row/column clues. Computing clues from an image is trivial, but solving clues back to an image is NP-complete (Ueda & Nagao, 1996).
NONOCRYPT uses this one-way property to create visual fingerprints for cryptographic keys:
- Any public key (ML-KEM, RSA, Ed25519, etc.) is hashed into a binary image
- The image's nonogram clues become a shareable puzzle
- To verify a key, solve the puzzle and compare the revealed image
- Same key always produces the same picture -- like Signal's safety numbers, but visual
The verification ceremony is interactive and engaging: you solve a puzzle, an image emerges, you compare. This makes key verification something people actually want to do.
# Interactive demo
python3 demo.py
# C version
make && ./nonocrypt_demo "HELLO" "test message"from nonocrypt import KeyFingerprint, compare_fingerprints
# Generate a visual fingerprint from any key bytes
fp = KeyFingerprint.from_bytes(public_key_bytes, grid_size=12)
# Display the image (your visual key identity)
print(fp.as_image())
# Share as a nonogram puzzle (clues only, no solution)
print(fp.as_puzzle())
# Compare two fingerprints
fp_alice = KeyFingerprint.from_bytes(alice_pk)
fp_bob = KeyFingerprint.from_bytes(alice_pk) # same key
print(fp_alice.matches(fp_bob)) # True
print(compare_fingerprints(fp_alice, fp_bob)) # visual side-by-side
# Detect tampering: even 1 bit change = completely different image
tampered = bytearray(alice_pk)
tampered[0] ^= 1
fp_evil = KeyFingerprint.from_bytes(bytes(tampered))
print(fp_alice.matches(fp_evil)) # False
print(fp_alice.similarity(fp_evil)) # ~50% (random chance)
# Interactive verification: solve the puzzle to see the image
solved_image = fp.verify_by_solving()
# Text-based key identities (pixel art)
fp = KeyFingerprint.from_text("ALICE")
print(fp.as_image()) 1. Alice computes her key fingerprint: fp = KeyFingerprint.from_bytes(pk)
2. Alice shows Bob the image (in person): print(fp.as_image())
3. Alice sends the puzzle (over network): puzzle = fp.serialize()
4. Bob receives and solves the puzzle: solved = fp.verify_by_solving()
5. Bob compares with what Alice showed: matches = (solved == expected)
6. If images match, the key is authentic!
Solving the puzzle is NP-hard, so it doubles as proof-of-work. The effort demonstrates the verifier engaged with the key material, not just skipped past a hex string comparison.
NONOCRYPT can also encrypt messages where the sender must solve the recipient's puzzle:
from nonocrypt import keygen_from_text, encrypt, decrypt
pk, sk = keygen_from_text("HELLO")
ct, _, solve_time = encrypt(pk, b"secret message") # NP-hard solving
plaintext = decrypt(sk, ct) # instantThis creates an unusual asymmetric cost model useful for anti-spam, rate limiting, and puzzle-based access control.
Hash arbitrary data into a visual nonogram puzzle:
from nonocrypt import nono_hash
row_clues, col_clues = nono_hash(b"data", grid_size=10)Commit to an image without revealing it:
from nonocrypt import commit, open_commitment
commitment, randomness = commit(image)
assert open_commitment(image, randomness, commitment)nonocrypt/
├── nonogram.py Core engine: clue computation, hybrid solver
├── nonocrypt.py KeyFingerprint (primary), encryption (secondary)
├── visual.py Text-to-pixel-art, ASCII rendering, animation
├── demo.py Interactive demo (6 demonstrations)
├── benchmark.py Performance benchmarks
├── analysis.py Viability analysis
├── nonocrypt.h/.c C implementation (standalone, zero dependencies)
├── demo_c.c C demo
├── benchmark_c.c C benchmarks
└── Makefile
The nonogram solver uses three optimizations:
- Leftmost/rightmost overlap -- O(n*k) per line with enumeration fallback for remaining unknowns
- Dirty-line tracking -- only re-solves rows/columns affected by changes
- MRV heuristic -- backtracking picks the most constrained cell first
| Feature | NONOCRYPT | Signal Safety Numbers | SSH Key Art | PGP Fingerprints |
|---|---|---|---|---|
| Format | Picture (nonogram) | Numeric (60 digits) | ASCII art (randomart) | Hex (40 chars) |
| Interactive | Solve a puzzle | Compare numbers | Glance at art | Compare hex |
| Engagement | High (fun) | Low (tedious) | Medium | Low |
| Tamper detection | Visual difference | Digit difference | Art difference | Hex difference |
| Memorable | Yes (images) | No | Somewhat | No |
| Shareable | As puzzle | As text | As text | As text |
Run python3 analysis.py for full empirical analysis.
As primary encryption: not viable (no average-case hardness proof, expensive encryption, unknown security level).
As visual verification layer: viable and compelling. Use alongside established post-quantum schemes (ML-KEM, ML-DSA). The nonogram puzzle gives users an engaging, memorable way to verify keys that's more intuitive than comparing hex strings.
Research prototype. Not peer-reviewed. Use NIST-standardized schemes for production cryptography.