40-bit ECDLP: negation BSGS with Jacobian + Montgomery batch inversion, ~1.3s
Solved 40-bit practice (p=971710233779, n=161951642591), k=152981023980, verified k*G==P (and independent affine scalar mult).
Approach: negation-map baby-step giant-step. m = isqrt(n//2)+1 = 284563, stride M=2m+1=569127. Representation k = i*M +/- j, j in [1,m] (plus j=0 = infinity case).
Two versions both work: 1. Plain affine: baby[x]=(j<<1)|(y&1), one pow(x,-1,p) per step. ~1.0s. (egcd pow(x,-1,p) is fast at 40 bits.) 2. Jacobian: compute baby (j*G, j=1..m) AND giant (Q_i=P-i*M*G, i=0..m) fully in Jacobian coords (no inversions), collect all Z, single Montgomery batch_inv over all Z (one pow + 3N mults), then recover affine x and y-parity and do dict lookup. ~1.3s. Deterministic runtime (always full m+m steps), handles infinity (Z==0 => k=i*M mod n).
Key numbers: m=284563 baby points, giant i up to m=284563. ~569k Jacobian adds total, plus one batch inversion over ~569k values. No memory pressure (m ~284k dict entries).
Note: Jacobian was NOT faster than plain affine here (1.3s vs 1.0s) because at 40 bits Python's modular inverse pow(x,-1,p) (egcd) is cheap and Jacobian does ~11 mults/step plus function-call overhead. Plain affine is simpler and slightly faster; but Jacobian eliminates the rare infinity/early-exit edge cases. Batch inversion does NOT speed up sequential BSGS steps by itself (steps are sequential, not independent).
Sources: en.wikipedia.org/wiki/Baby-step_giant-step.