Runtime — SIMD-0529

Why Changing a Solana Cryptographic Syscall’s Compute Price Requires a New Feature Gate

11 min read
SolanaSyscallsFeature GatesRust

On 1 November Agave gave the sol_big_mod_exp syscall a new feature key. The syscall did not change that day. Its price had changed five days earlier: the exponentiation in an RSA-2048 signature check went from 15,457 compute units to 24,108. The old key had already shipped, with the old price, in every Agave release since 4.3.0.

We traced the key through Agave and Firedancer, timed the arithmetic both prices pay for in Rust, on the same library Agave calls, and checked what each client charges when a call fails. The syscall is SIMD-0529’s modular exponentiation, the building block for RSA signatures and other big-number cryptography.


A feature key names one behaviour

A feature gate is a switch that every validator flips at the same slot. What the switch turns on is written in each validator’s own code. If two releases define it differently, the cluster splits the moment it flips.

Suppose the old key had been activated while both versions ran, and take a transaction whose compute budget leaves 20,000 units for one RSA-2048 check. At the old price the call succeeds and the program writes its result; at the final price it runs out of compute and the transaction fails. Nodes on Agave 4.4.0 and nodes on newer code would disagree about that account, and so about the bank hash.

Agave’s activation check cannot see a price. Its CLI refuses to activate a feature, unless forced with a hidden flag, when less than 95% of stake runs the same feature set as the CLI, and that feature set is the first four bytes of a hash over the sorted feature keys a release knows. A price change under an unchanged key leaves it untouched. A new key is what makes the change visible: after the rekey, the only code that knows expHkkp… is code that charges the final price.

The rule is not new. In March 2025 Anza rekeyed SIMD-0215, the accounts lattice hash, for the same reason: its code had to change after the gate had shipped in Agave 2.2.


Where the old key had already gone

Agave implements SIMD-0529 with the draft price, key expH2pp…Jul 18
Agave 4.3.0, the first release with itSep 18
Firedancer implements it on main: final price, old keyOct 21
SIMD-0529 adopts the final priceOct 23
Agave master moves to the final price, same keyOct 27
Agave 4.4.0 ships with the draft priceOct 28
Firedancer rekeys; SIMD and Agave rekeys openedOct 30
Agave and SIMD rekeys merged: expHkkp…Nov 1

Agave #12321 added the syscall in July; SIMD #699 replaced its draft price after benchmarks, and Agave #16231 implemented the new one. Firedancer wrote its version straight to the final numbers while the SIMD change was still under review, then rekeyed first. Agave #16585 and SIMD #715 followed, with the stated reason that the new price needs a new key.

From 21 October on Firedancer’s main branch, and from 27 October on Agave’s master, expH2pp… meant the final price, while in every Agave release it meant the draft one. Firedancer moved to the new key on 30 October, Agave on 1 November. Firedancer’s releases listed the key but marked it not implemented. Nothing in these pull requests mentions an activation, and the SIMD is still marked Idea.

This is the syscall’s third key. A 2023 version under EBq48m8… was repriced in place in 2024, when its pull request said the gate was blocked and not active on any network, and removed in April 2026 because its price was still too low.


The two prices

textsol_big_mod_exp, compute units per call
Draft: Agave 4.3.0 to 4.4.0, key expH2pp...
  x    = max(base_len, modulus_len)           in bytes
  mult = x^2                    if x <= 64
         x^2/4 + 96x - 3072     if x <= 1024
  bits = max(adjusted_exponent_length, 75)
  exponent 1:   422 + ceil(15 * mult / 189)
  otherwise:    422 + ceil(mult * bits / 189)

Final: Agave master from 27 Oct, key expHkkp... from 1 Nov
  w     = ceil(max(base_len, modulus_len) / 8)  in 64-bit words
  mult  = w^2 + 22
  words = floor(adjusted_exponent_length / 64) + 1
  exponent 1:   50 + ceil(mult / 15)
  otherwise:    50 + mult * (3 + 20 * words)

The draft is Ethereum’s EIP-198 pricing scaled to compute units (v4.4.0 code, constants). The final price changes three things. It counts the operands in 64-bit words and adds a fixed 22 to every multiplication. It charges the exponent per 64-bit word instead of per bit, with no minimum. And it prices a plain reduction, exponent 1, at a fifteenth of one multiplication instead of fifteen multiplications. The flat part drops from 422 to 50.


What each price pays for

We copied both cost functions from the pinned source, checked them against the five published values for each, and timed the arithmetic they price: solana-big-mod-exp 4.0.0 on num-bigint 0.4.6, the versions Agave’s lockfile pins, with jemalloc as in agave-validator. Operands are the worst case for their length, as in the calibration: an odd modulus with its top bit set, every exponent bit set, a random full-length base. Each time is the median of 15 batches on one core of an Apple M1 Pro; two runs agreed within 2.6%.

textPrice vs time, solana-big-mod-exp 4.0.0, M1 Pro
shape                      draft CU   final CU       µs   ns/CU   ns/CU
                                                          draft   final
RSA-1024, e = 65537           5,705      6,444     35.3     6.2     5.5
RSA-2048, e = 65537          15,457     24,108      125     8.1     5.2
RSA-3072, e = 65537          28,461     53,548      274     9.6     5.1
RSA-4096, e = 65537          44,715     94,764      478    10.7     5.0
8 B mod, 64-bit exp             448        579      3.5     7.8     6.1
8 B mod, 4096-bit exp         1,809     29,559      169    93.4     5.7
32 B mod, 256-bit exp         1,804      3,204     27.7    15.4     8.6
64 B mod, 512-bit exp        11,497     14,068     94.6     8.2     6.7
256 B mod, 129-bit exp       26,082     65,948      337    12.9     5.1
256 B mod, 2048-bit exp     410,776    672,628    3,415     8.3     5.1
  same, top bit only        410,776    672,628    3,409     8.3     5.1
512 B mod, 64-bit exp        44,715     94,764      481    10.8     5.1
512 B mod, 65-bit exp        44,715    177,124      888    19.9     5.0
512 B mod, 1024-bit exp     604,566  1,330,164    6,574    10.9     4.9
512 B base mod 32 B           9,281        325      2.0     0.2     6.1
512 B base mod 256 B          9,281        325      2.8     0.3     8.6
Spread of time per unit across the 16 shapes, draft price440×
The same, final price1.7×
Time for a 65-bit vs a 64-bit exponent, 512-byte modulus×1.84
Draft price for that step×1.00
Final price for that step×1.87

The draft price undercharged long exponents on small operands. An 8-byte modulus with a 4,096-bit exponent cost 1,809 units and took 169 µs: 93 ns per unit, against 8 for RSA-2048, about eleven times cheaper for the same work. EIP-198 prices a multiplication at the square of the operand’s length in bytes, 64 for 8 bytes, which misses the fixed cost of each multiplication that dominates small operands. The final formula’s added 22 is that cost.

It overcharged reductions. Exponent 1 cost 9,281 units for 2 to 3 µs of work, between 27 and 39 times more per unit than an RSA-2048 check. The final price is 325. And it ignored the exponent below 75 bits: every 512-byte call with an exponent of up to 76 bits cost 44,715 units, although moving from a 64-bit to a 65-bit exponent took 1.84 times as long, because num-bigint works through the exponent one 64-bit word at a time. The final price steps at the same place.

Density does not matter: a 2,048-bit exponent with only its top bit set took as long as one with every bit set, so pricing by length is right for this library. Under the final price, the RSA sizes and every 256- and 512-byte shape land between 4.9 and 5.5 ns per unit on our machine, while the small operands and the reductions take 5.7 to 8.6, so on an M1 Pro those calls buy the most time per unit. SIMD #699 calibrated the price on AMD EPYC validators replaying full blocks at about 7.1 ns per unit, with margin. One core without a VM can show the shape of the curve, not the rate.


The operand rules did not change

Between Agave 4.4.0 and master only the price moved: the syscall’s checks and the crate it calls are the same. The SIMD sets rules that differ from Ethereum’s precompile on purpose:

Byte order of every operand and the resultlittle-endian
Longest base, exponent or modulus512 bytes
Modulusodd, > 1
Result lengthmodulus_len
0 to the power 01

EIP-198 also accepts even moduli; SIMD-0529 rejects them so that implementations can rely on the faster reduction an odd modulus allows. 512 bytes covers RSA-4096. With a 512-byte modulus, any exponent longer than 1,024 bits costs more than a transaction’s 1.4 million units: 1,412,524 at 1,025 bits.


What a failing call costs

The SIMD fixes an order: read and check the parameters, lengths, pointers and modulus, then compute and charge the price, then do the arithmetic. Aborts before the charge “MUST NOT charge the syscall compute cost”.

Neither client works that way. Agave and Firedancer both charge the flat 50 units before reading the parameters, and the whole price before reading the base and modulus, checking the modulus, or checking the output buffer. Firedancer’s source says it in a comment: a bad modulus fails “having been charged”.

textCompute units charged by a call that aborts
call aborts because               SIMD-0529 text   Agave, Firedancer
params pointer not readable        0                50
an operand over 512 bytes          0                50
exponent not readable              0                50
modulus_len is 0                   0                full price
modulus even, 0 or 1               0                full price
base or modulus not readable       0                full price
result buffer not writable         0                full price

Because the two clients charge the same, this cannot split the cluster today; it shows up in the compute units a failed transaction reports. But the SIMD lists abort behaviour among the things implementations must agree on, and a third client built from its text would charge failing calls differently. Before activation, either the text or the code has to move. Programs that go through the crate never reach these cases: its big_mod_exp checks the lengths and the modulus itself and returns None before calling the syscall.


For program developers

The crate programs call, solana-big-mod-exp 4.0.0 from June, still exports the draft constants: BIG_MOD_EXP_BASE_CU = 422, BIG_MOD_EXP_CU_DIVISOR = 189 and BIG_MOD_EXP_MIN_EXPONENT_LENGTH = 75. Agave no longer reads them. Budget from the final formula instead:

RSA-2048, exponent 6553724,108
RSA-3072, exponent 6553753,548
RSA-4096, exponent 6553794,764
Reduce a 512-byte number325

Those are the syscall alone; the padding and hash checks that make a signature check are the program’s own work. On chain the crate’s function calls the syscall, which exists only once expHkkp… is active, and on 1 November no release of either client contained that key. Like the P-256 syscalls, it will be usable only after both clients ship it and the feature activates. How the compute limit you request then affects your transaction’s priority is a separate calculation.


What this does not show

We did not run either client’s syscall: no release charges the final price, and the Agave we have installed, 4.2.1, predates the syscall. The cost functions are copied from Agave’s v4.4.0 tag and from master at c84fccdb (1 November) and match the SIMD pull request’s published values. Times are one core of an M1 Pro without the VM, so they show how each price tracks the work across inputs, not what a validator spends. Firedancer computes with s2n-bignum rather than num-bigint, so its times would differ, though it charges the same price. Nothing here measures whether any cluster ever activated a key.


sol_big_mod_exp: Questions People Actually Ask

Why did SIMD-0529 get a new feature gate?

Its price changed after the old key had shipped. Agave 4.3.0 to 4.4.0 carry the key expH2pp… with the draft price; Agave’s master branch moved to the final price on 27 October under the same key. Had that key been activated, nodes on those releases and nodes on newer code would have charged different amounts for the same call and disagreed about which transactions run out of compute. The new key, expHkkp…, is known only to code that charges the final price.

How many compute units does sol_big_mod_exp cost to verify an RSA signature?

With exponent 65537, the final SIMD-0529 price is 24,108 compute units for RSA-2048, 53,548 for RSA-3072 and 94,764 for RSA-4096. The draft price in Agave 4.3.0 to 4.4.0 was 15,457, 28,461 and 44,715. Padding and hash checks run in the program and cost extra.

What is the compute-unit formula for sol_big_mod_exp?

Take w = ceil(max(base length, modulus length) / 8) and m = w² + 22. If the exponent is 1, the call costs 50 + ceil(m / 15). Otherwise it costs 50 + m × (3 + 20 × k), where k is the number of 64-bit words the exponent spans, counted from its highest set bit with the EIP-198 adjusted length.

Does sol_big_mod_exp accept even moduli or big-endian input?

No. Unlike Ethereum’s EIP-198 precompile, the modulus must be odd and greater than 1, every operand is a little-endian unsigned integer of at most 512 bytes, and the result is written as exactly as many bytes as the modulus.

Can Solana programs call sol_big_mod_exp yet?

Not as of 1 November 2026. The syscall exists only while its feature, now expHkkp…, is active, and no release of Agave or Firedancer contained that key on that date. SIMD-0529 was still marked Idea.


The one-line version

A feature key stands for one behaviour, and the old big_mod_exp key had shipped in Agave 4.3.0 to 4.4.0 with the draft price, so the final price, whose time per unit varied 1.7-fold across our inputs against the draft’s 440-fold, needed a key that only code charging it would know.

Sources: SIMD-0529 at 0434aab3, SIMD pull requests #699, #715 and #265; Agave #12321, #16231, #16585, #2807 and #12192, the v4.4.0 tag and master at c84fccdb; Firedancer commits f0ebe3b0 and ced4e9b0 and release v26.10.5. Timings are from our own Rust harness on solana-big-mod-exp 4.0.0.

xroot.devOn-chain research, published in full
𝕏 @xrootdev
← Back to all notes