Precompile Summary

AddressNameGasInputOutputBehavior
0x09BLAKE2Frounds (first 4 bytes of input, big-endian uint32) × 1 — i.e., gas equals the number of compression roundsExactly 213 bytes: rounds (4 bytes, big-endian uint32), h (64 bytes, state vector), m (128 bytes, message block), t_0 (8 bytes, offset counter low), t_1 (8 bytes, offset counter high), f (1 byte, finalization flag: 0 or 1)64 bytes (updated state vector h after one compression)Executes the BLAKE2b compression function F. This is not a complete hash: callers must implement full BLAKE2b (padding, counter updates across blocks, correct initial h, finalization) by iterating F. Introduced at Istanbul (EIP-152) mainly for Ethereum–Zcash interoperability (e.g., Equihash PoW verification).

Threat Surface

BLAKE2F exposes a low-level cryptographic primitive on-chain: a single invocation runs one BLAKE2b compression round schedule controlled entirely by caller-supplied state (h, m), counters (t_0, t_1), round count, and finalization bit (f). Unlike precompiles that implement end-to-end algorithms (for example, SHA256 over arbitrary-length input), BLAKE2F shifts all algorithmic correctness — padding, byte counting, last-block handling, and parameter initialization — to the smart contract or off-chain prover. That split is the dominant security story: a “working” integration can still be cryptographically meaningless if any BLAKE2b construction rule is violated.

The surface also has economic and operational dimensions. Gas is linear in a 32-bit rounds field chosen by the caller, which is unlike most precompiles (fixed gas or length-based pricing). That makes estimation, forwarding gas, and griefing first-class concerns whenever rounds is even partly user-influenced. Finally, the precompile has seen relatively low mainnet use (mostly Zcash-related paths), so implementations and audits encounter fewer shared test vectors and production footguns than SHA256 or ECRECOVER, increasing the odds that client or application bugs persist longer.


Smart Contract Threats

T1: Gas Cost Manipulation via Round Count (Medium)

  • Unbounded nominal gas from rounds. Gas charged equals the rounds value interpreted as a big-endian uint32. An attacker can choose rounds = 0xFFFFFFFF (4,294,967,295), implying ~4.3 billion gas for the precompile alone — far beyond any block gas limit, so the transaction fails rather than draining the chain. The practical risk is targeted griefing and brittle integrations, not network-wide execution of that call.
  • User-controlled rounds without validation. If a contract forwards calldata or memory from an untrusted party into BLAKE2F without bounding or whitelisting rounds, callers can force reverts or exhaust gas budgets in nested calls, breaking liveness or enabling denial-of-service against dependent workflows.
  • Fixed gas budgets on staticcall. Contracts that cap gas forwarded to 0x09 assuming a small constant number of rounds can fail intermittently when input supplies a larger rounds, causing unexpected reverts or failed proof verification even when the rest of the logic is sound.

Why it matters: BLAKE2F ties execution cost to a single attacker-controlled scalar. Misconfigured verification or parsing pipelines turn that knob into a reliable griefing primitive and makes gas estimation harder than for length-priced precompiles.

T2: Incorrect BLAKE2b Construction (High)

  • Compression-only primitive. BLAKE2F is the internal F function, not BLAKE2b hashing. A correct caller must implement message padding (last block padded to 128 bytes per spec), maintain total bytes hashed in t_0 / t_1, set f = 1 only on the last block, and initialize h (and other BLAKE2b parameters) per the standard. Omitting or reordering these steps yields deterministic but wrong “hashes.”
  • False sense of security. Wrong constructions can still produce fixed-length outputs that look like hashes in Solidity (e.g., truncated or compared as bytes32). Downstream assumptions — uniqueness, collision resistance, binding to a specific message — can fail without any revert from the precompile.
  • Interop and proof verification. Systems verifying Zcash Equihash or other BLAKE2b-based witnesses must match exact serialization and block splitting. Any deviation accepts invalid proofs or rejects valid ones, depending on how checks are written.

Why it matters: The protocol gives you a correct F; it does not give you a correct BLAKE2b. Application-level mistakes silently destroy cryptographic meaning, which is especially severe when BLAKE2F gates proof acceptance or cross-chain claims.

T3: Non-Aligned Input Data Bug (Implementation / Historical) (Medium)

  • Client memory layout sensitivity. EthereumJS had a consensus-relevant bug where BLAKE2F could return incorrect results when the 213-byte input was not laid out at offset 0 of the backing Uint8Array slice. DataView was constructed without accounting for byteOffset, so reads could target the wrong physical bytes, corrupting both gas accounting (from the rounds field) and the cryptographic computation.
  • Cross-client divergence risk. Any bug of this class — interpreting buffer slices incorrectly — can produce fork-level disagreement between nodes until fixed and adopted network-wide.

Why it matters: Even a “pure” precompile is only trustworthy if the VM’s memory and buffer plumbing matches the spec on every client. Slice/offset bugs are easy to introduce in managed-language EVM implementations and hard to catch without dedicated differential tests.

T4: Finalization Flag Misuse (Medium)

  • Domain of f. The final byte must be 0 or 1. If f > 1, the call fails and returns empty output (no 64-byte result), consistent with other precompile failure modes.
  • f = 0 on the last block. Using f = 0 on what should be the terminating compression yields an intermediate state, not a BLAKE2b output. Verifiers that treat that state as a final digest can accept non-finalized or malleable representations, depending on surrounding logic.
  • Proof systems. Equihash and similar constructions depend on exact BLAKE2b semantics. Finalization errors break the mapping between on-chain F invocations and the intended hash.

Why it matters: f is a single byte but encodes algorithm phase. Getting it wrong is a semantic bug with no cryptographic safety net: the precompile faithfully runs F; it does not know whether your block was truly “last.”


Protocol-Level Threats

P1: EthereumJS Consensus Bug from Input Data Misalignment (PR #3201, December 2023)

DataView initialization in the TypeScript EVM did not account for byteOffset when reading the BLAKE2F input from a subarray of contract memory. Depending on where the 213 bytes lived in the underlying buffer, clients could compute different outputs and gas than spec-conforming implementations. The issue was fixed in ethereumjs/ethereumjs-monorepo#3201.

P2: Unusual Gas Model — Cost Tied to rounds, Not Input Length

Most precompiles use fixed gas or pricing derived from input size. BLAKE2F’s gas is rounds × 1, independent of how “heavy” the 128-byte message block is in a bitwise sense. That complicates static gas analysis, EIP-1559 estimation, and layer-2 metering assumptions that treat precompiles as predictable from calldata length alone.

P3: Low Usage and Weaker Shared Scrutiny

Primary motivation was Zcash interoperability; on-chain call volume is low compared to SHA256 or identity-style precompiles. Fewer production paths means fewer public audits, fuzz corpora, and incident lessons, so both client and application edge cases may remain latent longer than for heavily exercised precompiles.


Edge Cases

Edge CaseBehaviorSecurity Implication
rounds = 00 gas; returns input h unchanged (no compression work)Callers must not assume “non-trivial hashing” occurred; can be abused to waste logic elsewhere or to bypass assumed work if higher layers misinterpret the outcome
rounds = 0xFFFFFFFFGas requirement ~4.3B; exceeds block limitTransaction cannot succeed on mainnet; usable for griefing if a contract surfaces untrusted rounds
f = 0Non-final compression; intermediate hMust not be treated as BLAKE2b output; verifiers can accept wrong PoW or proof material if they equate intermediate state with a digest
f = 1Final compression for that blockRequired on the true last block per BLAKE2b; omitting f = 1 on earlier duplicate-looking paths breaks standard semantics
f > 1Call fails; empty returnSolidity callers must check output length; treating empty as success can desynchronize verification
Input length ≠ 213 bytesCall fails; empty returnRobust contracts must validate length before interpreting success
Input shorter than 213 bytes zero-padded by callerPadding becomes part of h / m / countersSilent wrong compression inputs — classic memory-padding bug class
h all-zero (or other non-standard initial state)Still a well-formed call if lengths and f are validUnusual parameterization may be invalid BLAKE2b at the algorithm level even though F executes

Real-World Exploits

Exploit 1: EthereumJS BLAKE2F Misalignment (2023)

  • Root cause: Incorrect DataView construction over a Uint8Array view with non-zero byteOffset, so BLAKE2F read misaligned bytes for rounds, h, m, and counter fields. This corrupted both gas derived from rounds and the cryptographic state update.
  • Details: The bug manifested when BLAKE2F input bytes were not at the start of the underlying ArrayBuffer-backed buffer — a normal situation for subarrays and certain memory copy paths in JS VMs. Other clients using correct offset handling disagreed with affected EthereumJS versions on the same transaction trace.
  • Precompile’s role: BLAKE2F’s specification is deterministic; the failure was entirely in client buffer wiring, not in the on-chain address or interface. Nonetheless, any incorrect F implementation is a consensus hazard.
  • Impact: Potential chain split between fixed and unfixed nodes for transactions invoking 0x09 under triggering layouts; incorrect local simulation for tools built on the buggy stack.
  • References:

Attack Scenarios

Scenario A: Gas Griefing via User-Controlled rounds

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
/**
 * @notice Anti-pattern: forwards untrusted `rounds` into BLAKE2F without bounds.
 * Attacker sets rounds = 0xFFFFFFFF to force huge gas requirement or revert.
 */
contract VulnerableBlake2fRouter {
    address constant BLAKE2F = address(0x09);
 
    function verifyWithUntrustedRounds(
        bytes calldata untrusted213 // caller claims this is valid BLAKE2F input
    ) external view returns (bytes memory) {
        require(untrusted213.length == 213, "len");
        // VULNERABLE: first 4 bytes (rounds) are attacker-controlled
        (bool ok, bytes memory out) = BLAKE2F.staticcall(untrusted213);
        require(ok, "call");
        return out;
    }
}
 
// Attack: submit untrusted213 with rounds = 0xFFFFFFFF (big-endian).
// Staticcall requires ~4.29e9 gas → always fails under block gas limit,
// griefing any workflow that must complete this call.

Scenario B: Incorrect BLAKE2b Construction — Missing Finalization on Last Block

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
 
/**
 * @notice Simplified sketch: single F invocation with f=0 even when this is the last block.
 * Real BLAKE2b requires f=1 on the final block; otherwise h is NOT the hash output.
 */
contract WrongFinalizationExample {
    address constant BLAKE2F = address(0x09);
 
    function compressLastBlockWrong(
        bytes memory h64,
        bytes memory m128,
        uint64 t0,
        uint64 t1,
        uint32 rounds
    ) internal view returns (bytes memory hOut) {
        require(h64.length == 64 && m128.length == 128, "sizes");
        bytes memory input = new bytes(213);
        // rounds (BE)
        input[0] = bytes1(uint8(rounds >> 24));
        input[1] = bytes1(uint8(rounds >> 16));
        input[2] = bytes1(uint8(rounds >> 8));
        input[3] = bytes1(uint8(rounds));
        // copy h, m
        for (uint256 i; i < 64; i++) input[4 + i] = h64[i];
        for (uint256 j; j < 128; j++) input[68 + j] = m128[j];
        // t0, t1 little-endian in BLAKE2b; illustrative only
        for (uint256 k; k < 8; k++) input[196 + k] = bytes1(uint8(t0 >> (8 * k)));
        for (uint256 k; k < 8; k++) input[204 + k] = bytes1(uint8(t1 >> (8 * k)));
        // VULNERABLE: last block but f = 0 (non-final)
        input[212] = 0x00;
 
        (bool ok, bytes memory out) = BLAKE2F.staticcall(input);
        require(ok && out.length == 64, "blake2f");
        return out;
    }
}
 
// Impact: any equality check against an expected BLAKE2b digest will fail OR,
// if a custom verifier mistakenly treats intermediate h as final, invalid proofs
// could be accepted depending on off-chain expectations.

Mitigations

ThreatMitigationImplementation
T1: Gas / griefing via roundsHard-cap rounds to the BLAKE2b constant (12 for BLAKE2b) or to your protocol’s required countrequire(rounds == 12, "rounds"); after parsing the first 4 bytes; reject unknown values
T1: Gas forwardingForward explicit gas to 0x09 based on known roundsBLAKE2F.staticcall{gas: rounds + buffer}(input) after validating rounds; never trust user-supplied gas budgets for critical paths
T2: Wrong BLAKE2bReuse audited BLAKE2b implementations and test vectorsPrefer off-chain hashing with on-chain verification only where spec matches; if composing F on-chain, mirror RFC 7693 and Zcash reference tests byte-for-byte
T2: Proof verificationPin exact serialization (endianness, padding, counter progression)Differential-test your contract against a reference implementation; include negative tests with wrong padding and wrong f
T3: Client bugsRun multi-client fuzzing / differential testing for 0x09Include non-zero memory offsets in VM tests; track security advisories for your execution client
T4: f misuseEncode f in a single type-safe pathReject invalid flags: only allow f == 0 or f == 1 before staticcall; centralize last-block handling so f cannot be set incorrectly
Input lengthReject anything except 213 bytesrequire(input.length == 213) before call; do not manually zero-pad shorter buffers into F

Compiler/EIP-Based Protections

  • EIP-152 (Istanbul): Defines the 0x09 precompile interface, exact 213-byte layout, gas formula (rounds × 1), and the BLAKE2b F function semantics used by the EVM.
  • Solidity / high-level languages: No first-class blake2f opcode; callers use staticcall — compilers do not automatically enforce BLAKE2b construction rules. Use libraries and formalized specs rather than ad hoc byte assembly.
  • Testing culture: Prefer shared vectors from BLAKE2b and Zcash/Equihash references; add regression tests for empty output (failed precompile) vs 64-byte success.

Severity Summary

Threat IDCategorySeverityLikelihoodReal-World Precedent
T1Smart ContractMediumMediumGriefing-style patterns (oversized parameters causing failure) are common; BLAKE2F’s rounds field is an unusually direct gas amplifier
T2Smart ContractHighMediumMany “wrong hash construction” bugs in crypto integrations; BLAKE2F raises the baseline complexity because F is not a full hash
T3Protocol / ClientMediumLow (post-fix)EthereumJS BLAKE2F misalignment (PR #3201) demonstrated real consensus risk class
T4Smart ContractMediumMediumFinal-byte phase errors are a known footgun in multi-block hash APIs
P1ProtocolHighLow (patched)Concrete client bug with consensus implications until remediated and deployed
P2ProtocolLowN/ADesign characteristic: complicates estimation rather than directly stealing funds
P3OperationalMediumN/ALow mainnet utilization reduces exposure but also reduces shared scrutiny

PrecompileRelationship
SHA256 (0x02)Full Merkle-Damgård hash over arbitrary-length input; contrasts with BLAKE2F exposing only a single BLAKE2b compression. Useful mental model: end-to-end hash vs internal round function.
RIPEMD160 (0x03)Another full hash precompile; same high-level lesson — different construction rules and length semantics than BLAKE2F.
IDENTITY (0x04)Often appears in data-prep pipelines; incorrect copying or slicing before BLAKE2F can feed wrong m or counters.
POINT_EVALUATION (0x0A)Another cryptographic primitive precompile (polynomial evaluation at a point) used in modern verification stacks; like BLAKE2F, correct surrounding algebra and encoding is caller responsibility.