Skip to content

Unify compute-budget system for heavy opcodes #81

@msinkec

Description

@msinkec

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_CHECKSIGVERIFYpkg/arkade/opcode.go:1782
  • OP_CHECKSIGADDpkg/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
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:

  • pkg/arkade/opcode.go

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 ❗

Metadata

Metadata

Assignees

Labels

enhancementNew feature or request

Type

No type
No fields configured for issues without a type.

Projects

No projects

Relationships

None yet

Development

No branches or pull requests

Issue actions