Skip to content

Repository files navigation

NONOCRYPT

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!

The idea

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:

  1. Any public key (ML-KEM, RSA, Ed25519, etc.) is hashed into a binary image
  2. The image's nonogram clues become a shareable puzzle
  3. To verify a key, solve the puzzle and compare the revealed image
  4. 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.

Quick start

# Interactive demo
python3 demo.py
# C version
make && ./nonocrypt_demo "HELLO""test message"

Python API

fromnonocryptimportKeyFingerprint, compare_fingerprints# Generate a visual fingerprint from any key bytesfp=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 fingerprintsfp_alice=KeyFingerprint.from_bytes(alice_pk)
fp_bob=KeyFingerprint.from_bytes(alice_pk) # same keyprint(fp_alice.matches(fp_bob)) # Trueprint(compare_fingerprints(fp_alice, fp_bob)) # visual side-by-side# Detect tampering: even 1 bit change = completely different imagetampered=bytearray(alice_pk)
tampered[0] ^=1fp_evil=KeyFingerprint.from_bytes(bytes(tampered))
print(fp_alice.matches(fp_evil)) # Falseprint(fp_alice.similarity(fp_evil)) # ~50% (random chance)# Interactive verification: solve the puzzle to see the imagesolved_image=fp.verify_by_solving()
# Text-based key identities (pixel art)fp=KeyFingerprint.from_text("ALICE")
print(fp.as_image())

Verification ceremony

 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.

Additional features

Nonogram encryption (proof-of-work model)

NONOCRYPT can also encrypt messages where the sender must solve the recipient's puzzle:

fromnonocryptimportkeygen_from_text, encrypt, decryptpk, sk=keygen_from_text("HELLO")
ct, _, solve_time=encrypt(pk, b"secret message") # NP-hard solvingplaintext=decrypt(sk, ct) # instant

This creates an unusual asymmetric cost model useful for anti-spam, rate limiting, and puzzle-based access control.

Nonogram hash

Hash arbitrary data into a visual nonogram puzzle:

fromnonocryptimportnono_hashrow_clues, col_clues=nono_hash(b"data", grid_size=10)

Commitment scheme

Commit to an image without revealing it:

fromnonocryptimportcommit, open_commitmentcommitment, randomness=commit(image)
assertopen_commitment(image, randomness, commitment)

Architecture

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

Solver

The nonogram solver uses three optimizations:

  1. Leftmost/rightmost overlap -- O(n*k) per line with enumeration fallback for remaining unknowns
  2. Dirty-line tracking -- only re-solves rows/columns affected by changes
  3. MRV heuristic -- backtracking picks the most constrained cell first

How it compares

FeatureNONOCRYPTSignal Safety NumbersSSH Key ArtPGP Fingerprints
FormatPicture (nonogram)Numeric (60 digits)ASCII art (randomart)Hex (40 chars)
InteractiveSolve a puzzleCompare numbersGlance at artCompare hex
EngagementHigh (fun)Low (tedious)MediumLow
Tamper detectionVisual differenceDigit differenceArt differenceHex difference
MemorableYes (images)NoSomewhatNo
ShareableAs puzzleAs textAs textAs text

Viability

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.

Disclaimer

Research prototype. Not peer-reviewed. Use NIST-standardized schemes for production cryptography.

About

encryption via nonogram

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages