From e0ffe5a2eddb49cd7fd8516e2982ec0efa76d392 Mon Sep 17 00:00:00 2001 From: Chris Duncan Date: Thu, 1 Oct 2026 13:30:59 -0700 Subject: [PATCH] Bias carry shifts to reduce instruction count and improve performance. --- src/assembly/ed25519/fe.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/src/assembly/ed25519/fe.ts b/src/assembly/ed25519/fe.ts index 3ad204b..fba24bd 100644 --- a/src/assembly/ed25519/fe.ts +++ b/src/assembly/ed25519/fe.ts @@ -452,36 +452,43 @@ export function fe_mul (h: FieldElement, f: FieldElement, g: FieldElement): void let h37_sum = v128.add(v128.shuffle(h34, h78, 0, 2), v128.shuffle(h23, h67, 1, 3)) let h89_sum = v128.add(v128.shuffle(h89, h90, 0, 2), v128.shuffle(h78, h89, 1, 3)) - const add24 = v128.splat(1 << 24) - const add25 = v128.splat(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((1 << 24) + (1 << 62)) + const bias25 = v128.splat((1 << 25) + (1 << 62)) + const unbias25 = v128.splat(1 << 37) + const unbias26 = v128.splat(1 << 36) let carry: v128 - carry = v128.shr(v128.add(h04_sum, add25), 26) + carry = v128.sub(v128.shr(v128.add(h04_sum, bias25), 26), unbias26) h04_sum = v128.sub(h04_sum, v128.shl(carry, 26)) h15_sum = v128.add(h15_sum, carry) - carry = v128.shr(v128.add(h15_sum, add24), 25) + carry = v128.sub(v128.shr(v128.add(h15_sum, bias24), 25), unbias25) h15_sum = v128.sub(h15_sum, v128.shl(carry, 25)) h26_sum = v128.add(h26_sum, carry) - carry = v128.shr(v128.add(h26_sum, add25), 26) + carry = v128.sub(v128.shr(v128.add(h26_sum, bias25), 26), unbias26) h26_sum = v128.sub(h26_sum, v128.shl(carry, 26)) h37_sum = v128.add(h37_sum, carry) - carry = v128.shr(v128.add(h37_sum, add24), 25) + carry = v128.sub(v128.shr(v128.add(h37_sum, bias24), 25), unbias25) h37_sum = v128.sub(h37_sum, v128.shl(carry, 25)) let h48_sum = v128.add(v128.shuffle(h04_sum, h89_sum, 1, 2), carry) - carry = v128.shr(v128.add(h48_sum, add25), 26) + carry = v128.sub(v128.shr(v128.add(h48_sum, bias25), 26), unbias26) h48_sum = v128.sub(h48_sum, v128.shl(carry, 26)) let h59_sum = v128.add(v128.shuffle(h15_sum, h89_sum, 1, 3), carry) - carry = v128.shr(v128.add(h59_sum, add24), 25) + carry = v128.sub(v128.shr(v128.add(h59_sum, bias24), 25), unbias25) h59_sum = v128.sub(h59_sum, v128.shl(carry, 25)) // wrap requires multiplying carry from h9 by 19 let h60_sum = v128.add(v128.shuffle(h26_sum, h04_sum, 1, 2), v128.mul(carry, i64x2(1, 19))) - carry = v128.shr(v128.add(h60_sum, add25), 26) + carry = v128.sub(v128.shr(v128.add(h60_sum, bias25), 26), unbias26) h60_sum = v128.sub(h60_sum, v128.shl(carry, 26)) let h71_sum = v128.add(v128.shuffle(h37_sum, h15_sum, 1, 2), carry) -- 2.52.0