The Arkade VM enforces script size (MaxScriptSize = 10000), stack size (MaxStackSize = 1000), and BIP-342's per-input sigops budget — but it does not enforce a per-script op-count limit. BIP-342 dropped that limit on purpose.
That trade-off is sound for plain Bitcoin tapscript, where the per-call cost of every opcode is small or already charged against the sigops budget.
Several heavy opcodes added to Arkade do not yet fit that model. They have per-call costs orders of magnitude above other opcodes, and the engine charges nothing against the sigops budget for them. They are only constrained by 10 KB script-size cap.
We should pick a design for a compute-budget brake.
What the Engine Charges Today
Only the BIP-342 sigops budget exists, defined at pkg/arkade/engine.go:48-77:
const sigOpsDelta = 50
func newTaprootExecutionCtx(inputWitnessSize int32) *taprootExecutionCtx {
return &taprootExecutionCtx{
sigOpsBudget: sigOpsDelta + inputWitnessSize,
}
}
Starting budget per input is 50 + witness_size_bytes; each charged signature opcode deducts 50.
Charged opcodes today, via callers of tallysigOp():
OP_CHECKSIG / OP_CHECKSIGVERIFY — pkg/arkade/opcode.go:1782
OP_CHECKSIGADD — pkg/arkade/opcode.go:1869
Notably not charged, despite being non-trivial:
OP_CHECKSIGFROMSTACK (~84 µs / call)
OP_ECMULSCALARVERIFY
OP_TWEAKVERIFY
OP_ECADD
OP_ECMUL
OP_ECPAIRING
OP_MODEXP
#79
#80
Measured Per-Call Costs
Apple Silicon, single-threaded:
| Operation |
Per-call |
vs Schnorr verify |
OP_ECADD (any curve) |
1.5 – 3.7 µs |
~0.03× |
OP_ECMUL (256-bit scalar, any curve) |
25 – 45 µs |
0.3 – 0.5× |
Schnorr verify (~OP_CHECKSIGFROMSTACK) |
84 µs |
1× |
OP_ECPAIRING (1 pair) |
314 µs |
3.7× |
OP_ECPAIRING (16 pairs, current max) |
2.04 ms |
24× |
OP_MODEXP (capped at 64-byte operands today) |
~60 µs (extrap.) |
~0.7× |
OP_MODEXP if cap were lifted (520-byte operands) |
13.5 ms |
167× |
OP_ECPAIRING already has a hard cap:
maxECPairingCount = 16
pkg/arkade/ec_ops.go:30
OP_MODEXP has a 64-byte operand cap added purely as a DoS brake:
Both are ad-hoc per-opcode caps rather than a system the next heavy opcode can plug into.
Worst-Case Script CPU Today
Given MaxScriptSize = 10000, no op-count cap, and the per-call costs above, a maximally adversarial script can drive:
- ~5,000
OP_ECADDs → ~15 ms CPU per input
- ~5,000
OP_ECMULs at 256-bit → ~200 ms CPU per input
- ~5,000 max-pair
OP_ECPAIRINGs → ~10 s CPU per input ❗
- ~5,000
OP_MODEXPs with the current 64-byte cap → ~300 ms CPU per input
- ~5,000
OP_MODEXPs if uncapped → ~67 s CPU per input ❗
The Arkade VM enforces script size (
MaxScriptSize = 10000), stack size (MaxStackSize = 1000), and BIP-342's per-input sigops budget — but it does not enforce a per-script op-count limit. BIP-342 dropped that limit on purpose.That trade-off is sound for plain Bitcoin tapscript, where the per-call cost of every opcode is small or already charged against the sigops budget.
Several heavy opcodes added to Arkade do not yet fit that model. They have per-call costs orders of magnitude above other opcodes, and the engine charges nothing against the sigops budget for them. They are only constrained by 10 KB script-size cap.
We should pick a design for a compute-budget brake.
What the Engine Charges Today
Only the BIP-342 sigops budget exists, defined at
pkg/arkade/engine.go:48-77:Starting budget per input is
50 + witness_size_bytes; each charged signature opcode deducts 50.Charged opcodes today, via callers of
tallysigOp():OP_CHECKSIG/OP_CHECKSIGVERIFY—pkg/arkade/opcode.go:1782OP_CHECKSIGADD—pkg/arkade/opcode.go:1869Notably not charged, despite being non-trivial:
OP_CHECKSIGFROMSTACK(~84 µs / call)OP_ECMULSCALARVERIFYOP_TWEAKVERIFYOP_ECADDOP_ECMULOP_ECPAIRINGOP_MODEXP#79
#80
Measured Per-Call Costs
Apple Silicon, single-threaded:
OP_ECADD(any curve)OP_ECMUL(256-bit scalar, any curve)OP_CHECKSIGFROMSTACK)OP_ECPAIRING(1 pair)OP_ECPAIRING(16 pairs, current max)OP_MODEXP(capped at 64-byte operands today)OP_MODEXPif cap were lifted (520-byte operands)OP_ECPAIRINGalready has a hard cap:maxECPairingCount = 16pkg/arkade/ec_ops.go:30OP_MODEXPhas a 64-byte operand cap added purely as a DoS brake:pkg/arkade/opcode.goBoth are ad-hoc per-opcode caps rather than a system the next heavy opcode can plug into.
Worst-Case Script CPU Today
Given
MaxScriptSize = 10000, no op-count cap, and the per-call costs above, a maximally adversarial script can drive:OP_ECADDs → ~15 ms CPU per inputOP_ECMULs at 256-bit → ~200 ms CPU per inputOP_ECPAIRINGs → ~10 s CPU per input ❗OP_MODEXPs with the current 64-byte cap → ~300 ms CPU per inputOP_MODEXPs if uncapped → ~67 s CPU per input ❗