# EVMMAX + EOF https://github.com/ethereum/EIPs/pull/5843 ## Setup section Section contents: | name | length | value | description | |---------------|----------|---------------|-------------| | bit_width_multiplier | 1 bytes | | `bit_width = bit_width_multiplier * 64` | | max_memory_slot | 1 bytes | | the size of maximum memory expansion in words | | memory_offset | 2 bytes | | offset of the memory (in bytes) where the EVMMAX memory starts; this memory can be accessed by regular EVM instructions too | | modulus | n bytes | | the modulus with `n = bit_width / 8` bytes; big endian | | montgomery_modulus | 8 bytes | | big endian | | r_squared | n bytes | | big endian | Could use "custom" sections in EOF for this? ### Validation At deployment time this setup section is validated: 1. `modulus` must be odd. 2. `(modulus x motgomery_modulus + 1) % modulus == 0`. 3. Check every EVMMAX instructions' immediate memory slots to be `<= max_memory_slot`, otherwise the instruction is invalid. 4. `r_squared == ((1 << bit_width) ** 2) % modulus` Furthermore we could specify acceptable `modulus` figures at introduction of EVMMAX, keep extending the supported values, and potentially drop this limitation in the future. Knowing the `modulus` upfront opens up the possibility for chosing optimal algorithms before execution. (A good example is BLS12-381.) ### Execution 1. When an EOF contract with the EVMMAX setup section is instantiated, the contents of the section are loaded into the `evmmax_state`. 2. Memory expansion is performed and charged for `memory_offset + ((max_memory_slot + 1) * bit_width) / 8`. 3. The `r_squared` value is copied to `memory_offset` (i.e. it is stored in slot 0). 4. Since instruction gas costs are based on `bit_width`, they are precalculated here. ## Gas formula ```python MULMONTX_GAS_A = 0.1 MULMONTX_GAS_B = 0.7 ADDMODX_GAS_A = 0.2 ADDMODX_GAS_B = 0.6 def gas_mulmontx(bit_width): return ceil(MULMONTX_GAS_A * ((bit_width / 64) ** 2) + MULMONTX_GAS_B) def gas_addmodx(bit_width): return ceil(ADDMODX_GAS_A * (bit_width / 64) + ADDMODX_GAS_B) def gas_submodx(bit_width): return gas_addmodx(bit_width) ``` ## Instructions We introduce some helpers: ```python def slot_to_mem_offset(slot): return evmmax_state.memory_offset + (slot * bit_width) / 8 def slot_value(slot): return (slot_to_mem_offset(slot), slot_to_mem_offset(slot + 1)) ``` We introduce three new instructions: 1. `ADDMODX{out_slot, x_slot, y_slot}` (0x22) 2. `SUBMODX{out_slot, x_slot, y_slot}` (0x23) 3. `MULMODX{out_slot, x_slot, y_slot}` (0x24) The `{}` notation means that each of the arguments are 8-bit immediate bytes following the instruction, i.e. `ADDMODX{1, 2, 3}` is encoded as `0x22 0x01 0x02 0x03`. There is identical runtime checking at each instruction: if `slot_value(out_slot)`, `slot_value(x_slot)` or `slot_value(y_slot)` is `>= modulus`, then abort with OOG. The `TOMONTX` instruction is not needed anymore, because it can be accomplished with `MULMODX(out_slot, x_slot, 0}`. Notice that due to deploy time validation, there is no need to: - Perform stack checks for the immediate arguments. - Perform memory expansion. - Perform runtime gas calculation based on the `bit_width` (as it can be calculated once during the set up).