{"record":{"id":"c649168d6013d15d","repo":"linera-io/linera-protocol","slug":"hash-must-be-32-bytes","errorCode":null,"errorMessage":"hash must be 32 bytes","messagePattern":"hash must be 32 bytes","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"linera-bridge/contracts/evm-bridge/src/service.rs","lineNumber":197,"sourceCode":"        let params: BridgeParameters = self.runtime.application_parameters();\n        format!(\"0x{}\", hex::encode(params.token_address))\n    }\n\n    /// The configured EVM JSON-RPC endpoint, or empty if finality verification\n    /// is disabled.\n    async fn rpc_endpoint(&self) -> String {\n        self.state.rpc_endpoint.get().clone()\n    }\n\n    /// Whether a deposit with the given hash has been processed.\n    ///\n    /// The hash is the hex-encoded keccak-256 of the deposit key\n    /// (see [`evm_bridge::DepositKey::hash`]).\n    async fn is_deposit_processed(&self, hash: String) -> bool {\n        let bytes: [u8; 32] = hex::decode(hash.strip_prefix(\"0x\").unwrap_or(&hash))\n            .expect(\"invalid hex\")\n            .try_into()\n            .expect(\"hash must be 32 bytes\");\n        self.state\n            .processed_deposits\n            .contains(&bytes)\n            .await\n            .expect(\"failed to check processed deposits\")\n    }\n\n    /// Verifies that the given EVM block hash is finalized on the source chain.\n    ///\n    /// Makes the EVM JSON-RPC calls in the service runtime so that the contract\n    /// sees a single deterministic oracle response (the boolean result) instead\n    /// of multiple raw HTTP responses with non-deterministic headers.\n    async fn is_block_hash_finalized(&self, block_hash: String) -> bool {\n        let bytes: [u8; 32] = hex::decode(block_hash.strip_prefix(\"0x\").unwrap_or(&block_hash))\n            .expect(\"invalid hex\")\n            .try_into()\n            .expect(\"hash must be 32 bytes\");\n        let rpc_endpoint = self.state.rpc_endpoint.get().clone();","sourceCodeStart":179,"sourceCodeEnd":215,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/contracts/evm-bridge/src/service.rs#L179-L215","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before — 31 bytes after decoding (leading zero dropped by a bigInt path)\nquery { isDepositProcessed(hash: \"0xfa851...\" ) }  # 62 hex chars -> panic \"hash must be 32 bytes\"\n\n# after\nfn to_query_hash(key: &DepositKey) -> String { format!(\"0x{}\", hex::encode(key.hash())) } # always 64 hex chars\n# then: query { isDepositProcessed(hash: to_query_hash(key)) }","handlingStrategy":"validation","validationCode":"// Enforce exact 32-byte length client-side (64 hex chars):\nlet h = hash.strip_prefix(\"0x\").unwrap_or(&hash);\nif h.len() != 64 || !h.chars().all(|c| c.is_ascii_hexdigit()) {\n    return Err(format!(\"expected 32-byte hex hash, got {hash:?}\"));\n}\nquery_is_deposit_processed(hash).await","typeGuard":"fn is_32_byte_hex(s: &str) -> bool {\n    let h = s.strip_prefix(\"0x\").unwrap_or(s);\n    h.len() == 64 && h.chars().all(|c| c.is_ascii_hexdigit())\n}","tryCatchPattern":"// Check errors[] for 'hash must be 32 bytes' and correct the producer of the\n// hash (usually a leading-zero-stripping conversion) rather than the query site.","preventionTips":["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"],"tags":["linera","bridge","graphql","hash","input-length","panic"],"backgroundTag":"invalid-hash-length","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}