# MonadBFT Explained Part 4: RaptorCast **Published by:** [Cryptolytic](https://paragraph.com/@cryptolytic/) **Published on:** 2025-08-22 **URL:** https://paragraph.com/@cryptolytic/monadbft-explained-part-4-raptorcast ## Content IntroductionFinally, we are at the final blog in our MonadBFT series. In the previous blog part 3, I have explained the issues of naive block propagation which can lead to bottleneck and are not scalable. But the main focus was to explore and learn about the basics of fountain or rateless erasure code and Raptor code, starting from the LT codes and improving it with precoding. So I hope you get the idea of how Raptor codes work in general as in this blog we will build on the Raptor code and explore RaptorCast, the proposal propagation protocol used in MonadBFT that saves leader upload bandwidth during the propose phase.RaptorCast Broadcast StructureLet’s recap the motivation behind RaptorCast. In the naive propose phase of a leader-centric BFT protocol, the leader must broadcast its proposal to every validator individually. This design introduces several issues: the leader becomes a bottleneck, the workload is unevenly distributed, and the system becomes unscalable as the leader's upload bandwidth grows linearly with the number of validators. One intuitive improvement is to have the leader divide its proposal into chunks and distribute different sets of chunks to different validators. However, because no validator receives the full proposal, a second layer of broadcast is required. After receiving its assigned chunks from the leader, each validator must share them with others. In this way, validators eventually receive chunks from one another and are able to reconstruct the full proposal. This two-layer broadcast structure is the core of RaptorCast and forms a 2-level tree, as illustrated in the figure below.To compare the efficiency of this two-layer broadcast structure with the naive approach, let’s look at their upload bandwidth cost. Assume there are $$N + 1$$ validators, with one acting as the leader and broadcasting a block of size $$B$$. In the naive approach, the leader sends the entire block to each of the N other validators. The total upload cost is: $$N \times B$$ In contrast, the two-layer broadcast distributes the upload cost across all validators. Here's the breakdown:Leader to Validators: The leader splits the block into multiple chunks and organizes them into $$N$$ sets, each containing an equal portion of the data. If the block is divided into $$k \times N$$ chunks, then each set has k chunks, and the size of each set is $$B / N$$. Each validator receives one set, so the leader's upload cost is: $$(B / N) \times N = B $$$Validators to Validators: Each validator helps rebroadcast its received data to the remaining $$N – 1$$ validators. The upload cost per validator is: $$$ (B / N) \times (N – 1) = B – (B / N) $$$ Across all validators, the total upload cost is: $$$ N \times [B – (B / N)] = N × B – B $$$Total Upload Bandwidth Cost: Leader cost + Validators’ cost = $$B + (N \times B – B) = N \times B$$. Interestingly, this is the same total cost as the naive approach. So what’s the benefit? The key difference is that the workload is no longer concentrated on the leader. By distributing the upload cost across validators, the system avoids a single point of bottleneck and becomes more scalable and network-efficient.Factors to ConsiderThe broadcast strategy discussed earlier works well in theory, but there are several things to consider:Message Loss: To make broadcasting efficient and fast, UDP is typically used instead of TCP. TCP is connection-based and guarantees that all data is reliably delivered in order. UDP, in contrast, is connectionless and focuses only on sending messages, without checking whether they arrive. This makes it faster and more suitable for broadcasting, but some messages can be lost along the way.Messages Dropped by Byzantine Validators: What if some validators don’t help propagate the chunks they receive? This isn’t just message loss — they intentionally drop or withhold data to prevent other validators from successfully reconstructing the proposal.Message Tamper Resistance: Another type of attack is when malicious validators try to modify the content of chunks or messages. How can the leader be sure that honest validators receive the correct data without it being altered? And how can validators be confident that the messages they receive actually came from the leader?These are the real-world issues RaptorCast must solve in addition to designing an efficient tree-based broadcast structure. Fortunately, the first two challenges, which are message loss and dropped messages, can be mitigated using erasure coding by adding redundancy to the message. Specifically, RaptorCast uses R10, a member of the Raptor code family, which we discussed earlier in Part 3. As for tamper resistance, this can be achieved through the use of a Merkle tree. However, a naive implementation can introduce high computational overhead, so RaptorCast includes optimizations to make this more efficient. The remainder of this blog focuses on three key properties: resilience to message loss, resilience to message drop caused by Byzantine behavior, and tamper resistance.Handling Message Loss and Drop Through RedundancyWe can’t control how messages get lost or which nodes might fail. So unfortunately, message loss can't be fully prevented. A natural way to deal with this problem is to add redundancy to the messages, so that even if some parts are lost, the proposal can still be reconstructed. To figure out how much redundancy needs to be added, let’s first define the setup. (The example I’m about to walk through is based on the Monad article [1].) Assume the system is composed of n validators, where validator i holds stake $$S_i$$, and the total stake in the system is $$S_T = \sum_i S_i$$. Also assume that the number of chunks each validator receives is proportional to its stake. Let the proposal that the leader wants to broadcast be divided into M chunks. Then validator i would receive: $$M × S_i/ S_T$$ chunks In practice, we should use ceil($$M × S_i / S_T$$) since the value may not be an integer, but for simplicity, we’ll ignore rounding here. This setup is similar to our earlier ideal-case broadcast, with one twist: each validator now receives a different number of chunks based on its stake. In the examples discussed below in Redundancy Factor for Message Loss and Byzantine Behavior, we assume that after the original $$M$$ chunks are encoded into $$K$$ chunks (where $$K > M$$), the original message can be successfully recovered as long as M chunks are received. Now it’s time to calculate how much redundancy we need.Redundancy Factor for Message Loss (r_ml)Let’s start with message loss. RaptorCast uses a two-layer broadcast structure, from the leader to validators and then from validators to other validators. Message loss can occur at both layers before a validator receives the chunks to begin reconstructing the proposal. Let:$$p_{loss_lv}$$ be the probability that a message is lost during the leader-to-validator broadcast$$p_{loss_{vv}}$$ be the probability that a message is lost during the validator-to-validator broadcastFor simplicity, we assume that $$p_{loss_lv}$$ and $$p_{loss_vv}$$ are constant across all validators. Suppose all validators are honest (for now). Our goal is to ensure that each validator receives enough chunks to reconstruct the original proposal, just like in the ideal case. Let’s walk through the process for validator $$i$$.Ideal Case (no message loss):The leader splits the proposal into $$M$$ chunksValidator $$i$$ receives $$M × S_i/ S_T$$ chunks from the leaderThen $$i$$ forwards exactly $$M × S_i / S_T$$ chunks to the other validatorsSo the full flow looks like: Leader → sends $$M × S_i / S_T$$ → validator $$i$$ → sends $$M × S_i / S_T$$ → other validatorsWith Message Loss (Deriving r_ml):To tolerate loss, the leader encodes the original $$M$$ chunks into $$K > M$$ chunks by adding redundancy. Let $$K = M × r_{ml}$$, where $$r_{ml}$$ is the redundancy factor for message loss. Now suppose:The leader sends $$LV_i$$ chunks to validator $$i$$Due to loss, validator $$i$$ only receives $$VV_i = LV_i × (1 – p_{loss_lv})$$ chunks So $$LV_i = VV_i / (1 – p_{loss_lv})$$Then:Validator $$i$$ forwards those $$VV_i$$ chunks to other validatorsDue to further loss, each validator receives $$M × S_i / S_T$$ chunks from validator $$i$$ So $$$ VV_i × (1 – p_{loss_vv}) = M × S_i/ S_T $$$ Thus, $$$ VV_i = (M × S_i / S_T) / (1 – p_{loss_vv}) $$$Note that I use the terms $$LV_i$$ and $$VV_i$$ to represent the number of chunks sent from the leader to validator $$i$$, and from validator $$i$$ to other validators, respectively. I chose this naming to make it easier to keep track of the parameters. Now, substituting $$VV_i$$ into $$LV_i$$, we get:$$ LV_i = (M × S_i / S_T) / [(1 – p_{loss_vv})(1 – p_{loss_lv})] $$We know that the total number of encoded chunks the leader needs to generate ($$K$$) is the sum of all $$LV_i$$, which represents the total number of messages the leader must send to all validators:$$ K = M × r_{ml} = \sum_i LVᵢ = \sum_i [(M × S_i / S_T) / ((1 – p_{loss_vv})(1 – p_{loss_lv}))] $$Since $$\sum_i S_i = S_T$$, the expression simplifies to:$$ M × r_{ml} = M / [(1 – p_{loss_vv})(1 – p_{loss_lv})] $$So the redundancy factor for message loss is:$$ r_{ml} = 1 / [(1 – p_{loss_vv})(1 – p_{loss_lv})] $$In the ideal case, where $$p_{loss_lv} = 0$$ and $$p_{loss_vv} = 0$$, this gives $$r_{ml} = 1$$, meaning no redundancy is needed and $$K = M$$.Redundancy Factor for Byzantine Behavior (r_bb)Now, let’s derive the redundancy factor required to handle message drops caused by Byzantine behavior. As before, the leader encodes the original $$M$$ chunks of the message into $$K > M$$ chunks to add redundancy. In this case, we define: $$K = M × r_{bb}$$, where $$r_{bb}$$ is the redundancy factor needed to tolerate message drops due to Byzantine validators. For simplicity, assume there is no message loss (i.e., $$r_{ml} = 1$$) and that the leader is honest. If the leader were malicious, the system would not be able to make progress anyway, and a new leader would eventually be elected. We will combine message loss and Byzantine behavior later. We continue using the same notation:$$LV_i$$ is the number of message chunks sent from the leader to validator $$i$$.$$VV_i$$ is the number of chunks validator $$i$$ sends to other validators.The goal remains the same — to ensure that each honest validator receives enough chunks to reconstruct the original proposal. Since the leader is honest and we are assuming no message loss, message drops can only occur during the second layer of broadcast. Therefore, $$LV_i = VV_i$$ for all $$i$$ in the first layer. According to the assumptions of MonadBFT, malicious validators can hold at most one-third of the total stake, meaning up to $$S_T / 3$$ of the stake might refuse to forward chunks or actively try to disrupt the process. However, the leader does not participate in the second layer of broadcast, so we only consider the total stake excluding the leader when calculating the ratio of power held by malicious validators during this phase. Let $$S_L$$ be the stake held by the leader. Then, the relative weight of malicious validators during the second broadcast layer is: (Byzantine stake) / (total stake excluding the leader) = $$(S_T / 3) / (S_T – S_L) = S_T / (3S_T – 3S_L)$$.Deriving r_bbNow we can compute the required redundancy factor by following these steps:The total number of chunks broadcast during the second layer is: $$$ \sum_i VV_i = \sum_i LV_i = K $$$Since we assume no message loss, the number of chunks that can be dropped by malicious validators is at most: $$$ \text{(malicious stake weight)} × K = (S_T / (3S_T – 3S_L)) × K $$$The number of chunks successfully broadcast by honest validators is at least: $$$ (1 – S_T / (3S_T – 3S_L)) × K = (2S_T – 3S_L) / (3S_T – 3S_L) × K $$$For the proposal to be reconstructable, the total number of successfully received chunks must equal M. From step 3, we have: $$$ M = (2S_T – 3S_L) / (3S_T – 3S_L) × K $$$ Substituting $$K = M × r_{bb}$$, we get: $$$ M = (2S_T – 3S_L) / (3S_T – 3S_L) × M × r_{bb} $$$ Solving for $$r_{bb}$$, we get: $$$ r_{bb} = (3S_T – 3S_L) / (2S_T – 3S_L) $$$Putting r_ml and r_bb Together: The Final Redundancy Factor (R)So now we have the redundancy factors required to tolerate both message loss ($$r_{ml}$$) and Byzantine message drop ($$r_{bb}$$). But there is one more factor to consider. Earlier in this blog, we assumed that the original message could be reconstructed using exactly $$M$$ chunks. But as discussed in the previous post, this is not entirely true for the Fountain code family, including Raptor codes. These codes are probabilistic, meaning that receiving $$M$$ chunks gives a high probability of successful decoding, but not a full guarantee. To reach near-certain success, it’s typical to add around 10 percent overhead, or $$1.1 × M$$ chunks. This is likely the reason behind the name R10, where the "10" refers to this 10 percent redundancy overhead. We’ll refer to this decoding overhead as: $$r_{oh}$$, the overhead redundancy factor, where $$r_{oh} ≈ 1.1$$.Combining All FactorsSo, to make sure the leader’s proposal survives:Message loss from the normal broadcast processMessage drop due to Byzantine behaviorDecoding failure probability in Raptor codesThe total number of encoded chunks the leader needs to generate is:$$ K = r_{ml} × r_{bb} × r_{oh} × M $$Substituting in the factors that we just derived gives:$$ K = [1 / ((1 – p_{loss_vv})(1 – p_{loss_lv}))] × [(3S_T – 3S_L) / (2S_T – 3S_L)] × 1.1 × M $$Or rearrange them a bit:$$ K = M × (1.1 × (3S_T – 3S_L)) / [(1 – p_{loss_vv})(1 – p_{loss_lv})(2S_T – 3S_L)] $$So the actual redundancy factor R that incorporates all three effects is:$$ R = (1.1 × (3S_T – 3S_L)) / [(1 – p_{loss_vv})(1 – p_{loss_lv})(2S_T – 3S_L)] $$$ Where:$$p_{loss_lv}$$ is the probability of message loss during the leader to validator broadcast$$p_{loss_vv}$$ is the probability of message loss during the validator to validator broadcast$$S_T$$ is the total stake of all validators, including the leader$$S_L$$ is the stake held by the leaderIt is not the prettiest formula, but well, it is what it is.Ensuring Tamper Resistance for Broadcasted ChunksThe encoding strategy we discussed earlier helps protect against message loss, dropped chunks from Byzantine validators, and also improves the chance of successful decoding by adding redundancy. However, there is still one important problem left: tampering. Even if chunks are delivered, malicious validators might modify them before forwarding them to others. This issue can be even more serious. A single altered chunk could break the decoding process or introduce corrupted data into the reconstructed proposal. In the worst case, honest validators might end up decoding an invalid proposal without noticing. So it is not enough to make sure that messages are delivered. Validators must also be confident that the chunks they receive are exactly what the leader originally produced, without being changed along the way. One obvious fix is to have the leader sign every chunk using its private key. But this is not really practical. If there are hundreds or thousands of chunks, the signing process becomes expensive. And once again, the leader becomes the bottleneck, the exact issue we were trying to solve in the first place. A better way is to reduce the number of signing operations by signing in groups. RaptorCast handles this using Merkle trees. Here is the idea. The leader divides the chunks into groups, builds a Merkle tree for each group, and signs only the Merkle root instead of every chunk. Then, when the chunks are broadcast, each one is sent along with a Merkle proof, the corresponding Merkle root, and the signature that attests to that root. If you are not familiar with Merkle trees, that is totally fine. The idea is simple, and you can find explanations online or check out my note here [3]. Let’s go through a quick example with 8 chunks in a group to keep things simple (in reality, RaptorCast uses 32 chunks per group, which forms a Merkle tree of depth 5).The diagram shows a Merkle tree built from 8 chunks labeled c₁ through c₈. Each chunk is hashed to produce H(c₁), ..., H(c₈). These hashes are then combined in pairs. For example, H(c₁,c₂) is the hash of H(c₁) concatenated with H(c₂); H(c₃,c₄) is the hash of H(c₃) concatenated with H(c₄); and so on. The process continues upward by taking the concatenated result of two hashes from the previous level, hashing it again, and repeating this until we reach the final Merkle root at the top: H(c₁,c₂,c₃,c₄,c₅,c₆,c₇,c₈). This root is what the leader signs. So, what is a Merkle proof? A Merkle proof is a list of sibling hashes along the path from a specific leaf to the Merkle root. It serves as evidence that a particular chunk belongs to a Merkle tree. From our example, we can prove that c₄ is part of the tree with root H(c₁,c₂,c₃,c₄,c₅,c₆,c₇,c₈). The Merkle proof in this case consists of H(c₃), H(c₁,c₂), and H(c₅,c₆,c₇,c₈). Here’s how a receiver can verify the inclusion of c₄, given the root and the proof:Hash c₄ to get H(c₄)Concatenate H(c₄) with H(c₃) and hash the result to get H(c₃,c₄)Concatenate H(c₃,c₄) with H(c₁,c₂) and hash the result to get H(c₁,c₂,c₃,c₄)Concatenate H(c₁,c₂,c₃,c₄) with H(c₅,c₆,c₇,c₈) and hash the result to get the rootCheck if this final hash equals the Merkle root providedIf the final hash matches the root, we can be sure that c₄ was indeed part of the original group of chunks. The key idea here is that if we can trust the Merkle root, we can use it to verify any chunk in the tree. That’s why the leader only needs to sign the root of each Merkle tree. Receivers can then use the leader’s public key to check whether a root is valid. There’s no need to sign every individual chunk or Merkle proof. If a chunk or proof is tampered with, the computed hash simply won’t match the signed root. In our example, one signed root covers 8 chunks. In RaptorCast, each root actually covers 32 chunks, thanks to a Merkle tree of depth 5. This drastically reduces the number of signatures needed. Instead of signing every chunk, the leader signs just one root per group of 32, cutting down the number of signing operations by a factor of 32. The tradeoff is the Merkle proof. Each leaf in a Merkle tree of depth $$N$$ (with $$2^N$$ leaves) requires $$N$$ sibling hashes in its proof. So in our case, each chunk needs 3 hashes as its proof. That means each chunk is packaged together with the Merkle proof and the signed Merkle root. Still, this approach is much more efficient than signing every chunk individually.Summary and Putting Everything TogetherAcross this blog and the previous one, we’ve explored the key ideas behind Raptor codes and how RaptorCast builds on them. We’ve seen how the leader can add redundancy to protect against message loss and message drop, while also increasing the chance of successful decoding. Then we looked at how RaptorCast ensures tamper resistance, so validators can be confident the chunks they receive haven’t been altered. Let’s walk through the full flow again. The leader begins by splitting its proposal into $$M$$ chunks. To make the proposal more resilient, it encodes those $$M$$ chunks into $$K$$ chunks using R10. The number of encoded chunks, $$K$$, is calculated based on three factors: message loss during broadcast, message drop from malicious validators, and the probabilistic nature of Raptor codes, which need slightly more than $$M$$ chunks to successfully decode with high confidence. Next, the leader needs to make sure these chunks are not modified during propagation. Instead of signing every chunk, the leader groups the $$K$$ chunks into sets of 32. For each group, it builds a Merkle tree and signs the root. Then, each chunk is sent along with its Merkle proof and the signed Merkle root. This allows validators to verify that each chunk really came from the correct leader without needing a signature on every individual chunk. One signature now covers 32 chunks, which significantly reduces the signing cost. The cost is a small increase in chunk size due to the Merkle proof, but the tradeoff is worth it. And that wraps up the series. I hope you enjoyed it and now have a better understanding of RaptorCast and MonadBFT. If you have any feedback or thoughts, feel free to reach out — I’d love to hear from you.References[1] https://www.category.xyz/blogs/raptorcast-designing-a-messaging-layer [2] https://docs.monad.xyz/monad-arch/consensus/raptorcast#user-content-fnref-2 [3] https://docs.google.com/document/d/1KCv-exlJLSrqk2QOa6j49Ubch-48f3NFpW3yoMkqHTY/edit?usp=sharing ## Publication Information - [Cryptolytic](https://paragraph.com/@cryptolytic/): Publication homepage - [All Posts](https://paragraph.com/@cryptolytic/): More posts from this publication - [RSS Feed](https://api.paragraph.com/blogs/rss/@cryptolytic): Subscribe to updates - [Twitter](https://twitter.com/CryptolyticLabs): Follow on Twitter