Solidity API
Two contracts ship in hardhat/contracts/:
| Contract | Primitives |
|---|---|
BoxMath.sol | Polynumber, Multinumber, polynomial evaluation, truncation, caret product |
PixelMath.sol | Pixel, vexel operations, Pythagorean triple generation |
BoxMath.sol
Contract: hardhat/contracts/BoxMath.sol
All arithmetic is exact integer (uint256) — no fixed-point scaling, no SCALE constant. Values passed in and returned are raw natural numbers, matching the TypeScript bigint API exactly.
Structs
struct Polynumber {
uint256 coefficient;
uint256[] exponents;
}
struct Multinumber {
Polynumber[] terms;
}
pow
function pow(uint256 base, uint256 exp) public pure returns (uint256)
Integer exponentiation. pow(b, 0) returns 1; pow(b, 1) returns b.
boxMath.pow(2, 10); // 1024
boxMath.pow(3, 0); // 1
caretProduct
function caretProduct(uint256[][] memory boxes) public pure returns (uint256[] memory)
Tensor product of an array of arrays. Equivalent to caretProduct(...boxes) in TypeScript.
const M = await boxMath.caretProduct([
[1n, 2n], [1n, 3n], [1n, 5n], [1n, 7n], [1n, 11n]
]);
// M.length === 32 (2^5 elements)
// M includes 2310n (2·3·5·7·11) and 1n (empty product)
polynumberDegree
function polynumberDegree(Polynumber memory m) public pure returns (uint256)
Sum of all exponents — .
await boxMath.polynumberDegree({ coefficient: 1n, exponents: [1n, 1n] }); // 2n
evaluatePolynumber
function evaluatePolynumber(Polynumber memory m, uint256[] memory point)
public pure returns (uint256)
Evaluates .
await boxMath.evaluatePolynumber(
{ coefficient: 1n, exponents: [1n, 1n] },
[10n, 20n]
); // 200n
multiplyPolynumbers
function multiplyPolynumbers(Polynumber memory a, Polynumber memory b)
public pure returns (Polynumber memory)
Multiplies coefficients and adds exponent vectors.
const x = { coefficient: 1n, exponents: [1n] };
const y = { coefficient: 1n, exponents: [0n, 1n] };
const [coeff, exps] = await boxMath.multiplyPolynumbers(x, y);
// coeff === 1n, exps === [1n, 1n]
evaluateMultinumber
function evaluateMultinumber(Multinumber memory p, uint256[] memory point)
public pure returns (uint256)
Sums each term evaluated at point.
const k = { terms: [{ coefficient: 1n, exponents: [1n, 1n] }] };
await boxMath.evaluateMultinumber(k, [100n, 200n]); // 20000n
addMultinumber
function addMultinumber(Multinumber memory a, Multinumber memory b)
public pure returns (Multinumber memory)
Concatenates term lists — box union with multiplicity.
multiplyMultinumber
function multiplyMultinumber(Multinumber memory a, Multinumber memory b)
public pure returns (Multinumber memory)
Pairwise polynumber product across both term lists — the Cauchy / box product.
const product = await boxMath.multiplyMultinumber(B, C);
product.terms.length; // 6 (3 × 2 pairs)
truncate
function truncate(Multinumber memory p, uint256 k)
public pure returns (Multinumber memory)
Drops all terms with degree > k.
ethers v6 has a known bug where decoding single-element uint256[] arrays inside nested struct return values throws TypeError: Cannot assign to read only property '0'. Any function that returns Polynumber or Multinumber must be called via provider.call and decoded with AbiCoder.defaultAbiCoder() using unnamed tuple type strings:
const coder = ethers.AbiCoder.defaultAbiCoder();
const addr = await boxMath.getAddress();
const calldata = boxMath.interface.encodeFunctionData("truncate", [p, 1n]);
const raw = await ethers.provider.call({ to: addr, data: calldata });
const [[terms]] = coder.decode(["((uint256,uint256[])[])"], raw);
// terms is a plain array — no named-property assignment attempted
Passing unnamed types ((uint256,uint256[]) instead of (uint256 coefficient,uint256[] exponents)) suppresses the named-key assignment path in ethers that triggers the bug.
const p = {
terms: [
{ coefficient: 2n, exponents: [0n, 0n] },
{ coefficient: 3n, exponents: [1n, 0n] },
{ coefficient: 1n, exponents: [2n, 0n] }, // degree 2 — dropped
],
};
// use explicit decode pattern above
// terms.length === 2
// evaluateMultinumber(truncated, [5n]) === 17n (2 + 3·5)
PixelMath.sol
Contract: hardhat/contracts/PixelMath.sol
All arithmetic is exact uint256 — no division, no fixed-point scaling.
Struct
struct Pixel {
uint256 m;
uint256 n;
}
A pixel is a 2-listbox of natural numbers. Think of it as a matrix index pair: m is the row, n is the column.
pixelProduct
function pixelProduct(Pixel memory a, Pixel memory b)
public pure returns (bool ok, Pixel memory result)
The pixel product (Definition 11): when ; otherwise ok = false and the result is nothing (represented as (0,0)).
await pm.pixelProduct({ m: 3n, n: 4n }, { m: 4n, n: 11n });
// ok = true, result = { m: 3n, n: 11n }
await pm.pixelProduct({ m: 3n, n: 4n }, { m: 5n, n: 11n });
// ok = false (4 ≠ 5 — nothing)
When passing a pixel returned by one call into a subsequent call, reconstruct a plain object first:
const [, rawAB] = await pm.pixelProduct(a, b);
const ab = { m: rawAB.m, n: rawAB.n }; // plain object — not ethers Result
const [ok, result] = await pm.pixelProduct(ab, c);
pixelTranspose
function pixelTranspose(Pixel memory p) public pure returns (Pixel memory)
. Satisfies .
pixelIsDiagonal
function pixelIsDiagonal(Pixel memory p) public pure returns (bool)
Returns true when m == n.
pythagoreanTriple
function pythagoreanTriple(Pixel memory p)
public pure returns (bool ok, uint256 a, uint256 b, uint256 c)
For pixel with , returns the Pythagorean triple . Returns ok = false when the precondition is not met.
await pm.pythagoreanTriple({ m: 2n, n: 1n }); // ok=true, 3n, 4n, 5n
await pm.pythagoreanTriple({ m: 3n, n: 2n }); // ok=true, 5n, 12n, 13n
await pm.pythagoreanTriple({ m: 4n, n: 3n }); // ok=true, 7n, 24n, 25n
Maxel operations
A maxel is represented as MaxelEntry[] — a sparse list of (Pixel, uint256) pairs.
struct MaxelEntry {
Pixel pixel;
uint256 coeff;
}
maxelTranspose
function maxelTranspose(MaxelEntry[] memory M)
public pure returns (MaxelEntry[] memory)
Transposes every pixel in the list.
maxelProduct
function maxelProduct(MaxelEntry[] memory M, MaxelEntry[] memory N)
public pure returns (MaxelEntry[] memory)
Computes . Pixel products that are nothing are dropped; coefficients of identical result pixels are merged by summation.
// Example 22
const M = [
{ pixel: { m: 0n, n: 0n }, coeff: 1n },
{ pixel: { m: 1n, n: 0n }, coeff: 1n },
];
const N = [
{ pixel: { m: 1n, n: 0n }, coeff: 1n },
{ pixel: { m: 0n, n: 2n }, coeff: 1n },
{ pixel: { m: 2n, n: 3n }, coeff: 1n },
];
const MN = await pm.maxelProduct(M, N);
// MN = [{ pixel: [0,2], coeff: 1 }, { pixel: [1,2], coeff: 1 }]
Vexel operations
Vexels are represented as plain uint256[] — a dense coefficient vector where index i holds the coefficient for singleton [i].
vexelAdd
function vexelAdd(uint256[] memory u, uint256[] memory v)
public pure returns (uint256[] memory)
Element-wise addition; pads the shorter vector with zeros.
await pm.vexelAdd([1n, 2n, 0n], [0n, 3n, 4n]); // [1n, 5n, 4n]
await pm.vexelAdd([1n, 2n], [0n, 3n, 4n]); // [1n, 5n, 4n]
vexelScale
function vexelScale(uint256[] memory u, uint256 scalar)
public pure returns (uint256[] memory)
await pm.vexelScale([1n, 2n, 3n], 3n); // [3n, 6n, 9n]
vexelDot
function vexelDot(uint256[] memory u, uint256[] memory v)
public pure returns (uint256 sum)
Inner product over the shorter length.
await pm.vexelDot([1n, 2n, 3n], [4n, 5n, 6n]); // 32n (1·4 + 2·5 + 3·6)