linera-io/linera-protocol · warning

hash must be 32 bytes

Error message

hash must be 32 bytes

What it means

After hex-decoding succeeds, is_deposit_processed converts the decoded bytes into [u8; 32] and expects success. The panic means the string was valid hex but decoded to a byte vector whose length is not exactly 32 — the handler requires a full keccak-256-sized deposit hash. It surfaces as a GraphQL error on the query.

Source

Thrown at linera-bridge/contracts/evm-bridge/src/service.rs:197

        let params: BridgeParameters = self.runtime.application_parameters();
        format!("0x{}", hex::encode(params.token_address))
    }

    /// The configured EVM JSON-RPC endpoint, or empty if finality verification
    /// is disabled.
    async fn rpc_endpoint(&self) -> String {
        self.state.rpc_endpoint.get().clone()
    }

    /// Whether a deposit with the given hash has been processed.
    ///
    /// The hash is the hex-encoded keccak-256 of the deposit key
    /// (see [`evm_bridge::DepositKey::hash`]).
    async fn is_deposit_processed(&self, hash: String) -> bool {
        let bytes: [u8; 32] = hex::decode(hash.strip_prefix("0x").unwrap_or(&hash))
            .expect("invalid hex")
            .try_into()
            .expect("hash must be 32 bytes");
        self.state
            .processed_deposits
            .contains(&bytes)
            .await
            .expect("failed to check processed deposits")
    }

    /// Verifies that the given EVM block hash is finalized on the source chain.
    ///
    /// Makes the EVM JSON-RPC calls in the service runtime so that the contract
    /// sees a single deterministic oracle response (the boolean result) instead
    /// of multiple raw HTTP responses with non-deterministic headers.
    async fn is_block_hash_finalized(&self, block_hash: String) -> bool {
        let bytes: [u8; 32] = hex::decode(block_hash.strip_prefix("0x").unwrap_or(&block_hash))
            .expect("invalid hex")
            .try_into()
            .expect("hash must be 32 bytes");
        let rpc_endpoint = self.state.rpc_endpoint.get().clone();

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Compute the hash exactly as documented: keccak-256 of the DepositKey, then hex::encode — always 32 bytes
  2. Validate length client-side: /^0x[0-9a-fA-F]{64}$/ (64 hex chars = 32 bytes)
  3. Preserve leading zeros when converting hex to avoid short values
  4. In a fork of the service, return a GraphQL error instead of panicking on try_into failure

Example fix

# before — 31 bytes after decoding (leading zero dropped by a bigInt path)
query { isDepositProcessed(hash: "0xfa851..." ) }  # 62 hex chars -> panic "hash must be 32 bytes"

# after
fn to_query_hash(key: &DepositKey) -> String { format!("0x{}", hex::encode(key.hash())) } # always 64 hex chars
# then: query { isDepositProcessed(hash: to_query_hash(key)) }
Defensive patterns

Strategy: validation

Validate before calling

// Enforce exact 32-byte length client-side (64 hex chars):
let h = hash.strip_prefix("0x").unwrap_or(&hash);
if h.len() != 64 || !h.chars().all(|c| c.is_ascii_hexdigit()) {
    return Err(format!("expected 32-byte hex hash, got {hash:?}"));
}
query_is_deposit_processed(hash).await

Type guard

fn is_32_byte_hex(s: &str) -> bool {
    let h = s.strip_prefix("0x").unwrap_or(s);
    h.len() == 64 && h.chars().all(|c| c.is_ascii_hexdigit())
}

Try / catch

// Check errors[] for 'hash must be 32 bytes' and correct the producer of the
// hash (usually a leading-zero-stripping conversion) rather than the query site.

Prevention

When it happens

Trigger: Passing a 20-byte address, a 64-char-with-prefix-but-31-byte hash, a truncated hash (e.g. first 16 bytes for a short key), or hex of a DepositKey struct that is not 32 bytes; concatenating a prefix and hash incorrectly so byte count is off.

Common situations: Clients that store hashes in variable-length columns and trim zero bytes; using the deposit key's raw fields (chain id + u32) hashed with a non-keccak or truncated algorithm; frontend bigInt-to-hex conversions that drop leading zeros.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/c649168d6013d15d. Report an issue: GitHub.