feat: expose LibTomMath modular arithmetic as primitives#6026
feat: expose LibTomMath modular arithmetic as primitives#6026
LibTomMath modular arithmetic as primitives#6026Conversation
|
cc @timohanke — this was your request 🎯 |
LibTomMath modular arithmetic via Prim
LibTomMath modular arithmetic via PrimLibTomMath modular arithmetic via Prim
7d9284a to
873abcc
Compare
|
@timohanke — your modular arithmetic primitives are ready! 🎉
Getting here was a bit of a Grothendieck experience — rather than cracking the nut with a hammer, we let the rising sea of understanding slowly submerge the problem. From missing libtommath transitive dependencies, through WASI allocator incompatibilities, to discovering that Motoko's compact BigNum representation requires proper unboxing for ternary operations — each wrong theory peeled back a layer until the fix emerged naturally from the architecture. Build artifacts (macOS, Ubuntu, ARM) are available from the CI run linked above. E.g. macOS ARM64 is here: https://github.com/caffeinelabs/motoko/actions/runs/24552026361/artifacts/6490288624 |
|
Thanks @ggreif . This is awesome. Looking forward to try it out. We should also consider exposing Further down the road we can also consider exposing but let's see first how the ones already added perform in practice. |
Good point. We could do
|
|
Also |
LibTomMath modular arithmetic via PrimLibTomMath modular arithmetic as primitives
Add intAddMod, intSubMod, intMulMod, intPowMod primitives backed by mp_addmod, mp_submod, mp_mulmod, mp_exptmod from LibTomMath. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Required by s_mp_exptmod_fast at link time. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Required transitively by mp_invmod (called from mp_exptmod for negative exponents) and mp_reduce/mp_dr_reduce. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…gets
- GADT constructors: AddMod, SubMod, MulMod, PowMod on MOTerm Integer
- Evaluatable, unparseMO, Arbitrary, shrinking for all four ops
- modularProps test group wired into main test tree
- nix targets: tests.qc-{arith,modular,conversions,utf8,matching}
- fix: add missing s_mp_invmod_fast, s_mp_invmod_slow to TOMMATHFILES
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… test size - prop_verifies takes TestCases, not MOTerm Integer - Shadow pattern vars in eval (cleaner: do a <- eval a) - Reduce modulus to ≤1002 and exponent to ≤9 for speed - withMaxSuccess 50 for modular suite Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Required by s_mp_exptmod_fast for fast modular exponentiation path. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The Prim type listing now includes intAddMod, intMulMod, intPowMod, intSubMod — update expected output for 4 fail tests × 2 modes. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
(3 - 7) mod 10 = 6, not -4. mp_submod always returns non-negative. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…antics
- Remove #[cfg(feature = "ic")] from bigint_{addmod,submod,mulmod,exptmod}
so they're available in wasmtime/WASI test mode (matching bigint_pow)
- Fix OCaml interpreter to return non-negative results for positive modulus
(matching LibTomMath's mp_*mod behavior, not OCaml's rem)
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Small integers in Motoko are stored as tagged scalars, not heap- allocated BigInts. The OtherPrim codegen was calling bigint_addmod etc. directly, passing tagged scalars where the RTS expects BigInt pointers — causing wrong results or traps. Fix: add slow_only3 to MakeCompact (both classical and enhanced backends) that boxes any tagged scalar arguments before calling the RTS, and compacts the result back. This mirrors try_unbox2 used by the existing binary arithmetic ops. Also bump QC modular suite to 100 samples. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
8992c79 to
36ad7b2
Compare
The 4 BigNum.compile_*mod helpers introduced on this branch used the pre-#5953 E.call_import env "rts" form; converting to the E.call_rts shorthand adopted by master keeps the file consistent. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
After testing this in some benchmarks I would like to see this function exposed as well: The benchmarks showed an advantage of The benchmarks showed no advantage of |
Per benchmark feedback (PR #6026 review), \`mp_exptmod\` shows real speedup over user-code reimplementation because the algorithmic loop stays inside C; the trio \`addmod\`/\`submod\`/\`mulmod\` doesn't help because they're just bundled add+mod / sub+mod / mul+mod and the small-int fast path is bypassed. \`mp_invmod\` is in the same league as \`mp_exptmod\` — extended Euclidean is a C-side loop that would otherwise pay an FFI hop per iteration if rolled in user Motoko. Plumbing mirrors \`intPowMod\` exactly: - \`rts/Makefile\`: bindgen allowlist + already in C-source list - \`rts/.../bigint.rs\`: \`bigint_invmod\` wrapper - both backends: import + new \`slow_only2\` helper + \`compile_invmod\` in BigNumType signature, MakeCompact wrapper, and BigNumLibtommath slow path; \`OtherPrim "intInvMod"\` dispatch - \`prim.ml\`: interpreter via in-OCaml extended Euclidean, traps when no inverse exists - \`src/prelude/prim.mo\`: \`intInvMod : (Int, Int) -> Int\` - \`test/run/modular-arith.mo\`: positive cases + round-trip via \`intMulMod a (intInvMod a m) m == 1\`. All seven phases green. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Go for it! Thanks for the feedback! |
Four tests dump the full primitive listing (no-timer-canc, no-timer- set, suggest-long-ai, suggest-short-ai); each gets one extra line \`intInvMod : (a : Int, m : Int) -> Int;\` slotted alphabetically between \`intAddMod\` and \`intMulMod\`. Pure accept. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
|
Great, thank you. In raw EC arithmetic the results still differ, between 5-20% improvement. In the full final application of ECDSA-verify the improvement shrinks down to 1%. I then tried using Montgomery form throughout ECDSA-verify and the result was a 20% improvement. That was by implementing everything in use code. So my next request would be to expose these four from libtommath:
Then maybe we can speed up Montgomery form even further. |
Summary
Expose LibTomMath modular arithmetic operations as Motoko primitives:
Prim.intAddMod(a, b, m)—(a + b) mod mviamp_addmodPrim.intSubMod(a, b, m)—(a - b) mod mviamp_submodPrim.intMulMod(a, b, m)—(a * b) mod mviamp_mulmodPrim.intPowMod(base, exp, m)—base^exp mod mviamp_exptmodAll operate on
Int(arbitrary precision).intPowModis particularly useful for cryptographic applications (RSA, Diffie-Hellman, etc.) where modular exponentiation with large integers is needed.Changes
rts/Makefilerts/motoko-rts/src/bigint.rscompile_enhanced.ml,compile_classical.mlmo_values/prim.mlsrc/prelude/prim.motest/run/modular-arith.moTest plan
nix build .#tests.unitpassesnix build .#tests.releasepasses🤖 Generated with Claude Code
TODO
Changelog.md