EVMMAX Case Study: BLS12381 G1 Point Multiplication

This document presents benchmarks and costs for an EVM implementation of BLS12381 G1 point multiplication using EVMMAX opcodes. The basic double-and-add algorithm is used. Benchmarks and gas cost estimations of two native implementations are provided for comparison.

The double-and-add algorithm is the basis for the worst-case-input pricing model for the G1/G2-Mul precompiles proposed in EIP-2537.

Implementation

The algorithm iterates each bit in the scalar accumulating successive point additions and doublings to calculate a result:

def G1Mul(point: G1Point, scalar: int) -> G1Point:
    acc = G1InfPoint()
    for bit in iter_bits_msb_first(scalar):
        acc = point_double(acc)
        if bit == 1:
            acc = point_add(acc, point)
    
    return acc

There are various types of elliptic curve coordinate systems and formulas for point addition and doubling. The EVM and Rust implementations of G1Add implement the formulas described in algorithms 7 and 9 from this paper which use projective coordinates. The Geth implementation is from an older version of Kilic and implements different formulas for addition and doubling which use Jacobian coordinates.

The Geth point addition/doubling formulas use less arithmetic operations than the formulas used by the EVM/Rust implementations. However, they are not "complete": Strictly following the given formulas produces incorrect results for certain valid inputs. For example, the incomplete formula for point_double does not produce a correct result when the input is the infinity point.

Because of this, an implementation of the incomplete formulas must perform additional checks to handle certain inputs correctly. For compiled code, the overhead of these checks is minimal compared to the cost of the arithmetic. However, implemented with EVM opcodes they are costly in terms of gas and it is more economical to use the complete formulas which allow the implementation to avoid non-arithmetic operations.

Benchmark Types

Costs and benchmarks are presented for two EVMMAX variants:

  • evmmax-eip5843 - EIP-compliant implementation of the spec in Go
  • evmmax-asm384 - x86_64 GoAssembly implementation of the arithmetic provided by Supranational team in 2020. The implementation is modified from the EIP to make the value format little-endian. It also currently lacks checks that inputs are less than the modulus. ADDMODX, SUBMODX and MULMONTX opcodes are priced to 1, 1 and 2 respectively for the 320-384bit input size.

Two native implementations of double-and-add are benchmarked:

  • geth - Code from geth bls12381 crypto package with assembly implementations for arithmetic operations
  • rust - Rust implementation using complete point addition and doubling formulas

Costs

Note: for the native implementation benchmarks, gas cost is calculated by scaling runtime using 25 ns/gas.

Worst Case Input: All bits of the scalar are 1

Implementation Runtime (ns) Gas Cost
evmmax-eip5843 1501579 60668
evmmax-asm384 897000 39485
rust 490000 19600
geth 300146 12006

Average-ish Input:

Chose the group order (52435875175126190479447740508185965837690552500527637822603658699938581184513) with 122 0s and 134 1s in its binary representation.

Implementation Runtime (ns) Gas Cost
evmmax-eip5843 1047375 46632
evmmax-asm384 643000 31862
rust 335000 13400
geth 198461 7938

Code

https://github.com/jwasinger/evmmax-bls12-381

Select a repo