Celestia Q2 2025 Hana Library Audit Report Final v2
Security Audit Report
CELESTIA Q2 2025: HANA LIBRARY
Authors: Last Revised Ivan Gavran, Martin Hutle, Marius 2025/08/04 Poke, Carlos Rodriguez Celestia Q2 2025 Hana Library
Contents Audit overview 2 The Project . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Scope of this Report . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Audit Plan . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2 Conclusions . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
Audit Dashboard 3 Target Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Engagement Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3 Severity Summary . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
System Overview 4 Architecture Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4 Protocol Overview . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
Threat Model and Inspection Results 7
Findings 15 Error when loading blobs not propagated . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 16 Unnecessary panic upon failed verification . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 17 DA Provider for Celestia Data Source mimics DA Provider for Ethereum Data Source too literally . . . . . . 18 Blob header not fully verified . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 19 Miscellaneous code findings . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 20
Appendix: Vulnerability classification 21
Disclaimer 24
Informal Systems © 2025 < Table of Contents 1 Celestia Q2 2025 Hana Library
Audit overview The Project In June 2025, Celestia engaged Informal Systems ↗ to work on a partnership and conduct a security audit of the following items:
● celestiaorg/hana ↗, branch main, commit 0bfe515 ↗
Scope of this Report Hana library is a rust component used in ZK-proving OP Stack chains through OP Succinct or Kailua. The purpose of Hana is to serve as a library for OP Stack x Celestia chains derivation pipeline. Its crates are used to fetch the data and verify that the data was published to Celestia and properly referenced by a Blobstream deployment. The audit scope was:
● The hana crates ↗, as well as ● The host ↗ (excluding bin/host.rs)
In particular the host re-uses a lot of features from Kona. The audit was inspecting the Hana code only, assuming the parts that are used from Kona are correct.
Audit Plan The audit was conducted between June 5th, 2025, and June 24th, 2025 by the following personnel:
● Ivan Gavran ● Martin Hutle ● Marius Poke
● Carlos Rodriguez
Conclusions The audit was conducted on a stable code version. The audit was performed using a property/threat-based approach, in which the code was fully inspected analyzed along the execution paths. The code showed a high level of quality and organization. During the audit we identified no major issues, and reported 2 low-severity findings and 3 informational findings.
Informal Systems © 2025 < Table of Contents 2 Celestia Q2 2025 Hana Library
Audit Dashboard Target Summary ● Type: Implementation ● Platform: Rust ● Artifacts:
– https://github.com/celestiaorg/hana/tree/main/crates ↗ – https://github.com/celestiaorg/hana/tree/main/bin/host ↗ (excluding bin/host.rs)
Engagement Summary ● Dates: 5.6.2025 - 24.6.2025 ● Method: Manual code review
Severity Summary
Finding Severity Number
Critical 0 High 0 Medium 0 Low 2 Informational 3 Total 5
Informal Systems © 2025 < Table of Contents 3 Celestia Q2 2025 Hana Library
System Overview Celestia Hana is a library that adapts the Optimism (OP) Stack’s derivation pipeline to work with Celestia as a modular data availability (DA) layer instead of Ethereum. In Optimistic Rollups, the derivation pipeline is responsible for reconstructing the L2 chain state from posted data. Hana interacts with and implements interfaces of the Kona’s derive derivation library. Hana enables secure transaction data retrieval from Celestia, ensuring correctness through cryptographic proofs that link Celestia’s DA commitments back to Ethereum via the Blobstream bridge. Following Kona’s pipeline structure, Hana integrates DA abstractions, an asynchronous hint system for data prefetching, and cryptographic verification of the obtained data.
Architecture Overview Main components (under audit)
● Host: Retrieves and verifies data from Ethereum and Celestia. ● Oracle System: Coordinates data flow between client and host. ● DA Provider: Abstracts over Celestia and Ethereum for data retrieval.
External components (out of scope)
● Client (Kona-based): Drives execution of L2 blocks for state verification. ● Ethereum: Stores rollup state commitments and frame references. ● Celestia: Provides DA for transaction blobs.
● SP1 Blobstream Contract: Posts and verifies ZK proofs linking Celestia data roots to Ethereum.
Basic usage
- A rollup state transition is challenged.
- The Hana pipeline is instantiated with trusted boot data.
- The pipeline retrieves transaction data using L1 pointers.
- Verified data is executed using Kona.
- A computed output root is compared with the claimed one to resolve the challenge.
Protocol Overview The protocol logic is triggered during a challenge to a claimed rollup state transition. The Hana pipeline reconstructs the L2 chain state from Celestia blobs and validates it against the claimed output root. The following diagram describes the main steps of the protocol.
Informal Systems © 2025 < Table of Contents 4 Celestia Q2 2025 Hana Library
Figure 1: Architecture overview
Informal Systems © 2025 < Table of Contents 5 Celestia Q2 2025 Hana Library
Figure 2: Protocol overview
Informal Systems © 2025 < Table of Contents 6 Celestia Q2 2025 Hana Library
Threat Model and Inspection Results Hana implements data fetching for data published to Celestia into the Kona derivation pipeline. Upon receiving next block request from the derivation pipeline, Hana fetches the requested data efficiently. This combines L1 (Ethereum) and DA (Celestia) sources: when fetching the data, there is no trust given but data’s correctness is verified by Hana. Fetching is done using client and host processes, where the client only communicates to the host, which then communicates to external sources. The integration with Kona is done by implementing the function next of the Kona’s DataAvailabilityProvider in- terface inCelestiaDADataSource.next(block_ref, batcher_address) → PipelineResult<Bytes>. This function takes a block reference as BlockInfo and an Ethereum address as parameters, and returns the fetched block. The main property of Hana is that when a request for data arrives, it should deliver correct data back to the pipeline, provided that the data exists. It needs to do so without placing trust into any external entity. In the following list of properties we detail the building blocks of this main property:
Data Blobs Fetching
- If the next data blob of block_ref is a Celestia pointer, the blob where this pointer refers to is returned. Else, the data blob from Ethereum is returned. This disambiguation between when to fetch Celestia’s data versus only Ethereum data is done correctly.
● A blob that is a Celestia pointer has the following structure:
Byte Description Value
0 version_byte 0x01 1 commitment_type 0x01 2 da_layer_byte 0x0c 3..10 height (u64 = 8 bytes) 11..42 commitment (Merkle hash) (32 bytes)
● The version_byte and the da_layer_byte are checked by the implementation. The commitment_type is not checked, and all blobs that are not Celestia pointers are deferred to Etherum.
● Although the Hana library is supposed to be used in a specific context only (namely only Celestia as data source, and only normal Ethereum blobs as fallback) the header should be completely verified and in case of a non-fitting header an error should be raised. See Informational Finding “Blob header not fully verified”.
● Based on the information we obtained we assume that every blob (even if there are several blobs per block_ref) is prefixed by the batcher with a correct header (starting with 0 for an Ethereum blob and 0x01 for a Celestia blob).
● Kona ensures that next() is called for the same block_ref until an EOF temporary error is thrown and then calls clear() to reset before blobs from a new block are requested:
– Each call to next_data ↗ will result in an attempt to read data calling next() ↗. If there is no data to return (self.data empty raising a Temporary error here ↗, propagated in CelestiaDASource.next ↗, propagate further here ↗), a Temporary error will be returned, resulting in calling self.provider.clear(), which calls DataAvailabilityProvider’s clear ↗.
Informal Systems © 2025 < Table of Contents 7 Celestia Q2 2025 Hana Library
– The result of calling CelestiaDASource’s clear will be that ↗ ➞ self.data ↗ is emptied ➞ self.open ↗ is set to false, enabling operations for the new block
- In subsequent calls of next(), no blob is fetched twice.
● In both, the Celestia and Ethereum implementation of DataAvailabilityProvider, the flag self.open is used to ensure that only in the first call to next() (after a clear()) the data is loaded (load_blobs() return Ok() immediately if open is true). ● The data is obtained from sending the hint (hash of height and commitment) to the oracle.
● Then next() returns always the first element of the vector data, and removes this element. Thus no blob is
returned twice.
-
The host correctly interprets what blob was requested by the client. Checks:
-
Correct determination of height to fetch from pointer_data. ● Height and commitment are correctly extracted from the pointer_data ● Both are passed to celestia_source.next → load_blobs → celestia_fetcher.blob_get ● Both are encoded as Hint (little-endian height, hash(commitment))
-
The correct blob is placed in the KV store. ● The hint handler correctly decodes the hint and fetches the right blob ● The blob is placed in the KV store with hash(hint)
-
The client fetches the correct block from the KV store. ● The client fetches ↗ the blob using hash(hint)
Data Verification
- The correctness of the fetched data is properly verified. That is, the implementation of checks terminates for every input, accepts all correct data blobs, and rejects all incorrect data blobs. Checks:
- For every data blob, the chain of proofs establishes correctness (share proofs, storage proofs, data root proofs). These proofs are given the parameters corresponding to the relevant block. Conclusion: The checks are done both on the host and on the client. The client’s checks are performed in the function blob_get (← load_blobs ← next), upon receiving the tuple (blob, blobstream_proof) from the oracle (code ref ↗). Then the following sequence of verification steps is taken:
- verify_data_commitment: the Blobstream contract posted the commitment to the L1 state (code ref ↗)
- the proof’s block_header’s hash matches the boot-provided l1_block_hash
- the Blobstream contract exists at the (boot-provided) expected address
- the data commitment exists at the expected storage slot
- share_proof.verify: the data blob was posted on Celestia (code ref ↗).
- data_root_tuple_proof.verify: The data root (on Celestia) was included in the commitment posted by the Blobstream contract (code ref ↗). Overall, the three verification steps create a chain of reasoning that says 1) The data was really published on Celestia, as witnessed by the Celesetia’s header (data root); 2) That data root is among the ones that the Blobstream contract committed to in its commitment and emitted an event about; 3) This commitment indeed exists in L1 storage at the expected place, posted by the correct Blobstream contract.
Informal Systems © 2025 < Table of Contents 8 Celestia Q2 2025 Hana Library
The host does the same sequence of checks, but it also retrieves all the proofs for the client and creates a BlobstreamProof data structure (code ref ↗). The BlobstreamProof consists of the following fields:
● data_root: a hash of the Celestia’s header at a given height (as received in the hint ↗), obtained from the Celestia light client (code ref ↗ & ref ↗). ● data_commitment, proof_nonce: determined through the find_data_commitment() function (code
ref ↗); see below, for more details on the analysis of this function. ● data_root_tuple_proof: a data root tuple inclusion proof obtained from the Celestia light client (code
ref ↗). ● share_proof: a share proof obtained from the Celestia light client (code ref ↗). Its parameters (trans-
forming EDS based indices into ODS) were calculated correctly, as established below. ● storage_root, storage_proof, account_proof: data necessary to verify the L1 storage proofs (code
ref ↗), obtained from the L1 provider, through the get_proof method (code ref ↗). ● state_root, block_header: the L1 state root and block header, obtained from the L1 provider through
the get_block_by_hash method (code ref ↗), with the L1 block hash set during the host configuration to the hash of the L1 head block (code ref ↗). ● blobstream_balance, blobstream_nonce, blobstream_code_hash: data necessary to construct a
TrieAccount data structure on the L1, obtained from L1 provider’s functions (code ref ↗).
- The location of the share proof is determined ↗ correctly. Conclusion: We checked the calculations ↗ transforming between Original Data Square (ODS) and Extended Data Square (EDS) based indices and they are done correctly. As we suggested in the set of improvements in the finding “Miscellaneous code findings”, a separate function would improve the clarity of the calculation.
- find_data_commitment() will locate the data commitment if it exists. If it does not exist, it will raise an error. Conclusion: The function iterates backwards from the l1_head_block_number parameter to 0 and checks if there is a DataCommitmentStored event for which the parameter celestia_height is in between its startBlock and endBlock. It is guaranteed to find such event if it exists between blocks 0 and l1_head_- block_number. If it does not exist, it is guaranteed to terminate (because ↗ start decreases, eventually becomes 0, and the function returns at start == 0). Finally, there is a separate question if the loop will ever reach all the way to 0, which would cause a long outer loop (combined with an inner loop over logs). There is a calculation inside the fetch function ↗, in which it calculates safe L1 head ↗ (the L1 block for which the Celestia heights mentioned in the transactions are smaller than the height of the latest Celestia block committed to in the blobstream). Thus calculated l1_head is then used as cfg.l1_head in the prover, which is the source ↗ of the l1_head_block_number parameter used in find_data_commitment().
Interfacing with Kona
- Hana handles well all types of requests that may come, not only Celestia-related. The host implements trait OnlineHostBackendCfg ↗: impl OnlineHostBackendCfg for CelestiaChainHost { type HintType = HintWrapper; type Providers = CelestiaChainProviders; }
where HintType is defined as HintWrapper, an enumeration with the possible types of hint requests that Hana’s host can handle:
Informal Systems © 2025 < Table of Contents 9 Celestia Q2 2025 Hana Library
pub enum HintWrapper { Standard(HintType), CelestiaDA, }
where HintType is an enumeration of all possible hint types supported by Kona ↗. When the client wants to read data from the host, it first sends a hint request to the host through the hint file descriptor, which signals a request for the host to prepare the data for reading. The host implements the HintHandler trait ↗, which handles hint requests for both values of the HintWrapper enumeration ↗. 2. Hana implements correctly all Kona’s interfaces, respecting the assumptions on the behaviour of these components, and checking its own assumptions about the inputs. Hana’s host implements here ↗ the PreImageServerStarter trait ↗: #[async_trait] pub trait PreimageServerStarter { async fn start_server<C>( &self, hint: C, preimage: C, ) -> Result<JoinHandle<Result<(), SingleChainHostError>>, ,→ SingleChainHostError> where C: Channel + Send + Sync + 'static; }
The PreimageServerStarter trait is needed to abstract the process of starting and running the preimage server (i.e., the host process that manages data access through the hint-based system). It standardises how a preimage server is initialised and run with communication channels for hints and preimage requests, a backend for handling those requests (online or offline host), and a key-value storage for the preimage data ↗. Hana’s implementation is exactly the same as the one found in Kona for a single chain setup ↗. The start_server() function of PreimageServerStarter trait takes two parameters:
- hint: C: A channel for hint communication used by the preimage server to receive hints from the client about what data it needs. In the file-based implementation, it uses FileChannel with HintRead / HintWrite descriptors.
- preimage: C: A channel for preimage data communication used to receive preimage requests and send back the requested data. In the file-based implementation, it uses FileChannel with PreimageRead / PreimageWrite descriptors. Hana implements here ↗ the HintHandler trait ↗: /// A [HintHandler] is an interface for receiving hints, fetching remote ,→ data, and storing it in the /// key-value store. #[async_trait] pub trait HintHandler { /// The type configuration for the [HintHandler]. type Cfg: OnlineHostBackendCfg;
/// Fetches data in response to a hint. async fn fetch_hint( hint: Hint<<Self::Cfg as OnlineHostBackendCfg>::HintType>,
Informal Systems © 2025 < Table of Contents 10 Celestia Q2 2025 Hana Library
cfg: &Self::Cfg, providers: &<Self::Cfg as OnlineHostBackendCfg>::Providers, kv: SharedKeyValueStore, ) -> Result<()>; }
The HintHandler trait is necessary because it serves as an abstraction layer that receives hints from the client about what data it needs, fetches the data from the appropriate sources and processes it into the required format, and makes it available through the preimage store. Hana implements CelestiaChainHintHandler specifically to handle:
● Standard L1/L2 data requests (via SingleChainHintHandler) ● Celestia-specific data requests (via CelestiaDA hint type)
The fetch_hint() function of HintHandler takes four parameters:
- hint: HintCfg::HintType: Contains the type of hint and associated data.
- cfg: &Cfg: Contains configuration data (type Cfg is defined as CelestiaChainHost).
- providers: &Cfg::Providers: Provides access to different data sources (L1, L2, Celestia).
- kv: SharedKeyValueStore: Used to store the fetched (preimage) data on the correct hash key. The implementation handles concurrent access appropriately. Hana’s host implements here ↗ the OnlineHostBackendCfg trait ↗: /// The [OnlineHostBackendCfg] trait is used to define the type ,→ configuration for the /// [OnlineHostBackend]. pub trait OnlineHostBackendCfg { /// The hint type describing the range of hints that can be received. type HintType: FromStr<Err = HintParsingError> + Hash + Eq + PartialEq + ,→ Clone + Send + Sync;
/// The providers that are used to fetch data in response to hints. type Providers: Send + Sync; }
The
Excerpt (19996 of 44403 characters). Read the whole page on informalsystems/audits ↗