code wiki / (root) / nx_bitops.nx

nx_bitops.nx source

↩ module page · 122 lines · 5253 B

1// nx_bitops.nx -- 64-bit bitwise helpers (gold-standard reference). 2// Delegates to nx_bits.nx for FAST intrinsic dispatch (rolq/rorq / 3// rol/ror Zbb) while preserving the math-correctness phrasing of the 4// soft fallback as in-place documentation. 5 6import "nx_bits.nx" 7 8// 9// NishiLang's `>>` operator is ARITHMETIC right shift -- it sign-extends 10// the high bit. For any 64-bit value where the high bit can be set 11// (hash state lanes, crypto rotation primitives, etc.) a naive `x >> n` 12// will corrupt the result with sign-extended 1s. 13// 14// This file is the GOLD STANDARD for shift / rotate idioms. Per the 15// Pillar 4 doctrine (NISHI_INTELLIGENT_CAPABILITY_LAYER.md) and per 16// memory entry [[feedback-nishilang-shift-arith-not-logical]], the 17// `bench/bench_shift_audit.sh` grader scans every runtime/*.nx file 18// against the patterns DEFINED HERE. 19// 20// WAT-target note: cross-module imports drag in atomics IR that the 21// WAT backend doesn't lower; substrate-WASM crypto modules therefore 22// inline these helpers VERBATIM. The grader verifies each inlined 23// copy matches the gold standard. If you change a function here, the 24// downstream crypto modules must be re-synchronised + re-KAT'd. 25// 26// Verified callers (all KAT-green at https://nishifamily.com/video/substrate.html): 27// nx_sha256_wasm.nx (32-bit, but rotation idiom matches) 28// nx_sha384_wasm.nx (64-bit lanes) 29// nx_sha512_wasm.nx (64-bit lanes) 30// nx_hmac_sha512_wasm.nx (composes sha512) 31// nx_hkdf_sha512_wasm.nx (composes hmac) 32// nx_sha3_256_wasm.nx (64-bit lanes; canonical _rotl64 source) 33// nx_shake128_wasm.nx (Keccak shares _rotl64) 34// nx_shake256_wasm.nx (Keccak shares _rotl64) 35// nx_poly1305_wasm.nx (26-bit limbs; safe-by-construction) 36// nx_x25519_wasm.nx (26/25-bit limbs; safe-by-construction) 37// 38// license_tier: INDEPENDENT_REDERIVE 39// genealogy_id: nishi-core/substrate-discipline 40// lineage_id: nishi_bitops_q1 41// safe_shift_audit: gold_standard 42 43// === nx_lshr64 -- logical right shift (treat x as unsigned 64-bit) === 44// Mask AFTER the arithmetic shift to clear sign-extended high bits. 45// 46// Correct mask: (1 << (64 - n)) - 1 keeps the bits that ACTUALLY 47// moved into low positions 48// 49// Wait -- read that again carefully: 50// * arithmetic shift right by `n` moves `(64-n)` real bits down, 51// and fills the top `n` bits with sign extension 52// * the low `(64-n)` bits are the bits we want to keep 53// * mask = (1 << (64-n)) - 1 keeps those bits 54// 55// !! THIS IS THE BUG SHAPE !! The mask formula above looks right but 56// only works when shifting by a SMALL amount. When `n = 63` (the 57// extreme case in rotl(x, 1)), `(1 << (64-63)) - 1 = 0x1` -- correct. 58// When `n = 1` (the extreme case in rotl(x, 63)), 59// `(1 << (64-1)) - 1 = (1 << 63) - 1 = 0x7FFF_FFFF_FFFF_FFFF` 60// -- which has all 63 low bits set, which is correct for n=1 shift. 61// 62// So `(1 << (64-n)) - 1` is actually CORRECT for lshr64. The bug 63// from feedback-nishilang-shift-arith-not-logical is specific to 64// the ROTATION-COMPOSITION case below, where we already split into 65// two halves -- not to a bare logical shift. 66// 67// For nx_lshr64 we use the simpler equivalent: 68func nx_lshr64(x: i64, n: i64) -> i64 { 69 let nn: i64 = n & 63 70 if nn == 0 { return x } 71 return (x >> nn) & ((1 << (64 - nn)) - 1) 72} 73 74// === nx_rotl64 -- rotate left, canonical idiom === 75// 76// Naive rotl is `(x << n) | (x >> (64 - n))` but the right-shift half 77// must be logical, not arithmetic. Substitute nx_lshr64-style mask. 78// 79// The shift-distance for the right half is `(64 - n)`; the count of 80// surviving real bits is therefore `64 - (64 - n) = n`. 81// Hence the right-mask exponent is `n`, not `64 - n`. 82// 83// ROTL(x, n) = (x << n) | ((x >> (64-n)) & ((1 << n) - 1)) 84// 85// !! GOLD STANDARD !! Any deviation will silently break >50% of inputs. 86// Delegated to nx_bits_rotl64 (rolq on x86_64 / rol on rv64 Zbb) -- 87// 1 cycle vs ~5 ops of the mask-shift phrasing. 88func nx_rotl64(x: i64, n: i64) -> i64 { 89 return nx_bits_rotl64(x, n) 90} 91 92func nx_rotr64(x: i64, n: i64) -> i64 { 93 return nx_bits_rotr64(x, n) 94} 95 96// === Self-KAT helper (callable from smokes) === 97// Returns 0 on full pass, non-zero bitmap of which subtest failed. 98// (Single-result self-test so a smoke can call once + check zero.) 99func nx_bitops_self_test() -> i64 { 100 var fail_mask: i64 = 0 101 // rotl(0x8000000000000001, 1) should be 3 102 if nx_rotl64(0x8000000000000001, 1) != 0x0000000000000003 { 103 fail_mask = fail_mask | 1 104 } 105 // rotl(0x8000000000000000, 32) should be 0x80000000 106 if nx_rotl64(0x8000000000000000, 32) != 0x0000000080000000 { 107 fail_mask = fail_mask | 2 108 } 109 // rotl(0xff00ff00ff00ff00, 4) should be 0xf00ff00ff00ff00f 110 if nx_rotl64(0xff00ff00ff00ff00, 4) != 0xf00ff00ff00ff00f { 111 fail_mask = fail_mask | 4 112 } 113 // rotr(0x3, 1) = 0x8000000000000001 114 if nx_rotr64(0x3, 1) != 0x8000000000000001 { 115 fail_mask = fail_mask | 8 116 } 117 // lshr(-1, 1) = 0x7FFFFFFFFFFFFFFF 118 if nx_lshr64(0xffffffffffffffff, 1) != 0x7fffffffffffffff { 119 fail_mask = fail_mask | 16 120 } 121 return fail_mask 122}