Understanding the Torrent File Format and Bencoding

Arpit Bhayani

Arpit Bhayani

Aug 08, 2022 • 8 min read

Play

In distributed peer-to-peer (P2P) systems, transferring large files without a single bottleneck requires decentralization, integrity verification, and robust coordination. In the BitTorrent protocol, the entry point to any transfer is the humble .torrent file.

While end users simply load a .torrent file into their client to initiate a download, under the hood it functions as a cryptographic manifest and communication bootstrapping document. This article explores the internal anatomy of a .torrent file, the P2P swarm lifecycle, and Bencoding—the lightweight serialization format that BitTorrent uses instead of JSON or XML.


1. The Torrent File Lifecycle & Swarm Dynamics

A .torrent file contains metadata about the target files and tracker infrastructure, not the actual file payloads. Before diving into the byte-level format, it is critical to understand the swarm dynamics it coordinates.

+-----------------------+
| Web Server / Portal   |
| (Distributes .torrent)|
+-----------+-----------+
            |
            | 1. Download .torrent file
            v
    +---------------+           2. Announce (HTTP/UDP)
    | BitTorrent    | ----------------------------------> +-------------+
    | Client        | <---------------------------------- |   Tracker   |
    | (Leecher)     |           3. Peer List (IP:Port)    +-------------+
    +-------+-------+
            |
            | 4. Download Pieces & Verify SHA-1
            v
    +---------------+       5. Seed to Swarm       +---------------+
    | Peer (Seeder) | ---------------------------> | Other Leechers|
    +---------------+                              +---------------+

Seeders vs. Leechers

  • Leecher: Any peer actively downloading the file who does not yet possess 100% of the content.
  • Seeder: A peer that possesses 100% of the pieces and shares upload bandwidth with the swarm.

Swarm Survival

For a torrent to remain downloadable, the swarm must have at least one complete copy of the data. This can be satisfied by a single seeder or a collective group of leechers whose aggregate pieces span the entire file. If all seeders leave and the remaining leechers hold an incomplete subset of pieces, the torrent becomes “dead.”

The Incentive Model: BitTorrent vs. Cryptocurrencies

Unlike modern blockchain-based peer-to-peer networks that offer financial tokens for bandwidth or storage participation, standard BitTorrent operates with no built-in economic incentives for seeding. Once a download completes, the client switches from leecher to seeder by default, relying on altruism, tit-for-tat bandwidth choking algorithms, or private tracker ratio rules.


2. The Internal Structure of a .torrent File

A .torrent file is conceptually a key-value dictionary serialized using Bencoding. It provides all the bootstrap parameters required to find peers and verify data.

Top-Level Keys

KeyTypeDescription
announceStringThe primary URL of the BitTorrent tracker (HTTP/HTTPS/UDP).
created byString(Optional) Software/client that generated the .torrent file.
creation dateInteger(Optional) Unix epoch timestamp when the file was created.
commentString(Optional) Free-form description or notes from the author.
encodingString(Optional) Encoding used for strings (defaults to utf-8).
infoDictionaryCore dictionary describing the files, sizes, and integrity hashes.

The Announce URL and Trackers

The BitTorrent network is hybrid P2P; it utilizes a tracker (a centralized or semi-decentralized HTTP/UDP service) to coordinate peer discovery. The announce key directs the client to the tracker, allowing the client to say: “I am joining the swarm for this InfoHash. Here is my IP/Port; please return a list of active peers.”


3. The info Dictionary: Single-File vs. Multi-File Mode

The info dictionary contains the immutable description of the target payload. Its contents vary depending on whether it describes an individual file or an entire directory tree.

Single-File Format

Used when distributing an individual file (e.g., an Ubuntu .iso):

  • name: Suggested file name (e.g., ubuntu-22.04-desktop-amd64.iso).
  • length: File size in bytes.
  • md5sum: (Optional) 32-character hexadecimal MD5 hash of the entire file.

Multi-File Format

Used when streaming a folder containing multiple files and subdirectories:

  • name: The name of the root directory.
  • files: A list of dictionaries, where each entry represents a file:
    • length: Size of the individual file in bytes.
    • md5sum: (Optional) MD5 hash of this specific file.
    • path: A list of strings specifying the relative directory hierarchy.

Architectural Insight: OS-Agnostic Path Handling

Instead of storing paths as formatted strings like subdir/data/file.txt (Unix) or subdir\data\file.txt (Windows), BitTorrent specifies paths as a list of path segment strings:

["subdir", "data", "file.txt"]

This deliberate design decision prevents separator collisions across operating systems, allowing Windows, macOS, and Linux clients to reconstruct the directory hierarchy using their native path separators.


4. Content Verification: Pieces, Blocks, and Hashes

Because BitTorrent downloads chunks out-of-order across untrusted peers, data integrity cannot rely on trusting remote nodes. The info dictionary solves this via two critical fields:

  1. piece length: The fixed size of each piece in bytes (commonly powers of two: 256 KB, 512 KB, 1 MB, 2 MB, or 4 MB).
  2. pieces: A concatenated binary string of 20-byte SHA-1 hashes, one for each consecutive piece of the file data.

How Piece Hashing Works

Consider a 3 MB file split into 1 MB pieces:

File Content (3 MB Total):
+--------------------+--------------------+--------------------+
|    Piece 0 (1 MB)  |    Piece 1 (1 MB)  |    Piece 2 (1 MB)  |
+--------------------+--------------------+--------------------+
         |                    |                    |
         v                    v                    v
    SHA-1 Hash           SHA-1 Hash           SHA-1 Hash
     (20 bytes)           (20 bytes)           (20 bytes)
       [S0]                 [S1]                 [S2]
         \                    |                   /
          \                   |                  /
+-------------------------------------------------------------+
| `pieces` string: S0 + S1 + S2 (60 bytes total binary blob) |
+-------------------------------------------------------------+
  • Total Pieces: total_length/piece_length\lceil \text{total\_length} / \text{piece\_length} \rceil
  • Total Length of pieces string: number_of_pieces×20 bytes\text{number\_of\_pieces} \times 20 \text{ bytes}

When downloading, the client requests smaller sub-pieces (called blocks, usually 16 KB each). Once all blocks of a piece arrive, the client computes the SHA-1 hash of the assembled piece and compares it to the corresponding 20-byte slice in the pieces string. If the hash matches, the piece is marked complete and saved to disk. If corrupt, it is discarded and re-requested.


5. The Bencoding Specification

A .torrent file is not serialized in JSON, Protobuf, or YAML. It uses Bencoding (pronounced Bee-encoding), a binary-safe, deterministic serialization standard with zero delimiter ambiguity.

Bencoding supports exactly four fundamental data types:

1. Byte Strings

Strings are formatted as <length>:<contents>, where <length> is an ASCII-encoded integer representing the string’s length in bytes, followed by a colon : and the raw bytes.

  • Syntax: <length>:<string>
  • Example: arpit \rightarrow 5:arpit
  • Binary Safety: Because strings are length-prefixed, they can contain null bytes, arbitrary binary data, or raw cryptographic hashes (such as the binary blob inside the pieces key).

2. Integers

Integers start with the delimiter i, followed by the number in base-10 ASCII digits, and terminate with e.

  • Syntax: i<integer>e
  • Examples:
    • 10 \rightarrow i10e
    • -42 \rightarrow i-42e
    • 0 \rightarrow i0e

3. Lists

Lists begin with the character l, contain any sequence of bencoded values (strings, integers, lists, or dictionaries), and terminate with e.

  • Syntax: l<bencoded_elements>e
  • Example: A list containing ["a", "b", 1]
    • "a" \rightarrow 1:a
    • "b" \rightarrow 1:b
    • 1 \rightarrow i1e
    • Encoded Result: l1:a1:bi1ee

4. Dictionaries

Dictionaries represent associative key-value maps. They begin with d, terminate with e, and contain alternating pairs of bencoded keys and bencoded values.

  • Requirement: Keys must be bencoded byte strings and must appear in lexicographical order.
  • Syntax: d<key1><val1><key2><val2>...e
  • Example: {"a": 1, "b": 2}
    • Key "a" \rightarrow 1:a
    • Value 1 \rightarrow i1e
    • Key "b" \rightarrow 1:b
    • Value 2 \rightarrow i2e
    • Encoded Result: d1:ai1e1:bi2ee

6. Parsing Bencoded Data: Conceptual Logic

Writing a Bencoding parser is an exercise in recursive descent parsing. Because the leading byte uniquely signals the data type, parsing requires minimal lookahead.

                    Read Next Character
                            |
      +-----------+---------+---------+-----------+
      |           |                   |           |
     'i'         'l'                 'd'     '0' through '9'
      |           |                   |           |
      v           v                   v           v
   Integer       List            Dictionary     String
 Read until  Parse elements      Parse Key/Val  Read digits until ':',
    'e'       until 'e'           until 'e'     then read N raw bytes

Parsing Rules

  1. If character is 'i': Consume until 'e'. Parse the intermediate characters as a base-10 integer.
  2. If character is '0' through '9': Read all digits until the colon ':'. Convert digits to integer NN. Read the next NN bytes verbatim.
  3. If character is 'l': Initialize a list. Recursively parse values until encountering 'e'. Consume 'e' and return the list.
  4. If character is 'd': Initialize a dictionary. Recursively parse alternating strings (keys) and values until encountering 'e'. Consume 'e' and return the map.

Because the entire .torrent file starts with d and ends with e, the parser yields a single nested dictionary that exposes all tracker URLs, file names, piece sizes, and validation hashes.


Summary of Key Takeaways

  • Static Metadata Role: The .torrent file contains only control information (announce URL, file names, structure, and integrity hashes), completely decoupled from the file payload.
  • Trustless Verification: By splitting files into deterministic piece lengths and storing raw concatenated 20-byte SHA-1 hashes, clients can verify data downloaded from untrusted peers before assembling the file.
  • Cross-Platform Pathing: The multi-file path property uses a list of strings (["dir", "file.ext"]) rather than platform-specific path separators to ensure compatibility across all operating systems.
  • Simplicity of Bencoding: With only four types (string, integer, list, dict), Bencoding provides a binary-safe, easy-to-parse, and unambiguous serialization format that powers the BitTorrent peer-to-peer ecosystem.
Arpit Bhayani

Principal Engineer II at Razorpay - building Agent Studio, Ex-staff engg at GCP Memorystore & Dataproc, Creator of DiceDB, ex-Amazon Fast Data, ex-Director of Engg. SRE and Data Engineering at Unacademy. I spark engineering curiosity through my no-fluff engineering videos on YouTube and my courses