Repository navigation
Route large bignum arithmetic through OpenSSL - #20
Conversation
bignum.sub returned a wrong value instead of raising when the subtrahend had more limbs than the minuend: sub_raw only read b to a's width, so a wider b whose low limbs happened not to borrow slipped through. It now raises on a wider subtrahend, and the tests cover that case and equal-width underflow. The bignum and random selftests restored the acceleration flag to the CRYPTO_USE_OPENSSL default rather than the caller's setting, so a driver that called crypto.use_openssl(true) and then crypto.selftest() was left unaccelerated, which on Control4 makes SRP unusable. openssl_wrapper.enabled() exposes the flag so they can hand the caller's choice back, crypto.selftest() fails any module that leaves it changed, and the runner's lpack pass now runs crypto.selftest() with the flag flipped from the environment default.
mod_exp was the only bignum operation that used lua-openssl. mul, divmod, mod and mod_mul now do too when the estimated pure-Lua work reaches four limb products per limb converted in or out, which is where the bytes-in/hex-out conversion pays for itself: at 3072 bits they run 5-30x faster. Calls whose pure-Lua cost is linear (add, sub, a one-limb factor or divisor, a narrow quotient, reduced operands) stay in Lua, where they are faster at every size measured. Each routed operation must reproduce its own multi-limb known answer before the binding is trusted with it, results are unchanged either way, and bignum.is_accelerated(op) reports per operation. CI asserts all five on every real lua-openssl rock.
There was a problem hiding this comment.
Reviewed b91226f, scoped to the second commit; 73edb20 is #19 and is reviewed there.
The routing matches the rule in the body. A zero divisor never reaches the binding: div_work is 0 for it in divmod/mod, and mod_mul's require_nonzero raises inside the pcall so the pure-Lua error comes through. The ^%x+$ guard closes the signed-hex hole, and the per-op verdict cache resets when the binding changes.
Checked:
./run_tests.shclean on LuaJIT 2.1, Lua 5.5 and 5.1 (no binding).- Mutants, each fails the suite:
ROUTE_MIN_RATIO4 to 3, hex guard removed,require_nonzeromade a no-op. - CI green at this head. The three openssl-matrix logs show
accelerated=truefor all five ops on 0.8.5, 0.9.2 and 0.11.1.
Once #19 lands this branch needs a rebase. That moves the head, so it will need a fresh approval.
There was a problem hiding this comment.
Re-reviewed the delta since b91226f: 7dc23f3 (comment and doc trim on this branch) and the merge of #19's branch (1db4d66). #19's own trim commit, da66617, is reviewed on #19.
- 1db4d66 is a clean merge: its tree equals
git merge-tree --write-tree 7dc23f3 da66617. - 7dc23f3 changes no code. With comment-only and blank lines removed,
bignum.luais identical before and after, and the comparison catches a one-character mutation (ROUTE_MIN_RATIO = 3). No block comments orluacheck:-style directives are touched. Thebuild.yml,init.luaandopenssl_wrapper.luahunks change only comments. - The shortened comments still match the code. The routing test builds a fresh stand-in per shape, so "runs twice (check, then work)" holds. In the zero-divisor test,
calls("mulmod") == 1is the known-answer check alone. The CLAUDE.md and README rewrites keep the routing rule, the per-operation check and the narrow-mulJIT caveat. run_tests.shpasses under LuaJIT 2.1 and Lua 5.5 at 1db4d66. CI is 12/12 green at this head.
…mber-support-2a0c40
Depends on #19. This branch's first commit is #19. Merge that first; once it lands, this diff shrinks to the second commit.
What changes
crypto.bignumalready routedmod_expthrough lua-openssl'sopenssl.bn. Nowmul,divmod,modandmod_muldo too, using the same machinerymod_expused:bn.text, hex out throughbn.tohex.mod_expdown with it.pcalland falls back to pure Lua.bignum.is_accelerated(op)takes an optional operation (defaultmod_exp), sosrp.is_accelerated()is unchanged.When it routes
The OpenSSL side's cost is almost all conversion, which is linear in the limbs moved. So an operation routes only when its estimated pure-Lua work reaches 4 limb products per limb converted in or out. Calls whose pure-Lua cost is linear stay in Lua at any width:
addandsubPure-Lua time ÷ OpenSSL time, including conversions (lua-openssl 0.8.5, Apple Silicon):
One known trade-off. With LuaJIT's JIT on, a routed
mulwhose narrower factor is under about 24 limbs runs about 2x slower than pure Lua. That covers SRP'su*x, about 2 µs. Under Lua 5.4 and LuaJIT-joffthe same shapes win 1.6-2.7x. Nothing records whether Control4's LuaJIT runs its JIT, so the rule doesn't assume either way. The full table and reasoning are in the comment that opensbignum.lua's OpenSSL section.Binding quirks guarded against
Measured on the 0.8.5, 0.9.2 and 0.11.1 rocks:
divmod,mod,mulmodandpowmodreturn 0 for a zero divisor instead of raising.tohexwrites negative values as-….Both are refused, so the call falls back to pure Lua, which reports the proper error.
Verification
crypto-portable.lua. Results were cross-checked against Python. Every routing decision matched the documented rule exactly.is_accelerated(op)for all five operations on each real rock.Test plan
make checkclean./run_tests.shpasses on Lua 5.4 with each of the three rocks (acceleration on and off), LuaJIT with 0.8.5 (on and off), and Lua 5.5make test-matrix(Lua 5.1-5.4, LuaJIT)