Field Arithmetic: M31 → CM31 → QM31
Deep dive into the Mersenne-31 field tower implementation in Solidity
Overview
The Circle STARK verifier operates over a three-level algebraic extension tower built on the Mersenne-31 prime. This page covers the mathematical definitions, Solidity implementation details, packing conventions, and test vectors for each level.
All arithmetic is implemented in M31Lib.sol (311 lines).
1. M31 — The Base Field
Definition
This is the largest Mersenne prime that fits in 31 bits. Elements are integers in .
Why Mersenne-31?
Mersenne primes enable shift-based modular reduction. For any product (at most 62 bits):
Proof: Write . Then .
The result may be , requiring at most one subtraction. This is dramatically cheaper than general modular reduction.
Solidity Implementation
// M31 addition: single add + conditional subtract
function m31Add(uint32 a, uint32 b) internal pure returns (uint32) {
uint256 sum = uint256(a) + uint256(b);
if (sum >= P) sum -= P;
return uint32(sum);
}
// M31 multiplication: Mersenne reduction
function m31Mul(uint32 a, uint32 b) internal pure returns (uint32) {
uint256 product = uint256(a) * uint256(b);
uint256 lo = product & P; // low 31 bits
uint256 hi = product >> 31; // high bits
uint256 r = lo + hi;
if (r >= P) r -= P;
return uint32(r);
}
// M31 inversion via Fermat: a^(p-2) mod p
function m31Inv(uint32 a) internal pure returns (uint32) {
require(a != 0, "M31: division by zero");
return m31Exp(a, P - 2);
}Representation
M31 elements are stored as uint32. The maximum valid value is . Zero is represented as 0, not as .
Test Vectors
| Operation | Input | Result |
|---|---|---|
m31Add(P-1, 1) | ||
m31Add(P-1, P-1) | ||
m31Mul(2, 2) | ||
m31Mul(P-1, P-1) | ||
m31Inv(2) |
2. CM31 — Complex Extension
Definition
Elements are where and .
This is the Gaussian integers modulo . The polynomial is irreducible over because is a quadratic non-residue modulo (since ).
Packing Convention
CM31 is packed into uint64:
- Low 32 bits: real part ()
- High 32 bits: imaginary part ()
function cm31Pack(uint32 real, uint32 imag) internal pure returns (uint64) {
return uint64(real) | (uint64(imag) << 32);
}Arithmetic
Multiplication: Standard complex multiplication
4 M31 multiplications + 1 addition + 1 subtraction.
Conjugation: . Negates only the imaginary part.
Inversion: Uses the norm identity
The denominator is an M31 element (guaranteed nonzero for nonzero CM31), inverted via m31Inv.
Negation: . Negates both components.
cm31Neg vs cm31Conj
These are distinct operations:
cm31Neg(a + bi)= — negates both partscm31Conj(a + bi)= — negates only imaginary
This distinction is critical for QM31 complex conjugation (see Section 3).
3. QM31 — Quartic Extension (SecureField)
Definition
Elements are where (CM31) and .
The constant is chosen so that is irreducible over . This means must be a quadratic non-residue in CM31.
QM31 is Stwo's SecureField — the field used for all random challenges, ensuring they are drawn from a space of size .
Packing Convention
QM31 is packed into uint128:
- Low 64 bits: first CM31 component ()
- High 64 bits: second CM31 component ()
Which expands to four M31 values:
- Bits [0..31]: (real part of )
- Bits [32..63]: (imaginary part of )
- Bits [64..95]: (real part of )
- Bits [96..127]: (imaginary part of )
function qm31FromM31(uint32 a, uint32 b, uint32 c, uint32 d) internal pure returns (uint128) {
return qm31Pack(cm31Pack(a, b), cm31Pack(c, d));
}Arithmetic
Multiplication: In :
This requires 4 CM31 multiplications + 1 CM31 multiplication by + 2 CM31 additions.
Since is a small constant, the multiplication by is:
which is 4 M31 multiplications + 2 M31 additions + 1 M31 subtraction — slightly cheaper than a general CM31 multiply.
Inversion: Analogous to CM31:
The denominator is a CM31 element, inverted via cm31Inv.
Complex Conjugation
Bug Alert: This Was Wrong Initially
The initial implementation performed element-wise CM31 conjugation: . This is incorrect.
Stwo defines complex conjugation for QM31 as:
impl ComplexConjugate for QM31 {
fn complex_conjugate(&self) -> Self {
Self(self.0, -self.1) // Negate ENTIRE second CM31
}
}For :
| Operation | Result | Description |
|---|---|---|
| QM31 conjugate (correct) | Negate entire second CM31 | |
| CM31 conjugate on each half (wrong) | Negate imaginary of each CM31 |
The correct Solidity implementation:
function _conjQ(uint128 q) internal pure returns (uint128) {
return M31Lib.qm31Pack(
M31Lib.qm31First(q), // Keep first CM31 unchanged
M31Lib.cm31Neg(M31Lib.qm31Second(q)) // Negate ENTIRE second CM31
);
}Note: cm31Neg (negate both real and imaginary) is used, not cm31Conj (negate only imaginary). This is the algebraic conjugation in the extension : it maps , which negates the coefficient of as a whole CM31 element.
Why This Matters
The complex conjugate appears in every DEEP quotient line equation:
If is computed incorrectly, the line coefficients and are wrong. This propagates through the numerator and corrupts the quotient. Since each quotient is accumulated with a power of , a single wrong quotient eventually corrupts the entire FRI input.
The bug was subtle because:
- For QM31 elements with or , both implementations agree
- The first several columns' quotients appeared correct
- The error only became visible after accumulating enough wrong contributions
4. Circle Point Arithmetic
Definition
Points on the unit circle over .
Group Operation
This is complex multiplication restricted to the unit circle. The identity is .
Generator
with .
The generator order equals (not as in multiplicative groups). This is a fundamental difference from standard STARK domains.
Scalar Multiplication
To compute , we use binary square-and-multiply:
function circleMul(uint32 x, uint32 y, uint256 scalar)
internal pure returns (uint32 rx, uint32 ry)
{
rx = 1; ry = 0; // Identity
while (scalar > 0) {
if (scalar & 1 == 1) {
(rx, ry) = circleAdd(rx, ry, x, y);
}
(x, y) = circleAdd(x, y, x, y);
scalar >>= 1;
}
}Doubling (x-coordinate only)
For FRI line domains, we only need the x-coordinate after doubling:
This avoids computing the y-coordinate entirely.
5. Gas Costs
Approximate gas costs for each operation (measured via forge test):
| Operation | Gas | Notes |
|---|---|---|
| M31 add/sub | ~30 | Single conditional branch |
| M31 mul | ~50 | Mersenne reduction |
| M31 inv | ~4,000 | 31 iterations of square-and-multiply |
| CM31 mul | ~250 | 4 M31 muls + 2 M31 adds |
| CM31 inv | ~5,000 | 2 M31 muls + 1 M31 inv |
| QM31 mul | ~1,200 | 4 CM31 muls + CM31-by-R + 2 CM31 adds |
| QM31 inv | ~7,000 | 2 CM31 muls + 1 CM31 inv + 1 CM31 neg |
| Circle point add | ~300 | 4 M31 muls + 1 M31 add + 1 M31 sub |
| Circle scalar mul (31-bit) | ~10,000 | ~31 iterations of add + double |
QM31 multiplication is the dominant cost in the verifier — it appears in every quotient accumulation and every FRI fold.