Skip to content

prove({ prover: newLocalProver() }) is 18-49x slower than prove() with no argument #350

Description

@Mustdzyl

Passing a prover to prove() is around fifty times slower than leaving the argument off, for the same transaction.

I hit this while timing proving for an app, and the gap was large enough that I assumed my measurement was wrong. It reproduces on the same request object, in the same page, back to back.

Repro

const script = await client.compile.txScript({
  code: "use miden::core::sys\nbegin\n  push.1 drop\n  exec.sys::truncate_stack\nend\n",
});
const request = new TransactionRequestBuilder().withCustomScript(script).build();

// A — no prover argument
let staged = await client.transactions.executeRequest(account, request);
await staged.prove();

// B — explicit local prover
const prover = TransactionProver.newLocalProver();
staged = await client.transactions.executeRequest(account, request);
await staged.prove({ prover });

Timings on macOS, 8 cores, Chrome 152, @miden-sdk/miden-sdk@0.15.9 single threaded, testnet:

A  prove()                            1925 ms   1267 ms
B  prove({ prover })                209043 ms  62244 ms

A second run on the same setup gave 2940 / 2726 ms for A and 53242 / 49823 ms for B. The later B call is faster than the first in both runs, so this is not degradation over time. The ratio lands between 18x and 49x depending on the run.

Both paths are local. I checked that by pulling the network down after the client was ready and the transaction executed, then calling prove() with no argument: it completed offline in 1742 ms. So the fast path is not delegating to a remote prover.

Where the paths diverge

proveResult in the SDK sends the two cases to different calls, and defaultProver is null unless the app sets one:

const prover = opts?.prover ?? defaultProver;
return prover
  ? await inner.proveTransaction(result, prover)
  : await inner.proveTransaction(result);

The two branches resolve different provers: custom_prover.get_prover() versus client.prover(). newLocalProver() builds LocalTransactionProver::new(ProvingOptions::default()). I could not work out from here what client.prover() resolves to, so I am reporting the measurement rather than guessing at the cause.

This may be the same routing asymmetry described in #312, though that one is about batch() and the main thread, and here both calls go through the worker wrapper.

Why it matters

newLocalProver() reads as the ordinary way to prove locally, and an app that wants local proving will reach for it. Nothing in the README or the API surface suggests the argument changes performance by this much. A note in the docs would help even if the behaviour is intended.

Environment: macOS, Chrome 152, node 24, pnpm 10.18, SDK 0.15.9, testnet.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions