Chapter 03 · 15 minutes
Pack the crate
Whatever crosses the gap must be one file, complete, and checkable on the other side. Pack Parrot into a sealed crate that comes out byte-for-byte the same every time.
Goal
Pack a release into a single file with a manifest of hashes and a seal, and see why reproducible packing matters when the only link to the island is a person reading out a hash.
Pack it
uv run python -m ferry pack
new seal key in dock/seal.key, and a copy handed to the island (once, in person)
packed dock/parrot-1.0.0.crate 12 files, 30,720 bytes
sha256 4fed1c4d54357115a1fd08311f6bd9eb19d7e7cbe8fac2d17b0eee2a96024cb3
The first pack makes a seal key and puts a copy on the island. In real life that happens once, in person, before any software crosses.
Now pack again, without changing anything:
uv run python -m ferry pack
The sha256 line is the same. Your hash differs from the one above because your seal key is your own, but your two packs match each other.
Look inside
A crate is a plain tar file, so tar can list it. It ships with macOS, Linux and Windows 10 or later:
tar -tvf dock/parrot-1.0.0.crate
-rw-r--r-- 0/0 1208 1970-01-01 00:00 MANIFEST.json
-rw-r--r-- 0/0 65 1970-01-01 00:00 SEAL
-rw-r--r-- 0/0 122 1970-01-01 00:00 parrot/__init__.py
-rw-r--r-- 0/0 1680 1970-01-01 00:00 parrot/__main__.py
...
Three things to notice:
- MANIFEST.json lists the SHA-256 of every file. The island checks each one.
- SEAL is an HMAC of the manifest, made with the seal key. Change the manifest and the seal no longer fits.
- Every file has owner 0 and the time 1970-01-01, the zero timestamp (
tarshows it in your time zone). That is what makes the crate reproducible.
Why the same bytes every time
Normal archives record who packed them and when. Pack twice and you get two different hashes, so nobody can tell a rebuilt crate from a tampered one. lab/ferry/crate.py fixes every field that could vary:
def write(out: Path, entries: dict[str, bytes]) -> None:
"""Write a tar with fixed times, owners and order, so it is byte-for-byte reproducible."""
out.parent.mkdir(parents=True, exist_ok=True)
with tarfile.open(out, "w", format=tarfile.PAX_FORMAT) as tar:
for name, data in entries.items():
info = tarfile.TarInfo(name)
info.size, info.mtime, info.mode = len(data), 0, 0o644
tar.addfile(info, io.BytesIO(data))
The files go in sorted order too. Now the hash is a fingerprint of the content alone, and you can announce it over a separate channel: a phone call, a ticket, a printed change request. The island compares it before it trusts anything.
About the seal
An HMAC uses one shared key: whoever can check a seal can also make one. That is fine for learning, and wrong for real releases. Real setups sign with a private key that never leaves the build system, and ship only the public key to the island, using tools like minisign, GPG or cosign. With cosign, verify offline against a key you ship. The public transparency log is on the internet, and the island cannot reach it.
Check it works
- Two packs in a row print the same
sha256. tar -tvfshowsMANIFEST.json,SEALand the twelve Parrot files.
Exercise
Change one phrase in lab/parrot/phrases.json and pack. The hash changes. Undo your change, for example with git checkout parrot/phrases.json, and pack again. Do you get the first hash back? Explain why that matters to the person on the island.
More to try
- Run
tar -xOf dock/parrot-1.0.0.crate MANIFEST.jsonand find the hash ofparrot/phrases.json. Hash the file yourself withsha256sumorGet-FileHashand compare. gzipstores a timestamp in its header, which breaks reproducible builds unless you pass-n. Find the same problem in a tool you use, and the fix (search forSOURCE_DATE_EPOCH).