LocalTransactionProver::prove calls miden_prover::prove_sync, the final-proof API, so every local prove also generates the precompile-VM STARK for its deferred claims. Precompile proving is meant to happen at batch level; the local prover should call prove_partial_sync and ship the claims as a wire.
Symptom. A locally proven transaction whose auth verifies ECDSA signatures carries DeferredProof::Stark (~277 KB) next to the ~100 KB VM STARK, and that precompile STARK is 55-72% of local prove time.
Repro. Prove any ECDSA-authenticated transaction with LocalTransactionProver::default() and match on proven.proof().deferred_proof(): it is Stark { .. }. Cause: crates/miden-tx/src/prover/mod.rs:17 imports prove_sync and :148 calls it (same on next and 0.16.0-rc.9). prove_partial_sync is the documented API for claims "proved later by a delegated or batching prover" (miden-prover 0.29.4, src/lib.rs:164).
Expected. DeferredProof::Wire; the claims are checked natively by the verifier and proven at batch level. On next the verifier side already exists: TransactionVerifier handles PrecompileStatus::Deferred via validate_deferred_witness (crates/miden-protocol/src/transaction/verifier.rs:76-95), so there the fix is the prover switch plus tests.
Impact. Browser local proving (MT wasm, 6 threads) is 3.5x slower than necessary for guarded accounts and 2.8x for single-sig: with the one-line switch, guarded 7.99 s to 2.27 s and single-sig 4.58 s to 1.66 s; native guarded 1.68 s to 0.63 s. The proven transaction shrinks from 391 KB to 104 KB.
On 0.16.0-rc.9 (VM 0.29.4) the switch alone is not deployable: miden_verifier::verify returns UnsupportedDeferredProof for Wire, and both the node RPC (crates/rpc/src/server/api/submit_proven_tx.rs:108) and the validator (bin/validator/src/tx_validation/mod.rs:52) call TransactionVerifier::verify, so a client cannot opt in. Fixing 0.16 needs TransactionVerifier to accept partial proofs (Verifier::verify_partial exists in 0.29.4; rehydrating the wire re-evaluates every node under the registry, so native settlement is a root check) plus the node picking it up.
Evidence: runtime probes (0.16.0-rc.9 protocol crates, VM 0.29.4, web-sdk 0.16.0-rc.7)
Native, LocalTransactionProver::default().prove(executed), printing proven.proof().deferred_proof():
# 2 ECDSA claims (guarded multisig: 1 approver at threshold 1 + guardian)
tx: total_cycles=29095 auth_procedure=19847
DEFERRED_PROOF Stark { proof: 276768 bytes, public_root: Word([10652471043400787702, 12148757343284257120, 17083288024192604590, 7234156251587950621]) }
VM_PROOF 101663 bytes
# 1 ECDSA claim (BasicAuth)
tx: total_cycles=17720 auth_procedure=9865
DEFERRED_PROOF Stark { proof: 283680 bytes, public_root: Word([13415207134405930995, 16485817212495615892, 12092707907113953811, 2871210035726898790]) }
VM_PROOF 99711 bytes
# same two shapes after switching miden-tx to prove_partial_sync
DEFERRED_PROOF Wire (partial, claims NOT proven)
VM_PROOF 100447 bytes # 2 claims
VM_PROOF 100895 bytes # 1 claim
Browser, client.proveTransaction(txResult, TransactionProver.newLocalProver()), proven.serialize().length:
single-sig provenTxBytes 378589
guarded provenTxBytes 391045
guarded, prove_partial_sync build provenTxBytes 104118
Evidence: code pointers
crates/miden-tx/src/prover/mod.rs:17 use miden_prover::{ExecutionProof, Word, prove_sync}; and :148 prove_sync( (next at b5cbb34; identical in 0.16.0-rc.9).
- miden-prover 0.29.4
src/lib.rs:123 prove_sync (final), :164 prove_partial_sync, :235-241 prove_deferred_state inside the precompile_vm span, :248-258 the wire variant.
- miden-core 0.29.4
src/proof.rs:261-275 DeferredProof::{Empty, Wire, Stark}.
- miden-verifier 0.29.4
src/lib.rs:177-188 resolve_final_deferred_root: Wire yields UnsupportedDeferredProof; src/lib.rs:123-135 verify_partial returns Unsettled(DeferredState).
- miden-protocol 0.16.0-rc.9
src/transaction/verifier.rs:33 calls miden_verifier::verify (full).
- node
next: crates/rpc/src/server/api/submit_proven_tx.rs:108 and bin/validator/src/tx_validation/mod.rs:52 call TransactionVerifier::new(MIN_PROOF_SECURITY_LEVEL).verify.
- web-sdk
crates/web-client/src/new_transactions.rs:713 LocalTransactionProver::default() for newLocalProver().
Measurements (M5, 4P+6E; native concurrent; browser MT wasm at 6 threads, 9 timed proves per run after a discarded warm-up, medians)
| shape |
prover |
native prove |
browser prove |
proven tx |
| single-sig ECDSA |
prove_sync (today) |
1376 ms |
4584 ms |
379 KB |
| single-sig ECDSA |
prove_partial_sync |
602 ms |
1663 ms |
~104 KB |
| guarded (2 ECDSA claims) |
prove_sync (today) |
1678 ms |
7987 ms |
391 KB |
| guarded (2 ECDSA claims) |
prove_partial_sync |
630 ms |
2272 ms |
104 KB |
The native precompile_vm span accounts for the whole difference (744 ms and 998 ms respectively; 0 ms with the partial API). The guarded premium over single-sig drops from 302 ms to 28 ms, since only the main trace remains and both shapes pad to 2^15 rows.
The one-line switch used for the "partial" rows, applied to 0.16.0-rc.9 and reverted afterwards:
-use miden_prover::{ExecutionProof, Word, prove_sync};
+use miden_prover::{ExecutionProof, Word, prove_partial_sync};
...
- let (stack_outputs, proof) = prove_sync(
+ let (stack_outputs, proof) = prove_partial_sync(
LocalTransactionProver::provecallsmiden_prover::prove_sync, the final-proof API, so every local prove also generates the precompile-VM STARK for its deferred claims. Precompile proving is meant to happen at batch level; the local prover should callprove_partial_syncand ship the claims as a wire.Symptom. A locally proven transaction whose auth verifies ECDSA signatures carries
DeferredProof::Stark(~277 KB) next to the ~100 KB VM STARK, and that precompile STARK is 55-72% of local prove time.Repro. Prove any ECDSA-authenticated transaction with
LocalTransactionProver::default()and match onproven.proof().deferred_proof(): it isStark { .. }. Cause:crates/miden-tx/src/prover/mod.rs:17importsprove_syncand:148calls it (same onnextand 0.16.0-rc.9).prove_partial_syncis the documented API for claims "proved later by a delegated or batching prover" (miden-prover 0.29.4,src/lib.rs:164).Expected.
DeferredProof::Wire; the claims are checked natively by the verifier and proven at batch level. Onnextthe verifier side already exists:TransactionVerifierhandlesPrecompileStatus::Deferredviavalidate_deferred_witness(crates/miden-protocol/src/transaction/verifier.rs:76-95), so there the fix is the prover switch plus tests.Impact. Browser local proving (MT wasm, 6 threads) is 3.5x slower than necessary for guarded accounts and 2.8x for single-sig: with the one-line switch, guarded 7.99 s to 2.27 s and single-sig 4.58 s to 1.66 s; native guarded 1.68 s to 0.63 s. The proven transaction shrinks from 391 KB to 104 KB.
On 0.16.0-rc.9 (VM 0.29.4) the switch alone is not deployable:
miden_verifier::verifyreturnsUnsupportedDeferredProofforWire, and both the node RPC (crates/rpc/src/server/api/submit_proven_tx.rs:108) and the validator (bin/validator/src/tx_validation/mod.rs:52) callTransactionVerifier::verify, so a client cannot opt in. Fixing 0.16 needsTransactionVerifierto accept partial proofs (Verifier::verify_partialexists in 0.29.4; rehydrating the wire re-evaluates every node under the registry, so native settlement is a root check) plus the node picking it up.Evidence: runtime probes (0.16.0-rc.9 protocol crates, VM 0.29.4, web-sdk 0.16.0-rc.7)
Native,
LocalTransactionProver::default().prove(executed), printingproven.proof().deferred_proof():Browser,
client.proveTransaction(txResult, TransactionProver.newLocalProver()),proven.serialize().length:Evidence: code pointers
crates/miden-tx/src/prover/mod.rs:17use miden_prover::{ExecutionProof, Word, prove_sync};and:148prove_sync((nextat b5cbb34; identical in 0.16.0-rc.9).src/lib.rs:123prove_sync(final),:164prove_partial_sync,:235-241prove_deferred_stateinside theprecompile_vmspan,:248-258the wire variant.src/proof.rs:261-275DeferredProof::{Empty, Wire, Stark}.src/lib.rs:177-188resolve_final_deferred_root:WireyieldsUnsupportedDeferredProof;src/lib.rs:123-135verify_partialreturnsUnsettled(DeferredState).src/transaction/verifier.rs:33callsmiden_verifier::verify(full).next:crates/rpc/src/server/api/submit_proven_tx.rs:108andbin/validator/src/tx_validation/mod.rs:52callTransactionVerifier::new(MIN_PROOF_SECURITY_LEVEL).verify.crates/web-client/src/new_transactions.rs:713LocalTransactionProver::default()fornewLocalProver().Measurements (M5, 4P+6E; native concurrent; browser MT wasm at 6 threads, 9 timed proves per run after a discarded warm-up, medians)
prove_sync(today)prove_partial_syncprove_sync(today)prove_partial_syncThe native
precompile_vmspan accounts for the whole difference (744 ms and 998 ms respectively; 0 ms with the partial API). The guarded premium over single-sig drops from 302 ms to 28 ms, since only the main trace remains and both shapes pad to 2^15 rows.The one-line switch used for the "partial" rows, applied to 0.16.0-rc.9 and reverted afterwards: