operator * method
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]);
}