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
- Compute the hash exactly as documented: keccak-256 of the DepositKey, then hex::encode — always 32 bytes
- Validate length client-side: /^0x[0-9a-fA-F]{64}$/ (64 hex chars = 32 bytes)
- Preserve leading zeros when converting hex to avoid short values
- 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
- Never convert hashes through BigNumber/bigint; keep them as hex strings
- Store hashes in fixed 64-char columns
- Unit-test hash formatting around values with leading zero bytes
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
- invalid hex
- RegisterFungibleBridge requires an authenticated signer
- SetRpcEndpoint requires an authenticated signer
- failed to query chain ID from RPC endpoint
- invalid block header RLP
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/c649168d6013d15d.
Report an issue: GitHub.