
In our last article, we described how we turned a JPEG XL decoder into an artificial CPU and used it to produce what was, as far as we could determine, the first JXL SHA-256 hashquine image.
This time we did it with HEIC, producing what is, as far as we can determine, the first HEIC SHA-256 hashquine image. HEIC turned out to be a far harder target. Getting there took a deep understanding of the format, a search for surfaces inside the decoder that could host basic computational primitives, and in the end a custom compiler written from scratch rather than a patched off-the-shelf encoder.
The exact 536,870,946-byte file has this SHA-256:
802cc6d654ad15aafbb1ca5db4824292cc39fd72cf23fd80e1230d90679b9ff1
Decode the final image in that same file with stock Apple ImageIO and its pixels spell:
802cc6d654ad15aa fbb1ca5db4824292 cc39fd72cf23fd80 e1230d90679b9ff1
The two values are identical. The visible text is not metadata, an overlay, a filename, or a digest inserted after the fact. The HEVC decoder reconstructs a 564-picture computation. That computation executes the SHA-256 message schedule and all 64 compression rounds, performs feed-forward, converts the 256 digest bits to hexadecimal, evaluates a font ROM, and makes the last picture display the result.
The authoring side is a custom compiler. The playback side is Apple’s unmodified decoder.
| file | sha256_hashquine.heic, 536,870,946 bytes (2^29 + 34) |
| container | HEIF image sequence (msf1 brand), one hvc1 track |
| coding | monochrome (4:0:0) 8-bit HEVC, Range Extensions profile |
| pictures | 564, each 16,384 x 6,976 luma samples |
| prediction | one intra (I) picture, then 563 inter-predicted P pictures |
| output | picture 563 (zero-based) displays the file's SHA-256 in a 3x5 hex font |
Viewing note. Apple ImageIO reports all 564 pictures and correctly decodes picture 563, the final zero-based index shown above. Some photo-library UIs choose picture 0 as an image sequence’s poster frame; that picture is intentionally a mostly gray computation surface. The hashquine claim and verification concern the final decoded picture.
Image decoders are interpreters for small languages
A weird machine appears when a system’s existing state transitions can be interpreted as a programming model its designers did not intend to expose. This need not involve memory corruption. A parser, decompressor, packet engine, or image decoder already accepts a language and updates state according to rules selected by its input. If those rules supply useful logic, storage, and ordering, a file can describe a computation as well as content.
The best-known example is FORCEDENTRY, NSO Group’s zero-click iMessage exploit. In Google Project Zero’s words: “Using over 70,000 segment commands defining logical bit operations, they define a small computer architecture with features such as registers and a full 64-bit adder and comparator.” That machine was built from JBIG2 image operations and bootstrapped by a memory-corruption bug. Ours needs no bug at all.
HEVC intra prediction supplies local data movement. Residuals provide controlled corrections. The raster dependency order supplies causality. PCM supplies an input aperture. Inter prediction supplies a bus between pictures. The final luma samples are the output device.
This is a finite, purpose-built computer. It does not need to be Turing complete to be interesting or to calculate SHA-256. It needs enough storage, Boolean logic, modular addition, routing, and time for this one fixed program: one SHA-256 compression plus glyph rendering is about 150,000 two-input gates.
The distinction matters. “Can this decoder implement an unbounded universal computer?” and “Can one legal file make this decoder evaluate a particular 64-round circuit?” are very different questions. We solved the latter.
HEIC, HEIF, and the decoder surface we used
HEIF, standardized as ISO/IEC 23008-12, is a media container built on the ISO Base Media File Format, the same nested-box structure as an .mp4. “HEIC” is the common filename extension for HEIF content whose pictures use HEVC/H.265 coding. Apple introduced system HEIF support with iOS 11 and macOS High Sierra, explained the image-item and sequence model at WWDC 2017, and has used HEIC as the iPhone’s default photo format ever since. On Apple platforms those files are decoded by ImageIO, which is what we test against.
A HEIF file can hold its pictures in two ways, and the final artifact uses the second:
HEIF still image
HEIF image sequence (our file)
That “any physical order” detail is how the self-reference works, as we’ll see.
HEVC reconstructs pictures from blocks. At the level used by our compiler, a cell is one 8x8 luma coding unit containing four 4x4 prediction units. An intra-coded unit selects a predictor that reads already reconstructed samples to its north, west, or northwest, then applies any coded residual. A P picture may instead use motion compensation to copy samples from an earlier reference picture:
We pin the decoder into an exactly predictable configuration: cu_transquant_bypass so residuals are added losslessly, PCM for raw literals, and the deblocking and SAO in-loop filters disabled. With those settings every output sample is an exact integer function of its references, and our software model of the predictor reproduces the decoder bit for bit. If decoding weren’t exact, “the pixels equal the hash” could never hold.
Those “north,” “west,” and “previous picture” relationships define a physical machine. Within one picture, a block can only read what has already been decoded, so information flows East and South. Moving a value West is possible only one cell per row, and only where the Z-order makes the top-right reference available. Keeping a value alive costs a column for every row it survives. Those two facts, East/South is cheap, West is expensive, and state pays rent, shape everything that follows.
| 8x8 coding unit | configurable processing element |
| directional intra mode | local wire or sample selector |
| transform residual | constant, correction, or gate term |
| north/west availability | causal timing rule |
| raster reconstruction | clock and instruction order |
| PCM coding unit | literal-byte input aperture |
| P-picture motion vector | inter-picture load/store address |
| decoded luma plane | register fabric and display |
| HEIF sample table | program order independent of physical byte order |
The compiler emits the bitstream directly: VPS, SPS, PPS, slices, prediction modes, residual coefficients, motion vectors, PCM blocks, CABAC bins, and finally the HEIF box and sample tables. We did not patch a decoder, and the final artifact does not depend on a custom codec feature. We didn’t patch an encoder either: encoders are built to choose modes that minimize error, and we need to dictate every mode, residual, and motion vector.
A useful benchmark for AI coding systems
This project also became a surprisingly effective benchmark for coding agents and their harnesses. Across our own attempts with GPT-6 Ultra in Codex and Claude Opus 5.5 in Claude Code, agents repeatedly declared the target impossible. Even after being shown successful intermediate artifacts, several returned to the same failed single-picture assumptions.
The difficult part was not recalling the SHA-256 equations. It was changing the abstraction when the evidence invalidated it: from pixels to signal tracks, from tracks to a systolic array, from values to virtual registers, and finally from one picture to an image sequence whose decoder supplies the state transport. That tests hypothesis revision, measurement, and artifact-level verification more than code generation alone.
The PXF clue: let state flow through stages
One useful analogy came from Cisco’s Parallel eXpress Forwarding, or PXF, work. PXF’s network processor, nicknamed “Toaster”, arranged 16 packet-processing CPUs in a 4x4 grid: packets moved across a row as a pipeline while the columns ran in parallel with a shifted phase. Related Cisco patents describe processors built as systolic-array pipelines: each stage owns a register file and functional units, processes a context, and passes that context to the next stage.
The broader architectural idea goes back to H. T. Kung’s classic “Why Systolic Architectures?”: map a computation onto regular local stages and keep data moving through them. That was a much better mental model than pretending we had a normal random-access CPU. It wasn’t quite enough on its own, but it gave us the right questions:
- What is the packet context? Our 128-byte persistent SHA register bank.
- What is a stage? One bounded HEVC P picture.
- What are the bypass paths? Intra-prediction wires and inter-picture motion copies.
- Where do register dependencies live? In the compiler’s slot map and checkpoint object.
- How do we avoid a global crossbar? Place values locally, rename them, and insert bounded repack stages.
Prior FPGA and ROM-exploitation experience helped for the same reason. The job felt less like writing software and more like closing timing and routing on a strange, fixed-function fabric. A mathematically smaller Boolean expression could still be physically worse if it added a long wire, consumed a reference sample, or boxed another route out of the raster.
Why HEIC was much harder than JXL
The JXL construction had unusually generous primitives. Its Meta-Adaptive tree supplied explicit branching, tagged 31-bit values, lookup-like leaves, many state channels, and a strict channel/raster schedule. We could compile large finite mappings into one image and treat optional channels as wide state lanes.
HEVC gave us smaller, more physical pieces:
- reconstructed samples rather than wide tagged words;
- local north, west, and diagonal dependencies rather than arbitrary channel reads;
- fixed predictor geometry rather than a large decision tree;
- a strict no-future-reference rule within a picture;
- practical decoded-dimension limits in Apple ImageIO;
- reference-sample boundary effects that make an isolated primitive behave differently when composed;
- arbitrary byte values that do not survive every long intra-prediction path exactly.
The dimension limit is sharp, and we measured it. We wrapped solid test pictures of increasing size in HEIF with a clap (clean aperture) crop, so the output buffer stays small, and decoded them to memory through Core Graphics/ImageIO:
| 32,768 x 32,768 | 1,073,741,824 = 2^30 |
| 32,768 x 33,792 | 1,107,296,256 |
| 16,384 x 65,536 | 2^30 |
| 8,192 x 131,072 | 2^30 |
A single coded picture may hold at most 2^30 samples, whatever its shape; without a crop, the output buffer fails first, near 2^29. As we’ll see, one flat picture of SHA-256 needs roughly ten times that.
The HEIC compiler therefore had to solve problems recognizable from digital design and backend compiler engineering: cell characterization, logic synthesis, register allocation, liveness, placement, routing, crossing, carry propagation, bank assignment, motion-vector reachability, and physical verification.
The first mental model that held up was a one-dimensional cellular automaton drawn as a two-dimensional space-time diagram. Columns are signal tracks. Rows are time. Every coding unit reads legal north/west/northwest neighbors, applies a local transition, and writes samples once. SHA-256 has fixed rates and data-independent control, so it also fits the static-schedule intuition of synchronous dataflow: there is no runtime branch deciding how many tokens a round consumes.
Three layout laws followed:
- Keep each live bit on a known vertical track for as long as possible.
- Make transport local and obey one uniform causal shear.
- Let carry move through time or through a dedicated local wavefront; do not repeatedly gather entire words across the canvas.
Finding the actual instruction set
We characterized each candidate coding unit by reconstructing all relevant input combinations in a software model, then promoted only the cells that survived composition and Apple decoding.
The Boolean alphabet is deliberately narrow: 128 means zero and 129 means one. Gates come from the only nonlinearity HEVC prediction has, integer rounding and clipping. Take DC mode, which averages the reference samples:
dc = (sum_x T_x + sum_y L_y + N) >> (log2 N + 1)
Give a single 4x4 DC unit north references [128+a, 128+a, 128+b, 128] and west references [128+b, 128+c, 128+c, 128]:
dc = 128 + floor((2(a+b+c) + 4) / 8) = 128 + [a+b+c >= 2] = 128 + Maj(a,b,c)
DC’s rounding is a majority vote. Likewise, vertical mode 26 corrects its first column with pred[0][y] = T_0 + ((L_y - T_NW) >> 1); with T_0 = 129, L_y = 128, T_NW = 128 + a it yields 129 + (-a >> 1) = 128 + (1-a), an exact NOT. The cell library records these finds directly:
// src/cells.hpp // NOT one 4x4 PU, mode 26 (or 10): the zero-angle edge correction // T0 + ((L[y]-TL)>>1) with T0=129, L=128, TL=128+a gives 129-a. // MAJ one 4x4 DC PU: north [a,a,b,128], west [b,c,c,128] -> uniform // 128+Maj(a,b,c) (DC's +4 rounding is the threshold)
One bit register lives at south sample x=3 of an 8x8 coding unit. Horizontal packets use east samples y=4,5,6. That separation produced the first decisive primitive: a real crossover.
// src/point_fabric.hpp
inline hevc::CU hold() { return hevc::nxn_cu({26,26,26,26}); }
inline hevc::CU cross() { return hevc::nxn_cu({ 2,18,34,11}); }
inline hevc::CU read() { return hevc::nxn_cu({27,18,34,34}); }
inline hevc::CU write() { return hevc::nxn_cu({27,27,16, 2}); }
inline hevc::CU gate(bool is_and, bool forward_result=false) {
std::array<int,64> residual{};
residual[31] = 1;
if (is_and) residual[30] = -1;
return hevc::nxn_cu(
{2,18,33,forward_result ? 2 : 11}, residual);
}An earlier exhaustive search had concluded that no single-cell crossover existed. That conclusion silently assumed the two signals had to use the same sample lane. They did not.
With cross, a vertical register survives while an independent horizontal operand passes through the same 8x8 unit. The fabric becomes a small coarse-grained reconfigurable array, rather than a planar collection of mutually blocking wires.
The working primitive set became:
| hold | preserve the vertical bit |
| read | broadcast a stored bit into a horizontal lane |
| cross | carry vertical and horizontal signals through one cell |
| write | deposit a horizontal value into a vertical register |
| gate(and/or) | combine north and west inputs |
| XOR macro | six-row composition built from predictor and residual cells |
| packet lane | move up to three independent horizontal bits |
| point-left | bounded one-row westward register move |
Every primitive was checked at its observable boundary samples, not only at a convenient center pixel. That detail caught several designs that were logically right in isolation and unusable when tiled.
The first complete compiler, and the 11-billion-pixel wall
Once the crossing cell existed, we could compile the complete SHA graph into one picture. The front end built a word-level DAG with these operations:
enum Op { INPUT, CONST, XOR, AND, OR, ADD };
struct Word {
int id;
int rotate = 0; // a view, not copied data
int shift = 0;
};
Word sigma(Word x, int a, int b, int c, bool small=false) {
return gate(XOR,
gate(XOR, rotate(x,a), rotate(x,b)),
small ? shift(x,c) : rotate(x,c));
}The important word in that snippet is “view.” A rotate is a tag attached to a virtual value. The compiler changes the bit-to-track mapping when a consumer reads it; it does not first build a physically rotated copy. This is the same idea as pointer arithmetic or a renamed register view.
Addition used a local ripple-carry adder. For bit j, the compiler forms propagate and generate:
P_j = A_j XOR B_j, G_j = A_j AND B_j
then ripples:
C_{j+1} = G_j OR (P_j AND C_j), S_j = P_j XOR C_jThe fabric’s forward-result gate lets all 32 carry steps occupy one CU row as a horizontal wavefront. Including operand preparation and the XOR macros, one 32-bit add costs about 20 CU rows.
The complete one-picture lowering worked in the reconstruction oracle: one full round composed decode-exact, with all 2,176 probed samples matching software SHA-256, and four chained rounds matched at all 7,808 probes. It was also hopelessly large:
width: 1,040 coding units height: 168,129 coding-unit rows pixels: 8,320 x 1,345,032 = 11.19 billion
Instrumentation showed why.
| global compaction | 82,212 |
| operand copies | 70,432 |
| addition | 11,048 |
| XOR | 4,224 |
| AND / OR | 192 |
Only about nine percent of the height was arithmetic. Ninety-one percent was moving bits.
The allocator behaved like a register allocator with no spill memory. It bump-allocated result blocks toward the east. Dead values left holes. When the frontier reached the edge, compact() shifted every live register west through a long staircase. An operand copy then gathered 32 independently routed bits into a new stride-four block. Peak liveness was roughly 832 bits: the 16-word message-schedule window, eight SHA working words, and temporaries.
We implemented interval-batched copies, first-fit hole reuse, uniform stride-four storage, alternate Ch/Maj forms, and carry-save experiments. They improved individual cases but did not change the basic result. The last measured version still required roughly 9.26 billion pixels, about 8.6x the 2^30 ceiling.
This led to an important compiler lesson: optimize the physical cost model, not the algebraic expression count. Replacing XORs with more AND/OR nodes looked cheaper until each added node paid for another 32-row gather. Carry-save arithmetic did not help while delivery dominated. The placement problem had to be solved first.
Slices, folds, and the halfway-automated routing process
The point fabric naturally suggests bit slices: repeat a small local arrangement for bit positions 0 through 31. Within a slice, A[j], B[j], propagate, generate, and carry tracks can sit next to one another. The same intra-prediction row then computes all 32 independent Boolean gates in parallel. The densest slice we found is a complete full adder in one 2x2 block of cells, with the carry running straight East and the sum dropping straight South:
// src/chainadd.hpp // [ U0 (planar) xor_cell ] west (top row) = 127 + c_in // [ U17 flood2 ] north = 129 + P_j , 129 + G_j // east, all 8 rows = 129 + (G_j | (P_j & c_in)) (carry out, uniform) // south, flood2 col 0 = 128 + (P_j ^ c_in) (sum bit) // Planar with the -1 west level is AND; DC with equal levels is OR.
Composition is where slices bite. A carry that ripples across the full width of the picture through a row of horizontal-copy cells destroys any value travelling South through that row. We fixed that three ways over the course of the project: a diagonal adder whose carry never drives a full-width row, the crossing cell above, and finally an architecture in which state doesn’t have to cross adders at all.
Folds occur when a logical path reaches the physical edge or when a carry/message path needs to re-enter a reusable region. They are expensive because HEVC reference availability is directional. A mathematically valid fold may ask for an upper-right sample across a coding-tree boundary where that sample is not yet available. The folds that work are staircases of shift cells placed only where the Z-order permits; for two-samples-per-bit bus words they are lossless at every distance we tested, and they let us build a bounded-width accumulator that stays 536 CUs wide however many operands it adds.
We handled the chaining problems with a halfway-automated process:
- Search: a primitive search engine enumerates about 107 decoder-proven cells and their small compositions, and classifies every edge sample as
128 + f(a,b,g)for a Booleanf. - Generate a candidate layout from live ranges, typed stage contracts, and preferred track positions; the router automatically inserts a verified format adapter whenever a producer’s port format differs from its consumer’s.
- Use the software reconstruction oracle to inspect every exported sample.
- Emit a small native HEVC control artifact.
- Decode it through FFmpeg and Apple ImageIO.
- Feed exact failures back into the placement constraints, and encode the hard-won rules as assertions, so an illegal layout throws instead of silently decoding wrong.
The compiler tracks occupancy at CU granularity, reference halos at sample granularity, and logical values at word and bit granularity. Its router can move one register, a three-lane packet, or a packed byte. Its allocator knows that a location may be free for Boolean tracks and still unusable because a prediction reference would cross a boundary.
This process closed one full round, then four, then longer schedule fragments. It also made the remaining wall undeniable: even a perfect single-picture placement had too little room for all of the transport and presentation work under Apple’s accepted dimensions.
The breakthrough: the HEVC sequence is the time axis
The solution was to stop insisting that the entire computer live in one picture.
HEIF can contain an HEVC image sequence. HEVC already has a byte-exact transport engine between pictures: inter prediction. So we turned each bounded P picture into one hardware stage and treated motion compensation as a state bus.
One picture imports a 128-byte register bank from its predecessor, performs one schedule, round, feed-forward, plane, glyph, or atlas operation, and exports the updated bank at known sample coordinates. If the next operation cannot reach those coordinates with legal motion vectors, the compiler inserts a repack picture that moves every byte a bounded distance toward its canonical slot.
This is where we finally made the decoder do more work for us. The decoder already contains a highly optimized motion-compensation engine and decoded-picture buffer. Encoding hundreds of hand-built intra-picture copies was wasteful. One ordinary inter-coded unit can ask the decoder to fetch the required sample from the previous picture. Motion vectors are in quarter-sample units, so a byte at column x_src reaches column x_dst with mv_x = 4 * (x_src - x_dst):
// src/byte_program.hpp: importing the bank at the top of a picture
int mvx = 4 * (source_samples[i].x - (8*x + 7));
int mvy = 4 * (source_samples[i].y - 7);
if (mvx < -32768 || mvx > 32767 || mvy < -32768 || mvy > 32767)
throw std::runtime_error("byte resume motion-vector range");
set(x, 0, hevc::inter_cu(mvx, mvy)); // fetch from the previous picture
set(x, 1, pointfabric::main_read());
set(x, 2, byteregister::read());
set(x, 3, byteregister::hold());The source comment states the rule that made the design work: “Keep byte tracks fixed within a picture; frame-to-frame motion performs the only byte-safe repacking.”
The final logical pipeline contains:
| source I picture | 1 |
| initial relay | 1 |
| schedule updates W16..W63 | 48 |
| SHA compression rounds | 64 |
| feed-forward words | 8 |
| digest nibble bit-planes | 8 |
| font rows | 10 |
| atlas rows | 20 |
| final presentation | 1 |
| logical stages | 161 |
Bounded repacking adds 403 transport pictures, producing 564 pictures in total. Every picture is 2,048x872 coding units, or 16,384x6,976 luma pixels: about 1.14 x 10^8 samples, roughly a tenth of Apple’s per-picture limit. The file is large, but no individual decoded picture exceeds the chosen Apple acceptance geometry.
You can see this schedule directly in the file’s sample-size table. The 158 compute stages are the 158 pictures coded at 2.1–3.4 MB; the relay and the 403 repack pictures are the 404 pictures of 100–200 KB; the source I picture (30,790 bytes) and the presentation picture (81,704 bytes) are the two smallest. In the first 16 rounds, three or four transport pictures precede each round; once the message schedule starts, the rhythm becomes four transport pictures, a schedule stage, two transport pictures, and a round.
Here is what one compute stage looks like when decoded: picture 4, the first SHA round, rendered one dot per 8x8 CU. Dark gray is idle (every sample 128), green is active Boolean logic (129), blue and orange are byte lanes carrying values below and above 128, black and white are 0 and 255.

What the compiler actually contains
By the end, “custom compiler” was the only honest description.
1. SHA-256 front end
The front end implements the functions from NIST FIPS 180-4. At round t:
T1 = h + Sigma1(e) + Ch(e,f,g) + K_t + W_t (mod 2^32) T2 = Sigma0(a) + Maj(a,b,c) (mod 2^32) a' = T1 + T2, e' = d + T1
The schedule ring updates in place:
W_t = sigma1(W_{t-2}) + W_{t-7} + sigma0(W_{t-15}) + W_{t-16} (mod 2^32)2. Word IR and rotation views
The word IR contains INPUT, CONST, XOR, AND, OR, and ADD. Rotates and shifts are annotations on edges. Constant folding removes operations before physical lowering. Each small program has an independent evaluator, so the compiler can compare every expected word with the placed circuit.
The actual round builder is compact because the complexity lives in the lowerer:
auto hs = p.add(h, p.sigma(e, 6, 11, 25));
auto ch = p.gate(XOR, g, p.gate(AND, e, p.gate(XOR, f, g)));
auto t1 = p.add(p.add(p.add(hs, ch), p.constant(K[t])), w);
auto ne = p.add(d, t1);
auto maj = p.gate(OR, p.gate(AND,a,b),
p.gate(AND,c,p.gate(OR,a,b)));
auto t2 = p.add(p.sigma(a,2,13,22), maj);
p.outputs = { p.add(t1,t2), ne };Ch is written as g XOR (e AND (f XOR g)) and Maj as (a AND b) OR (c AND (a OR b)): equivalent to the textbook forms, but cheaper on this fabric.
3. Physical lowerer and router
The lowerer expands byte operands into 32 Boolean tracks, allocates temporary panels, places gates and adders, routes operands through crossover cells, packs result bits back into bytes, and records the exact output samples that the next picture must import. It is occupancy aware. Each live bit has a column, each packed byte has a port, and each route reserves its reference halo. Non-overlapping routes share a row by interval partitioning.
4. Persistent registers and virtual renaming
The architectural register file is a 128-byte bank: 32 words. An 8-bit sample can hold a whole byte, so a word needs four cells instead of 32. The byte register cells are small, and each read, cross, and write contract was checked over all 65,536 input pairs:
// src/byte_register.hpp
// Consumes N3; produces S3..S7 and E4..E6 equal to N3.
inline hevc::CU read(){return hevc::nxn_cu({26,10,26,10});}
// Preserves S3=N3 and independently passes W4/W5/W6 to E4/E5/E6.
inline hevc::CU cross(){return hevc::nxn_cu({2,18,34,18});}
// Requires W5=W6=H; writes H to S3/S7.
inline hevc::CU write(){return hevc::nxn_cu({26,26,14,10});}
// Independently preserves N3 at S3 and N7 at S7.
inline hevc::CU hold(){return hevc::nxn_cu({26,26,26,26});}- slots
0..7hold the working state; - slots
8..23hold the 16-word schedule ring; - slots
24..31retain the initial chaining state for feed-forward.
Only two state words are physically overwritten in a round: new a goes into the slot that held h, and new e goes into the slot that held d. The rest of the SHA state shift is pointer renaming, exactly like register renaming in a real CPU:
kernel(program, mapping, {state_slots[7], state_slots[3]});
state_slots = {
state_slots[7], state_slots[0], state_slots[1], state_slots[2],
state_slots[3], state_slots[4], state_slots[5], state_slots[6]
};The schedule works the same way. W[t] occupies slot 8 + (t mod 16). Once its last old use has occurred, W[t+16] overwrites that slot. There is no physical 16-word shift.
5. Bytes to bits and back
Arbitrary bytes can’t enter Boolean gates, so every stage begins by normalizing the bytes it needs into eight 128/129 point tracks (a 12-row panel) and ends by packing computed bits back into bytes (a 59-row panel). The packer does its arithmetic with the decoder’s own predictors. A fixed “gain” cell computes
R(P) = floor((8P + 24*clip(P + floor(P/2), 0, 255) + 16) / 32)
and after subtracting 127, eighteen applications map the levels 1 and 2 to 1 and 255; subtracting one and applying one more step gives exactly 0 and 255. Eight DC-average cells then fold the bits together, least significant first, starting from q = 0:
q <- ceil((q + 255*b_j) / 2) (j = 0..7) => q = ceil((255 * byte) / 256) = byte for every 0 <= byte <= 255
The decoder’s averaging is the bit-packing instruction.
6. Picture scheduler and motion-vector-bounded repacking
The picture scheduler wraps each small program in a Checkpoint. Before a compute picture, it canonicalizes the bank. Its repack planner chooses unique target tracks near canonical columns while keeping every source-to-target displacement within the supported motion-vector range.
for (int i = 0; i < 128; ++i) {
int want = 4 + i;
int lo = std::max(0, source[i] - 511);
int hi = std::min(width - 1, source[i] + 511);
target[i] = nearest_unused(want, lo, hi);
}This is a constrained assignment problem, not a blind slide. The compiler inserts as many bounded pictures as needed for convergence and proves that all 128 destinations are distinct.
7. HEVC and HEIF backend
The backend directly writes monochrome HEVC with 32x32 coding-tree units, 8x8 minimum coding units, NxN prediction units, PCM where needed, and explicit L0 motion vectors in P slices, each referencing only the previous picture. It then writes an HEIF image-sequence container with one sample per chunk and hvc1 sample entries.
The hvc1 detail was empirical and load bearing. A semantically similar hev1 remux was rejected by the Apple path we were targeting; retaining the hvc1 configuration contract made the final sequence acceptable to ImageIO.
The self-reference construction
A hashquine sounds circular: the image must display a digest that depends on the bytes encoding the display. We avoided a cryptographic fixed-point search by using SHA-256’s iterative chaining state. SHA-256 is a Merkle–Damgård construction: the digest depends only on the chaining state after all but the last block (the midstate) and on that last block. It is the same property behind length-extension attacks.
Let the final file be:
F = P || S || FE80
where:
Pis an already fixed prefix of exactly2^29 = 536,870,912bytes;Sis the 32-byte SHA-256 chaining state after hashingP;FE80is two ordinary bytes of the file;- the total file length is
536,870,946bytes.
Because |P| is a multiple of SHA-256’s 64-byte block size, let H_P denote the eight-word chaining state immediately after P. We write:
S = BE32(H_P[0]) || ... || BE32(H_P[7])
An ordinary SHA-256 implementation processes the final 34 file bytes, then appends its own 0x80, zero fill, and 64-bit length. Its last compression block is therefore:
S || FE 80 80 || 21 zero bytes || BE64(8|F|)
The decoder imports S as its initial chaining state and also uses those bytes as message words W0..W7. It hardwires W8 = 0xFE808000, zeroes W9..W13, and places the file bit length in W14..W15. Because the file is larger than 512 MiB, its bit length 8|F| = 4,294,967,568 = 2^32 + 0x110 no longer fits in 32 bits, so W14 = 0x00000001 and W15 = 0x00000110, and the circuit’s length input had to be widened to 64 bits. Both paths execute exactly the same compression and feed-forward:
SHA256(F) = Compress(H_P, S || FE8080 || 21*00 || BE64(8|F|))
The clever container trick is that physical file order and decode order need not match. The finalizer places the P-picture program samples first inside mdat, pads the physical prefix to exactly 2^29 bytes, and stores the source I-picture bytes last. The sample tables still identify that source as logical sample zero, so the decoder visits it first. Hashing follows byte order; decoding follows the sample table.
That means the fixed prefix can contain almost the entire computer before the 32-byte value that initializes it.
The finalizer performs these steps:
- Build the complete sequence using a 32-byte placeholder.
- Reorder the physical sample payloads while preserving logical sample order in
stco/stsc/stsz. - Fill the prefix to exactly
2^29bytes. - Stream SHA-256 over that prefix and extract its chaining state.
- Check that substituting the state does not create a forbidden HEVC emulation-prevention pattern at the replacement boundary.
- Append the 32-byte state and
FE80. - Hash the complete file independently.
No digest search is involved. Once the prefix is fixed, the suffix follows deterministically. In the delivered file the I picture’s sample starts at byte offset 536,840,156 and ends exactly at end of file, and its tail is the prefix midstate:
prefix 536870912 bytes, tail 34 bytes midstate : 3f4afacaa0d8b13afeffe5e47d93eb0b582721cf29ee35a5337f95162009a22b tail[0:32]: 3f4afacaa0d8b13afeffe5e47d93eb0b582721cf29ee35a5337f95162009a22b MATCH: file ends with its own prefix midstate
You can even see it: the bottom-right row of luma samples in decoded picture 0 reads 3f 4a fa ca a0 d8 b1 3a ..., the midstate itself.
From eight words to 64 readable glyphs
The output path is its own small compiler.
First, eight digest-plane stages transpose four SHA words at a time. Four resulting 32-bit values hold the four nibble bits for 32 hexadecimal digits. That makes the next stage word parallel: one Boolean operation evaluates the same font pixel for 32 different glyphs.
The font is a compact 3x5 design for 0–9 and a–f. For each of its 15 pixels, the compiler uses a minimized sum-of-products over nibble bits a,b,c,d. For example, the top-middle pixel is lit for every digit except 4 (0100), so it compiles to just a OR ~b OR c OR d. This is exactly the narrow place where an ordered binary decision diagram or read-once selector perspective helps: four input bits select one of sixteen fixed glyphs. Modeling the whole SHA core as an OBDD would explode; modeling a four-bit font ROM is ideal.
for (int column = 0; column < 3; ++column) {
int pixel = 3 * font_row + column;
Word sum = p.constant(0);
for (const char* term : minimized_terms[pixel]) {
Word product = p.constant(~0u);
for (int k = 0; k < 4; ++k)
if (term[k] != '-')
product = p.gate(AND, product,
is_lower(term[k]) ? nibble[k] : not_nibble[k]);
sum = p.gate(OR, sum, product);
}
p.outputs.push_back(sum);
}The compiler writes the result into a 128x48-CU glyph atlas. Atlas rows are committed bottom to top so a later route never passes through an already finalized row.
One late decoder discrepancy forced the last architectural change. Long intra-prediction paths preserved the 128/129 Boolean alphabet but did not preserve every arbitrary byte exactly under Apple ImageIO. The software reconstruction model did. Instead of fighting that behavior, the atlas stage uses inter prediction to copy the twelve arbitrary bytes it needs from the preceding picture into three disjoint normalization panels. A fast normalizer expands them to bit tracks; a gain stage maps 128/129 to exact black/white 0/255 samples. (A still image could look glyphs up through an ICC color profile acting as a ROM, but ImageIO ignores track ICC profiles on HEIF image sequences, so here the font is computed.)
The presentation picture then lets the decoder enlarge the atlas. Each source font pixel gets one independently addressed motion-copy seed. HEVC horizontal intra mode 10 fills the rest of the first row; vertical mode 26 fills the remaining rows. One computed atlas pixel becomes a 128x128 visible square almost entirely through decoder-native prediction.
That is the final form of the principle that unlocked the project: if the decoder already has a correct, cheap transport or replication primitive, compile to it instead of rebuilding it from smaller gates.
Verification
We verified the artifact from both directions.
The byte-level side:
$ shasum -a 256 sha256_hashquine.heic 802cc6d654ad15aafbb1ca5db4824292cc39fd72cf23fd80e1230d90679b9ff1
The decoder side used Apple ImageIO on the final artifact:
file bytes: 536,870,946
ImageIO picture count: 564
final picture index: 563
decoded dimensions: 16,384 x 6,976
decoded glyphs: 64 of 64
decoded text: 802cc6d654ad15aafbb1ca5db4824292
cc39fd72cf23fd80e1230d90679b9ff1
cell mismatches: 0The verifier samples the center of every 3x5 glyph cell, converts the observed bitmap back through the same fixed font table, and compares all 64 characters with the independently hashed file. The final picture’s samples are exactly 0, 128, and 255, so the read-back is unambiguous, and the comparison is exact. The container side was checked independently: the file’s last 34 bytes are the midstate of its first 2^29 bytes.
We also used several layers of differential testing during compilation:
- word-IR evaluation against an independent SHA-256 reference;
- exhaustive truth tables for each primitive;
- boundary-sample checks for every coding unit;
- full-picture software reconstruction probes;
- FFmpeg decode controls;
- Apple ImageIO decode controls;
- final glyph OCR against the physical file digest.
The software model remained useful, but Apple’s decoder was the acceptance oracle. Any design on which they disagreed was redesigned around the shipped behavior.
What computer did we build?
The final machine is best described as a finite, spatially and temporally unrolled, decoder-hosted processor:
| program | prediction modes, residuals, motion vectors, and sample tables |
| ALU | AND/OR cells, six-row XOR, ripple-carry word adder |
| registers | 128/129 vertical tracks and packed-byte bank |
| virtual registers | Word rotation/shift views and state_slots |
| register allocator | liveness and occupancy maps in Lowerer |
| router | read/cross/write cells and three-lane packets |
| memory/bus | inter-picture motion compensation |
| clock | raster order within a picture; POC across pictures |
| control | compile-time fixed SHA schedule |
| ROM | SHA constants and minimized hexadecimal font functions |
| input | PCM source aperture near the physical end of the file |
| output | final monochrome luma picture |
It resembles a CGRA, a systolic array, and a cellular automaton depending on which level one examines. At the bit level it is a local CA. At the word level it is a routed dataflow machine. At the picture level it is a pipeline of state-transforming stages. At the container level it is a self-referential program whose physical and logical orders have been deliberately separated.
Those are not competing metaphors. They are views of the same machine at different compiler layers.
Security implications
This artifact is not a vulnerability, does not corrupt memory, and does not by itself escape a sandbox. It computes a fixed SHA-256 continuation and draws pixels using valid decoder operations.
It does demonstrate why decoder semantics deserve attention beyond conventional parser bugs. The FORCEDENTRY analysis showed the security relevance of composing standardized image operations into a logic-gate machine. A semantic computer can perform deterministic work before, after, or alongside a memory-safety flaw. In principle, that work could help an attacker transform embedded data, choose among precomputed strategies, regularize timing, or create more predictable decoder state without executing injected native code.
That possibility does not mean a format-level machine “defeats” modern mitigations. Arm MTE and Apple’s Memory Integrity Enforcement still protect memory accesses and object lifetimes in the domains where they apply. A standards-conforming circuit has no magical authority to read arbitrary memory or forge a tag. Its relevance is compositional: if a separate bug provides an unsafe transition, a rich semantic machine may reduce the amount of fragile post-corruption work needed to reach or stabilize that transition.
Several properties of this particular construction are worth flagging on their own:
- Image sequences are a quieter surface. Everything here lives in inter prediction and the HEIF sequence track boxes: paths that “image” handling reaches, but that receive far less scrutiny than the still-image path.
- What you see depends on which picture a viewer shows. Picture 0 of this file is a gray field; picture 563 carries the payload. Thumbnailers, content scanners, perceptual hashes, and moderation pipelines that look only at a poster frame can see something entirely different from a viewer that scrubs the sequence.
- Per-picture limits don’t bound total work. 564 pictures of 1.14 x 10^8 samples is about 6.4 x 10^10 decoded samples from one 512 MiB file.
- Provenance assumes inert data. Media that can evaluate a chosen circuit over its own bytes complicates schemes, from C2PA manifests to forensic deduplication, that treat an image as passive content.
The most useful question is not merely “is each operation safe?” It is “what computations become possible when thousands of safe operations are composed under attacker-controlled dataflow?”
Closing the loop
The final solution did not come from discovering one magical HEVC mode. It came from using the right abstraction at each failure:
- orthogonal sample lanes turned an apparent planar wall into a crossover;
- rotation tags turned bit movement into pointer-like views;
- schedule and state maps turned physical shifts into register renaming;
- instrumentation revealed that movement, not SHA arithmetic, consumed the canvas;
- image sequences turned pictures into time steps;
- motion compensation became a byte-safe state bus;
- sample-table indirection separated physical hash order from decoder execution order;
- decoder-native replication scaled a tiny computed atlas into readable text.
The result is a legal HEIC image sequence containing a specialized computer. Stock Apple ImageIO reconstructs that computer, runs its fixed SHA-256 program, and produces the file’s own digest as visible pixels.
Get the artifact and source
The 536,870,946-byte hashquine, the custom compiler, the verification tools, and the experiments behind every measurement in this post.