operator * method

Uint256 operator *(
  1. Uint256 other
)

Multiply, keeping only the low 256 bits (wrapping). Row-scanning (operand-scanning) schoolbook multiply: for each limb a[i] of this, walk every limb b[j] of other and accumulate a[i]*b[j] into acc[i+j], threading carry from acc[i+j] to acc[i+j+1] via Uint64.mac — the same single-carry-chain pattern Uint64.operator* itself uses one level down (multiply one operand by a single digit of the other, adding into a result array with carry propagating to the next position).

This is deliberately not the "sum every (i, j) pair with i + j == k into column k, then carry to column k + 1" Comba layout: a column can have up to 4 nonzero terms, and their combined carry-out doesn't fit in the single Uint64 that layout carries forward. A previous version of this operator got exactly that wrong: it fed the high-word carry out of one term back into the next term's low-word accumulation instead of keeping it at its own weight, silently corrupting the result whenever a column had more than one nonzero term (e.g. 2 * Uint256.max came out as 0xFFFF...FFFE with the top three limbs dropped instead of Uint256.max - 1). Row-scanning sidesteps the problem entirely: each mac call's carry only ever needs to reach the next output limb, one weight up, which is exactly what Uint64.mac's (result, carryOut) pair already guarantees is safe.

Any carry left over past output limb 3 represents overflow beyond 256 bits and is dropped, matching this operator's documented wrapping semantics.

Implementation

Uint256 operator *(Uint256 other) {
  final acc = List<Uint64>.filled(4, Uint64.zero);
  final a = [_d0, _d1, _d2, _d3];
  final b = [other._d0, other._d1, other._d2, other._d3];
  for (var i = 0; i < 4; i++) {
    var carry = Uint64.zero;
    for (var j = 0; j < 4 - i; j++) {
      final k = i + j;
      final (sum, newCarry) = Uint64.mac(acc[k], a[i], b[j], carry);
      acc[k] = sum;
      carry = newCarry;
    }
    // Any remaining `carry` here would land at limb `i + 4`, past the
    // kept width — dropped, matching the wrapping semantics above.
  }
  return Uint256._(acc[3], acc[2], acc[1], acc[0]);
}