Quantstamp reviewed Datachain (Datachain - ELC For Bridge - BSC) on 6 Dec 2024: 6 findings, 0 critical; 5 marked fixed, 1 acknowledged.
Report hosted on certificate.quantstamp.com.
Trustblock does not review code.
Statuses are the security firm’s own, from its report.
| Severity | Open | Acknowledged | Fixed |
|---|---|---|---|
| Critical | 0 | 0 | 0 |
| High | 0 | 0 | 0 |
| Medium | 0 | 0 | 1 |
| Low | 0 | 1 | 4 |
Suppose we want to finalize a target block `x` with reference to a trusted block `w`. In order to finalize `x`, it is required to provide a consecutive sequence of headers starting at `x` and ending in two adjacent headers (call them the "finalizing blocks" `y` and `z`) that contain BLS signatures from their corresponding active validator set. At the start of every epoch, a new validator set is elected. This validator set becomes active once an epoch-specific checkpoint is reached: before the checkpoint, headers must be signed by validators from the trusted epoch; starting from the checkpoint, headers are required to be signed by the new validator set; before the checkpoint, BLS signatures used to finalize blocks must come from validators of the trusted epoch; *after* the checkpoint, such BLS signatures must come from the new validator set. This process updates with every subsequent validator set election. Ordinarily, finalizing incorrect blocks is difficult because 14 out of 21 validators of an active validator set would need to sign BLS signatures for two consecutive blocks in order to finalize a block that comes prior. The light client also ensures that at least 8 validators in an untrusted epoch are from the previous trusted epoch. However, it appears to be possible for one malicious validator, Alice, to introduce 13 malicious validators into an untrusted validator set along with herself in the following scenarios. Along with (or inside) an incorrect target block, Alice transmits a fake new validator set and a sequence of fake headers that she signs herself in order to pass the `verify_seal()` check, up to and including the checkpoint. She will also submit finalizing headers post-checkpoint with BLS signatures coming from the malicious validator set, meeting quorum. See the "Exploit Scenario" section below for an example-based explanation of the issue. In the worst case, the finalization of incorrect blocks may impact components of the LCP project that rely on the light client for correct data: for example, if a bridge were to be fed incorrect data regarding the state of the `IBCStore`'s storage root, it could be assumed that messages were broadcast when they in fact weren't, resulting in, for example, the issuing of funds on a destination chain without the appropriate conditions.
The current light client implementation is in violation of the [specification](https://github.com/datachainlab/parlia-elc/blob/main/SPEC.md#client-initialisation), which states that clients must be initialized only with heights greater than zero.
When a "State ID" is being generated from a client state and a consensus state, the client state is first simplified by calling `ClientState::canonicalize()`. This sets the `latest_height` to `{revision_number: self.chain_id.version, revision_height: 0}`. As such, there is an implicit correspondence between `chain_id.version` and `latest_height.revision_number`. However, no such correspondence is enforced when instantiating a `ClientState` (e.g. in `ClientState::try_from(value: RawClientState)`). Therefore, via data corruption, it is in principle possible for client states that are intended to refer to the same client state to be different via `chain_id.version`, and therefore result in different state IDs being generated for each.
In communications with the client, it was determined that for any given `ClientState`, the `latest_height.revision_number` value should be equal to 0. However, when decoding a `RawClientState`, there is no validation indicating that a non-zero `revision_number` is forbidden. This may enable the use of invalid client states in the ELC.
The current light client implementation is only compatible with blocks generated after the Bohr fork. In itself, this is merely an operational consideration as it represents the expectation of the developer. However, if a block height from before the Bohr fork is submitted, the client may behave unexpectedly because it lacks validation to prevent the submission of pre-Bohr fork blocks. The header parsing and BLS signature verification logic rely on the structure introduced in BEP-341 and BEP-402, which added fields like turn length data and modified signature verification processes. In BEP-341, the header's extra data field includes the turn length information, and the parsing logic accounts for this when extracting vote attestation data. Similarly, BEP-402 introduced changes to signature verification by adding new fields that must be verified. The parsing functions, such as `get_vote_attestation` and `get_validator_bytes_and_tern_term`, are designed to work with the modified header structure post-Bohr fork. When creating a client, it is assumed that the latest block height exceeds the Bohr fork height, but there is no explicit validation to enforce this. Submitting a block from before the Bohr fork can lead to parsing errors or unexpected client behaviour.
The report names a repository or files. Open the report to see its scope.
Repository and files the report names (44)
236 projects reviewed · 332 reports · latest Oct 2026
See Quantstamp’s auditsSomething wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.
Tell us