<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/">
  <channel>
    <title>Magical Share blog</title>
    <link>https://apps.zincirx.com/magicalshare/blog/</link>
    <atom:link href="https://apps.zincirx.com/magicalshare/blog/feed.xml" rel="self" type="application/rss+xml"/>
    <description>News and guides from Magical Share, the app that sends files between devices with a screen and a camera, with no internet.</description>
    <language>en</language>
    <lastBuildDate>Wed, 28 Oct 2026 09:00:00 GMT</lastBuildDate>
    <image>
      <url>https://apps.zincirx.com/magicalshare/icon.png</url>
      <title>Magical Share blog</title>
      <link>https://apps.zincirx.com/magicalshare/blog/</link>
    </image>
    <item>
      <title>How we send files over QR codes: a fountain code, a 30x faster encoder and a camera pipeline in Flutter</title>
      <link>https://apps.zincirx.com/magicalshare/blog/qr-code-file-transfer-fountain-codes/</link>
      <guid isPermaLink="true">https://apps.zincirx.com/magicalshare/blog/qr-code-file-transfer-fountain-codes/</guid>
      <pubDate>Wed, 28 Oct 2026 09:00:00 GMT</pubDate>
      <description>How Magical Share sends files from a screen to a camera: an LT fountain code, a fast QR encoder, a Flutter camera pipeline, and end-to-end encryption.</description>
      <category>QR code file transfer</category>
      <category>fountain code</category>
      <category>LT code</category>
      <category>Flutter</category>
      <category>zxing-cpp</category>
      <category>offline file sharing</category>
      <category>end-to-end encryption</category>
      <content:encoded><![CDATA[      <figure>
        <img src="https://apps.zincirx.com/magicalshare/blog/qr-code-file-transfer-fountain-codes/pipeline.jpg" alt="Diagram of a transfer in six steps: the sender builds an envelope, applies a fountain code and shows a QR stream; the receiver's camera reads it, a peeling decoder rebuilds the blocks, and the file is decrypted and checked" width="1600" height="900" decoding="async">
        <figcaption>The whole path, from file to file. Steps 1 to 3 run on the sending screen, 4 to 6 on the receiving phone.</figcaption>
      </figure>

      <p>Magical Share sends files between nearby devices with nothing but light. One screen plays a stream of QR codes, and a phone camera reads them back into the file. There is no Wi-Fi, Bluetooth or internet anywhere in the path. The <a href="https://apps.zincirx.com/magicalshare/blog/send-files-with-light/">product walkthrough</a> shows it from the user's side, and the <a href="https://apps.zincirx.com/magicalshare/web/">web app</a> lets you try sending from a browser.</p>
      <p>This post is about the engineering. A camera pointed at a screen is a noisy, one-way channel, and four problems decide whether it works:</p>
      <ol>
        <li>The receiver misses frames: motion blur, glare, autofocus, a hand that wobbles.</li>
        <li>The sender has to draw about 60 large QR codes a second, including in a browser on a single thread.</li>
        <li>The phone has to decode them at camera speed.</li>
        <li>Anyone in the room can film the screen.</li>
      </ol>
      <p>Everything below is Dart and Flutter, running on Android, iPhone, macOS and the web from one codebase.</p>

      <h2>Why a fountain code</h2>
      <p>The obvious design is a carousel: number the chunks, loop through them, and let the receiver collect what it missed on the next lap. It works, but the tail is slow. Once you have 95% of the chunks, you wait for exactly the missing ones to come around again, and missing one of those costs another full lap.</p>
      <p>A fountain code removes the "exactly". We use a Luby transform (LT) code. The file is split into <em>K</em> source blocks, and every frame carries the XOR of a few of them. Any set of slightly more than <em>K</em> frames rebuilds the file, no matter which ones you caught. The receiver can join halfway through the stream, and a missed frame costs one frame of time, not a lap.</p>
      <p>We chose the design by simulation. A systematic carousel needed 1.43 <em>K</em> frames at 2,000 blocks, even with no loss at all. The LT code needed about 1.15 <em>K</em> frames for a receiver joining mid-stream, whatever the loss rate.</p>
      <p>The degree of each frame, meaning how many blocks it mixes, comes from a robust soliton distribution with c = 0.1 and δ = 0.5. Most frames mix a handful of blocks, a few mix many, and some carry a single block. The receiver runs a peeling decoder. Each single-block frame reveals a block, and XORing that block out of the waiting frames turns some of them into new single-block frames. That repeats until all <em>K</em> blocks are known.</p>

      <h2>Both sides must pick the same blocks</h2>
      <p>A frame doesn't list the blocks it mixed. Its header carries the session id, the sequence number and the degree, and both sides derive the block indices from those three numbers with a small integer-only random number generator:</p>
      <pre><code>List&lt;int&gt; blockIndicesFor({sessionId, seq, k, degree}) {
  if (degree == 1) return [seq % k];
  final rng = Xorshift32(mix32(sessionId ^ mix32(seq)));
  // ...then pick `degree` distinct indices below k
}</code></pre>
      <p>Integer-only matters because the same derivation runs as native code on phones and as JavaScript or WebAssembly in the browser. Floating point appears in one place only: the sender samples the degree from the soliton distribution and writes it into the header, so a rounding difference there can't put the two sides out of step. Golden tests pin the derivation, and changing it would need a new wire version.</p>
      <p>Each frame starts with a 28-byte header:</p>
      <div class="scroll">
        <table>
          <thead><tr><th>Bytes</th><th>Field</th></tr></thead>
          <tbody>
            <tr><td>2</td><td>Magic, <code>MS</code></td></tr>
            <tr><td>1</td><td>Wire version</td></tr>
            <tr><td>1</td><td>Flags</td></tr>
            <tr><td>4</td><td>Session id</td></tr>
            <tr><td>4</td><td>Sequence number</td></tr>
            <tr><td>4</td><td><em>K</em>, the number of source blocks</td></tr>
            <tr><td>2</td><td>Block length</td></tr>
            <tr><td>4</td><td>Envelope length</td></tr>
            <tr><td>2</td><td>Degree</td></tr>
            <tr><td>4</td><td>CRC-32 of the header and payload</td></tr>
          </tbody>
        </table>
      </div>
      <p>The receiver checks the CRC and the header's limits before it allocates anything, so a hostile code can't make it reserve gigabytes.</p>

      <h2>Drawing 60 QR codes a second</h2>
      <p>Each frame goes into a version 40 QR code, the largest there is: 177 × 177 modules holding up to 2,953 bytes at error correction level L. L is the weakest level, and that's fine here. The fountain code already treats a code that fails to decode as one lost frame.</p>
      <p>We also skip the mask search. A normal QR encoder tries all eight masks and scores each one, which is most of the work for a large symbol. Every mask is valid for a decoder, so the stream always uses mask 4. In our experiments a fixed mask decoded as reliably as the search for dense frames.</p>
      <p>With the mask fixed, almost everything about a version 40 symbol is constant: the finder and timing patterns, the order data bits are placed in, which bits the mask flips, and the Reed-Solomon generator polynomials. Our encoder computes those once and caches them. Each new frame only builds its codewords, computes error correction with log tables and drops the bits into place. It produces exactly the same symbols as <code>package:qr</code>, about 30 times faster. That is what makes the web app possible, where the whole stream runs on one browser thread.</p>
      <p>The sender offers three speeds:</p>
      <div class="scroll">
        <table>
          <thead><tr><th>Preset</th><th>Bytes per code</th><th>Codes per screen</th><th>Frames a second</th><th>Ceiling</th></tr></thead>
          <tbody>
            <tr><td>Fast</td><td>2,953</td><td>2</td><td>30</td><td>171 KB/s</td></tr>
            <tr><td>Balanced</td><td>1,465</td><td>2</td><td>30</td><td>84 KB/s</td></tr>
            <tr><td>Robust</td><td>700</td><td>1</td><td>20</td><td>13 KB/s</td></tr>
          </tbody>
        </table>
      </div>
      <p>The ceiling assumes every code is caught. Real transfers catch a fraction of them, which is where the fountain code earns its keep.</p>
      <figure>
        <img src="https://apps.zincirx.com/magicalshare/blog/qr-code-file-transfer-fountain-codes/mac-sending.jpg" alt="The Mac app showing two large QR codes side by side while sending a PDF" width="1600" height="1000" loading="lazy" decoding="async">
        <figcaption>The Mac app sending: two version 40 codes side by side, replaced 30 times a second.</figcaption>
      </figure>

      <h2>Reading at camera speed</h2>
      <p>On the phone, camera frames go to background isolates that decode with zxing-cpp through FFI. We run one worker for every two CPU cores, up to three, so decoding never blocks the camera or the interface. In field tests a frame took 4 to 45 ms to decode, and no frame was dropped for lack of a free worker.</p>
      <p>The surprise was the camera, not the decoder. Our first Android build used the CameraX implementation of Flutter's camera plugin. It fetched each frame's image planes over extra platform-channel round trips and waited for them before delivering the next frame. We asked for 60 fps and got 6 to 12. With a Mac sending to a Galaxy A53, 650 KB took 45 to 50 seconds, about 13 KB/s.</p>
      <p>Switching to <code>camera_android</code>, the Camera2 implementation, fixed it, because it sends each frame in one message. At 720p and 30 fps, the same phone received 1.9 MB in 39.5 seconds: 48 KB/s. It caught only 32% of the frames over the run and still finished. With a carousel, a 32% catch rate would have meant lap after lap.</p>

      <h2>Safe even if someone films the screen</h2>
      <p>A screen is a broadcast medium, so the file is sealed before it becomes codes. The envelope holds the file name, type, size and SHA-256 hash. The body is compressed with Deflate when that makes it smaller, and then encrypted.</p>
      <ul>
        <li><strong>Key mode.</strong> The receiving phone makes a fresh X25519 key pair every time its Receive screen opens, and shows the public key as a QR code. The sender scans it, derives a key with HKDF-SHA256 and encrypts with ChaCha20-Poly1305. Both screens show an eight-digit check number taken from the SHA-256 of the public key, so two people can confirm they locked onto the same phone. Receive keys are never stored.</li>
        <li><strong>Passphrase mode.</strong> For when scanning isn't practical. The key comes from Argon2id with 19 MiB of memory and 2 iterations, the OWASP minimum. The cost settings travel in the envelope, and the receiver refuses settings outside safe limits, so a hostile sender can't make it burn gigabytes of memory.</li>
        <li><strong>Open mode.</strong> No encryption, with a warning before sending.</li>
      </ul>
      <figure>
        <img src="https://apps.zincirx.com/magicalshare/blog/qr-code-file-transfer-fountain-codes/phone-send.jpg" alt="Three phone screens: the receiver's one-time key with check number 7284 2112, the Send screen locked to a receiver, and the stream of QR codes" width="1600" height="1123" loading="lazy" decoding="async">
        <figcaption>Key mode: the receiver's one-time key, the sender locked to it, and the stream.</figcaption>
      </figure>
      <p>The envelope's protection header is authenticated as associated data, so it can't be swapped. The receiver refuses compression bombs and cleans file names so they can't escape the save folder. After decrypting, it checks the SHA-256 of the whole file before showing it.</p>

      <h2>Testing a channel made of light</h2>
      <p>Most of this is testable without a camera:</p>
      <ul>
        <li>The codec, crypto and fountain tests run as plain Dart.</li>
        <li>A pipeline test renders the sender's stream to pixels, decodes the codes from those pixels with an independent decoder, and rebuilds the file.</li>
        <li>Golden tests pin the block-index derivation, so a harmless-looking refactor can't break compatibility with apps already installed.</li>
      </ul>
      <p>Field tests cover the rest: real screens, real cameras and real hands. While it works, the receiver shows the camera frame rate, decode time, codes per second, the share of frames caught, and the speed. That is how we found the CameraX bottleneck.</p>

      <h2>No internet, really</h2>
      <p>The Android release build doesn't declare the <code>INTERNET</code> permission, so the app cannot open a network socket at all. We also strip the network-state, audio and storage permissions that plugins add. The Mac build is sandboxed with no network entitlement. Google Play Billing still works, because the purchase runs inside the Play Store app; we confirmed it with a test purchase on a release build.</p>

      <h2>If you build one</h2>
      <ul>
        <li>Use a fountain code from day one. It makes every other problem cheaper.</li>
        <li>Measure the camera's frame rate before you optimize the decoder. Ours was the bottleneck by a factor of three.</li>
        <li>Keep everything both sides compute in integers, and pin it with golden tests.</li>
        <li>Expect 50 to 100 KB/s with ordinary phones. That is right for documents, photos, keys and boarding passes, not for videos.</li>
      </ul>
      <p>You can try the sender in your browser at <a href="https://apps.zincirx.com/magicalshare/web/">apps.zincirx.com/magicalshare/web</a> and catch the file with <a href="https://play.google.com/store/apps/details?id=com.zincirx.magicalshare">Magical Share for Android</a>. Questions about the design are welcome at <a href="mailto:info@zincirx.com">info@zincirx.com</a>.</p>
]]></content:encoded>
    </item>
    <item>
      <title>Send files with light: sharing between devices with no internet at all</title>
      <link>https://apps.zincirx.com/magicalshare/blog/send-files-with-light/</link>
      <guid isPermaLink="true">https://apps.zincirx.com/magicalshare/blog/send-files-with-light/</guid>
      <pubDate>Wed, 28 Oct 2026 09:00:00 GMT</pubDate>
      <description>Send files between phones and computers with no internet, Wi-Fi or Bluetooth. Magical Share uses QR codes and your camera, end-to-end encrypted.</description>
      <category>offline file sharing</category>
      <category>send files without internet</category>
      <category>QR code file transfer</category>
      <category>end-to-end encryption</category>
      <category>iPhone to Android</category>
      <category>privacy</category>
      <content:encoded><![CDATA[      <figure>
        <img src="https://apps.zincirx.com/magicalshare/blog/send-files-with-light/hero.jpg" alt="Three phones running Magical Share: the home screen, a stream of QR codes, and the list of received files" width="1600" height="686" decoding="async">
        <figcaption>Magical Share sends a file as a stream of QR codes. The other device catches it with its camera.</figcaption>
      </figure>

      <h2>The two-foot problem</h2>
      <p>Your phone and your laptop sit two feet apart on the same desk. Yet moving one photo between them usually means a round trip through a server on another continent.</p>
      <p>We email files to ourselves. We message them to our own chats. We upload a PDF to a cloud drive just to download it again a minute later. Each of those copies lands on someone else's computer, tied to an account, and often stays there long after we forget about it.</p>
      <p>And when there is no Wi-Fi at all, on a plane, in a basement office, or on a locked-down work network, most of those tricks stop working entirely.</p>
      <p>We wanted something simpler: point one screen at another, and the file just moves. No internet, no account, no copy left behind anywhere. That idea became <strong>Magical Share</strong>.</p>

      <h2>Meet Magical Share</h2>
      <p>Magical Share moves files between nearby devices using light. The sending device shows a stream of moving codes on its screen. The receiving phone reads them with its camera and rebuilds the file.</p>
      <p>Nothing goes over the internet, Wi-Fi or Bluetooth. There is nothing to pair and no account to create. It works between an iPhone and an Android phone, from a computer to a phone, and anywhere there is no signal.</p>
      <p>It comes in three forms:</p>
      <ul>
        <li><strong>A free web app</strong> that sends from any computer's browser, at <a href="https://apps.zincirx.com/magicalshare/web/">apps.zincirx.com/magicalshare/web</a>.</li>
        <li><strong>An Android app</strong> that sends and receives, on <a href="https://play.google.com/store/apps/details?id=com.zincirx.magicalshare">Google Play</a>.</li>
        <li><strong>A Mac app</strong> that sends, on the <a href="https://apps.apple.com/app/id6821344244">Mac App Store</a>. The iPhone and iPad version is on its way.</li>
      </ul>

      <h2>How it works, in plain English</h2>
      <p>Every transfer goes through three steps.</p>
      <ol>
        <li><strong>Packed and sealed.</strong> The app wraps the file with its name, size and a SHA-256 fingerprint. It can compress the file and encrypt it.</li>
        <li><strong>Turned into a fountain of codes.</strong> The file is split into blocks, and each frame mixes a few blocks together. The screen shows two large QR codes at a time, 30 times a second.</li>
        <li><strong>Caught and checked.</strong> The receiver decodes camera frames in the background and works out the original blocks. Once it has them all, it checks every byte against the fingerprint before showing the file.</li>
      </ol>
      <p>The clever part is the fountain code. Think of filling a glass under a fountain: it doesn't matter which drops you catch, only that you catch enough. The receiver can start scanning at any moment, and a missed or blurry frame costs only a fraction of a second.</p>
      <p>In simulation, a receiver joining mid-stream needed only about 15% more frames than the minimum, however many frames it missed along the way.</p>

      <h2>Locked to one phone, even if someone films your screen</h2>
      <p>A screen full of codes is visible to anyone in the room. So Magical Share encrypts the file before it ever reaches the screen. You choose one of three protection modes:</p>
      <ul>
        <li><strong>Key.</strong> The receiving phone shows a code holding a fresh public key. The sender scans it, and from then on only that phone can open the file. Both screens show the same eight-digit number, so you can confirm you locked onto the right phone.</li>
        <li><strong>Passphrase.</strong> Say a word out loud instead of scanning a code. The encryption key is derived from it with Argon2id.</li>
        <li><strong>Open.</strong> No protection, for things like a public flyer. The app warns you before sending.</li>
      </ul>
      <p>Under the hood, Key mode uses X25519 key agreement, HKDF-SHA256 and ChaCha20-Poly1305. Receive keys are created fresh every time you open the Receive screen, and they are never stored.</p>

      <h2>Send from any browser: the web app</h2>
      <p>The quickest way to try Magical Share is the web app at <a href="https://apps.zincirx.com/magicalshare/web/">apps.zincirx.com/magicalshare/web</a>. It runs in a modern browser on any computer, and sending from it is free.</p>
      <figure>
        <img src="https://apps.zincirx.com/magicalshare/blog/send-files-with-light/web-app.jpg" alt="The Magical Share web app with a Choose a file to send button and links to the App Store and Google Play" width="1600" height="1000" loading="lazy" decoding="async">
        <figcaption>Magical Share on the web. The file is never uploaded: the page turns it into codes right in your browser.</figcaption>
      </figure>
      <p>How to use it:</p>
      <ol>
        <li>Open <a href="https://apps.zincirx.com/magicalshare/web/">apps.zincirx.com/magicalshare/web</a> on your laptop or desktop.</li>
        <li>Click <strong>Choose a file to send</strong>, and set a passphrase so only someone who knows it can open the file.</li>
        <li>On your phone, open Magical Share and tap <strong>Receive</strong>, then <strong>Start scanning</strong>.</li>
        <li>Point the phone's camera at the computer screen. Make the codes big and bright. Missed codes don't matter.</li>
      </ol>
      <p>Nothing is uploaded. The page turns the file into codes right in your browser, so the file never leaves your computer. Opening a file sent from the web uses Magical Share Pro or its free 7-day trial on the phone.</p>

      <h2>Send and receive on your phone</h2>
      <p>The phone app does both jobs. Get it on <a href="https://play.google.com/store/apps/details?id=com.zincirx.magicalshare">Google Play</a> for Android. The iPhone version is coming to the App Store.</p>
      <p><strong>To receive a file:</strong></p>
      <ol>
        <li>Open Magical Share and tap <strong>Receive</strong>. The screen shows your one-time key and an eight-digit number.</li>
        <li>Tap <strong>Start scanning</strong> and point the camera at the sender's screen.</li>
        <li>The file appears as soon as every byte checks out, and it is kept in the app's private Received list.</li>
      </ol>
      <p><strong>To send a file:</strong></p>
      <ol>
        <li>Tap <strong>Send</strong> and pick a file.</li>
        <li>Choose a protection mode. With <strong>Key</strong>, scan the receiver's code and check that both screens show the same number.</li>
        <li>Pick a speed: <strong>Fast</strong> for phones held close and steady, <strong>Balanced</strong> for a comfortable distance.</li>
        <li>Tap <strong>Start sending</strong> and hold your screen toward the other camera.</li>
      </ol>
      <figure>
        <img src="https://apps.zincirx.com/magicalshare/blog/send-files-with-light/phone-send.jpg" alt="Three phone screens: the receiver's one-time key with the number 7284 2112, the Send screen with Key protection selected, and the stream of QR codes" width="1600" height="1123" loading="lazy" decoding="async">
        <figcaption>Left: the receiver's one-time key. Middle: the sender locks the file to that key. Right: the stream of codes.</figcaption>
      </figure>
      <p>Received files stay inside the app, on that device only, until you delete them. If you would rather keep nothing, switch off <strong>Keep received files</strong> and each file is discarded when you leave it. <strong>App lock</strong> can require Face ID, a fingerprint or your device PIN, and relocks the app after 30 seconds away.</p>
      <figure>
        <img src="https://apps.zincirx.com/magicalshare/blog/send-files-with-light/phone-receive.jpg" alt="Three phone screens: a received file marked decrypted and checksum verified, the Received list, and the App lock and privacy settings" width="1600" height="1123" loading="lazy" decoding="async">
        <figcaption>Left: a file arrives, decrypted and checksum-verified. Middle: the private Received list. Right: App lock and privacy settings.</figcaption>
      </figure>
      <p>The app speaks 30 languages and follows your device language.</p>

      <h2>Send from your Mac</h2>
      <p>The <a href="https://apps.apple.com/app/id6821344244">Mac app</a> is a sender. A large display makes an excellent transmitter, because bigger codes are easier for a phone camera to read. Pick a file, set a passphrase, and click <strong>Start sending</strong>.</p>
      <figure>
        <img src="https://apps.zincirx.com/magicalshare/blog/send-files-with-light/mac-sending.jpg" alt="The Mac app showing two large QR codes side by side while sending a passphrase-protected PDF" width="1600" height="1000" loading="lazy" decoding="async">
        <figcaption>The Mac app shows two large codes side by side. Here, a 475 KB PDF protected with a passphrase.</figcaption>
      </figure>

      <h2>Private by design</h2>
      <p>Privacy in Magical Share is how the app is built, not a setting you have to find:</p>
      <ul>
        <li>The Android app has no internet permission at all, so it cannot open a network connection.</li>
        <li>The Mac app runs in Apple's sandbox with no network access.</li>
        <li>There are no accounts, analytics, crash reporters, ads or trackers. The App Store privacy label reads "Data Not Collected".</li>
        <li>File names from senders are cleaned so they can't escape the save folder, and malformed input is rejected before it is processed.</li>
      </ul>

      <h2>Where it shines, and what it isn't for</h2>
      <p>Magical Share works where other sharing doesn't:</p>
      <ul>
        <li>On a plane, in a basement, or abroad without mobile data.</li>
        <li>Between an iPhone and an Android phone.</li>
        <li>On hotel or office Wi-Fi that blocks devices from seeing each other.</li>
        <li>On managed devices where Bluetooth or Wi-Fi is switched off.</li>
      </ul>
      <p>It is built for documents, photos, contacts, notes, boarding passes and Wi-Fi passwords. Typical speeds are 50 to 100 KB per second, so a 500 KB PDF takes about 5 to 10 seconds. A large video is better sent another way.</p>
      <p>One caution: the sending screen flashes rapidly at full brightness. If you are sensitive to flashing lights, don't look at the sending screen.</p>

      <h2>Pricing</h2>
      <ul>
        <li>Free to download.</li>
        <li>Receiving from other phones is always free.</li>
        <li>Sending is free for 7 days, then a one-time Pro unlock of US$3.99. No subscription and no account.</li>
        <li>Sending from the web app is free. Opening web files on a phone needs Pro or the trial.</li>
      </ul>
      <p>If you try it, we'd love to hear what you sent and where. Write to us at <a href="mailto:info@zincirx.com">info@zincirx.com</a>.</p>
]]></content:encoded>
    </item>
  </channel>
</rss>
