]> git.codecow.com Git - nano25519.git/commitdiff
Bias carry shifts to reduce instruction count and improve performance.
authorChris Duncan <chris@codecow.com>
Thu, 1 Oct 2026 20:30:59 +0000 (13:30 -0700)
committerChris Duncan <chris@codecow.com>
Thu, 1 Oct 2026 20:30:59 +0000 (13:30 -0700)
src/assembly/ed25519/fe.ts

index 3ad204b99ef97cdc9761cc3bdc0411cf8723c28b..fba24bdbec63c9a45dfcaecb79e776531a6f797f 100644 (file)
@@ -452,36 +452,43 @@ export function fe_mul (h: FieldElement, f: FieldElement, g: FieldElement): void
        let h37_sum = v128.add<i64>(v128.shuffle<i64>(h34, h78, 0, 2), v128.shuffle<i64>(h23, h67, 1, 3))
        let h89_sum = v128.add<i64>(v128.shuffle<i64>(h89, h90, 0, 2), v128.shuffle<i64>(h78, h89, 1, 3))
 
-       const add24 = v128.splat<i64>(1 << 24)
-       const add25 = v128.splat<i64>(1 << 25)
+       // Signed i64x2 shifts are emulated on x64, unsigned ones are native. Adding
+       // 2⁶² keeps every sum non-negative (|h| ≤ 2⁶⁰·⁴), and the unsigned shift then
+       // floors in fewer instructions. The bias is removed by subtraction, and the
+       // value to remove is the bias minus the original size: 2⁶²⁻²⁶ = 2³⁶ (or
+       // 2⁶²⁻²⁵ = 2³⁷). After unbiasing, the result is the signed carry.
+       const bias24 = v128.splat<i64>((<i64>1 << 24) + (<i64>1 << 62))
+       const bias25 = v128.splat<i64>((<i64>1 << 25) + (<i64>1 << 62))
+       const unbias25 = v128.splat<i64>(<i64>1 << 37)
+       const unbias26 = v128.splat<i64>(<i64>1 << 36)
        let carry: v128
 
-       carry = v128.shr<i64>(v128.add<i64>(h04_sum, add25), 26)
+       carry = v128.sub<i64>(v128.shr<u64>(v128.add<i64>(h04_sum, bias25), 26), unbias26)
        h04_sum = v128.sub<i64>(h04_sum, v128.shl<i64>(carry, 26))
        h15_sum = v128.add<i64>(h15_sum, carry)
 
-       carry = v128.shr<i64>(v128.add<i64>(h15_sum, add24), 25)
+       carry = v128.sub<i64>(v128.shr<u64>(v128.add<i64>(h15_sum, bias24), 25), unbias25)
        h15_sum = v128.sub<i64>(h15_sum, v128.shl<i64>(carry, 25))
        h26_sum = v128.add<i64>(h26_sum, carry)
 
-       carry = v128.shr<i64>(v128.add<i64>(h26_sum, add25), 26)
+       carry = v128.sub<i64>(v128.shr<u64>(v128.add<i64>(h26_sum, bias25), 26), unbias26)
        h26_sum = v128.sub<i64>(h26_sum, v128.shl<i64>(carry, 26))
        h37_sum = v128.add<i64>(h37_sum, carry)
 
-       carry = v128.shr<i64>(v128.add<i64>(h37_sum, add24), 25)
+       carry = v128.sub<i64>(v128.shr<u64>(v128.add<i64>(h37_sum, bias24), 25), unbias25)
        h37_sum = v128.sub<i64>(h37_sum, v128.shl<i64>(carry, 25))
        let h48_sum = v128.add<i64>(v128.shuffle<i64>(h04_sum, h89_sum, 1, 2), carry)
 
-       carry = v128.shr<i64>(v128.add<i64>(h48_sum, add25), 26)
+       carry = v128.sub<i64>(v128.shr<u64>(v128.add<i64>(h48_sum, bias25), 26), unbias26)
        h48_sum = v128.sub<i64>(h48_sum, v128.shl<i64>(carry, 26))
        let h59_sum = v128.add<i64>(v128.shuffle<i64>(h15_sum, h89_sum, 1, 3), carry)
 
-       carry = v128.shr<i64>(v128.add<i64>(h59_sum, add24), 25)
+       carry = v128.sub<i64>(v128.shr<u64>(v128.add<i64>(h59_sum, bias24), 25), unbias25)
        h59_sum = v128.sub<i64>(h59_sum, v128.shl<i64>(carry, 25))
        // wrap requires multiplying carry from h9 by 19
        let h60_sum = v128.add<i64>(v128.shuffle<i64>(h26_sum, h04_sum, 1, 2), v128.mul<i64>(carry, i64x2(1, 19)))
 
-       carry = v128.shr<i64>(v128.add<i64>(h60_sum, add25), 26)
+       carry = v128.sub<i64>(v128.shr<u64>(v128.add<i64>(h60_sum, bias25), 26), unbias26)
        h60_sum = v128.sub<i64>(h60_sum, v128.shl<i64>(carry, 26))
        let h71_sum = v128.add<i64>(v128.shuffle<i64>(h37_sum, h15_sum, 1, 2), carry)