The Secret Maze

A visual journey through the art of hiding messages in perfect mazes

Scroll to begin

1: Hidden in Plain Sight

Imagine you need to send a secret message to a friend. In a world of surveillance and interception, how can you hide your communication in something that looks completely innocent?

Welcome to the world of steganography: the art of hiding messages in plain sight. In contrast to cryptography, which makes messages undecipherable, steganography makes them invisible.

Today, we'll explore how we can hide binary messages inside the structure of mazes.

We'll start by discussing some foundational material, quickly touch upon a paper this work was inspired by, then go into an explanation of the research behind this work.

💡 Why Mazes?

Mazes are perfect for steganography because:

  • They look innocent -- it's just a puzzle or game, right?
  • Small changes are hard to notice
  • They have mathematical properties we can exploit

2: Creating the Perfect Maze

Before we can hide messages, we need to create a maze to hide it in. We'll stick to perfect mazes A perfect maze is a spanning tree - a graph where every cell connects to every other cell by exactly one path, with no loops or isolated areas. for now.

Click to generate a maze

We use the recursive backtracker algorithm, which works like an explorer carving passages through solid rock. It keeps going forward until it hits a dead end, then backtracks to find unexplored areas. You can actually use whichever algorithm you like here (there are a lot!) - for simplicity we've kept it to just one.

3: The Secret Keys

To hide a message, both the sender and receiver need to agree on two key vertices in the maze. These are a shared secret that determines how and where the message will be hidden.

Two secret key vertices are already selected.

Path found! Click button to select new random vertices

The unique path between these vertices forms what we call the hidden tree — a connected subtree of the maze that only the sender and the receiver know about. This is where the magic happens — we'll embed our message along its edges in a way that's invisible to anyone who doesn't know the keys.

🌳 The Hidden Tree

In graph theory terms, the hidden tree is a connected subtree of the maze spanning tree. It's "hidden" because without the key vertices, there's no way to distinguish it from any other path in the maze. Note that the algorithm can support multiple key vertices (not just 2), creating a more complex tree structure for higher security. We've kept it to 2 for simplicity.

4: Finding the Hiding Spots

Not every location along the path can hide information. We need to find special cells called embeddable vertices — these are cells ON the solution path that have neighbors NOT on the path. These off-path neighbors provide the walls we can manipulate to encode our message.

📍 Key Concept: Embeddable Vertices

An embeddable vertex must:

  • Be located on the solution path (the hidden tree)
  • Have one or two neighbors that are NOT on the path
  • These off-path neighbors create "dead ends" we can connect or disconnect

The Original Approach: LLC Algorithm

The LLC (Lee, Lee, Chen) algorithm, developed by Lee et al., identifies embeddable cells — special vertices along the solution path that have exactly two neighbors not on the path. For each such cell, it can hide one bit by choosing which neighbor to connect to.

Scroll here to see LLC algorithm results...

LLC Characteristics

  • Identifies embeddable cells (vertices on the path with 2 neighbors off the path)
  • Cell-based approach: chooses which of 2 edges to keep
  • Embeds 1 bit per embeddable cell
  • Sample capacity: 0 bits for this maze

LLC works well, but there's room for improvement. Our work, EEDGE, realizes that instead of choosing between two edges, we can make independent decisions for each edge.

The Improved Solution: EEDGE Algorithm

EEDGE (Embeddable EDGE) solves LLC's limitations by working directly with edges, enabling a higher capacity.

Scroll here to see EEDGE algorithm results...

EEDGE Advantages

  • Wall-based approach enables higher capacity embedding
  • 1-embeddable cells: 1 bit (blue)
  • 2-embeddable cells: 2 bits (magenta)
  • Higher capacity: 0 bits

Algorithm Comparison

LLC (Lee et al.):
0 bits
1 bit per embeddable cell
~260 bits in 64×64 maze
EEDGE:
0 bits
✓ 1 bit per embeddable edge
~780 bits in 64×64 maze

Improvement factor varies by properties of the path: 3× for RecursiveBacktracker, 2.4× for Kruskal

🤓 Deep Dive: Algorithm Details & Performance

EEDGE Pseudocode

function FIND-EMBEDDABLE-EDGES(maze, key):
    hiddenTree = SOLVE-MAZE(key[0], key[1])
    embeddableEdges = []
    
    for vertex in hiddenTree:
        neighbors = GET-NEIGHBORS(vertex) - hiddenTree
        if |neighbors| == 1:
            embeddableEdges.add((vertex, neighbors[0]))
        else if |neighbors| == 2:
            embeddableEdges.add((vertex, neighbors[0]))
            embeddableEdges.add((vertex, neighbors[1]))
    
    return embeddableEdges

function EMBED(maze, message, key):
    edges = FIND-EMBEDDABLE-EDGES(maze, key)
    keepEdges = hiddenTree.edges
    discardEdges = []
    
    for i in 0..message.length:
        if message[i] == 1:
            keepEdges.add(edges[i])
        else:
            discardEdges.add(edges[i])
    
    return REGENERATE-MAZE(keepEdges, discardEdges)

Performance Data (64×64 maze, 5 key vertices)

Maze Algorithm LLC Capacity EEDGE Capacity Improvement
RecursiveBacktracker 260 bits 780 bits 3.0×
Kruskal 144 bits 364 bits 2.5×
Prim 109 bits 264 bits 2.4×
HuntAndKill 154 bits 433 bits 2.8×

Note: RecursiveBacktracker produces mazes with longer, windier paths, resulting in more embeddable vertices and higher capacity for both algorithms.

5: Hiding the Message

Using the EEDGE algorithm, we encode our binary message by choosing which walls to keep or remove.

Capacity: 0 bits

Enter a binary message to embed

Encoding Rules:

  • 1 = Keep wall (shown in red)
  • 0 = Remove wall (shown in green)

✨ Error-Free by Design

Unlike image steganography which may suffer from compression artifacts and bit errors, maze steganography is perfectly discrete. A wall either exists or it doesn't. This guarantees accurate message recovery.

6: Making It Look Natural

We have our embedded walls, but it's not a maze if it just has isolated walls. We use Kruskal's algorithm to regenerate a valid maze that preserves our hidden message.

Click to create the steganographic maze

The algorithm adds random walls while ensuring:

  • The maze remains valid (no loops)
  • Our embedded walls stay exactly as needed
  • The result looks similar to any other random maze

🔧 Why Kruskal's Algorithm?

Kruskal's algorithm is perfect for regeneration because it needs no algorithmic changes to support our needs, simplifying the correctness proof. It processes edges in random order to build a minimum spanning tree, keeping a set of edges that were visited and should not be added. EEDGE simply changes some of the state:

  • Keep edges: Forced to be included by adding them first
  • Discard edges: Removed from the candidate list entirely
  • Other edges: Added randomly as Kruskal's normally would

This ensures our message is preserved while the maze looks (almost) naturally random.

7: Revealing the Secret

The receiver uses the same key vertices to find the solution path, identify the embeddable locations, and read the message from the wall patterns.

Click to extract the hidden message

The key insight here is that without knowing the exact key vertices, finding the message is computationally difficult. The number of possible key combinations is quite large.

🔐 Security Analysis

For a maze of size w × h with k key vertices, the number of possible combinations is:

N = Σi=2k C(w×h, i) × i!

For a 64×64 maze with 4 key vertices, this equals approximately 2.8 × 1014 possible combinations.

8: The Complete Picture

You've now experienced the full journey of maze steganography — from generating a perfect maze to extracting a hidden message. But this interactive demo only scratches the surface of the research.

🔍 What We've Seen So Far

  • Perfect mazes are mathematical structures with unique properties
  • Strategic locations along paths can hide information
  • We can increase capacity by focusing on walls, not cells
  • Security comes from the astronomical number of possible keys

Ready to Go Deeper?

Continue scrolling to discover:

  • The human story behind the EEDGE algorithm
  • Key insights from the paper
  • Closing thoughts: reflections on the paper, this blogpost, and AI in general

Let's peek behind the curtain...

9: Behind the Paper

Every algorithm has a human story. EEDGE began in the Computer Science Labs at LUMS, and was further developed one thanksgiving break in Mountain View, California.

In 2009, Lee et al. published a clever technique for hiding messages in mazes. Their approach used special cells along the solution path — it worked, but but it was not optimal. In early 2010, I was a starry eyed college freshman, doing my CS 100 project. I wanted to show off my coding skills, so for my project I built an interactive maze generator + solver in MATLAB. But that was boring. So I found a way to hide messages in the maze, implementing this algorithm. It was cool! I had a ton of fun.


Fast forward 3 years. I'm in California, interning at SRI, and bored over a holiday break. I've always wanted to learn Haskell, and I decided to give it a shot. I generally like to pick up new languages by implementing projects I've already built (to minimize the complexity, and focus on learning differences). So I go to town. The LLC algorithm was fairly imperative -- and required some translation to work correctly in idiomatic Haskell. But I got it working, and the code was pretty clean when stripped to its core essentials. Haskell has that elegance to it -- I don't want to sound like a Haskell fanboy (the code is hard to read now, 10 years later -- stupid past Hasnain trying to be too smart) -- but just the forcing function of having to write code totally differently made me think about the problem in a different way.

💡 The Key Insight

It was actually pretty simple. Huh. What if I change this 1 to a 2?

Like that was it. Here's an excerpt from the code:

getEmbeddableCells :: EncryptionAlgorithm -> Int -> Int -> [[Cell]] -> [(Cell, [Wall])]
getEmbeddableCells encalgo w h solutions = embeddable
    where
    allCells = concat solutions
    exceptKeys = allCells L.\\ (nub $ fmap head solutions ++ fmap last solutions)
    cells = H.fromList allCells
    predicate = if encalgo == LLC then (== 2) else (> 0)
    list = [ (c, nn) 
                | c <- exceptKeys, let n = neighbours w h c,
                  let nn = [k | k <- n, not (k `H.member` cells)],
                  predicate (length nn)]
    embeddable = sort $ snd $ L.foldl (helper encalgo w h) (H.fromList [], []) list

There really was no reason the code was looking for just 1 neighbor, algorithmically, when formulated this way. I just changed the code in a few more places to have such a check, and ... EEDGE was born. In graph theory terms, I moved from a vertex-based approach to an edge-based one. It sounds simple, but the results speak for themselves: EEDGE could hide 2-3 times more data in the same maze, with the exact improvement depending on the maze generation algorithm used.


I decided to take this and ask my professor if I could get an independent study, because I was lazy and didn't want to take an extra course in my final semester. Senioritis is real. And.. felt like there was enough for a paper here. My gambit worked, kinda. We did get a paper submission out that semester (but it got rejected) and I had to refine the paper and resubmit the following year. Having a good algorithm wasn't enough. I needed to come up with enough material to get a coherent paper. Beyond that, I also needed to come up with a good story, some real world applications, and some results based on that (from surveys). Enter online rogue-like games — games like TOMENET where players explore randomly generated maze-like dungeons. Here was a perfect cover: a legitimate reason to transmit maze data over the internet, happening thousands of times every day. Did I mention I'm a fan of roguelikes? I went to the first few roguelike celebrations in person. They were great.


Aside: Sometimes the best ideas really do come from looking at things differently, or "stupid" shower thoughts. Listen to your heart! Try things out! I'm also personally kinda proud of the way this happened - a publication as an undergrad is nothing to sneeze at -- though I will admit it's not the most impactful publication nor something I like to stand by today (some of the content makes me cringe). It is what it is.

Now that we understand the human story, let's dive deeper into the paper for a bit...

10: Key Insights and Downsides

There were a few key things that were cool in this paper that I'd like to highlight. As well as some things, that, well, I'm not sure how to feel about in hindsight.

1. The simplicity of the idea

Once we frame the problem in terms of graphs, the extension becomes pretty obvious: instead of restricting ourselves to vertices, we can use edges and ... get much higher bang for our buck.

2. Extensibility

A lot of the prior research (at the time! I'm going by memory here, please do not murder me if I get some facts wrong) focused on picking a single algorithm, doing a detailed extension of it, and then some non-trivial proofs to prove things still worked. With the framing here, we are able to extend the steganography to a lot more maze generation algorithms. It became easier to do this when we were able to express this in terms of graphs. There's a much simpler way to prove correctness if your algorithm basically is "We use algorithm X, with just this initial state ..."

📊 Performance & Implementation

The entire EEDGE system was implemented in under 1,000 lines of code, including:

  • 7 maze generation algorithms
  • 4 regeneration algorithms
  • 3 solution algorithms
  • 2 complete stegosystems (LLC and EEDGE)

On the machines of its time, it was able to encode 21,000 bits per second, with most of the time spent on "standard" operations like maze generation and pathfinding.

Downsides

There's a *lot* to dislike about the paper too, to be fair. In particular a few things I'd like to call out:

  • The treatment of steganography is a bit naive. Yes, there's important practical applications. But also... there's harm to just willy nilly enabling it that wasn't called out
  • The security argument ... oof. Please slap me if I try to make a a similar case / "proof" of security in the future. With what I know now, I probably wouldn't make such a strong argument.
  • The setting could be motivated a bit more clearly, right now it's a bit awkward
  • Last, but not least -- the writeup is a bit inscrutable as it's clear the author (me) was trying to sound smart to make a simple idea sound "hard" and justify a publication.
However, I still stand by the fact that it's cool that I was able to do this as an undergrad.

With that said, let's dive into some closing thoughts, both about the paper and this blogpost itself.

11: Closing Thoughts

Every good research paper or blog should end with more questions than it answers. What's that in this context?

Unsolved Challenges

The paper tested EEDGE against multiple detection methods:

🔍 Classification Results

  • Graph Classifiers: SubdueCL and GAIA found no distinguishing features
  • gBoost: Performed worse than random guessing (45% accuracy)
  • User Study: 31 participants averaged only 1.54 correct identifications out of 10 (statistically equivalent to random guessing)

These results demonstrate that EEDGE-generated mazes are visually and algorithmically indistinguishable from regular mazes. But what about more sophisticated future analysis?

  • Advanced steganalysis: Could machine learning models trained specifically on EEDGE-generated mazes detect patterns?
  • Optimal key selection: Are some key vertex pairs better than others? The paper used random selection, but perhaps strategic choices could increase capacity.
  • 3D and beyond: What about hiding data in 3D mazes, or other graph structures entirely? Or fully leaning into roguelike design and generating full on levels>

I'd probably love to explore those questions more over time, as they are definitely interesting. Targeted techniques have often broken steganalysis (and that wasn't attempted in this paper)

🤔 Reflections on AI

Now for ... the hidden motivation behind this blogpost. I've always wanted to write an interactive blogpost. I just have never known how, and I've been so lazy. But now, thanks to the magic of AI agents, this blogpost exists. The cost of custom software is so low now, and it's just about ideas. We're now in an interesting world. The idea is mine. Almost all the words here are mine (aside from some structure/emojis inserted by Claude). But this post would never have happened, it's been on my literal backlog for over a decade. What's holding you back from trying new ideas?


Yes, this is vibe coded. I'm aware of at least 3 bugs here. But that's *okay*! The point gets across and that's what matters here. The fact that this exists was enough to make me go "holy shit".

📚 Dive Deeper

Credits & Acknowledgments

Inspiration

This interactive exploration was inspired by the beautiful tradition of explorable explanations and interactive educational content, particularly:

  • Bartosz Ciechanowski's stunning visual essays on physics and engineering
  • Julia Evans' incredibly helpful zines
  • The literate programming movement pioneered by Donald Knuth

Interactive Maze Generation

Special thanks to the maze generation algorithm community:

Thank you to all the researchers and educators who make complex ideas accessible through interactive content. Your work inspires the next generation of learners and creators.

Scroll to begin the journey