code wiki / _hdl_build / nx_u2048_smallops.nx
nx_u2048_smallops.nx
buildroot/runtime/_hdl_build/nx_u2048_smallops.nx
about
nx_u2048_smallops.nx -- F103e RUNG 6: big-by-SMALL arithmetic (mul, div, mod, add).
WHY THIS SHAPE, and not a general extended GCD: RSA needs d = e^-1 mod lambda(n). The textbook
route is a binary extended GCD over 2048-bit values, which needs (x + m) >> 1 -- and x + m
OVERFLOWS 2048 bits, while u2048_shr1 has no carry-in. That is a real trap, not a style choice.
The route taken instead exploits the fact that **e is small** (65537). Write:
k*lambda + 1 = d*e with k in [1, e-1]
Then k = -(lambda mod e)^-1 mod e -- an inverse of a SMALL number modulo a SMALL number, solvable
in plain i64 -- and d = (k*lambda + 1)/e, which is a big-by-small multiply and a big-by-small
divide. Every intermediate fits in an i64 because the limbs are 32-bit:
divide: rem < m <= 2^31, so (rem << 32) | limb < 2^63 -- proven by the bound, not hoped
multiply: (2^32-1)*(2^31-1) + carry < 2^63
★★★★★★ **THE PRIMITIVE YOU NEED IS OFTEN NARROWER THAN THE ONE THE TEXTBOOK NAMES — AND THE NARROW
ONE CAN BE EXACTLY THE ONE THAT FITS IN THE REGISTERS YOU HAVE.** A general u2048 modinv is a much
larger, more dangerous organ than this, and RSA does not need it.
★THE ORACLE IS PLAIN i64 ARITHMETIC. Every tooth loads a value that fits in 64 bits, runs the
u2048 path, and demands agreement with the `*`, `/`, `%` the compiler already gives us. Two
independent implementations; no recalled constants anywhere.
Usage: nx_u2048_smallops selftest
Exit: 0 GREEN | 1 RED. Log -> knowledge/status/nishi_os.log, verdict= LAST.
license_tier: ORIGINAL
dependencies 2 imports · 5 importers
imports: nx_syscalls.nxnx_u2048.nx
imported by: nx_authenticode_sign.nxnx_efivars_enrol.nxnx_rsa_keygen.nxnx_rsa_pkcs1_sign.nxnx_x509_emit.nx
call flow from main pre-order; caps 40 nodes / depth 6 declared; ↻ = already shown
structs
| none |
consts
| 27 | const SO_MAGIC_65537: i64 = 65537 |
| 28 | const SO_MAGIC_4294967295: i64 = 4294967295 |
| 29 | const SO_MAGIC_4294967296: i64 = 4294967296 |
| 30 | const SO_MAGIC_1099511627776: i64 = 1099511627776 |
| 31 | const SO_MAGIC_9007199254740993: i64 = 9007199254740993 |
| 32 | const SO_MAGIC_123456789012345: i64 = 123456789012345 |
| 33 | const SO_MAGIC_1000003: i64 = 1000003 |
| 34 | const SO_MAGIC_2147483647: i64 = 2147483647 |
| 35 | const SO_MAGIC_1000000007: i64 = 1000000007 |
| 36 | const SO_MAGIC_12345: i64 = 12345 |
| 38 | const SO_LIMB_MASK: i64 = 0xFFFFFFFF |
| 39 | const SO_MAX_SMALL: i64 = 2147483647 // 2^31-1: the bound the overflow proofs above assume |
functions
| 41 | func so_p(s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(1, s, n); return 0 } |
| 42 | func so_fp(fd: i64, s: *u8) -> i64 { var n: i64 = 0; while s[n] != (0 as u8) { n = n + 1 } sys_write(fd, s, n); return 0 } |
| 43 | func so_fn(fd: i64, v: i64) -> i64 |
| 54 | func so_set_i64(x: *i64, v: i64) -> i64 |
| 64 | func so_low64(x: *i64) -> i64 called by 1: so_selftest |
| 69 | func u2048_mul_small(out: *i64, a: *i64, k: i64) -> i64 called by 1: so_selftest |
| 84 | func u2048_div_small(out: *i64, a: *i64, m: i64) -> i64 |
| 99 | func u2048_mod_small(a: *i64, m: i64) -> i64 |
| 113 | func u2048_add_small(out: *i64, a: *i64, k: i64) -> i64 called by 1: so_selftest |
| 129 | func i64_modinv_small(a: i64, m: i64) -> i64 |
| 150 | func so_selftest() -> i64 |
| 268 | func main(argc: i64, argv: *i64) -> i64 calls 1: so_selftest |