Deterministic Random Number in EVM for Independent Recomputability

Hey everyone :waving_hand:

Ran into a problem that feels like a real gap in the EVM, curious if it’s been discussed before.

The core problem

We’re building a trustless AI agent system. Every claim an agent makes — verification results, settlement outcomes, audit records — should be independently verifiable by anyone with access to the data, without interacting with the chain, without chain-specific logic. You get the data. You verify. No RPC, no indexer, no “trust the contract.”

To do that, every on-chain record needs an identifier that a third party can recompute purely from the data they already hold. But here’s the catch: there is currently no way for a contract to generate a unique, reorg-resistant, independently-recomputable identifier purely from within the EVM — no external data, no tx hash, no log index, no off-chain oracle.

Concretely: we need to produce a credential inside the contract and hash it in the same execution — the identifier must be independently recomputable from data alone, without touching a chain endpoint. The difficulty is that the EVM gives us no primitive that is simultaneously unique per execution position, resistant to reorg, and derivable without external references. We cannot use txHash or log index — they are not accessible from within Solidity.

Today, nothing in Solidity gives you all three properties at once:

  1. Unique per execution position. Two calls at different code positions in the same transaction — different call depths, different storage states — need different identifiers. Not a per-transaction value, not a block-level value. Tied to where the code is executing and what state it observes.
  2. Reorg-resistant. If the chain reorgs, the same execution position on the new fork must produce a different identifier. A per-contract nonce fails here: it replays identically.
  3. Independently recomputable. A third party must be able to re-derive the same identifier from on-chain data alone, without access to the tx hash or log index (neither is accessible in Solidity anyway).

Existing primitives each miss at least one:

  • block.timestamp, block.number — don’t change on reorg at the same height
  • block.prevrandao — block-level, same for every tx in the block; can’t disambiguate calls within a block, and identical at the same slot across a reorg
  • BLOCKHASH(block.number) — always returns 0
  • Per-contract nonce — based on call count, not execution position; replays identically on reorg. Worse: inserting one extra verify() shifts every subsequent nonce, so previously generated identifiers can no longer be independently recomputed without knowing the exact call order.
  • Transaction hash / log index — not accessible from within a contract

The gap is real — a system built on portable, independently-recomputable identifiers can’t anchor them properly without this.

What would help

Two levels, which could be solved together or separately:

  • Block-level: a value that’s deterministic within a block but changes on reorg — e.g. a BLOCKID opcode hashing (chainid, block.number, block.timestamp, block.basefee, block.gaslimit).
  • Execution-position level: within the same block, two calls at different code positions in the same contract must diverge — achieved by binding the identifier to the contract’s current state at each call site (e.g. folding in a relevant storage slot or the contract’s state root), not to a call counter. The state changes naturally as the transaction runs; no nonce is needed, and a reorg replays the state, producing fresh identifiers.

Whether this belongs in the protocol or at the application layer is what I’d love the community’s take on. Is a BLOCKID-like primitive worth a hard fork, or is the right answer to keep every digest bound to its event and never detach it — accepting that independent recomputation from data alone isn’t always possible today?

We hit structurally the same tension off-chain, hours ago, working through a post-quantum key-binding with the trustless-ai group: a detached identifier (an in-object tag, a call-counter nonce) is always re-mintable, because retrofitting it changes nothing about the content it’s supposed to identify. Our answer converged on “the identifier is bound to its own content, never detached, and the ONLY thing external is the anchor proof-of-existence” — same shape as your second framing option, and for the same reason: a nonce/counter is a claim ABOUT the execution, not derived FROM it.

On the reorg leg specifically, a possible reframe: does the identifier need to exist synchronously, inside the same transaction (e.g., another call in the same tx needs to reference it)? If not, you may not need a BLOCKID opcode at all. Split the two properties across two different actors instead of one atomic opcode: the contract only needs to emit something unique-per-execution-position DURING execution (your own storage-fold proposal solves this cleanly, no nonce needed) – it does NOT need to be reorg-resistant at that moment. Reorg-resistance gets added later, by whoever recomputes the canonical id, folding in blockhash(minedBlockNumber) once that block is buried past your finality-confidence depth (blockhash is readable for the last 256 blocks, and by definition a block that deep is astronomically unlikely to reorg). You get a two-phase identifier – provisional during execution, canonical after finality – which is exactly the pending → confirmed shape every anchor-based proof system already uses (ours included, OTS pending → Bitcoin-confirmed, live today). No hard fork, and it composes with what you’re already building rather than needing new consensus.

If synchronous same-tx availability IS a hard requirement for your system, that reframe doesn’t help and the opcode discussion is the real one – worth saying which case you’re actually in, since the two have very different answers.

1 Like

@babyblueviper1 The two-phase approach is a real and workable path — and the pending → confirmed shape already being live in your OTS setup is strong evidence. For the current state of the stack, it absolutely works.

That said, it’d be even nicer if a BLOCKID-like primitive existed someday — then synchronous, same-transaction identifiers could be fully self-contained: a credential generated and hashed entirely inside the contract, carrying everything a consumer needs for verification within itself, no external EVM data, no awareness of which chain or system produced it. That’s the shape independent recomputation naturally wants: you get the digest, you recompute it, done. Two-phase gets you there eventually; one-phase gets you there immediately. What we have now is good — a primitive would make it great.

One clarification on the requirement itself: the random field in this scenario isn’t needed for its verifiability — there’s no need to prove that the field was legitimately produced. It’s needed purely for uniqueness — the guarantee that the same execution under a reorg produces a different value. A per-contract nonce fails exactly there. Whether the value came from a trusted source is irrelevant; whether it collides with its reorg-sibling is everything.

Appreciate the thoughtful pushback — this is exactly the kind of conversation the post was meant to start :saluting_face:

Sharp clarification – that reframes the actual requirement precisely: not “trustworthy,” but “diverges across reorg-siblings.” Worth naming: the EVM might already have exactly the primitive you’re describing, synchronous and self-contained.

Since the merge, PREVRANDAO (EIP-4399, block.prevrandao in Solidity) is a native, in-block, same-transaction source that genuinely varies across a reorg – it’s derived from the beacon chain’s RANDAO mix at that specific slot, so a reorged block at the same height gets a different prevrandao value (different slot, different validator), while a deterministic per-contract nonce replays identically on both branches of the fork. That’s exactly the property a nonce fails at.

The remaining piece is just within-block uniqueness (every call in the same block sees the same prevrandao) – but that’s solvable with something already execution-derivable and reorg-invariant on its own (a same-block call-position counter, the tx index, keccak(msg.data)). The counter alone fails at reorg-divergence (which you just named); prevrandao alone fails at within-block uniqueness; combined – hash(block.prevrandao, positionCounter) – both properties land inside one synchronous, same-transaction hash. No external anchor, no two-phase wait, no new opcode.

Would that combination actually satisfy the credential shape you’re picturing, or is there a specific reason PREVRANDAO doesn’t fit here? The usual caveat against it is validator grinding/manipulation, but that’s normally framed as a risk for high-stakes selection/lottery use cases specifically (where the manipulator profits from steering the value), not identifier-uniqueness ones like this, where the manipulator has nothing to gain from which specific value lands – only from whether it’s non-colliding, which they can’t control either way.